<Stealth>游戏中的动画与寻路控制

最近玩了下Unity AssetStore上《Stealth》游戏,感觉很有意思,所以花了好些时间来读它的代码,感觉最让我值得学习的是其中机器人守卫的动画与寻路控制,运用到了NavMeshAgent和Aniamtor的结合,还有向量投射,点积叉积的运算,所以记录一下:

其中大体思路是: 敌人按给定的四个点巡逻,如果玩家进入视线范围内就转身停下,然后追赶或者射击玩家。

这里写图片描述

敌人守卫使用了带位移的动画,而其寻路导航却是用了NavMeshAgent。 所以做法是:

> 位移:
不直接使用动画所带的位移(就是把Animator里的ApplayRootMotion的勾去掉),而是把动画里的位移读取到NavMeshAgen的速率,再另外自定设置speed。
这样之后NavMeshAgen就根据目标点会产生一个应该达到的速率值desiredVelocity来读取供控制BlendTree动画的两个参数(下面有介绍)。

> 转向:
机器人守卫transform.rotation直接读取动画中的rotation。


在敌人GameObject下的脚本DoneEnemyAnimation写“void
OnAnimatorMove()”,这个方法会在Animator的ApplayRootMotion勾掉后,并且动画带有位移时每帧执行,所以可以在这里读取动画中的位移和转向参数,然后赋给位移NavMeshAgent,以达到原本符合动画的位移速度,当然还有动画的转向角度也读取出来。

把这里的勾去掉,在代脚本里写”OnAnimatorMove()”读取动画位移:

把这里的勾去掉,在代脚本里写"OnAnimatorMove()"读取动画位移

先看看机器人敌人使用的Animator的构成:
一共用了3层Layer,带位移的是”BaseLayer”,另外的”Shooting”和”Gun”是控制开枪手臂对象目标玩家的带IK动画和开枪的动作,它们的权重都是设为1f,也就是当它们两个的执行条件为true时,它们的动画会覆盖掉BaseLayer的动画。
但我们需要关注的只是”BaseLayer”,在”BaseLayer”中使用了”BlendTree”混合动画,在左下角的”Parameter”增加两个参数”AngularSpeed”和”Speed”来控制这个”BlendTree”。

> 如何控制:
1,在Inspector中把”BlenTree”的”BlenType”选择为”2D Freeform Cartersian”;
2,然后把”BlendType”的x和y选择为”AngularSpeed”和”Speed”,然后就可以通过脚本获取到控制这两个参数后控制动画。

也可以移动动画控制坐标中的红点感受下对动画的操作和在Scene中查看两个控制参数的变化


这里写图片描述

机器人的Animator设置好了,要读取到动画的位移,那还需要让机器人敌人根据不同情景做出不同动画,这样才能够读取到这个动作的位移,要不没有动画,当然就不存在动画的位移。

所以有了这样的这个类:DoneEnemyAnimation.cs

using UnityEngine;
using System.Collections;

public class DoneEnemyAnimation : MonoBehaviour
{
    public float deadZone = 5f;                 


    private Transform player;                   
    private DoneEnemySight enemySight;          
    private NavMeshAgent nav;                   
    private Animator anim;                      
    private DoneHashIDs hash;                   
    private DoneAnimatorSetup animSetup;        


    void Awake ()
    {

        player = GameObject.FindGameObjectWithTag(DoneTags.player).transform;
        enemySight = GetComponent<DoneEnemySight>();
        nav = GetComponent<NavMeshAgent>();
        anim = GetComponent<Animator>();
        hash = GameObject.FindGameObjectWithTag(DoneTags.gameController).GetComponent<DoneHashIDs>();


        //因为勾掉Animator的ApplyRootMotion,添加“OnAnimatorMove”,所以动画的位移和转向都不起作用。
        //NavMeshAgent挂到了机器人上,nav导航的转向不一定和动画转向吻合,所以不使用nav的转向,转向是读取动画的转向来直接赋值到transform控制。
        nav.updateRotation = false;

        // 这个获取到动画的层“BaseLayer”层引用
        animSetup = new DoneAnimatorSetup(anim, hash);

        // 设置瞄准和开枪动画层的权重
        anim.SetLayerWeight(1, 1f);
        anim.SetLayerWeight(2, 1f);


        //换成弧度角计算
        deadZone *= Mathf.Deg2Rad;
    }


    void Update () 
    {

        NavAnimSetup();
    }


    void OnAnimatorMove()
    {

        // 设置NavMeshAgent的速度 等于上一帧的移动速度  =动画位移本应向量的增量/所使用的时间增量
        //拿到最后一帧的速度的原因:因为动画的位移速度不一定是均匀的,所以拿到最后一帧以最大的达到动画本身位移的此帧本应的移动
        nav.velocity = anim.deltaPosition / Time.deltaTime;


        // 敌人转向由动画控制
        //敌人的转向仍然由动画本身的转向控制
        transform.rotation = anim.rootRotation;
    }


    void NavAnimSetup ()
    {

        float speed;
        float angle;


        //如果玩家进入了敌人的视线
        if(enemySight.playerInSight)
        {

            //敌人停止
            speed = 0f;

            //为了让敌人转身面向玩家,求出需要转身的角度
            //这个角度 = 敌人的transform.forward向量 和 敌人到玩家的向量 的夹角
            angle = FindAngle(transform.forward, player.position - transform.position, transform.up);
        }
        else
        {

            //向量投影: 巡逻状态是期望速率在前方向上的投影的标量长度
            speed = Vector3.Project(nav.desiredVelocity, transform.forward).magnitude;


            //转向下个目标点需要转的角度
            angle = FindAngle(transform.forward, nav.desiredVelocity, transform.up);


            //如果转向目标点的剩余角度小于死角,则直面向目标点
            //为什么会产生死角,应为读取动画控制的Rotation值,而动画转向是随着机器人走路时会产生左右轻轻摇摆
            if(Mathf.Abs(angle) < deadZone)
            {

                //直接面向目标点,nav的desiredVelocity是nav期望达到的,应该达到的速率
                //面向这个矢量,也就是获得向前的方向,然后直接面向
                transform.LookAt(transform.position + nav.desiredVelocity);
                angle = 0f;
            }
        }


        //这个实力的Setup调用就是控制上面所提到控制BlendTree的两个参数,从而控制动画。之后才能从动画读出位移和转向。
        animSetup.Setup(speed, angle);
    }

    //
    float FindAngle (Vector3 fromVector, Vector3 toVector, Vector3 upVector)
    {

        if(toVector == Vector3.zero)

            return 0f;


        //创建一个float保存 当前向量和目标向量的夹角
        float angle = Vector3.Angle(fromVector, toVector);

        //(获取敌人的法向量,以便和当前机器人守卫的上方向进行点积运算)
        //叉积: 计算当前向量与目标向量的向量积 得到法向量(如果目标向量在当前向量的右边 那么法向量的方向向上).
        Vector3 normal = Vector3.Cross(fromVector, toVector);

        //(法向量与机器人守卫的上方向进行点积运算,这里只是为了知道向左转还是向右转,也就是获取夹角值的正负号)
        //点积: 计算法向量与敌人正上方向量的点积 如果是同方向 值为正 反之则为负
        angle *= Mathf.Sign(Vector3.Dot(normal, upVector));

        // 把角度转换为弧度
        angle *= Mathf.Deg2Rad;

        return angle;
    }
}

上面只是实现了动画移动转向的控制,AI的控制在这个类中:

DoneEnemyAI.cs:
这个类主要是给NavMeshAgent设置目标点和speed,如里面的下面的这个方法:

    void Patrolling ()
    {
        // 设置巡逻速度
        nav.speed = patrolSpeed;

        // 设置目标点
        if(nav.destination == lastPlayerSighting.resetPosition || nav.remainingDistance < nav.stoppingDistance)
        {
            // ... increment the timer.
            patrolTimer += Time.deltaTime;

            // If the timer exceeds the wait time...
            if(patrolTimer >= patrolWaitTime)
            {
                // ... increment the wayPointIndex.
                if(wayPointIndex == patrolWayPoints.Length - 1)
                    wayPointIndex = 0;
                else
                    wayPointIndex++;

                // Reset the timer.
                patrolTimer = 0;
            }
        }
        else
            // If not near a destination, reset the timer.
            patrolTimer = 0;

        // Set the destination to the patrolWayPoint.
        nav.destination = patrolWayPoints[wayPointIndex].position;
    }

就辣么样的完成了看起来毫无违和感的动画与位移转向结合了哈O(∩_∩)O~~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值