视频传送门:https://www.bilibili.com/video/BV15W411976h/?spm_id_from=333.999.0.0&vd_source=a9be95fd9abd5e6f7a6dd9c91565dd34
素材下载地址:https://pan.baidu.com/s/1bd8mM-8MxtPUAuVUS2g5BQ 密码:pb6i
Layout
采用Tall布局
。Project
面板采用One Column Layout
。Scenes文件夹
,存放主场景MainScene
以及后续的副本场景。Material文件夹
,存放各种材质。Hierarchy
中创建空物体,重命名为Plane
作为整个游戏的地表。Material文件夹
中,创建一个Plane的材质
,赋予给主场景中的Plane
,调节Plane
材质颜色即可修改地表的颜色,我这里调整设置为金黄色的地表。Plane材质
的属性smoothness
,设置为0或者1,我选择设置为1,地面就不会出现反光现象。Inspector
窗口去掉主场景中Plane的Mesh Collider组件
,因为该塔防游戏,我们不处理碰撞检测
。Prefab文件夹
,存放后续的各种预制体。新建一个cube
,调整合适的大小、高度,单击选中从Hierarchy
窗口拖到Project
窗口下,就可以把cube
设置为一个prefab
预制体。图1. Uity设置 创建地表 地表材质 |
---|
MapCube
。新建一个空物体命名为Map
,用来挂载管理所有的MapCube
。将所有的地块MapCube拖放到Map层级
下。这样通过在世界中查找Map节点
,就可以获取到所有的MapCube
地块。按住ctrl键
点击cube拖动,每次拖动距离是1m
。如果不按住ctrl键
,拖动距离的精度是任意的
。起点、路径、终点
。图2. 创建地图Map层级、创建地图块MapCube |
---|
删除路径所经过的所有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#脚本文件,编码控制摄像机坐标。Input.GetAxis(“Horizontal”)
获取水平轴滑动;Input.GetAxis(“Vertical”)
获得垂直轴滑动。Update()函数1min跑60帧
,Time.deltaTime可以获取当前Update()处于第几帧。当然,实际上一分钟可以跑超多的帧数,和机器的硬件配置有关。Space.World
,而不能使用自身坐标系Space.Self
。[-1,1]
。Input.GetAxis(“Mouse ScrollWheel”)
。图4. 创建控制摄像机脚本:获取水平轴、垂直轴的滑动值,在Update中实现摄像机前、后、左、右 |
---|
配置路点来实现怪物的寻路功能
。
Way
,在Way层级
下面挂载路径点WayPoint
(因为没有其它功能,所以每一个WayPoint
就是一个空物体)。点击Inspector
下的椭圆体,可以给WayPoint
修改颜色。Way
这个节点,新增一个c#脚本组件:WayPointController.cs
,用来管理所有挂载在Way上面的路径点。wayPointsPosition = new Transform[transform.childCount];
for (int idx=0; idx<wayPointsPosition.Length;++idx)
{
wayPointsPosition[idx] = transform.GetChild(idx);
}
图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) |
---|
Enemy Spawner脚本
,来管理敌人的孵化、生成。StartCoroutine(SpawnEnemy())
;不能继承MonoBehaviour
,是个独立的类。Wave记录了一波怪物的属性
:类型、数量、出生间隔。目前设定一波怪物中,只能出现一种怪物。GameObject.Instantiate(对象,位置,旋转)
yield return new WaitForSeconds(间隔)
图7. 敌人孵化器 |
---|
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; // 升级消耗;
}
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);
}
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
报错NullReferenceException
,原因是之前在场景中加了两个摄像机Camera,后来我删除了一个, 应该是把MainCamera删除了。解决办法是:把剩下的那个摄像机的Tag修改为MainCamera
即可。
在场景中新建空物体,重命名为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.2 Bursts
设置为一次发散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
控制金属性。Rigidbody
属性,去掉Use Gravity(使用重力)
属性。void OnTriggerEnter(Collider col)
;同理,离开触发器范围,会回调函数void OnTriggerEnter(Collider col)
。碰撞检测的效率优化:
在Edit
>Project Settings
>Physics
中,可以配置指定的层1
和层2
做物理的碰撞检测。给所有的敌人添加Enemy
Layer,给所有的武器添加Weapon
Layer。然后在Physics
中如下配置:球体
作为标准炮台
的子弹,调整子弹的缩放。标准炮台子弹
创建材质,添加刚体和碰撞盒。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后销毁;
Interactable
。World Space
。点击这个小正方形,可以调整血条Canvas的大小。移除Handle Slide Area
。血条的前背景图片,不能完整的覆盖后背景,需要调节一下节点Fill Area
,使得前后背景的大小一致。调节一下Slider的前背景为绿色。Transition
设置为Animation
,可以为按钮添加动画Transition
可以点击Auto Gen Animation
,会自动会按钮生成4个动画,分别对应:
可以用这种方式给UI做动效的效果,比如UI从左到右慢慢滑动消失
)Animation
是动画,一个按钮可以设计多个动画。然后用Animator
状态机来通过trigger
条件来控制不同动画之间的切换。