Unity塔防游戏
- B站教学视频链接:传送门
- 优化列表
- 第一课:制作预制体、材质
- 第二课:制作地图
- 第三课:制作路径
- 第四课:摄像机
- 第五课:配置路点
- 第六课:创建敌人,并且移动。
- 第七课:创建敌人孵化器
- 第八课:改进敌人的生成策略
- 第九课:创建三种炮台的Prefab
- 第十课:创建炮台选择的UI
- 第十一课:创建炮台的数据类(保存炮台数据)
- 第十二课:监听炮台选择的事件(保存选择的炮台)
- 第十三课:检测点击哪个MapCube
- 第十四课:检查是否足够可以创建炮塔
- 第十五课:资源管理(金钱消费和更新UI)
- 第十六课:钱不够,制作动画提示效果
- 第十七课:炮台的实例化
- 第十八课:做特效
- 第十九课:炮台搜敌
- 第二十课:制作子弹
- 第二十一课:子弹飞行
- 第二十二课:子弹碰撞盒
- 第二十三课:创建爆炸特效
- 第二十四课:fixbug修复敌人销毁时,子弹销毁
- 第二十五课:fixbug修复敌人销毁时,炮台的引用
- 第二十六课:敌人死亡时,销毁特效
- 第二十七课:手动配置一下所有敌人的销毁特效
- 第二十八课:控制炮管指向敌人射击子弹
- 第二十九课:
- 第三十一课:敌人添加血条
- 第三十二课:制作一个ui
- 第三十三课:给按钮添加动画
- 第三十四课:给按钮添加动画
- 第三十五课:给按钮添加动画
- 第三十六课:给升级面板添加动画
- 第三十七课:处理升级和销毁的点击
- 第三十八课:处理升级和销毁
- 第三十九课:升级特效、销毁特效
- 第四十课:2级标准炮塔
- 第四十一课:火箭弹炮塔
- 第四十二课:激光炮塔
- 第四十三课:激光炮塔的发射(LineRenderer)
- 第四十四课:激光伤害和伤害特效
- 第四十五课:2级激光炮塔
- 第四十六课:炮塔消耗
- 第四十七课:游戏结束时候的UI
B站教学视频链接:传送门
视频传送门:https://www.bilibili.com/video/BV15W411976h/?spm_id_from=333.999.0.0&vd_source=a9be95fd9abd5e6f7a6dd9c91565dd34
素材下载地址:https://pan.baidu.com/s/1bd8mM-8MxtPUAuVUS2g5BQ 密码:pb6i
优化列表
- 炮塔攻击的时候, 先隐藏一下炮弹这个节点。更像是炮弹发射出去的感觉。
第一课:制作预制体、材质
- Unity的
Layout
采用Tall布局
。 Project
面板采用One Column Layout
。- 在Project中创建
Scenes文件夹
,存放主场景MainScene
以及后续的副本场景。 - 在Project中创建
Material文件夹
,存放各种材质。 - 在
Hierarchy
中创建空物体,重命名为Plane
作为整个游戏的地表。 - 在
Material文件夹
中,创建一个Plane的材质
,赋予给主场景中的Plane
,调节Plane
材质颜色即可修改地表的颜色,我这里调整设置为金黄色的地表。 - 地面有点儿反光,调节
Plane材质
的属性smoothness
,设置为0或者1,我选择设置为1,地面就不会出现反光现象。 - 在
Inspector
窗口去掉主场景中Plane的Mesh Collider组件
,因为该塔防游戏,我们不处理碰撞检测
。 - 新建
Prefab文件夹
,存放后续的各种预制体。新建一个cube
,调整合适的大小、高度,单击选中从Hierarchy
窗口拖到Project
窗口下,就可以把cube
设置为一个prefab
预制体。
预制体简介:可以复用,在游戏运行时,可以重复创建。
这里把这个cube作为一格地板,后续会点击该cube,在地板上面制造炮台。整个地图大小是
30*15,总计450个地板(这个可以自己调整修改大小),地板铺在地表上面。
图1. Uity设置 创建地表 地表材质 |
---|
第二课:制作地图
- 将上述的cube重命名为MapCube,在场景中会创建很多的
MapCube
。新建一个空物体命名为Map
,用来挂载管理所有的MapCube
。将所有的地块MapCube拖放到Map层级
下。这样通过在世界中查找Map节点
,就可以获取到所有的MapCube
地块。 - ctrl+d复制1个MapCube,因为MapCube大小是4*4,两个cube之间计划设置为间隔1m距离。
按住ctrl键
点击cube拖动,每次拖动距离是1m
。如果不按住ctrl键
,拖动距离的精度是任意的
。- 花费一些时间,拖动创建出15*15的Map,然后下节课设计地图元素:
起点、路径、终点
。
图2. 创建地图Map层级、创建地图块MapCube |
---|
第三课:制作路径
- 在Map上面,挖出一条路径(
删除路径所经过的所有MapCube
即可) - 制作一个
RoadCube
,将RoadCube
制作为Prefab。 - 制作
RoadCube
的材质,将该材质设置给RoadCube
。 - 创建一个空物体,重命名为
Road
,将RoadCube
归类到Road
下。(所有的MapCube
归类到Map
下管理;所有的RoadCube
归类到Road
管理),这样归类的目的是方便之后查找。 - 把挖出来的路径,
从起点到终点,用RoadCube填充一遍
。 - 制作起点(绿色的正方体)和终点(红色的正方体)。(有个
小知识点
:因为要从Map中扣掉一个MapCube,即从Map层级中,使一个MapCube脱离该层级。可以通过GameObject=>Break Prefab Instance
来实现该操作)
图3. 移除MapCube,从起点(绿色正方体)到终点(红色正方体)创建一条路径Road |
---|
第四课:摄像机
左上角
,点击对应按钮
,可以切换
摄像机的移动
和旋转
模式。
(上下左右四个箭头的图标
:功能是控制预制体位置的。两个循环的箭头图标
:功能是控制预制体旋转的)- 给摄像机添加移动功能、给地图添加缩小放大功能:
- 因为已经对摄像机做了旋转,所以移动时用
世界坐标
(摄像机移动其实是前、后、左、右修改摄像机坐标) - 摄像头在垂直方向上的
上下
移动,就是地图的放大和缩小
功能。
- 因为已经对摄像机做了旋转,所以移动时用
- 开始制作功能:选中
Main Camera
,点击Add Component
输入Script
,然后点击New script
给摄像机添加一个c#脚本文件,编码控制摄像机坐标。 - 键盘的上下左右或wasd按键。接口:
Input.GetAxis(“Horizontal”)
获取水平轴滑动;Input.GetAxis(“Vertical”)
获得垂直轴滑动。 - 在c#脚本Update()函数中获取轴的滑动值。假设
Update()函数1min跑60帧
,Time.deltaTime可以获取当前Update()处于第几帧。当然,实际上一分钟可以跑超多的帧数,和机器的硬件配置有关。 - 因为摄像机经过了旋转,所以transform.Translate()移动相机时,要用世界坐标系
Space.World
,而不能使用自身坐标系Space.Self
。 - 获取的轴滑动值范围是
[-1,1]
。 - 鼠标滚轮控制地图的放大缩小。获取鼠标滚轮滑动值的函数:
Input.GetAxis(“Mouse ScrollWheel”)
。
图4. 创建控制摄像机脚本:获取水平轴、垂直轴的滑动值,在Update中实现摄像机前、后、左、右 |
---|
第五课:配置路点
配置路点来实现怪物的寻路功能
。
- 添加路径点:首先创建一个空物体
Way
,在Way层级
下面挂载路径点WayPoint
(因为没有其它功能,所以每一个WayPoint
就是一个空物体)。点击Inspector
下的椭圆体,可以给WayPoint
修改颜色。 - 给
Way
这个节点,新增一个c#脚本组件:WayPointController.cs
,用来管理所有挂载在Way上面的路径点。 - 脚本在Awake()函数中,获取节点下挂载的所有WayPoint。代码如下:
wayPointsPosition = new Transform[transform.childCount];
for (int idx=0; idx<wayPointsPosition.Length;++idx)
{
wayPointsPosition[idx] = transform.GetChild(idx);
}
获取子节点
:GetChild()
。
图5. 地图寻路路径:WayPoint1 ~ WayPoint11 |
---|
第六课:创建敌人,并且移动。
- 首先,我们制作一个预制体,命名为
Enemy
(敌人)。 - 在
Enemy
预制体上创建c#脚本:Enemy.cs
,来控制敌人的行为:移动、攻击等。 - 移动的思路:获取
Way
节点下所有子物体,放到一个数组中,这个数组保存了整张地图的所有路点。然后让敌人顺序移动到数组中路点的位置。就能从起点移动到终点。 - 控制物体移动的函数:
transform.Translate(移动的向量)
,举例:transform.Translate((positions[index].position - transform.position).normalized * Time.deltaTime * speed);
- 判断物体是否达到某个路点:
Vector3.Distance(位置1,位置2)<0.2f
Vector3.Distance(positions[index].position,transform.position)<0.2f
图6. 制作的预制体Enemy1、游戏游戏时在场景中创建的敌人Enemy1(Clone) |
---|
第七课:创建敌人孵化器
- 在空物体GameManager上面,挂载一个
Enemy Spawner脚本
,来管理敌人的孵化、生成。 - 在Start()函数中启动协程来创建怪物:
StartCoroutine(SpawnEnemy())
; - 怪物被记录到class Wave中,这个类
不能继承MonoBehaviour
,是个独立的类。 Wave记录了一波怪物的属性
:类型、数量、出生间隔。目前设定一波怪物中,只能出现一种怪物。- 孵化器脚本,根据波数控制脚本,来创建怪物。创建的接口:
GameObject.Instantiate(对象,位置,旋转)
- 暂停n秒:
yield return new WaitForSeconds(间隔)
图7. 敌人孵化器 |
---|
第八课:改进敌人的生成策略
- 改进后:前一波的敌人到达终点,消失以后,才会创建下一波敌人。
第九课:创建三种炮台的Prefab
第十课:创建炮台选择的UI
bug
:3个单选按钮,会全部被选中,显示黑色遮挡。解决方案
:把3个Toggle设置为一个group,这样每次选中显示1个黑色遮挡。
第十一课:创建炮台的数据类(保存炮台数据)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum TurrentType
{
LasserTurret, // 激光炮台;
MissleTurret, // 炮弹炮台;
StandTurret, // 标准炮台;
}
public class TurrentData{
public TurrentType type; // 炮台类型;
public GameObject turretPrefab; // 武器对应的prefab;
public int createCost; // 武器创建消耗;
public GameObject turretUpLevPrefab; // 武器升级后的prefab;
public int UpLevCost; // 升级消耗;
}
第十二课:监听炮台选择的事件(保存选择的炮台)
- 在UI的togger上,可以区别图标是否被点击。会抛出OnValueChange事件。
- 在BuildManager上实现监听函数,并绑定到UI上。
- 阿萨德
第十三课:检测点击哪个MapCube
- 射线检测,点击了哪个方块。先检查下方块上是否有炮台,有炮台弹出销毁界面,没有则创建。
- 给方块Cube添加一个Layer,这样射线检测的时候,只检测和MapCube的碰撞。
void Update()
{
do
{
if(!Input.GetMouseButtonDown(0)){
break; // 鼠标没有按下;
}
if(EventSystem.current.IspointerOverGameObject()){
break; // 属性按在了UI上;(检测鼠标调用该函数不需要传入参数,但是手机上的话,需要传入参数 Help=>Manual查看Api接口说明)
}
// 射线判断点击在哪个方块上;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); // 将鼠标点击的位置转换为一条射线;
RaycastHit hit;
// p1:射线 p2:碰撞结果 p3:最大距离 p4:需要检测的layer层,如果不传参数,就和所有的层做检测;
bool isCollider = Physice.Raycast(ray,out hit,1000,LayerMask.GetMack("MapCube"));
if(!isCollider){
break;
}
GameObject mapCube = hit.collider.GameObject; // 得到碰撞到的MapCube;
// &&& 开始炮台的建设;
}
while(false);
}
第十四课:检查是否足够可以创建炮塔
第十五课:资源管理(金钱消费和更新UI)
-
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
报错NullReferenceException
,原因是之前在场景中加了两个摄像机Camera,后来我删除了一个, 应该是把MainCamera删除了。解决办法是:把剩下的那个摄像机的Tag修改为MainCamera
即可。
第十六课:钱不够,制作动画提示效果
- 制作Animations动画:
-
选中需要制作动画的文本,然后点击
Window
>Animation
,打开Animation执制作器。 -
拖动红色的竖线,右键
Add Key
就可以添加动画的关键帧。 -
制作好以后,Food>Text下面就会挂上一个Animator组件,其中的controller指向了刚刚制作的动画。双击打开Text动画的状态机。
-
动画有一个
Entry
状态,这是动画的入口。原先Entry默认指向food_flicker
。因为需求是钱不够才会播放food_flicker
闪烁动画。所以右键新建一个Empty
状态,Entry默认指向Empty状态。然后新增两条线路,分别指向Empty
和food_flicker
。
-
第十七课:炮台的实例化
第十八课:做特效
-
在场景中新建空物体,重命名为
BuildEffect
。在BuildEffect
下新建一个Particle System
粒子系统。 -
旋转特效,设置x为-90,使其特效朝上运动 。
-
点击
BuildEffect
>Renderer
(Renderer
就是每一个粒子的效果),将RenderMode
修改为Mesh
。Mesh
就是一个网格。这里的游戏例子使用Cube
网格。 -
然后为Mesh创建一个
Material
(材质),修改一下材质的颜色属性Albedo
。保存命名为BuildEffectMaterial
。Smoothness
控制材质是否反光。 -
将新创建的材质,拖动到特效
BuildEffect
的Renderer
中的Material
。至此,我们完成了一个从下往上发散的特效。但是我们的建造特效,希望是从建筑中心,向四周发散的。所以我们需要调整一下参数。
-
首先,调节
Particle System
>Start Size
,设置为0.6
。再把Particle System
>Shape
的Shape
属性设置为Circle
,就可以看到特效向水平方向四周发散。
-
特效默认是循环的,这里我们只在创建的时候,发散一次就行。
7.1 修改
Particle System
>Emmission
,将Rate over Time
设置为0。
7.2Bursts
设置为一次发散30个cube小方块。
7.3 特效的持续时间,修改Start LifeTime
这里设置为1
秒。
7.4 修改Duration
为1
,表示每次循环时长为1
秒。
7.5 修改Size over Lifetime
,特效从开始到结束,是从大逐渐变小的。
-
这里做完特效,是水平扩散的。如果想要特效还是稍微朝上发散的话。需要修改
Particle System
>Shape
>Shape
的模式改为Cone
(圆锥体),整个特效就是下面这种效果。 -
特效有个属性
Looping
,控制其是否循环播放。
第十九课:炮台搜敌
- 修改
Missile
炮弹的属性:Smoothness
控制透明度,Metalic
控制金属性。 - 通过触发器来检测,哪些敌人进入到了攻击范围之内。
- 点击预制体>
Add Component
>Sphere Collider
添加一个球形触发器。 - 勾上属性
Is Trigger
,表示该物体是个触发器。 - 修改
Radius
,调整大小,就可以看到如下触发器。
- 点击预制体>
- 触发器要能正常检测敌人进入范围,需要添加刚体。
- 给预制体加上
Rigidbody
属性,去掉Use Gravity(使用重力)
属性。 - 在代码中定义敌人列表,如果敌人进入触发器范围,会回调函数
void OnTriggerEnter(Collider col)
;同理,离开触发器范围,会回调函数void OnTriggerEnter(Collider col)
。
- 给预制体加上
- 如何识别是敌人进入了触发器范围?
- 给所有的敌人添加一个
Enemy
的标签。
- 如果遇到炮台没有检测到敌人,可能是敌人没有添加
碰撞器
组件,检查一下对应的prefab,给敌人的prefab也添加上Sphere Collider
即可。
- 给所有的敌人添加一个
碰撞检测的效率优化:
在Edit
>Project Settings
>Physics
中,可以配置指定的层1
和层2
做物理的碰撞检测。给所有的敌人添加Enemy
Layer,给所有的武器添加Weapon
Layer。然后在Physics
中如下配置:
第二十课:制作子弹
- 在炮台的枪管前面,创建一个空物体,作为
射击开火点
,随后的子弹初始化位置会被设置在这里。 - 建造一个子弹的预制体,然后在Attack()函数被调用的时候,将子弹创建在fire_pos位置。
第二十一课:子弹飞行
第二十二课:子弹碰撞盒
- 创建一个
球体
作为标准炮台
的子弹,调整子弹的缩放。 - 为
标准炮台子弹
创建材质,添加刚体和碰撞盒。 - 给子弹添加
Bullet.cs
脚本,通过触发函数OnTriggerEnter
来检测和敌人的碰撞。
void OnTriggerEnter(Collider col)
{
if(col.tag == "Enemy")
{
col.GetComponent<Enemy>().Damage(damage); // 1.让敌人掉血;
GameObject.Instantiate(explosionEffect,transform.position,transform.rotation); // 2.播放受击特效;
Destroy(this.gameObject); // 3.销毁子弹
}
}
第二十三课:创建爆炸特效
- 创建
Particle System
,调整Renderer
>RenderMode
>Mesh
>Cube
。 - 调整
Shape(发散属性)
,勾选Sphere
表示球体,向四周发散。 - 只要播放一次,设置
Emission>Rate over Time
为0。Rate over Distance
添加一个范围数量60~80
的方块。 - 爆发的速度:
Start Speed
调大一点,则特效播放的速度也会加快。 Start Lifetime
控制存在的时长。Size over Lifetime
拖动曲线,可以控制特效的方块由大到小,或者由小到大。- 不需要循环播放的特效取消
Looping
。
- 子弹的速度如果太快了,子弹的刚体可能和敌人的刚体,检测不到。(可以通过设置刚体的属性
Collision Detection
>Continuous
来提高检测的精度) - 子弹之前到达敌人身上,不会爆炸,原因是没有勾上
Weapon
层,因为在工程设置中,敌人的碰撞体只会和Weapon
层做碰撞检测。 - 特效实例化以后,一定记得要销毁。
GameObject effect = GameObject.Instantiate(explosionEffect,transform.position,transform.rotation);
Destroy(effect,1); // 1s后销毁;
第二十四课:fixbug修复敌人销毁时,子弹销毁
第二十五课:fixbug修复敌人销毁时,炮台的引用
第二十六课:敌人死亡时,销毁特效
第二十七课:手动配置一下所有敌人的销毁特效
第二十八课:控制炮管指向敌人射击子弹
- 在turret.cs中持有一个炮管的变量(Transform turretHead;),在Update()函数中让该变量LookAt()敌人的位置即可。
- 代码完成以后,炮管会进行旋转。但是发现炮管旋转的中心是炮管的1/2处,并不是以炮台的基座为中心旋转点。
- 在StandTurret中新建一个空物体,保证空物体的中心和炮台基座的中心点是一致的,然后将炮管拖动到空物体节点下。这样炮管旋转就以基座为中心了。
- 修改以后,炮管确实围绕基座为中心进行旋转。但是炮管的初始朝向还是不正确。5.
- 我们是以y轴为中心轴,旋转炮管。所以保证蓝色的z轴在初始状态是和炮管的朝向是一致的。所以修改head节点的旋转(旋转90度) ,使蓝色z轴和炮管朝向一致。
第二十九课:
第三十一课:敌人添加血条
- 新建一个Canvas(画布),里面新建一个Slider(滑动条)来实现血条的功能。
- 这里Slider不需要交互,所以去掉选项
Interactable
。 - Slider所处的Canvas和原先的Canvas重合了,准备调整一下Canvas的大小,发现无法调节。需要修改Render Mode为
World Space
。点击这个小正方形,可以调整血条Canvas的大小。 - 因为不需要血条的那个小圆圈,所以
移除Handle Slide Area
。血条的前背景图片,不能完整的覆盖后背景,需要调节一下节点Fill Area
,使得前后背景的大小一致。调节一下Slider的前背景为绿色。 - 将血条Canvas移动到坐标(1,1,1)处,并且绑定到Enemy的节点下面。
- 在敌人脚本中,用Slider类声明变量hpSlider。然后修改控制hpSlider.value的值,来动态显示血条的百分比。
- 制作多个敌人的血条的时候,可以将已经制作好的预制体1的参数,copy复制到预制体2的控件上。
第三十二课:制作一个ui
第三十三课:给按钮添加动画
- button的
Transition
设置为Animation
,可以为按钮添加动画 - button的
Transition
可以点击Auto Gen Animation
,会自动会按钮生成4个动画,分别对应:- normal:常规时按钮的动画
- 鼠标移动到按钮上的动画
- 鼠标点击按钮的动画
- 按钮失效后的动画
第三十四课:给按钮添加动画
第三十五课:给按钮添加动画
第三十六课:给升级面板添加动画
- 实现的效果是从小到大,慢慢从0到1的比例缩放。(
可以用这种方式给UI做动效的效果,比如UI从左到右慢慢滑动消失
) Animation
是动画,一个按钮可以设计多个动画。然后用Animator
状态机来通过trigger
条件来控制不同动画之间的切换。- 先失效再生效,可以修正:点击不同炮塔的时候,没有播放show动画。