——个人笔记
敌人自动按路径行走
地图我是用了一个一个的cube组成的,然后挖空一些变成路,一个起点一个终点,然后在每个转弯的地方都加一个名为ponit的空物体来记录位置。
最后,我们获取所有的点,敌人只要从起点开始走向下一个ponit,到了再找下一个point继续走下去到终点或者死亡。
全地图:
部分point点标记如图:
我们会把所有的点放在一个物体下面,然后我们可以写一个脚本(挂载在图中的Points)来获取所有点,这样可以方便一会使用。
代码:
public class Points : MonoBehaviour {
public static Transform[] points;
void Awake()
{
//为什么用循环,而不用GetComponentsInChildren,
//因为我们要获取的事transform,他会把父级的也获取到。
points = new Transform[transform.childCount];
for (int i = 0; i < points.Length; i++)
{
points[i] = transform.GetChild(i);
}
}
}
那么在Enemy脚本中,我们就可以直接获取这些点来用了。
private Transform[] points ;
void Start () {
points = Points.points;
}
然后写一个Move的方法,在Update里调用就行了。
private int index=0;//点的索引
void Move()
{
//如果到达终点那么销毁自身
if (index >= points.Length)
{
//todo游戏失败或者扣血之类的操作
Destroy(this.gameObject);
return;
}
//移动
transform.Translate((points[index].position - transform.position).normalized*Time.deltaTime*speed);
//接近(到达)那个点,就往下一个点前进
if(Vector3.Distance(points[index].position,transform.position)<0.2)
{
++index;
}
}
敌人生成
敌人生成,我们最起码要做一个敌人的prefabs,然后考虑到我们的敌人是一波一波来袭,而且敌人数量是不确定的,生成间隔时间也不确定,那么我们先定义一个类Wave来表示这些数据。
[System.Serializable]//可序列化,这样我们public才能在面板看得见
public class Wave {
//敌人prefabs即长什么样子和属性
public GameObject enemyPrefab;
//个数
public int count;
//每个生成的间隔时间
public float time;
}
那么有了这个,我们只要定义一个数组,然后根据数据去生成就可以了。不明白?反手代码加注释,自己看一遍,就会懂。(有两个间隔时间,一个是一波一波敌人的间隔,一个是一个一个敌人之间的间隔,分清楚)
public class EnemySpawner : MonoBehaviour {
public Wave[] waves ;
//记录已存在敌人个数
public static float nowCount;
//没一波之间的间隔
float waveRate = 0.2f;
void Start () {
//开启协程(如果不懂协程可以自己去百度一下哦)
StartCoroutine(SpawnerEnemy());
}
IEnumerator SpawnerEnemy()
{
//遍历数组里面的wave信息
foreach(Wave wave in waves)
{
//根据wave里面的count来生成对应的敌人
for(int i=0;i 0)
{
yield return 0;
}
//现在没有敌人了,那么我们要等待一波之间的间隔时间,
//然后继续下一波的敌人来袭
yield return new WaitForSeconds(waveRate);
}
}
}
敌人销毁
敌人销毁分为两种,一种是被打死了,一种是到达终点了,在Move方法里面已经有到达终点时候到达终点销毁的情况,另外一种就是在Update或者在扣血的方法里加if(hp<=0) { Destroy(this.gameObject); }
这样就可以了。
塔的选择
塔的选择我使用了UI里面的Toggle和Toggle Group来完成,toggle就是选择的时候会有一个选择的状态显示,然后Toggle Group就是在其内的所有Toggle一次只能选择一个。在toggle_Group添加Toggle Group组件,然后下面创建3个toggle,且设置他们的Group为toggle_Group,如图:
然后我们就要编辑toggle使得其变成我们的塔菜单,首先如图中的Background的Image改为塔的图(大小自定义),Checkmark的Image改为一个圆设置半透明(大小比塔的图略大一点),然后Label的text改为¥+金钱数
,最后效果图就会这样:
这里只是实现了简单的选择功能,而我们真的的选择其实并没有完成。因为我们选择了哪一个塔,我们的电脑并不知道,那么,就要添加一个TurretData(塔的数据)了,然后我们要自己设置好每个塔的数据,然后设置其每次按下,就要获取那个按下塔的信息。
塔的信息(这里就用这三个塔做例子):
[System.Serializable]
public class TurretData {
//塔的prefabs
public GameObject turretPrefabs;
//升级后的prefabs
public GameObject turretUpPrefabs;
//建造的钱数
public float buildCost;
//升级的钱数
public float upCost;
//塔的类型
public Type type;
}
//塔的类型枚举,这里名称就是按图的顺序。
public enum Type
{
Turret,
MissileLauncher,
LaserBeamer,
}
那么我们怎么获取数据?首先获取这个数据要来干什么?是的用来建造,那么我们就要在管理建造的脚本(BuildManager)里来初始所有塔的信息和获取当前点击的塔的信息:
//所有塔的信息
public List turretDataslist;
//当前选择的塔的信息
private TurretData buildTurret;
//初始为选择第一个塔
void Start()
{
buildTurret = turretDataslist[0];
}
//定义三个触发按钮事件,为什么要有一个bool参数呢
//因为toggle触发的条件就是只要状态改变就会触发一次按钮事件
//我们只需要它从false变为true时才触发,所以就要加一个bool来限制
public void OnTurret(bool ison)
{
if(ison)
{
buildTurret = turretDataslist[0];
}
}
public void OnMissileLauncher(bool ison)
{
if (ison)
{
buildTurret = turretDataslist[1];
}
}
public void OnLaserBeamer(bool ison)
{
if (ison)
{
buildTurret = turretDataslist[2];
}
}
塔的生成
刚刚我们已经获取了要建造塔的信息了,那么我们现在要建造塔就很简单了,按下鼠标左键那么就建造了!可是,没那么简单,首先判断有没有按下鼠标左键,然后,我们还要判断是不是按在了cube(就是放塔的位置)身上和鼠标是不是在UI(选择塔的菜单)上,金币是否足够,cube是否已经有塔了。是不是很多条件,一步一步写就行了!还是在BuildManager脚本里写
void Update()
{
//按下鼠标左键
if(Input.GetMouseButton(0))
{
//是否点击在UI上
if(EventSystem.current.IsPointerOverGameObject()==false)
{
//发射射线检测(在MapCube层检测,要把放塔的cube的Layer设置为MapCube)
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
bool isCollider = Physics.Raycast(ray, out hit, 1000, LayerMask.GetMask("MapCube"));
//是否点击在cube上
if (isCollider)
{
//获取cube
MapCube mapCube = hit.collider.GetComponent();
//cube是否已经建造塔了
if(mapCube.turret ==null)
{
//金币是否足够
if(money >= buildTurret.buildCost)
{
//减少金币
ChangeMoney(-buildTurret.buildCost);
//建造塔
mapCube.BuildTurret(buildTurret);
}
}
}
}
}
}
cube上挂载的脚本(建造的代码是在这,上面只是管理)
public class MapCube : MonoBehaviour {
//当前cube上的塔
[HideInInspector]
public GameObject turret;
//建造塔
public void BuildTurret(TurretData turretData)
{
turret = turretData.turretPrefabs;
Instantiate(turret, transform.position, Quaternion.identity);
}
}
塔的攻击
首先我们要给塔一个触发器,来记录敌人进入攻击范围和离开攻击范围;然后,我们要有一个head(选择的枪)和一个fire(子弹生成的地方也就是开火的地方);子弹的prefabs不用讲也知道,最后还要设置一个开火间隔时间。大致要的东西都已经了解了,那么开始动手。
获取在攻击范围的所有敌人(给敌人添加"Enemy"
的标签,塔添加一个触发器):
//存放在范围的敌人的列表
private List enemys = new List();
void OnTriggerEnter(Collider col)
{
if (col.tag == "Enemy")
{
enemys.Add(col.gameObject);
}
}
void OnTriggerExit(Collider col)
{
if (col.tag == "Enemy")
{
enemys.Remove(col.gameObject);
}
}
上面这个很容易理解,但是其实是有bug的上面代码,我们的理解就是敌人进入范围那么我们就把敌人放到可攻击的列表中,离开范围就移除列表,这个逻辑完全正确,可是如果敌人是在可攻击范围内进入了终点或者给打死了,那么敌人就会销毁,但是这样是不会触发到OnTriggerExit
的,这样会导致我们在攻击的时候会报空指针的错误。怎么解决呢?那么我们就在开枪之前判断第一个敌人是否为空,为空就移除,重新再判断,直到第一个不会空才攻击(下面直接return是因为Update里面都是射击处理,才可以这样,如果还有其他处理就用一个循环来解决并把这个循环放到射击前面):
void Update () {
if (enemys.Count > 0)
{
if (enemys[0] == null)
{
enemys.Remove(enemys[0]);
return;
}
}
炮台旋转(瞄准):
if (enemys.Count > 0)
{
if ( enemys[0] != null)
{
//要把Y设置,因为Y也就是上下不用旋转
//一定要把枪口方向和head的Z轴方向一致
Vector3 target = enemys[0].transform.position;
target.y = head.position.y;
head.LookAt(target);
}
}
开火和开火的时间间隔:
private float timer;
private float attackTime=0.5f;
//这里是使得我们建造塔的时候
//已经有敌人在攻击范围,塔会立即开枪
void Start()
{
timer = attackTime;
}
void Update () {
//等待时间一直增加
timer += Time.deltaTime;
//已经超过等待时间,那么可以开火
if (timer > attackTime)
{
GameObject bulletGo = Instantiate(bullet, fire.position, fire.rotation);
bulletGo.GetComponent().SetTarget(enemys[0].transform);
//重新计时
timer = 0;
}
}
上面都是塔攻击的都是分成一部分一部分的如果你能自己组合起来那么就说明你理解且会运用了,不太懂怎么组合看下完整代码再理解一下哦~
Turret脚本的完整代码:
public class Turret : MonoBehaviour {
private List enemys = new List();
public Transform fire;
public Transform head;
public GameObject bullet;
private float timer;
private float attackTime=0.5f;
void Start()
{
timer = attackTime;
}
}
timer += Time.deltaTime;
if (enemys.Count > 0)
{
//炮台指向敌人
if ( enemys[0] != null)
{
Vector3 target = enemys[0].transform.position;
target.y = head.position.y;
head.LookAt(target);
if (timer > attackTime)
{
GameObject bulletGo = Instantiate(bullet, fire.position, fire.rotation);
bulletGo.GetComponent().SetTarget(enemys[0].transform);
timer = 0;
}
}
}
}
void OnTriggerEnter(Collider col)
{
if (col.tag == "Enemy")
{
enemys.Add(col.gameObject);
}
}
void OnTriggerExit(Collider col)
{
if (col.tag == "Enemy")
{
enemys.Remove(col.gameObject);
}
}
}