Unity 导航和融合动画的同步
2019-08-03 本文已影响0人
周末的游戏之旅
在unity中使用融合动画和导航来做AI是很常见的。
假设现在,我们的AI有一个2D Freefrom Directional的融合动画。那么我们要控制这个AI移动时的动画状态,就需要给动画一个移动的直线速度和旋转的角速度。
如何使用动画来计算出合适的直线速度和角速度?
首先要知道,在导航模式下的三条线(打开Navigation面板并切换到q模式):

第一条线是当前的实际速度
第二条先是期望速度,可以理解为下一步的速度。
这里注意,这里的实际速度是不能设为动画里的直线速度的,因为这个速度和动画的速度不是匹配的。我们要根据期望速度去计算一个速度。

上图中,方块表示玩家,F为玩家的前方,d为导航的期望速度。
因为我们的期望速度是在一帧中计算的,而动画也是一帧一帧的播放的,所以,同是一帧的时间则时间可以忽略。因此我们只需要计算距离即可。那么此时玩家在一帧中想前方移动的距离应当是d在f上的投影a。所以a就是此时的速度。
那么角速度就是a和d的夹角,因为是同一帧内,所以也可以忽略时间。不过在融合动画中使用的角速度是弧度。因此需要做一个专转换。这里还需要注意一点,直接使用Vector3的angle方法计算出来的角度是没有正负的,因此无法直接使用,需要配和叉乘来给角度加上符号。这样就能正确的进行左右转向了。
如此,只需要将计算好的直线速度和角速度赋给融合动画对应的参数即可得到动画中对应的状态。
那么有了动画的状态还不够,还需要设置导航的速度和动画状态匹配。
这个可以在OnAnimatorMove回调方法中设置导航的速度为animator中的一帧移动的距离除以一帧的时间,得到一个真实的实时直线速度。而角速度则不需要计算了,直接将动画中姿势的角度赋给AI的transform即可。
代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class EnemyAnimation : MonoBehaviour {
NavMeshAgent nav;
Animator ani;
float speed;
float angularSpeed;
Transform player;
public float dampSpeed = 1f;
public float deadZone = 4f;
EnemySightingAndHearing enemySightingAndHearing;
PlayerHealth playerHealth;
private void Awake()
{
enemySightingAndHearing = GetComponent<EnemySightingAndHearing>();
nav = GetComponent<NavMeshAgent>();
ani = GetComponent<Animator>();
player = GameObject.FindWithTag(StealthConst.PLAYER).transform;
playerHealth = GameObject.FindWithTag(StealthConst.PLAYER).GetComponent<PlayerHealth>();
}
private void Update()
{
//期望速度在自身前方的分向量
Vector3 forwordDir = Vector3.Project(nav.desiredVelocity, transform.forward);
speed = forwordDir.magnitude;
//计算夹角
float angle = Vector3.Angle(transform.forward, nav.desiredVelocity);
//转成弧度
angle *= Mathf.Deg2Rad;
//添加符号
if (Vector3.Cross(transform.forward, nav.desiredVelocity).y < 0)
{
angle *= -1;
}
//异常处理
if (nav.desiredVelocity == Vector3.zero)
{
angle = 0;
}
//赋值角速度
angularSpeed = angle;
if (Mathf.Abs(angle) <= deadZone && enemySightingAndHearing.playerInSight)
{
angularSpeed = 0;
transform.LookAt(player);
}
//装载参数
ani.SetFloat(StealthConst.PARSPEED_HASH, speed, dampSpeed, Time.deltaTime);
ani.SetFloat(StealthConst.ANGULARSPEED_HASH, angularSpeed, dampSpeed, Time.deltaTime);
if (playerHealth.PlayerAlive())
{
//设置射击参数
ani.SetBool(StealthConst.PLAYERINSIGHT_HASH, enemySightingAndHearing.playerInSight);
}
else
{
//设置射击参数
ani.SetBool(StealthConst.PLAYERINSIGHT_HASH, false);
}
}
private void OnAnimatorMove()
{
//导航和动画匹配
nav.velocity = ani.deltaPosition / Time.deltaTime;
Debug.Log(ani.deltaPosition);
transform.rotation = ani.rootRotation;
}
}