Unity笔记-05
练习项目敌人模块最终脚本(最后一部分以及总结)
其他内容请阅《Unity笔记-04》
项目需求(最后一部分):
为每人随机选择一条可以使用的路线,要求:敌人类型,产生的时间随机
最后一部分代码以及其他代码的最终修改
敌人生成器类
/// <summary>
/// 敌人生成器
/// </summary>
public class EnemyProduce : MonoBehaviour
{
/// <summary>
/// 敌人类型
/// </summary>
public GameObject[] EnemyType;
/// <summary>
/// 最大敌人数量
/// </summary>
public int maxCount;
/// <summary>
/// 最小敌人数量
/// </summary>
public int minCount;
/// <summary>
/// 场上存活的敌人数量
/// </summary>
[HideInInspector]
public int aliveCount=0;
/// <summary>
/// 路线集
/// </summary>
private WayLine[] lines;
/// <summary>
/// 敌人生成点
/// </summary>
private Vector3 BirthPoint;
/// <summary>
/// 制造敌人
/// </summary>
private void ProduceEnemy()
{
//随机延迟时间生成敌人
if (aliveCount < maxCount)
{
Invoke("CreateEnemy", Random.Range(1, 10));
aliveCount++;
}
else
{
return;
//不生成敌人
}
}
/// <summary>
/// 生成一个敌人
/// </summary>
private void CreateEnemy()
{
//随机选择一条可以使用的路线
WayLine[] ways = SelectIsUseableLines();
WayLine way = ways[Random.Range(0, ways.Length)];
//生成敌人类型的随机数,左闭右开
int RandomType = Random.Range(0, EnemyType.Length);
GameObject Enemy = Object.Instantiate(EnemyType[RandomType],BirthPoint,Quaternion.identity);
//配置物体信息
Enemy.GetComponent<EnemyAI>().wayLine=way;//配置当前敌人的寻路路线
Enemy.GetComponent<EnemyStatusInfo>().produce = this;//传递敌人生成器引用
}
/// <summary>
/// 初始化路线集合
/// </summary>
private void CalculateWayLines()
{
//初始化路线
lines = new WayLine[transform.childCount];//定义路线数组长度
for(int i = 0; i < lines.Length; i++)
{
Transform Wayline_i = transform.GetChild(i);//通过索引获得每一条路线(路点集)
lines[i] = new WayLine(Wayline_i.childCount);//创建路线实例
//lines[i].MyProperty = new WayPoints[Wayline_k.childCount];//定义路点数组长度
//lines[i].IsUsable = true;//设置路线可用
//以上两行代码耦合进Wayline的构造函数里
for(int j = 0; j < Wayline_i.childCount; j++)//初始化路点
{
lines[i].MyProperty[j] = new WayPoints(Wayline_i.GetChild(j).position);//创建点实例
//lines[i].MyProperty[j].position = Wayline_i.GetChild(j).position;//赋值:位置
//lines[i].MyProperty[j].IsUseable = true;//设置路点可用
}
}
}
private WayLine[] SelectIsUseableLines()
{
List<WayLine> SelectLines = new List<WayLine>();
foreach (var item in lines)
{
if (item.IsUsable) SelectLines.Add(item);
}
return SelectLines.ToArray();
}
private void Start()
{
BirthPoint = transform.GetComponentInParent<Transform>().position;//获得出生点
CalculateWayLines();//初始化路线集合
}
private void Update()
{
ProduceEnemy();
}
}
敌人生成器:用于生成敌人对象
生成对象的方法:
Object.Instantiate(需要生成的对象(GameObject),生成点(Vector3),四元数旋转值(Quaternion));
这里需要初始化路线集合,不能再像之前调试的时候通过拖拽得到。敌人生成器脚本给到路线集合的父空对象,该父空对象的孩子就是所有的路线,再通过上述的初始化代码得到所有的路线即可。
延时创造敌人可用Invoke(方法名,延迟时间)
方法达到效果。
而敌人类型暂时使用拖拽从Inspector
里获得
在创建敌人对象的时候,要配置相关信息,还要传递生成器引用,告诉这个单位它的生成器是谁,以便在单位死亡的时候好找到对应的生成器减少当前敌人存货数量,现阶段能力不够,以后会用到委托机制。
路线类
/// <summary>
/// 路线类
/// </summary>
public class WayLine
{
/// <summary>
/// 当前路点坐标
/// </summary>
public WayPoints[] MyProperty;
/// <summary>
/// 当前路线是否被占用
/// </summary>
public bool IsUsable;
public WayLine(int wayPointCount)
{
MyProperty = new WayPoints[wayPointCount];
IsUsable = true;
}
}
/// <summary>
/// 路点类
/// </summary>
public class WayPoints
{
/// <summary>
/// 点的坐标
/// </summary>
public Vector3 position;
/// <summary>
/// 点是否可用
/// </summary>
public bool IsUseable;
public WayPoints(Vector3 position)
{
this.position = position;
IsUseable = true;
}
}
路线与路点增加了构造函数,是为了使得敌人生成器中的代码看起来简洁一些,实例话和位置只需要传入构造函数即可。
敌人AI类
/// <summary>
/// 敌人AI
/// </summary>
[RequireComponent(typeof(EnemyAnimation))]
[RequireComponent(typeof(EnemyMotor))]
[RequireComponent(typeof(EnemyStatusInfo))]
public class EnemyAI : MonoBehaviour
{
public enum State
{
/// <summary>
/// 攻击状态
/// </summary>
Attack,
/// <summary>
/// 寻路状态
/// </summary>
PathFinding
}
/// <summary>
/// 运动类
/// </summary>
private EnemyMotor motor;
/// <summary>
/// 动画类
/// </summary>
private EnemyAnimation anim;
/// <summary>
/// 敌人当前状态
/// </summary>
private State currentState;
/// <summary>
/// 路线
/// </summary>
public WayLine wayLine;
/// <summary>
/// 初始化
/// </summary>
private void Start()
{
#region 初始化工具
motor = this.GetComponent<EnemyMotor>();//初始化移动马达
anim = this.GetComponentInParent<EnemyAnimation>();//获得父空物体上挂的动画脚本
#endregion
//这里删除之前调试时候的路线初始化代码
//#region 初始化路线
//wayLine = new WayLine();//创建路线实例
//wayLine.MyProperty = new WayPoints[Points.Length];//初始化点集长度
//for (int i = 0; i < Points.Length; i++)
//{
// wayLine.MyProperty[i] = new WayPoints();//创建点实例
// wayLine.MyProperty[i].position = Points[i].position;//初始化路点集
// wayLine.MyProperty[i].IsUseable = true;
//}
//#endregion
#region 初始化状态
currentState = State.PathFinding;
#endregion
}
private float AttackTime=0;
private float intervalTime=2;
/// <summary>
/// 渲染更新
/// </summary>
private void Update()
{
switch (currentState)
{
case State.Attack:
Attack();
break;
case State.PathFinding:
PathFinding();
break;
}
}
private void PathFinding()
{
//播放动画
//执行寻路
//检查状态
//修改状态
if (!motor.PathFinding(wayLine))
{
currentState = State.Attack;
}
}
private void Attack()
{
if (!anim.action.isPlay(anim.AttackAnimation))
{
//播放闲置动画,暂无
}
if (AttackTime < Time.time)
{
//执行攻击
//播放攻击动画
anim.action.Play(anim.AttackAnimation);
AttackTime += intervalTime;//动画播放时间-间隔也就是攻击后摇
}
//播放攻击后摇动画
//检查状态
//修改状态
}
}
敌人AI类删除之前调试时候的路线初始化代码
敌人马达类
/// <summary>
/// 敌人马达运动类,提供前进,注视旋转,寻路功能
/// </summary>
public class EnemyMotor : MonoBehaviour
{
/// <summary>
/// 设置旋转速度以及移动速度
/// </summary>
public float speed=2;
/// <summary>
/// 记录位移向量差
/// </summary>
private Vector3 Relative;
/// <summary>
/// 记录转到下次路点的旋转坐标
/// </summary>
private Quaternion rotation;
/// <summary>
/// 记录当前走到的路点索引
/// </summary>
private int count = 0;
/// <summary>
/// 记录上一个路点的索引
/// </summary>
private int lastCount;
#region 调试用数据
/ <summary>
/ 手动输入路点信息
/ </summary>
//public Transform[] Points;
/ <summary>
/ 获得路点信息,并封装
/ </summary>
//private WayLine WayLine;//路线
#endregion
/// <summary>
/// 获得控制件下对象的动画组件
/// </summary>
private Animation anim;
/// <summary>
/// 前进
/// </summary>
public void MoveForward(WayPoints Point)
{
if (Point.IsUseable)//判断该路点当前是否被占用
{
//transform.Translate(Relative.normalized*Time.deltaTime, Space.World);
transform.position = Vector3.MoveTowards(transform.position, Point.position,speed*Time.deltaTime);
anim.CrossFade("EnemyRun");
//注意:如果要判断是否到达,不要用“==”来判断,因为计算机是离散的,精度不可能完全精准,要用两点间的距离来判断
if (Vector3.Distance(transform.position, Point.position)<0.01f)
{
anim.Stop("EnemyRun");
lastCount = count;//存储上一个路点
Point.IsUseable = false;
count++;//当前路点自增
}
}
}
/// <summary>
/// 注视旋转
/// </summary>
public void LookRotate(Quaternion rotation)
{
this.transform.rotation = Quaternion.Slerp(this.transform.rotation,rotation,3*speed*Time.deltaTime);
}
/// <summary>
/// 寻路
/// </summary>
public bool PathFinding(WayLine wayLine)
{
wayLine.IsUsable = false;
if (wayLine!= null && count < wayLine.MyProperty.Length)
{
WayPoints Point = wayLine.MyProperty[count];
Relative = Point.position - this.transform.position;//获得向量差值
rotation = Quaternion.LookRotation(Relative);//获得旋转
if (transform.rotation != rotation)//如果方位不相同,那么进行旋转
{
LookRotate(rotation);
}
else
{
MoveForward(Point);
wayLine.MyProperty[lastCount].IsUseable = true;
}
return true;
}
else
{
return false;
}
}
/// <summary>
/// 初始化阶段
/// </summary>
private void Start()
{
anim = this.GetComponentInChildren<Animation>();
}
}
详解请看《Unity笔记-04》
敌人动画类与动画工具类
无改动,详情请看《Unity笔记-04》
敌人状态信息类
/// <summary>
/// 敌人状态信息类,提供敌人生命值,受伤,阵亡等功能
/// </summary>
public class EnemyStatusInfo : MonoBehaviour
{
[HideInInspector]
public EnemyProduce produce;
/// <summary>
/// 当前生命值
/// </summary>
private int HP;
/// <summary>
/// 最大生命值
/// </summary>
public int MaxHP;
/// <summary>
/// 受伤,减少生命值
/// </summary>
public void Damage(int attackNumber)
{
if (HP > 0)
{
HP -= attackNumber;
}
else
{
Death();
}
}
/// <summary>
/// 销毁延迟时间
/// </summary>
private float deathDelay=5;
/// <summary>
/// 阵亡
/// </summary>
public void Death()
{
var anim = this.GetComponent<EnemyAnimation>();
//播放死亡动画
anim.action.Play(anim.DeathAnimation);
//销毁物体
Destroy(this.gameObject, deathDelay);
//设置路线变为可用
this.GetComponent<EnemyAI>().wayLine.IsUsable = true;
//告诉生成器单位以及死亡,当前存活数量-1
produce.aliveCount--;
}
}
在敌人状态类拿到生成器引用,在敌人死亡的时候,设置路线变为可用,并且告诉生成器存活数量-1,从而让生成器继续生成敌人
实际调试的一点问题
偶尔会有物体偏移规定路线随意漂移,目前没有找到原因,不知道是否是电脑性能问题。
总结
项目的敌人模块已经基本完成,还有一些小瑕疵和问题待解决。之后还有武器模块,以及其他更多的模块要做。
在那之前,我需要完全消化和掌握敌人模块的相关知识点。并且要擅长代码的复用和简洁化,游戏的优化只有从这些细节做起才能慢慢进步。