Unity笔记-05

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,从而让生成器继续生成敌人

实际调试的一点问题

偶尔会有物体偏移规定路线随意漂移,目前没有找到原因,不知道是否是电脑性能问题。

总结

项目的敌人模块已经基本完成,还有一些小瑕疵和问题待解决。之后还有武器模块,以及其他更多的模块要做。

在那之前,我需要完全消化和掌握敌人模块的相关知识点。并且要擅长代码的复用和简洁化,游戏的优化只有从这些细节做起才能慢慢进步。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值