【Unity】U3D TD游戏制作实例(四)建造防御塔:防御塔生成器、一个int代表多选框,圆上任意点位的坐标计算、制作防御塔预制件
文章目录
- 本章内容介绍
- 制作生成器
- 生成器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代表多选框,圆上任意点位的坐标计算、制作防御塔预制件相关推荐
- 《Unity 3.x游戏开发实例》——2.13节适合上千款游戏的机制
本节书摘来自异步社区<Unity 3.x游戏开发实例>一书中的第2章,第2.13节适合上千款游戏的机制,作者 [加]Ryan Henson Creighton,更多章节内容可以访问云栖社区 ...
- 《Unity 3.x游戏开发实例》一第2章 让我们从天空开始
本节书摘来异步社区<Unity 3.x游戏开发实例>一书中的第2章,第2.1节,作者: [加]Ryan Henson Creighton 译者: 师蓉 责编: 陈冀康,更多章节内容可以访问 ...
- 《Unity 3.x游戏开发实例》——2.11节《Artillery Live!》
本节书摘来自异步社区<Unity 3.x游戏开发实例>一书中的第2章,第2.11节<Artillery Live!>,作者 [加]Ryan Henson Creighton,更 ...
- 《Unity 3.x游戏开发实例》一1.5 欢迎来到Unity 3D
本节书摘来异步社区<Unity 3.x游戏开发实例>一书中的第1章,第1.5节,作者: [加]Ryan Henson Creighton 译者: 师蓉 责编: 陈冀康,更多章节内容可以访问 ...
- 《Unity 3.x游戏开发实例》——1.2节风靡全球的Unity
本节书摘来自异步社区<Unity 3.x游戏开发实例>一书中的第1章,第1.2节风靡全球的Unity,作者邓文渊,更多章节内容可以访问云栖社区"异步社区"公众号查看 1 ...
- 《Unity 3.x游戏开发实例》——1.5节欢迎来到Unity 3D
本节书摘来自异步社区<Unity 3.x游戏开发实例>一书中的第1章,第1.5节欢迎来到Unity 3D,作者邓文渊,更多章节内容可以访问云栖社区"异步社区"公众号查看 ...
- 《Unity 3.x游戏开发实例》——2.10节注意
本节书摘来自异步社区<Unity 3.x游戏开发实例>一书中的第2章,第2.10节注意,作者 [加]Ryan Henson Creighton,更多章节内容可以访问云栖社区"异步 ...
- 【Unity】U3D TD游戏制作实例(一)创建敌人、加载预制件
文章目录 前言 本章实现效果 场景和敌人 文件目录结构 场景层次结构(Hierarchy) 处理模型 指定GameScript脚本 运行游戏 前言 TD(炮台防守)类游戏是比较经典的游戏类型,当年在 ...
- 【Unity】U3D TD游戏制作实例(三)相机管理器、生成敌人优化、敌人血槽小组件
文章目录 相机管理器 调整相机 敌人类优化 融合导航测试代码 敌人移动速度 销毁对象 加载敌人配置 敌人生成方式优化 血槽组件 相机管理器 调整相机 首先将主相机调整为正交镜头,这样可以防止模型畸变. ...
最新文章
- 你会去创建一个线程去处理压缩日志并删除吗?
- php获取访问量文本形式,php利用用文本统计访问量的方法图文详解
- Case when then esle end
- 启明云端分享|sigmastar debug工具使用说明(一)
- U9在SQL Server上的性能优化经验(转述) — 之 行版本快照
- linux怎样扩容目录,Linux系统下对目录扩容的方法介绍
- 保险报业携手万丈金数 探索大数据应用升级
- 计算map代码_大数据系列之计算框架MapReduce
- 快讯:Oracle 19c 新特性及官方文档抢鲜下载
- c swap方法在哪个库里面_覆膜条件下土壤水热动态与玉米种子生长的SWAP修正模型...
- quartus仿真30:D触发器构成的可重复序列111探测器
- 一、从0开始——黑客学习路线
- Linux使用strlen编译,strlen in NASM Linux
- Android os 4.4.4 魅族,魅族Mx3刷机包 Android 4.4.4 稳定版Flyme OS 3.7.3A 流畅顺滑体验
- 鸿蒙(HarmonyOS)支持低代码开发,无需HTML知识,就可以设计复杂界面
- 怎么缩小gif动图的体积?三步快速压缩gif体积
- nyoj112指数运算
- 双绞线接法详解双绞线的标准的由来与分析
- 分享五个绝对称得上妖艳古怪精灵的前端代码效果
- winscp是什么软件_文件传输软件WinSCP的使用
热门文章
- Python简单的枫叶形状的词云图
- 龙芯3000平台编译igb_uio.ko,构建dpdk环境
- vue尺寸的自适应 大屏自适应
- 数字IC/FPGA面试笔试准备(自用填坑中)
- Kewail平台(旗威)
- Eclipse使用频率最高的快捷键
- 使用MySQL Router实现高可用、负载均衡、读写分离
- iPhone和iPad开发中的图标大小和设置
- BZOJ_2343_[Usaco2011 Open]修剪草坪 _单调队列_DP
- android-studio 使用过程遇到的问题,持续中...