Unity贴地行走(反向动力学)
  2023/4/13 10:45:17
关键词:Unity 真实行走 贴地行走 
  Unity3D实现脚部IK-脚部贴合地形变化(反向动力学)
目标:实现脚部IK和头部IK,双脚的位置可以根据地形进行调整,可以使得头部朝向指定方向。
实现效果:首先要求模型是Humanoid类型(人形),才能调用Unity自带的AnimatorIK回调函数

主要的API:
头部:
m_animator.SetLookAtPosition(eyesIKTarget.position);// 注视的目标
m_animator.SetLookAtWeight(1f/*注视的全局权重*/, 0/*body的权重*/, 1/*头部权重*/, 1/*眼睛权重*/, 0.5f/*0-1,0为完全无约束,1是完全不能*/);//设置头部权重

脚部位置:
m_animator.SetIKPosition(AvatarIKGoal.LeftFoot/*关节位置,有4个选择*/, pointLeft);//设置脚的位置
m_animator.SetIKPositionWeight(AvatarIKGoal.LeftFoot, 1f);//设置脚部的位置变化权重

脚部旋转:
m_animator.SetIKRotation(AvatarIKGoal.RightFoot, Quaternion.Euler(to));
m_animator.SetIKRotationWeight(AvatarIKGoal.RightFoot, 1f);

手部同理。
代码实现:

//控制人物的IK,仅当使用人形骨架时才会执行此事件,勾选了IK Pass的layer才会调用到这个方法里,每个勾选了IK Pass的layer调用一次
    private void OnAnimatorIK(int layerIndex)
    {
      //头部IK
      if (eyesIKTarget)
      {
        m_animator.SetLookAtPosition(eyesIKTarget.position);//设置看向的目标点位置
        m_animator.SetLookAtWeight(1f/*注视的全局权重*/, 0/*body的权重*/, 1/*头部权重*/, 1/*眼睛权重*/, 0.5f/*0-1,0为完全无约束,1是完全不能*/);//设置头部权重
      }
      if (!IsGround)
        return;
      AnimatorStateInfo animInfo = m_animator.GetCurrentAnimatorStateInfo(0);
      if(animInfo.IsTag("Idle") || animInfo.IsTag("DefaultIdleState") || (animInfo.IsTag("MotionTree") && m_forwardSpeed <= 1f)){ }//可以IK变化的状态
      else
      {
        if (m_heightDelta != 0f)
        {
          m_heightDelta = 0f;
          m_CharaCtrl.center = new Vector3(0, m_heightOrigin, 0);//重置人物重心
        }
        return;
      }
      float heightDeltaLeft = 0f, heightDeltaRight = 0f;
      Vector3 pointLeft = leftFootIKTarget.position, pointRight = rightFootIKTarget.position;/**/

      //设置IK位置:
      if (leftFootIKTarget)
      {
        Ray r = new Ray(leftFootIKTarget.position + Vector3.up * 0.3f, -Vector3.up);
        RaycastHit Info1,Info2;
        int layerMask = (1 << 9);             //Player层
        layerMask = ~layerMask;               //只过滤Player层
        Debug.DrawRay(leftFootIKTarget.position + Vector3.up * 0.3f, -Vector3.up, Color.green);//可视化射线,scene视图中可以打开gizmo
        if (Physics.Raycast(r, out Info1, 0.7f, layerMask, QueryTriggerInteraction.Ignore))
        {
          heightDeltaLeft = (leftFootIKTarget.position.y - Info1.point.y);

          pointLeft = Info1.point;
          pointLeft.y += 0.08f;                             //IK有一个偏差值,不加容易会穿模(可能是我这个模型问题)

      //控制Ik角度:
          r.origin = leftFootIKTarget_assist.position + Vector3.up * 0.3f;
          if(Physics.Raycast(r , out Info2 , 0.7f,layerMask,QueryTriggerInteraction.Ignore))
          {
            //print("assist点" + Info2.point +"原点"+ Info1.point);
            float slopeAngle = Mathf.Atan2(Info1.point.y - Info2.point.y,Vector3.Distance(Info1.point,Info2.point));//脚的旋转角度增量
            //print("左脚坡度 " + slopeAngle);
            Vector3 to = leftFootIKTarget.rotation.eulerAngles;           //原始旋转值
            to.x += slopeAngle * Mathf.Rad2Deg;                   //绕x轴旋转的角度 + slopAngle
            m_animator.SetIKRotation(AvatarIKGoal.LeftFoot, Quaternion.Euler(to)); //设置脚的旋转
            m_animator.SetIKRotationWeight(AvatarIKGoal.LeftFoot, 1f);       //脚的旋转权重
          }
          
        }
      }
      if (rightFootIKTarget)
      {
        Ray r = new Ray(rightFootIKTarget.position + Vector3.up * 0.3f, -Vector3.up);
        RaycastHit Info1,Info2;
        int layerMask = (1 << 9);             //打开Player层
        layerMask = ~layerMask;               //只过滤Player层
        Debug.DrawRay(rightFootIKTarget.position + Vector3.up * 0.3f, -Vector3.up, Color.red);//可视化射线
        if (Physics.Raycast(r, out Info1, 0.7f, layerMask, QueryTriggerInteraction.Ignore))
        {
          heightDeltaRight = (rightFootIKTarget.position.y - Info1.point.y);
          pointRight = Info1.point;
          pointRight.y += 0.08f; //加一个参数比较好,因为目标的point位置可能不够准确

          //控制右脚旋转
          r.origin = rightFootIKTarget_assist.position + Vector3.up * 0.3f;
          if (Physics.Raycast(r, out Info2, 0.7f, layerMask, QueryTriggerInteraction.Ignore))
          {
            float slopeAngle = Mathf.Atan2(Info1.point.y - Info2.point.y, Vector3.Distance(Info1.point, Info2.point))/*返回的是弧度*/;
            print("右脚坡度 " + slopeAngle);
            Vector3 to = rightFootIKTarget.rotation.eulerAngles;
            to.x += slopeAngle * Mathf.Rad2Deg;//绕x轴旋转的角度 + slopAngle
            m_animator.SetIKRotation(AvatarIKGoal.RightFoot, Quaternion.Euler(to));
            m_animator.SetIKRotationWeight(AvatarIKGoal.RightFoot, 1f);
          }
        }
      }
      if (Mathf.Abs(heightDeltaLeft - heightDeltaRight) < 0.05f)//左右较为平坦,不降低重心
      {
        //print("较为平坦"+ "左差:" + heightDeltaLeft + "右差:" + heightDeltaRight);
        //print("高度差:" + m_heightDelta);
        m_CharaCtrl.center = new Vector3(0, m_heightOrigin, 0);//有待完善,还是会陷进去地面。。
        m_animator.SetIKPositionWeight(AvatarIKGoal.LeftFoot, 0f);
        m_animator.SetIKPositionWeight(AvatarIKGoal.RightFoot, 0f);
        return;
      }
      //地形不平坦:
      //降低重心(character高度不变下,使center提高)

      m_animator.SetIKPosition(AvatarIKGoal.LeftFoot, pointLeft);//不只是这个位置,还不够(就离谱)
      m_animator.SetIKPositionWeight(AvatarIKGoal.LeftFoot, 1f);

      m_animator.SetIKPosition(AvatarIKGoal.RightFoot, pointRight);
      m_animator.SetIKPositionWeight(AvatarIKGoal.RightFoot, 1f);

      m_heightDelta = Mathf.Lerp(m_heightDelta, Math.Max(heightDeltaRight, heightDeltaLeft), Time.deltaTime);

      m_CharaCtrl.center = new Vector3(0, m_heightOrigin + m_heightDelta*1.8f, 0);//有待完善,还是会有时候不够就悬空。。
    }

原文链接:https://blog.csdn.net/cycler_725/article/details/120295186
 [1] [2]

相关文章(向右看)..

·相关
U3D不支持MeshCollid/图
C#实现三维自动寻址导航技术
HDRP物理光照系统实现昼夜效果
unity之HDRP性能质量最优/图
Unity提升画质的几点注意,U
Unity3d鼠标缩放拖动固定步/图
Unity开发常见报错信息解析揭
unity使用GUI及时控制文字
unity控制物体运动标准代码
Unity如何使用HDRP,项目/图

·热点