123456833 发表于 2023-4-3 08:10

Unity NavMeshAgent 耦合动画移动

简单总结下navmeshagent使用中遇到的几个问题.
自己的游戏中是使用agent获取目标速度, 然后根据动画root motion更新GameObject位置.获取速度并旋转物体朝向的代码如下
      public Vector2 GetDirToPosbyNav(Vector3 pos)
      {
            Vector2 dir = Vector2.zero;
            if (!nav.isOnNavMesh)
            {
                nav.Warp(new Vector3(Position.x, 0f, Position.z));
                return dir;
            }

            NavMeshHit hit;
            if (!nav.Raycast(pos, out hit))//检测不到路线上的agent
            {
                if (!Physics.CheckBox(transform.position + nav.radius * Vector3.up, nav.radius * Vector3.one))
                  return GameUtil.To2DVector(pos - transform.position);
            }
            
            bool hasDest = nav.SetDestination(pos);
            bool hasPath = NavMesh.CalculatePath(Position, pos, NavMesh.AllAreas, path);
            if (!hasDest || !hasPath || path.status != NavMeshPathStatus.PathComplete)
            {
                return dir;
            }

            dir = new Vector2(nav.desiredVelocity.x, nav.desiredVelocity.z).normalized;
            return dir;
      }

      public void RotateToPosByNav(Vector3 pos)
      {
            Vector2 dir = GetDirToPosbyNav(pos);
            if (dir != Vector2.zero)
            {
                Vector3 targetFWD = new Vector3(dir.x, 0f, dir.y);
                transform.rotation = Quaternion.RotateTowards(transform.rotation,
                  Quaternion.LookRotation(targetFWD, Vector3.up), Const.MaxRoteAngDelta);
            }
      }更新物体位置的代码有两种方法, 顾名思义, 分别命名为UseNav和ForceAnim, nav是navmeshagent, anim是animator, 代码如下
      public void UseNav()//基于agent位移
      {
            nav.velocity = anim.velocity;//此处赋值在下一帧才会生效
            transform.position = nav.nextPosition; //nav.nextPosition就是agent圆柱状collider的位置
      }

      public void ForceAnim()//基于动画位移
      {
            nav.velocity = Vector3.zero;
            nav.nextPosition = tranform.position + anim.deltaPosition;
            transform.position = nav.nextPosition;
      }

      protected virtual void OnAnimatorMove()//调用位移更新
      {
         //两种方法二选一
         //ForceAnim();//适用于Idle等无位移动画, 避免被推走
         UseNav()//适用于攻击/移动等带位移的动画,避免穿模
      }

      private void InitNav()
      {
            nav.enabled = true;
            nav.updatePosition = false;//重要
            nav.updateRotation = false;//重要
            nav.isStopped = false;
            nav.autoBraking = false;
            nav.obstacleAvoidanceType = ObstacleAvoidanceType.LowQualityObstacleAvoidance;
      }
通常推荐的是第一种UseNav, 后来我又遇到了两个agent可以相互穿越或者被对方推着滑动的问题, 实验之后发现了agent另一个关键参数在起作用: ObstacleAvoidanceType枚举, 完全不规避为NoObstacleAvoidance, 其他是HighQualityObstacleAvoidance等, 控制agent A碰撞处于idle动画的agentB, 观察B的表现, 反复实验之后, 得到了下面的表格:
A\B(Avoid, UseNav)(Avoid,ForceAm)(NoAvd, UseNav)(NoAvd, ForceAm)(Avoid, UseNav)滑动原地原地原地(Avoid,ForceAm)滑动穿越穿越穿越(NoAvd, UseNav)滑动穿越穿越穿越(NoAvd, ForceAm)滑动穿越穿越穿越结论:

[*]只要受击体为Avoid+UseNav即为滑动, 与驱动体无关
[*]只要驱动体为Avoid+UseNav就不会穿越, 与受击体无关
[*]ForceAnim适用于Idle等无位移动画, 避免被推走, UseNav适用于攻击/移动等带位移的动画,避免穿模
页: [1]
查看完整版本: Unity NavMeshAgent 耦合动画移动