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);

扫描二维码关注公众号,回复: 14762383 查看本文章

手部同理。

代码实现:

        //控制人物的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);//有待完善,还是会有时候不够就悬空。。
        }

猜你喜欢

转载自blog.csdn.net/cycler_725/article/details/120295186