【Unity】使用RootMotion跟运动驱动NavMeshAgent导航

Unity目前(2020.3)还没有内置开箱即用的将导航与动画整合的方案,这里提供了一个将NavMeshAgent和带RootMotion跟运动的Animator整合的思路,利用RootMotion来驱动导航。

通过RootMotion来使角色向目标点移动,无需担心滑步问题,但需要处理RootMotion数据和NavMeshAgent数据的同步。数据同步过程在 OnAnimatorMove() 方法中执行,主要步骤是:① 禁用NavMeshAgent组件的位置更新(agent.updatePosition = false);② 将RootMotion数据应用到NavMeshAgent组件中,驱动NavMeshAgent更新状态和进行导航模拟(agent.velocity = animator.velocity);③ 【可选】将导航模拟结果设置回角色的Transform上,避免RootMotion将角色移动到导航不可达区域(animator.transform.position = agent.nextPosition)。另外,需要在角色转身时对其移动速度进行衰减处理,以免当角色RootMotion速度太快并且目标点在角色身后附近时,角色陷入圆周运动无法抵达目标点。

如果是不带RootMotion的角色,需要避免角色滑步,将角色的导航移动速度换算成动画状态机的运动BlendTree控制参数即可,例如 animator.SetFloat("LocomotionSpeed", agent.velocity.magnitude/maxLocomotionSpeed) ,这里可能会有更复杂的换算公式,具体取决于BlendTree形式和其参数。

一些额外的值得关注的点:① NavMeshAgent控制角色转向的速度非常慢,即使将AngualrSpeed设置为很大的值也依然很慢,可以禁用NavMeshAgent组件的旋转更新(agent.updateRotation = false),并自行控制角色旋转(建议不要使用Lerp和SLerp,他们在向背面旋转时效果不好);② 如果某一帧时NavMeshAgent组件处于stop状态(agent.isStopped == true),将其取消stop要等到下一帧才能生效,当帧为其设置目标点不会使导航生效(不确定是Unity改了还是之前测试错了,2020LTS没这个问题了)。

主要代码

这段示例代码中,只处理了动画向前移动的单向BlendTree(Idle、Walk、Run),没有处理复杂多方向BlendTree。

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.AI;
#if UNITY_EDITOR
using UnityEditor;
#endifpublic enum RemainingDistanceType
{/// <summary>/// 如果角色指定了目的地,此模式代表角色位置到目的地之间的距离。/// 如果角色指定了移动路径,此模式代表角色位置到移动路径终点之间的距离。/// </summary>PathEnd,/// <summary>/// 如果角色指定了目的地,此模式代表角色位置到目的地之间的距离。/// 如果角色指定了移动路径,此模式代表角色位置到下一个路径点之间的距离。/// </summary>WayPoint,/// <summary>/// 角色位置到下一个转向点之间的位置。/// 转向点可能是移动轨迹中的任意位置,不一定是给定的路径中的路径点。/// </summary>SteeringPoint
}[DisallowMultipleComponent]
[RequireComponent(typeof(Animator))]
[RequireComponent(typeof(NavMeshAgent))]
public class RootMotionNavMeshAgent : MonoBehaviour
{/// <summary>/// 若角色当前朝向与目标朝向之间的夹角小于此角度,则使角色停止转向。/// </summary>public const float RotateStopAngle = 1f;/// <summary>/// 设置是否激活组件。/// </summary>public bool Enabled{get{return enabled;}set{if (enabled && value == false){StopMoving();}enabled = value;}}/// <summary>/// 目标朝向。/// </summary>public Vector3? TargetForward { get; private set; }/// <summary>/// 目的地。/// </summary>public Vector3? Destination { get; private set; }/// <summary>/// 导航路径点(由 <see cref="NavMeshAgent"/> 计算得出)。/// </summary>public IEnumerable<Vector3> NavPathCorners { get { return _navMeshAgent.path.corners; } }/// <summary>/// 移动路径点(由外部指定)。/// </summary>public IEnumerable<Vector3> MovingPath { get { return _movingPath; } }/// <summary>/// 转身速度(角度/秒)。/// </summary>public float AngularSpeed{get { return _navMeshAgent.angularSpeed; }set { _navMeshAgent.angularSpeed = value; }}/// <summary>/// 若角色当前位置与目的地的距离(<see cref="GetRemainingLinearDistance"/>)小于此值(米),则视为到达目的地,使角色停止移动。/// </summary>public float StoppingDistance{get { return _navMeshAgent.stoppingDistance; }set { _navMeshAgent.stoppingDistance = value; }}/// <summary>/// 若角色当前位置与目的地的距离(<see cref="GetRemainingLinearDistance"/>)小于此值(米),则视为接近目标点,使角色开始减速。/// 若沿给定路径点移动,则在接近目标点时,使角色开始向路径中的下一个目标点移动。/// </summary>public float ApproachDistance{get { return _approachDistance; }set { _approachDistance = value; }}/// <summary>/// 控制角色移动的动画参数名称。/// </summary>public string AnimationLocomotionParam{get { return _animationLocomotionParam; }set{_animationLocomotionParam = value;_animationLocomotionParamHash = Animator.StringToHash(_animationLocomotionParam);}}/// <summary>/// 使角色原地站立的动画参数值。/// </summary>public float AnimationStandingValue{get { return _animationStandingValue; }set { _animationStandingValue = value; }}/// <summary>/// 使角色向前移动的动画参数值。/// </summary>public float AnimationMovingValue{get { return _animationMovingValue; }set { _animationMovingValue = value; }}/// <summary>/// 使角色向前移动的动画参数最小值。/// </summary>public float AnimationMovingMinValue{get { return _animationMovingMinValue; }set { _animationMovingMinValue = value; }}/// <summary>/// 角色移动动画参数的Damp时长(秒)。/// </summary>public float AnimationLocomotionValueDampTime{get { return _animationLocomotionValueDampTime; }set { _animationLocomotionValueDampTime = value; }}/// <summary>/// 根据给定点在NavMesh上采样位置时,可以使用的最大偏移距离(米)。/// </summary>public float MaxNavMeshSampleDistance{get { return _maxNavMeshSampleDistance; }set { _maxNavMeshSampleDistance = value; }}/// <summary>/// 在NavMesh上采样位置时,使用的区域遮罩。/// </summary>public int NavMeshAreaMask{get { return _navMeshAreaMask; }set { _navMeshAreaMask = value; }}/// <summary>/// 控制角色移动的动画参数名称。/// </summary>[SerializeField]private string _animationLocomotionParam = "F_MoveSpeed";/// <summary>/// 使角色原地站立的动画参数值。/// </summary>[SerializeField]private float _animationStandingValue = 0.0f;/// <summary>/// 使角色向前移动的动画参数值。/// </summary>[SerializeField]private float _animationMovingValue = 1.0f;/// <summary>/// 使角色向前移动的动画参数最小值。/// </summary>[SerializeField]private float _animationMovingMinValue = 0.3f;/// <summary>/// 角色移动动画参数的Damp时长(秒)。/// </summary>[Range(0f, 0.5f)][SerializeField]private float _animationLocomotionValueDampTime = 0.1f;/// <summary>/// 根据给定点在NavMesh上采样位置时,可以使用的最大偏移距离(米)。/// </summary>[SerializeField]private float _maxNavMeshSampleDistance = 0.5f;/// <summary>/// 在NavMesh上采样位置时,使用的区域遮罩。/// </summary>[SerializeField]private int _navMeshAreaMask = NavMesh.AllAreas;/// <summary>/// 若角色当前位置与目的地的距离(<see cref="GetRemainingLinearDistance"/>)小于此值(米),则视为接近目标点,使角色开始减速。/// 若沿给定路径点移动,则在接近目标点时,使角色开始向路径中的下一个目标点移动。/// </summary>[SerializeField]private float _approachDistance = 0.8f;/// <summary>/// 动画机。/// </summary>private Animator _animator;/// <summary>/// 导航代理。/// </summary>private NavMeshAgent _navMeshAgent;/// <summary>/// 移动路径点(由外部指定)。/// </summary>private readonly Queue<Vector3> _movingPath = new Queue<Vector3>();/// <summary>/// 转向停止时的回调。/// 参数1:是否已转到目标方向。/// </summary>private Action<bool> _rotateStopCallback;/// <summary>/// 移动停止时的回调。/// 参数1:是否抵达目的地。/// </summary>private Action<bool> _moveStopCallback;/// <summary>/// 控制角色移动的动画参数Hash。/// </summary>private int _animationLocomotionParamHash;/// <summary>/// 动画机移动参数值。/// </summary>private float _animationLocomotionValue;/// <summary>/// 动画机移动参数值Damp辅助字段。/// </summary>private float _animationLocomotionValueDampVelocity;// 测试代码//[SerializeField]//private List<Vector3> _pathBuffer = new List<Vector3>();/// <summary>/// 设置角色朝向。/// </summary>/// <param name="forward">目标朝向。</param>/// <param name="stopMoving">是否停止正在进行的导航。</param>/// <param name="onRotateStop">转向停止时的回调,参数1:是否已转到目标方向。</param>/// <returns></returns>public bool SetForward(Vector3 forward, bool stopMoving = false, Action<bool> onRotateStop = null){StopRotating();forward.y = 0;if (forward.sqrMagnitude < Mathf.Epsilon){Debug.LogError("ERROR: Can't set forward to zero.");onRotateStop?.Invoke(true);return false;}if (Destination.HasValue){if (stopMoving){StopMoving();}else{Debug.LogError("ERROR: Can't set forward during the navigation.");onRotateStop?.Invoke(false);return false;}}TargetForward = forward.normalized;_rotateStopCallback = onRotateStop;return true;}/// <summary>/// 设置目的地。/// </summary>/// <param name="destination">目的地。</param>/// <param name="onMovingStop">导航停止时的回调,参数1:是否到达目的地。</param>/// <returns></returns>public bool SetDestination(Vector3 destination, Action<bool> onMovingStop = null){StopMoving();if (_navMeshAgent.SetDestination(destination)){// 注意不要直接使用 destination ,它可能不在NavMesh上Destination = _navMeshAgent.destination;_moveStopCallback = onMovingStop;return true;}onMovingStop?.Invoke(false);return false;}/// <summary>/// 设置移动路径。/// </summary>/// <param name="wayPoints">移动路径。</param>/// <param name="onMovingStop">导航停止时的回调,参数1:是否到达目的地。</param>public void SetPath(IEnumerable<Vector3> wayPoints, Action<bool> onMovingStop = null){StopMoving();_movingPath.Clear();foreach (var wayPoint in wayPoints){_movingPath.Enqueue(wayPoint);}if (_movingPath.Count == 0){onMovingStop?.Invoke(true);return;}while (_movingPath.Count > 0){Destination = _movingPath.Dequeue();if (_navMeshAgent.SetDestination(Destination.Value)){Destination = _navMeshAgent.destination;_moveStopCallback = onMovingStop;return;}Debug.LogError($"ERROR: Skip unreachable moving path point `{Destination.Value}`.", gameObject);}Debug.LogError("ERROR: There is no reachable point in path.", gameObject);// 所有路径点在导航网格上都不可达Destination = null;onMovingStop?.Invoke(false);}/// <summary>/// 停止转向。/// </summary>public void StopRotating(){if (!TargetForward.HasValue){return;}if (_rotateStopCallback == null){ 测试代码//Debug.LogError(Vector3.Angle(transform.forward, TargetForward.Value) < RotateStopAngle ? "### 转向完成" : "### 转向中断");TargetForward = null;return;}var deflectionAngle = Vector3.Angle(transform.forward, TargetForward.Value);var tempRotateStopCallback = _rotateStopCallback; 测试代码//Debug.LogError(deflectionAngle < RotateStopAngle ? "### 转向完成" : "### 转向中断");_rotateStopCallback = null;TargetForward = null;tempRotateStopCallback(deflectionAngle < RotateStopAngle);}/// <summary>/// 停止移动。/// </summary>public void StopMoving(){if (!Destination.HasValue){return;}if (_moveStopCallback == null){ 测试代码//if (GetRemainingLinearSqrDistance(RemainingDistanceType.PathEnd) > StoppingDistance * StoppingDistance)//{//    Debug.LogError("### NavStop: 未到达");//}//else//{//    Debug.LogError("### NavStop: 到达");//}Destination = null;_movingPath.Clear();_navMeshAgent.ResetPath();return;}var tempNavStopCallback = _moveStopCallback;_moveStopCallback = null;_navMeshAgent.ResetPath();if (GetRemainingLinearSqrDistance(RemainingDistanceType.PathEnd) > StoppingDistance * StoppingDistance){ 测试代码//Debug.LogError("### NavStop: 未到达");Destination = null;_movingPath.Clear();tempNavStopCallback(false);}else{ 测试代码//Debug.LogError("### NavStop: 到达");Destination = null;_movingPath.Clear();tempNavStopCallback(true);}}/// <summary>/// 移动角色位置。/// 若角色尚未停止移动动画,则需要在 LateUpdate 方法中调用此方法。/// </summary>/// <param name="targetPosition">角色目标位置。</param>/// <param name="stopMoving">是否停止正在进行的导航。</param>/// <returns></returns>public bool MoveCharacter(Vector3 targetPosition, bool stopMoving = false){if (NavMesh.SamplePosition(targetPosition, out var hit, MaxNavMeshSampleDistance, NavMeshAreaMask)){transform.position = hit.position;// 这两个方法会导致NavMeshAgent被中途的障碍物挡住//_navMeshAgent.nextPosition = hit.position;//_navMeshAgent.Move(hit.position - transform.position);if (stopMoving){_navMeshAgent.Warp(hit.position);StopMoving();}else if (Destination.HasValue){// Warp方法会导致导航停止_navMeshAgent.Warp(hit.position);_navMeshAgent.SetDestination(Destination.Value);}return true;}return false;}/// <summary>/// 获取角色当前所在位置到目的地的直线距离(米)。/// 注意此方法返回的不是 <see cref="NavMeshAgent.remainingDistance"/> ,两者之间有误差。/// </summary>/// <param name="distanceType">计算剩余距离的方式。</param>/// <returns></returns>public float GetRemainingLinearDistance(RemainingDistanceType distanceType){return Mathf.Sqrt(GetRemainingLinearSqrDistance(distanceType));}/// <summary>/// 获取角色当前所在位置到目的地的直线距离(米)的平方。/// 注意此方法返回的不是 <see cref="NavMeshAgent.remainingDistance"/> 的平方值,两者之间有误差。/// </summary>/// <param name="distanceType">计算剩余距离的方式。</param>/// <returns></returns>public float GetRemainingLinearSqrDistance(RemainingDistanceType distanceType){if (Destination.HasValue){switch (distanceType){case RemainingDistanceType.PathEnd:if (_movingPath.Count > 0){return Vector3.SqrMagnitude(transform.position - _movingPath.Last());}else{return Vector3.SqrMagnitude(transform.position - Destination.Value);}case RemainingDistanceType.WayPoint:return Vector3.SqrMagnitude(transform.position - Destination.Value);case RemainingDistanceType.SteeringPoint:return Vector3.SqrMagnitude(transform.position - _navMeshAgent.steeringTarget);default:throw new ArgumentOutOfRangeException(nameof(distanceType), distanceType, null);}}return 0;}private void Reset(){_navMeshAgent = GetComponent<NavMeshAgent>();if (ApproachDistance < StoppingDistance){ApproachDistance = StoppingDistance + 1e-5f;}}private void OnValidate(){if (Application.isPlaying){_animationLocomotionParamHash = Animator.StringToHash(AnimationLocomotionParam);}if (AnimationMovingMinValue < AnimationStandingValue){AnimationMovingMinValue = AnimationStandingValue;}_navMeshAgent = GetComponent<NavMeshAgent>();if (ApproachDistance < StoppingDistance){ApproachDistance = StoppingDistance + 1e-5f;}}private void Awake(){_animator = GetComponent<Animator>();_animationLocomotionParamHash = Animator.StringToHash(AnimationLocomotionParam);_navMeshAgent = GetComponent<NavMeshAgent>();_navMeshAgent.updatePosition = false;_navMeshAgent.updateRotation = false; // 导航转身太慢,手动转身if (ApproachDistance < StoppingDistance){ApproachDistance = StoppingDistance + 1e-5f;}}private void Update(){// 处理角色转向RotateCharacter(out float deflectionAngle);// 如果无目的地,停止移动if (!Destination.HasValue){SetAnimationLocomotionValueWithDamp(AnimationStandingValue);return;}// 计算剩余距离var remainingSqrDistance = GetRemainingLinearSqrDistance(RemainingDistanceType.WayPoint);// 优先处理路径,因为路径也会使用 NavDestination 属性if (_movingPath.Count > 0){if (remainingSqrDistance < ApproachDistance * ApproachDistance){var hasDestination = false;while (_movingPath.Count > 0){Destination = _movingPath.Dequeue();if (_navMeshAgent.SetDestination(Destination.Value)){Destination = _navMeshAgent.destination;hasDestination = true;break;}Debug.LogError($"ERROR: Skip unreachable moving path point `{Destination.Value}`.");}// 所有路径点在导航网格上都不可达,停止导航if (!hasDestination){StopMoving();return;}}}// 次优处理单个目的地// 已到达目的地else if (remainingSqrDistance < StoppingDistance * StoppingDistance){StopMoving();return;}// 处理转身速度衰减和接近目标点的速度衰减var animationMovingValue = Mathf.Min(DampMovingSpeedByDeflection(AnimationMovingValue, deflectionAngle),DampMovingSpeedByRemainingDistance());//var animationMovingValue = DampMovingSpeedByDeflection(AnimationMovingValue, deflectionAngle);// 设置移动参数SetAnimationLocomotionValueWithDamp(animationMovingValue);} 测试代码//private void LateUpdate()//{//    if (Input.GetKey(KeyCode.LeftControl))//    {//        // 设置朝向//        if (Input.GetMouseButtonDown(0))//        {//            var ray = UnityEngine.Camera.main.ScreenPointToRay(Input.mousePosition);//            if (Physics.Raycast(ray, out var hit, 100, -1))//            {//                var forward = hit.point - transform.position;//                SetForward(forward, true);//            }//        }//        // 移动位置//        if (Input.GetMouseButtonDown(1))//        {//            var ray = UnityEngine.Camera.main.ScreenPointToRay(Input.mousePosition);//            if (Physics.Raycast(ray, out var hit, 100, -1))//            {//                MoveCharacter(hit.point);//            }//        }//    }//    else//    {//        // 设置目的地//        if (Input.GetMouseButtonDown(0))//        {//            var ray = UnityEngine.Camera.main.ScreenPointToRay(Input.mousePosition);//            if (Physics.Raycast(ray, out var hit, 100, -1))//            {//                SetDestination(hit.point);//            }//        }//        // 添加路径点//        if (Input.GetMouseButtonDown(1))//        {//            var ray = UnityEngine.Camera.main.ScreenPointToRay(Input.mousePosition);//            if (Physics.Raycast(ray, out var hit, 100, -1))//            {//                _pathBuffer.Add(hit.point);//            }//        }//        // 设置路径点//        if (Input.GetMouseButtonDown(2))//        {//            SetPath(_pathBuffer);//            _pathBuffer.Clear();//        }//    }//}private void OnAnimatorMove(){// 仅使用导航高度,使角色贴合导航网格var pos = _animator.rootPosition;pos.y = _navMeshAgent.nextPosition.y;transform.position = pos;// 使代理贴合根运动_navMeshAgent.nextPosition = transform.position;// 使用根运动速度驱动导航速度_navMeshAgent.velocity = _animator.velocity;}/// <summary>/// 处理角色转向。/// </summary>/// <param name="deflectionAngle"></param>private void RotateCharacter(out float deflectionAngle){deflectionAngle = 0;// 若有目的地,则朝向下一个要到达的路径点if (Destination.HasValue){var targetForward = _navMeshAgent.steeringTarget - transform.position;targetForward.y = 0;targetForward.Normalize();// 处理转身transform.forward = Vector3.RotateTowards(transform.forward, targetForward,Time.deltaTime * AngularSpeed * Mathf.Deg2Rad, float.MaxValue);deflectionAngle = Vector3.Angle(transform.forward, targetForward);}// 否则朝向指定的方向else if (TargetForward.HasValue){// 处理转身transform.forward = Vector3.RotateTowards(transform.forward, TargetForward.Value,Time.deltaTime * AngularSpeed * Mathf.Deg2Rad, float.MaxValue);deflectionAngle = Vector3.Angle(transform.forward, TargetForward.Value);if (deflectionAngle < RotateStopAngle){ 测试代码//Debug.LogError("### 转向完成");if (_rotateStopCallback == null){TargetForward = null;}else{var tempRotateStopCallback = _rotateStopCallback;_rotateStopCallback = null;TargetForward = null;tempRotateStopCallback(true);}}}}/// <summary>/// 按角色当前朝向与目标移动方向的偏转角处理移动速率衰减。/// </summary>/// <param name="originalSpeed">衰减前的移动速率(米/秒)。</param>/// <param name="deflectionAngle">偏转角(角度)。</param>/// <returns></returns>private float DampMovingSpeedByDeflection(float originalSpeed, float deflectionAngle){deflectionAngle = Mathf.Abs(deflectionAngle);// 立方EaseOutIn衰减//var progress = 1 - deflectionAngle / 180;//var dampCoef = progress < 0.5f//    ? progress * 2 - Mathf.Pow(2, 3 - 1) * Mathf.Pow(progress, 3) // 2^(n-1)*(progress^n)//    : progress * 2 - (1 - Mathf.Pow(-2 * progress + 2, 3) / 2); // 1-((-2*x+2)^n)/2// 线性衰减var dampCoef = 1 - deflectionAngle / 180;var dampedSpeed = originalSpeed * dampCoef;return dampedSpeed;}/// <summary>/// 按角色当前距离导航目标点的距离来处理移动速率衰减。/// </summary>/// <returns></returns>private float DampMovingSpeedByRemainingDistance(){if (ApproachDistance < StoppingDistance){Debug.LogError($"ERROR: {nameof(StoppingDistance)}({StoppingDistance}) should be less than {nameof(ApproachDistance)}({ApproachDistance}).", gameObject);}// 注意,应该根据角色移动速度来调整各项速度计算参数var remainingSqrDistance = GetRemainingLinearSqrDistance(RemainingDistanceType.WayPoint);if (remainingSqrDistance < StoppingDistance * StoppingDistance){return 0;}if (remainingSqrDistance > ApproachDistance * ApproachDistance){return AnimationMovingValue;}// 只在接近终点或者中途路径点时进行减速,由导航生成的steeringTarget不做减少处理(因为走过头了也没啥影响)// 四次方EaseOutIn衰减var remainingDistance = Mathf.Sqrt(remainingSqrDistance);var progress = remainingDistance / ApproachDistance;var dampCoef = progress < 0.5f? progress * 2 - Mathf.Pow(2, 4 - 1) * Mathf.Pow(progress, 4) // 2^(n-1)*(progress^n): progress * 2 - (1 - Mathf.Pow(-2 * progress + 2, 4) / 2); // 1-((-2*x+2)^n)/2// 线性衰减//var dampCoef = remainingDistance / approachDistance;// 避免接近目标点时无限减速var dampedSpeed = Mathf.Max(AnimationMovingValue * dampCoef, AnimationMovingMinValue);return dampedSpeed;}/// <summary>/// 以带有Damp的形式设置动画的移动参数。/// 需要每帧调用才能使Damp生效。/// </summary>/// <param name="value"></param>private void SetAnimationLocomotionValueWithDamp(float value){_animationLocomotionValue = Mathf.SmoothDamp(_animationLocomotionValue, value,ref _animationLocomotionValueDampVelocity, AnimationLocomotionValueDampTime);_animator.SetFloat(_animationLocomotionParamHash, _animationLocomotionValue);}
}#if UNITY_EDITOR
[CustomEditor(typeof(RootMotionNavMeshAgent))]
internal class RootMotionNavMeshAgentEditor : UnityEditor.Editor
{/// <summary>/// 是否开始调试模式。/// </summary>private static bool _debugModeEnabled;/// <summary>/// 绘制Gizmos。/// </summary>/// <param name="target"></param>/// <param name="gizmoType"></param>[DrawGizmo(GizmoType.Active | GizmoType.Selected)]public static void DrawGizmos(RootMotionNavMeshAgent target, GizmoType gizmoType){if (!_debugModeEnabled){return;}// 当前导航状态if (target.Destination.HasValue){Gizmos.color = Color.green;var navWayPoint = target.transform.position;foreach (var pathCorner in target.NavPathCorners){Gizmos.DrawSphere(pathCorner, 0.1f);Debug.DrawLine(navWayPoint, pathCorner, Color.green, Time.deltaTime, false);navWayPoint = pathCorner;}}// 移动路径var movingPath = (Queue<Vector3>)target.MovingPath;if (movingPath.Count > 0 && target.Destination.HasValue){Gizmos.color = Color.white;var pathWayPoint = target.Destination.Value;Gizmos.DrawSphere(pathWayPoint, 0.1f);foreach (var wayPoint in movingPath){Gizmos.DrawSphere(wayPoint, 0.1f);Debug.DrawLine(pathWayPoint, wayPoint, Color.white, Time.deltaTime, false);pathWayPoint = wayPoint;}}}public override void OnInspectorGUI(){base.OnInspectorGUI();#region 调试开关EditorGUILayout.Space();var originalGUIBgColor = GUI.backgroundColor;if (_debugModeEnabled){GUI.backgroundColor = Color.yellow;}if (GUILayout.Button("调试模式")){_debugModeEnabled = !_debugModeEnabled;}GUI.backgroundColor = originalGUIBgColor;#endregion}
}
#endif

【Unity】使用RootMotion跟运动驱动NavMeshAgent导航相关推荐

  1. 《UnityAPI.NavMeshAgent导航网格代理》(Yanlz+Unity+SteamVR+云技术+5G+AI+VR云游戏+Unity+NavMeshAgent+立钻哥哥++OK++)

    <UnityAPI.NavMeshAgent导航网格代理> 版本 作者 参与者 完成日期 备注 UnityAPI_NavMeshAgent_V01_1.0 严立钻 2020.09.10 # ...

  2. 【游戏开发实战】Unity 2D游戏物理运动曲线轨迹预测,以愤怒的小鸟为例,轨迹曲线云团圈圈

    文章目录 一.前言 二.思考分析 三.场景搭建 1.导入图片素材 2.鸟预设 3.地面环境 4.曲线的点预设 5.预览效果 四.代码 1.鸟脚本:Bird.cs 2.曲线预测器:Trajectory. ...

  3. UNITY 2D RootMotion 小记

    最开始跟着某本书做了个demo,动画直接用的画好的一堆png导入sprite然后直接拖到animation的关键帧上,然后getkeydown给人物直接改速度.一切正常 第二天突然看到骨骼动画IK b ...

  4. Unity中BVH骨骼动画驱动的可视化理论与实现

    前言 找了很久使用BVH到unity中驱动骨骼动画的代码,但是都不是特别好用,自己以前写过,原理很简单,这里记录一下. 理论 初始姿态 在BVH或者其它骨骼动画中,一般涉及到三种姿势:A-pose,T ...

  5. Unity FACEGOOD Audio2Face 通过音频驱动面部BlendShape

    目录 Audio2Face简介 在Unity中应用 Audio2Face简介 在元宇宙的热潮下,为了让AI数字人渗透到更多的领域中,FACEGOOD已经将语音驱动口型的算法技术开源,开源地址: htt ...

  6. 用先进的运动驱动控制芯片实现电机的静音防抖

    步进电机的噪音从何而来? 步进电机广泛用于自动化.数字制造.医疗和光学设备等几乎所有类型的移动应用中. 步进电机的优点是成本相对较低,在不使用变速箱的情况下在静止和低速时具有高扭矩,以及对定位任务的固 ...

  7. 详解Unity中的Nav Mesh新特性|导航寻路系统 (二)

    前言 第一篇我们简要概述了新版NavMesh的一些新增功能,以及旧版的对比.本篇我们来讲一讲NavMeshSurface.NavMeshLink这两个组件的参数以及如何应用,接下来就进入正题吧. 本系 ...

  8. 【ADAMS学习记录】——STEP函数添加运动驱动

    STEP 函数为运动副添加驱动 step函数格式和定义 STEP(x,x0,h0,x1,h1) x:自变量,可以是时间或时间的任一函数 x0 :自变量的STEP函数开始值,可以是常数.函数表达式或设计 ...

  9. 【Unity】控制小球运动

    跟着B站教程,做了个简单的控制小球运动的场景,记录一下: 文章目录 搭建场景 小球运动脚本 相机跟随小球运动脚本 效果展示 搭建场景 建立地面Plane.小球Player和四面墙Wall. 小球运动脚 ...

最新文章

  1. 图数据库Titan安装与部署
  2. 复数类完整实现 + 四则运算符重载
  3. odom_out_and_back.cpp
  4. 局域网制作ftp服务器,如何架设内网ftp服务器 搭建ftp yum源
  5. 网关为0.0.0.0_距离ETH 2.0仅7天,目标价为?美元
  6. 如何快速调出软键盘_*小星推荐*—如何快速的制作模具3D装配档
  7. SQL Server schema 用法
  8. mooon-agent核心设计图
  9. 如何解决切换双系统导致windows没声音的问题
  10. 背包问题(Knapsack Problem)—— 完全背包问题 —— (1)背包价值最大
  11. html自动触发双击事件,js主动触发单击事件
  12. ckplayer x2去logo,改右键
  13. 最快零基础上手——latex文档标题、一级标题、二级标题、内容搭建
  14. 【系统函数】2. 系统的因果性、稳定性
  15. fastDB CLI
  16. irobot擦地机器人故障_Irobot Braava380t擦地机器人 操作使用说明
  17. Error: [$injector:unpr] angular.js
  18. python余弦定理求角_python余弦定理计算相似度
  19. rocket使用实例
  20. iphone表情显示问号_如何在iPhone上使用表情符号

热门文章

  1. K-verse 小型活动来袭!
  2. 迅为触控一体机工业人机界面HMI嵌入式显示屏安卓触摸屏提供商
  3. SIM800C使用HTTPS进行GET
  4. Python 和 Lua 学习比较 一
  5. 云计算就是把计算机资源都放到()上,云计算就是把计算机资源都放到上
  6. 学习Redis必须了解的N个常识
  7. 企业岗位申请表-Word简历可编辑下载
  8. 实现类似QQ记住密码的功能
  9. JAVA商城项目(微服务框架)——第7天nginx+cors解决跨域+品牌+分类查询
  10. Image Flow Field Detection