文章目录

  • 本章内容介绍
  • 制作生成器
  • 生成器Hierarchy
  • 高亮显示生成器
  • 制作箭塔菜单
  • 将创建菜单对齐到选中的生成器
  • 动态生成按钮
  • 动态计算按钮坐标
  • 制作预制件
  • 最终运行效果

本章内容介绍

由于目前预想的运行环境是PC端,但后续也可能移植到手机端,且有可能提供玩家自定义关卡的功能,所以将防御塔建造点设置为固定位置模式。也就是当鼠标停留在可建造防御塔的位置时显示一个建造区域,当鼠标点击这个区域时弹出建造防御塔的UI菜单。如下图:




演示效果如下:

Unity制作炮台防守游戏(2)建造炮台

制作生成器

用PS做一张图,将图的四个角涂上白线,中间部分做成半透明状。

然后在场景中创建一个 Cube ,调整形状。将 Cube 的阴影关闭,再给 Cube 创建一个材质。如下图:

将图片导入项目中,如下设置材质。

后续通过代码调整图片的透明度来实现防御塔生成器的显示与隐藏。

生成器Hierarchy


创建一个空节点,再给节点下放一个 Generator。Generator 节点上挂一个 DefenseGenerator 脚本。

DefenseGenerator 代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class DefenseGenerator : MonoBehaviour
{[HideInInspector]public GameObject Defense;void Start(){}/// <summary>/// 鼠标指向生成器事件/// </summary>public void OnRayGenerator(){}/// <summary>/// 选中生成器事件/// </summary>public void OnSelectGenerator(){Camera.main.WorldToScreenPoint(transform.position);Debug.Log("============" + Camera.main.WorldToScreenPoint(transform.position));}/// <summary>/// 建造防御塔/// </summary>public void BuildDefense(){}/// <summary>/// 销毁防御塔/// </summary>public void DestroyDefense(){}/// <summary>/// 升级防御塔/// </summary>public void UpgradeDefense(){}
}

高亮显示生成器

给 GameScript 添加一个脚本(DefenseManager),并做如下设置:

DefenseManager 代码如下:

using Excel;
using System;
using System.Collections.Generic;
using TDGameDemo.GameDefense;
using TDGameDemo.GameLevel;
using UnityEngine;
using UnityEngine.UI;public class DefenseManager : MonoBehaviour
{/// <summary>/// 检测射线/// </summary>private Ray ray;/// <summary>/// 射线击中点/// </summary>private RaycastHit hit;/// <summary>/// 屏幕外的一个点/// </summary>public Transform OutScreenPosition;/// <summary>/// 生成器UI/// </summary>public GameObject GeneratorUICanvas;/// <summary>/// 当前生成器位置/// </summary>private Transform currGeneratorPosition;/// <summary>/// 当前指针指向的生成器/// </summary>private GameObject currPointerGenerator;/// <summary>/// 当前选中的生成器/// </summary>private GameObject currSelectedGenerator;/// <summary>/// 生成点 ***************TODO****************/// </summary>public Transform _generatePoint;/// <summary>/// 创建菜单面板/// </summary>public GameObject CreatePanel;private bool isBtnClick;private Dictionary<int, List<DefenseConfig>> _defenseConfigs;private List<Transform> _createBtnList;private void Start(){currGeneratorPosition = OutScreenPosition;_createBtnList = new List<Transform>();InitConfig();InitCreatePanel(7);}/// <summary>/// 初始化创建菜单面板/// </summary>/// <param name="defenseTypeCode">当前关卡可以创建的防御塔类型合成码。例如:14代表2(炮塔)+4(毒液塔)+8(冰锥塔)。</param>public void InitCreatePanel(int defenseTypeCode){foreach (Transform createBtn in CreatePanel.transform){createBtn.transform.GetComponent<Image>().enabled = false;}for (int i = 0; i < Enum.GetValues(typeof(DefenseType)).Length; i++){if (((defenseTypeCode >> i) & 1) == 1){Transform createBtn = CreatePanel.transform.Find(Enum.GetName(typeof(DefenseType), (int)Mathf.Pow(2, i)));createBtn.GetComponent<Image>().enabled = true;createBtn.GetComponent<ETCButton>().onDown.AddListener(() => { OnCreateButtonDown(createBtn.name); });_createBtnList.Add(createBtn);}}// 根据防御塔数量决定按钮的旋转角度for (int i = 0; i < _createBtnList.Count; i++){// 计算旋转角度float angle = 360 / _createBtnList.Count * i + 90;// 使用公式算出按钮坐标//x = centerX + radius * cos(angle * 3.14 / 180)//y = centerY + radius * sin(angle * 3.14 / 180)_createBtnList[i].position = new Vector3(100 * Mathf.Cos(angle * Mathf.PI / 180), 100 * Mathf.Sin(angle * Mathf.PI / 180), 0);}}public void OnCreateButtonDown(string btnName){currGeneratorPosition = OutScreenPosition;isBtnClick = true;GameObject defensePrefab = Resources.Load<GameObject>(Level.DEFENSE_PREFAB_PREFIX + "Prefab_Defense_" + btnName + "_1");GameObject o = Instantiate(defensePrefab, currSelectedGenerator.transform.parent.position, Quaternion.identity, currSelectedGenerator.transform.parent);//o.GetComponent<DefenseBase>()._enemyGeneratePoint = _generatePoint;}private void LateUpdate(){if (!isBtnClick){// 判断鼠标有没有悬停在GeneratorUI上ray = Camera.main.ScreenPointToRay(Input.mousePosition);bool isCollider = Physics.Raycast(ray, out hit, 1000, LayerMask.GetMask("Generator"));if (isCollider){GameObject generator = hit.collider.gameObject;if (currPointerGenerator == null){currPointerGenerator = generator;}else{// 如果当前鼠标停留的生成器与之前不同if (currPointerGenerator != generator){currPointerGenerator.GetComponent<Renderer>().material.SetColor("_Color", Color.clear);currPointerGenerator = generator;}}currPointerGenerator.GetComponent<Renderer>().material.SetColor("_Color", new Color(0.1610479f, 0.5566038f, 0, 1));// 处理点击事件if (Input.GetMouseButtonDown(0)){SelectGenerator(currPointerGenerator);}}else{if (currPointerGenerator != null){currPointerGenerator.GetComponent<Renderer>().material.SetColor("_Color", Color.clear);}else{//currGeneratorPosition = OutScreenPosition;}// 处理点击事件if (Input.GetMouseButtonDown(0)){currSelectedGenerator = null;}}if (currSelectedGenerator == null){GeneratorUICanvas.transform.position = Camera.main.WorldToScreenPoint(OutScreenPosition.position);}else{GeneratorUICanvas.transform.position = Camera.main.WorldToScreenPoint(currGeneratorPosition.position);}}isBtnClick = false;}/// <summary>/// 选择生成器/// </summary>/// <param name="selectedGenerator">被选中的生成器</param>private void SelectGenerator(GameObject selectedGenerator){currSelectedGenerator = selectedGenerator;currGeneratorPosition = currSelectedGenerator.transform;判断是否已经有炮台存在//if (currGeneratorPosition.GetComponent<DefenseGenerator>().Defense == null)//{//    // 建造炮台//    //Debug.Log("建造炮台");//    currGeneratorPosition.GetComponent<DefenseGenerator>().OnSelectGenerator();//}//else//{//    // 升级炮台//    //Debug.Log(isCollider);//}}
}

代码中 LateUpdate 方法使用射线判断鼠标是否指向了生成器,并实现了生成器的显隐与选择功能。

制作箭塔菜单

在 UI 画布下创建一个菜单UI(GeneratorUICanvas)和一个肯定不会出现在屏幕内的空物体(OutScreenPosition)。在菜单UI中建立几个按钮(我使用的是EasyTouch里面提供的 ETCButton)。

替换按钮图片:

将创建菜单对齐到选中的生成器

在 LateUpdate 方法中调用了 SelectGenerator 方法,该方法用于将菜单对齐到选中的生成器。

动态生成按钮

为了增加游戏可玩性,可以给每个关卡单独配置允许建造哪些防御塔。为了简化配置项,我们将炮塔的 code 设置为2的n次方,这样就可以用一个数字表示多个防御塔了。

防御塔 code 按照下图配置:

如果我本关需要使用箭塔(1)、炮塔(2)、多重箭塔(64)、电塔(256),我们只需要在关卡配置文件中指定一个数字 323 (1 + 2 + 64 + 256)即可。

在程序中解析这个配置时只需要判断 code向右位移n位后再按位与1后得到的值 是否等于 1即可,其中code代表我们刚才设置的 323 。伪代码:

if (((code >> i) & 1) == 1)

真实代码:下图第127行。

上述代码需要提供一个枚举:

public enum DefenseType
{SingleArrow = 1,Cannon = 2,Poison = 4,Ice = 8
}

遍历枚举,根据上面的判断条件判断出那些按钮需要显示,并将这些按钮放到一个列表(_createBtnList)中,方便后续对按钮进行统一处理。

动态计算按钮坐标

由于按钮的数量是可变的,所以我将按钮的位置设计成围绕着生成器旋转排列。当按钮数量为 3 时,就每隔 120° 放一个按钮,当按钮数量为 4 时,就隔 90° 。

计算圆上某个点的坐标公式为:
x = centerX + radius * cos(angle * 3.14 / 180)
y = centerY + radius * sin(angle * 3.14 / 180)

实际代码:

通过遍历上一节生成的 _createBtnList ,计算每个按钮的旋转角度,再根据角度计算出按钮的坐标。最后再给这个角度增加90°,让起始坐标从 3 点钟方向变为 12 点钟方向。

制作预制件

先准备好要用的模型,将模型放到某一个生成器下,调整好大小,最后做成预制件。

将这些预制件放到 Resources 目录下,通过代码动态加载。

最终运行效果


更多内容请查看总目录【Unity】Unity学习笔记目录整理

【Unity】U3D TD游戏制作实例(四)建造防御塔:防御塔生成器、一个int代表多选框,圆上任意点位的坐标计算、制作防御塔预制件相关推荐

  1. 《Unity 3.x游戏开发实例》——2.13节适合上千款游戏的机制

    本节书摘来自异步社区<Unity 3.x游戏开发实例>一书中的第2章,第2.13节适合上千款游戏的机制,作者 [加]Ryan Henson Creighton,更多章节内容可以访问云栖社区 ...

  2. 《Unity 3.x游戏开发实例》一第2章 让我们从天空开始

    本节书摘来异步社区<Unity 3.x游戏开发实例>一书中的第2章,第2.1节,作者: [加]Ryan Henson Creighton 译者: 师蓉 责编: 陈冀康,更多章节内容可以访问 ...

  3. 《Unity 3.x游戏开发实例》——2.11节《Artillery Live!》

    本节书摘来自异步社区<Unity 3.x游戏开发实例>一书中的第2章,第2.11节<Artillery Live!>,作者 [加]Ryan Henson Creighton,更 ...

  4. 《Unity 3.x游戏开发实例》一1.5 欢迎来到Unity 3D

    本节书摘来异步社区<Unity 3.x游戏开发实例>一书中的第1章,第1.5节,作者: [加]Ryan Henson Creighton 译者: 师蓉 责编: 陈冀康,更多章节内容可以访问 ...

  5. 《Unity 3.x游戏开发实例》——1.2节风靡全球的Unity

    本节书摘来自异步社区<Unity 3.x游戏开发实例>一书中的第1章,第1.2节风靡全球的Unity,作者邓文渊,更多章节内容可以访问云栖社区"异步社区"公众号查看 1 ...

  6. 《Unity 3.x游戏开发实例》——1.5节欢迎来到Unity 3D

    本节书摘来自异步社区<Unity 3.x游戏开发实例>一书中的第1章,第1.5节欢迎来到Unity 3D,作者邓文渊,更多章节内容可以访问云栖社区"异步社区"公众号查看 ...

  7. 《Unity 3.x游戏开发实例》——2.10节注意

    本节书摘来自异步社区<Unity 3.x游戏开发实例>一书中的第2章,第2.10节注意,作者 [加]Ryan Henson Creighton,更多章节内容可以访问云栖社区"异步 ...

  8. 【Unity】U3D TD游戏制作实例(一)创建敌人、加载预制件

    文章目录 前言 本章实现效果 场景和敌人 文件目录结构 场景层次结构(Hierarchy) 处理模型 指定GameScript脚本 运行游戏 前言 TD(炮台防守)类游戏是比较经典的游戏类型,当年在 ...

  9. 【Unity】U3D TD游戏制作实例(三)相机管理器、生成敌人优化、敌人血槽小组件

    文章目录 相机管理器 调整相机 敌人类优化 融合导航测试代码 敌人移动速度 销毁对象 加载敌人配置 敌人生成方式优化 血槽组件 相机管理器 调整相机 首先将主相机调整为正交镜头,这样可以防止模型畸变. ...

最新文章

  1. 你会去创建一个线程去处理压缩日志并删除吗?
  2. php获取访问量文本形式,php利用用文本统计访问量的方法图文详解
  3. Case when then esle end
  4. 启明云端分享|sigmastar debug工具使用说明(一)
  5. U9在SQL Server上的性能优化经验(转述) — 之 行版本快照
  6. linux怎样扩容目录,Linux系统下对目录扩容的方法介绍
  7. 保险报业携手万丈金数 探索大数据应用升级
  8. 计算map代码_大数据系列之计算框架MapReduce
  9. 快讯:Oracle 19c 新特性及官方文档抢鲜下载
  10. c swap方法在哪个库里面_覆膜条件下土壤水热动态与玉米种子生长的SWAP修正模型...
  11. quartus仿真30:D触发器构成的可重复序列111探测器
  12. 一、从0开始——黑客学习路线
  13. Linux使用strlen编译,strlen in NASM Linux
  14. Android os 4.4.4 魅族,魅族Mx3刷机包 Android 4.4.4 稳定版Flyme OS 3.7.3A 流畅顺滑体验
  15. 鸿蒙(HarmonyOS)支持低代码开发,无需HTML知识,就可以设计复杂界面
  16. 怎么缩小gif动图的体积?三步快速压缩gif体积
  17. nyoj112指数运算
  18. 双绞线接法详解双绞线的标准的由来与分析
  19. 分享五个绝对称得上妖艳古怪精灵的前端代码效果
  20. winscp是什么软件_文件传输软件WinSCP的使用

热门文章

  1. Python简单的枫叶形状的词云图
  2. 龙芯3000平台编译igb_uio.ko,构建dpdk环境
  3. vue尺寸的自适应 大屏自适应
  4. 数字IC/FPGA面试笔试准备(自用填坑中)
  5. Kewail平台(旗威)
  6. Eclipse使用频率最高的快捷键
  7. 使用MySQL Router实现高可用、负载均衡、读写分离
  8. iPhone和iPad开发中的图标大小和设置
  9. BZOJ_2343_[Usaco2011 Open]修剪草坪 _单调队列_DP
  10. android-studio 使用过程遇到的问题,持续中...