3D RPG Course (六)实现Player的攻击动画

一、攻击动画的设置

如图,为PlayerAnim添加攻击动画,设置一个名为Attack的Trigger参数用于触发攻击动画

 

注意:Locomotion => BaseAttack的过程不需要设置动画退出时间,并且要添加Attack参数以触发动画

 

但是,Base Attack => Locomotion的过程需要设置1秒的动画退出时间以保证攻击动画完全播放,并且此处不需要设置Attack参数(Trigger就像一个按钮,按下去就会弹起来)

 

二、通过订阅事件实现鼠标点击朝敌人移动

类似于鼠标点击地面移动,我们需要在MouseManager中创建一个事件来连接鼠标点击敌人与玩家向敌人移动的操作,不同的是,此处的Action事件需要传入一个GameObject的数据(敌人为GameObject类型)

 public event Action<GameObject> ClickToAttackEvent;  //创建鼠标点击敌人的事件

由于这里不同事件的触发是通过gameObject.tag辨别的,故MouseManager中的事件触发代码可做以下优化:

 /// <summary>
    /// 鼠标点击控制
    /// </summary>
    public void OnMouseClick()
    {
        if(Input.GetMouseButtonDown(0) && hitInfo.collider != null)  //防止鼠标点击地图外时返回引用为空的错误
        {
            switch (hitInfo.collider.gameObject.tag) 
            {
                case "Ground":
                    {
                        ClickToMoveEvent?.Invoke(hitInfo.point);  //触发点击地面事件
                        break;
                    }
                case "Enemy":
                    {
                        ClickToAttackEvent?.Invoke(hitInfo.collider.gameObject);  //触发点击敌人事件
                        break;
                    }
            }
        }
    }

相应的,也要在PlayerController脚本中订阅该事件。由于该事件需要可接受GameObject类型形参的方法,故需要先创建一个这样的方法来接收事件传入的敌人信息(不能直接传入协程方法是因为协程的方法具有IEnumerator的返回值,与Action事件不符)

 MouseManager.Instance.ClickToAttackEvent += MoveToAttackEvent;  //订阅鼠标点击敌人事件
    /// <summary>
    /// 接收鼠标点击敌人的信息
    /// </summary>
    /// <param name="obj"></param>
    private void MoveToAttackEvent(GameObject obj)
    {
        if(obj.gameObject != null)  //判断攻击的敌人物体存在
        {
            AttackTarget = obj;
        }
    }

三、使用协程实现移动到敌人面前并攻击的效果

定义一个协程方法MoveToAttack()

首先,判断敌人是否在攻击距离之外,如果是,则Player要持续向敌人移动;当敌人进入攻击距离,则跳出while循环,进入攻击判定

注意:此处的攻击距离设置不当,会导致玩家到敌人面前时把敌人推开,或当敌人体积过大时会无法跳出循环。经过调试,此处的攻击范围判定为(玩家到敌人的距离)-(敌人导航代理半径)> 1(该数值可根据武器长度调整)时,应向敌人移动。同时,Player的导航代理应将Stopping Distance = 1(距离目标点1个单位时停下)

    /// <summary>
    /// 向敌人移动并攻击
    /// </summary>
    /// <returns></returns>
    private IEnumerator MoveToAttack()
    {
        //攻击距离判断
        //调整距离避免玩家推着敌人走的情况(手感优化)
        while(Vector3.Distance(AttackTarget.transform.position,transform.position) - AttackTarget.GetComponent<NavMeshAgent>().radius > 1)
        {
            agent.SetDestination(AttackTarget.transform.position);
            yield return null;
        }

        transform.LookAt(AttackTarget.transform);  //使Player向前的方向指向敌人
    }

 然后我们在上面的订阅事件的方法中启用这个协程即可

 /// <summary>
    /// 接收鼠标点击敌人的信息
    /// </summary>
    /// <param name="obj"></param>
    private void MoveToAttackEvent(GameObject obj)
    {
        if(obj.gameObject != null)  //判断攻击的敌人物体存在
        {
            AttackTarget = obj;
            StartCoroutine("MoveToAttack");  //使用方法名字符串启用协程
        }
    }

 

进行完上面的操作后,我们会发现一个问题:当点击了敌人并向敌人移动时,若鼠标点击地面也不会中断移动,Player会一直朝着敌人走。这时。我们需要在点击地面移动的订阅事件中将该协程停止

注意:此处StopCoroutine()方法的调用方式应与StartCoroutine()一致,此处都使用方法的字符串名称调用,因为直接传入方法会出bug(我也不知道为什么)

 /// <summary>
    /// 地面移动
    /// </summary>
    /// <param name="destination"></param>
    private void MoveToPoint(Vector3 destination)
    {
        StopCoroutine("MoveToAttack");  //暂停协程与启用协程均采用方法名字符串调用(二者调用方式需保持一致),用方法会出bug
        //StopAllCoroutines();  //使用该方法会暂停所有协程
        agent.SetDestination(destination);
    }

 

四、设置一个简单的攻击冷却时间

先定义一个冷却时间

private double lastAttackTime = 0.5f;  //攻击CD

冷却时间每帧衰减,在Update方法中实现

 private void Update()
    {
        lastAttackTime -= Time.deltaTime;  //冷却衰减
    }

进行冷却判断,如果冷却时间<= 0,则可执行攻击,执行后重置CD。将此段代码放入协程方法即可

//攻击判断(冷却时间结束后即可进行下一次攻击)
        if (lastAttackTime <= 0)
        {
            PlayerAnim.SetTrigger("Attack");

            //重置CD
            lastAttackTime = 0.5f;
        }

 

五、实现上述功能的PlayerController脚本代码

public class PlayerController : MonoBehaviour
{
    private NavMeshAgent agent;
    private Animator PlayerAnim;

    //攻击敌人相关参数
    private GameObject AttackTarget;
    private double lastAttackTime = 0.5f;  //攻击CD

    private void Awake()
    {
        agent = GetComponent<NavMeshAgent>();
        PlayerAnim = GetComponent<Animator>();
    }

    private void Start()
    {
        MouseManager.Instance.ClickToMoveEvent += MoveToPoint;  //订阅鼠标点击地面移动事件
        MouseManager.Instance.ClickToAttackEvent += MoveToAttackEvent;  //订阅鼠标点击敌人事件
    }

    private void Update()
    {
        lastAttackTime -= Time.deltaTime;  //冷却衰减
    }

    /// <summary>
    /// 地面移动
    /// </summary>
    /// <param name="destination"></param>
    private void MoveToPoint(Vector3 destination)
    {
        StopCoroutine("MoveToAttack");  //暂停协程与启用协程均采用方法名字符串调用(二者调用方式需保持一致),用方法会出bug
        //StopAllCoroutines();  //使用该方法会暂停所有协程
        agent.SetDestination(destination);
    }

    /// <summary>
    /// 接收鼠标点击敌人的信息
    /// </summary>
    /// <param name="obj"></param>
    private void MoveToAttackEvent(GameObject obj)
    {
        if(obj.gameObject != null)  //判断攻击的敌人物体存在
        {
            AttackTarget = obj;
            StartCoroutine("MoveToAttack");  //使用方法名字符串启用协程
        }
    }

    /// <summary>
    /// 向敌人移动并攻击
    /// </summary>
    /// <returns></returns>
    private IEnumerator MoveToAttack()
    {
        //攻击距离判断
        //调整距离避免玩家推着敌人走的情况(手感优化)
        while(Vector3.Distance(AttackTarget.transform.position,transform.position) - AttackTarget.GetComponent<NavMeshAgent>().radius > 1)
        {
            agent.SetDestination(AttackTarget.transform.position);
            yield return null;
        }

        transform.LookAt(AttackTarget.transform);  //使Player向前的方向指向敌人

        //攻击判断(冷却时间结束后即可进行下一次攻击)
        if (lastAttackTime <= 0)
        {
            PlayerAnim.SetTrigger("Attack");

            //重置CD
            lastAttackTime = 0.5f;
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值