Unity Mecanim动画系统 之 IK(Inverse Kinematics即反向动力学)的相关说明和简单使用

Unity Mecanim动画系统 之 IK(Inverse Kinematics即反向动力学)的相关说明和简单使用

Unity Mecanim动画系统 之 IK(Inverse Kinematics即反向动力学)的相关说明和简单使用

目录


一、简单介绍

Unity中的一些基础知识点。便于后期开发使用。

Unity动画系统,也称为“Mecanim”,提供了以下功能:

  • 简单的工作流程,设置动画的所有元素,包括对象,角色和属性。
  • 支持导入外部创建的动画片段和使用内置动画编辑器制作的动画片段。
  • 人型动画重新定位,动画角色的运动控制可以被所有的角色模型共享,即角色的外观(SkinedMesh)和运动(Animator)是分离的,它们互相组合之后形成最终的动画。
  • 用于编辑动画状态的的简化工作流程,即动画控制器。
  • 方便预览动画片段,以及片段之间的插值过渡。 这使得动画师可以独立于程序员工作,在不运行游戏的情况下,可以对原型和预览动画进行预览。
  • 管理动画与可视化编程工具之间的复杂交互。
  • 不同的身体部位可以使用不同的动画逻辑控制。
  • 动画的分层和掩蔽功能。

IK可以使人物和场景更加贴合,从而达到更加真实的游戏效果。

二、效果预览

www.zeeklog.com  - Unity Mecanim动画系统 之 IK(Inverse Kinematics即反向动力学)的相关说明和简单使用

三、IK(Inverse Kinematics)说明

IK(Inverse Kinematics)即反向动力学,即可以使用场景中的各种物体来控制和影响角色身体部位的运动,一般来说骨骼动画都是传统的从父节点到子节点的带动方式(即正向动力学),而IK则倒过来,由骨骼子节点带动骨骼父节点,具体情况比如人物走路踩到了石头就需要由脚的子节点来带动全身骨骼做出踩到石头的响应。

Unity中IK能设置的部位就是10个,分别是:头、左右手、左右脚、身体、左右手肘,左右膝盖

四、IK 设置部分说明

1、头部IK

1)两个API:Animator.SetLookAtPosition和Animator.SetLookAtWeight

public void SetLookAtPosition(Vector3 lookAtPosition);

这个方法用来设置头部看向的位置,比如看向你左边的窗户,头就会相应的旋转。

2)public void SetLookAtWeight(float weight, float bodyWeight = 0.0f, float headWeight = 1.0f, float eyesWeight = 0.0f, float clampWeight = 0.5f);

这个方法用来设置IK的权重,这个IK会和原来的动画进行混合。如果权重为1,则完全用IK的位置旋转;如果权重为0,则完全用原来动画中的位置和旋转,参数说明:

Weight 全局权重,后面所有参数的系数

  • bodyWeight 身体权重,身体参与LookAt的程度,一般是0
  • headWeight 头部权重,头部参与LookAt的权重,一般是1
  • eyesWeight 眼睛权重,眼睛参与LookAt的权重,一般是0(一般没有眼睛部分的骨骼)
  • clampWeight 权重的限制。0 代表没有限制(脖子可能看起来和断了一样),1代表完全限制(头几乎不会动,像是固定住了)。0.5代表可能范围的一半(180度)。

2、手脚(foot/knee/elbow)IK

1)API:

public void SetIKPosition(AvatarIKGoal goal, Vector3 goalPosition);

public void SetIKRotation(AvatarIKGoal goal, Quaternion goalRotation);

public void SetIKHintPosition(AvatarIKHint hint, Vector3 hintPosition);

手和脚,需要同时设置位置和旋转。

goal AvatarIKGoal枚举类型,包含:

LeftFoot 左脚

RightFoot 右脚

LeftHand 左手

RightHand 右手

hintAvatarIKHint 枚举类型,包含:

LeftKnee 左膝盖

RightKnee 右膝盖

LeftElbow 左手肘

RightElbow 右手肘

goalPosition/goalRotation IK目标位置/旋转

2)同样还有设置权重的API:

public void SetIKPositionWeight(AvatarIKGoal goal, float value);

public void SetIKRotationWeight(AvatarIKGoal goal, float value);

public void SetIKHintPositionWeight(AvatarIKHint hint, float value);

goal AvatarIKGoal / AvatarIKHint 枚举类型

value IK的权重,1代表完全使用IK值,0代表使用原动画的值

五、注意事项

0、必须是新动画系统Animator。

1、动画类型必须是Humanoid。

2、需要勾选对应Layer的IK Pass选项(在Layer的设置里)。

3、代码需要写在OnAnimatorIK这个事件方法里面。

六、实现步骤

简单实现 IK 五个部分的 IK 控制

1、新建工程,导入模型

www.zeeklog.com  - Unity Mecanim动画系统 之 IK(Inverse Kinematics即反向动力学)的相关说明和简单使用

2、新建一个 Animator Controller 控制器,并添加一个动画

www.zeeklog.com  - Unity Mecanim动画系统 之 IK(Inverse Kinematics即反向动力学)的相关说明和简单使用

3、 Animator Controller 设置中,勾选 IK Pass ,这样 IK 功能才能生效

www.zeeklog.com  - Unity Mecanim动画系统 之 IK(Inverse Kinematics即反向动力学)的相关说明和简单使用

4、把模型导入场景中,并添加对应控制球,用来 IK 控制其动画

www.zeeklog.com  - Unity Mecanim动画系统 之 IK(Inverse Kinematics即反向动力学)的相关说明和简单使用

5、把 Animator Controller 赋值个模型

www.zeeklog.com  - Unity Mecanim动画系统 之 IK(Inverse Kinematics即反向动力学)的相关说明和简单使用

6、新建一个脚本,控制是否激活 IK,不激活正常状态,激活可以 IK 球控制动画

www.zeeklog.com  - Unity Mecanim动画系统 之 IK(Inverse Kinematics即反向动力学)的相关说明和简单使用

7、把脚本,挂载到场景中,对并对应赋值

www.zeeklog.com  - Unity Mecanim动画系统 之 IK(Inverse Kinematics即反向动力学)的相关说明和简单使用

8、运行场景,效果如下

www.zeeklog.com  - Unity Mecanim动画系统 之 IK(Inverse Kinematics即反向动力学)的相关说明和简单使用

9、后期要做动作模拟,只要把数据动态对应赋值到控制球上即可

7、关键代码


using UnityEngine;

/// <summary>
/// IK 控制
/// </summary>
public class IKController : MonoBehaviour
{
    // 身体
    public Transform bodyObj = null;//与相关球体对应

    // 左脚
    public Transform leftFootObj = null; //与相关球体对应

    // 右脚
    public Transform rightFootObj = null; //与相关球体对应

    // 左手
    public Transform leftHandObj = null; //与相关球体对应

    // 右手
    public Transform rightHandObj = null; //与相关球体对应

    // 头的观察点
    public Transform lookAtObj = null; //与相关球体对应

    // 右手肘
    public Transform rightElbowobj = null; //与相关球体对应

    // 左手肘
    public Transform leftElbowobj = null; //与相关球体对应

    // 右膝盖
    public Transform rightKneeobj = null; //与相关球体对应

    // 左膝盖
    public Transform leftKneeobj = null; //与相关球体对应




    // 动画机
    private Animator avatar;

    // 是否激活 IK
    private bool ikActive = false;



    void Start()

    {
        //获取动画机
        avatar = GetComponent<Animator>();

    }



    void Update()
    {
        // 如果 IK 没有激活
        // 把对应的控制部分附上动画自身的值
        if (!ikActive)
        {

            if (bodyObj != null)
            {

                bodyObj.position = avatar.bodyPosition;

                bodyObj.rotation = avatar.bodyRotation;

            }



            if (leftFootObj != null)
            {

                leftFootObj.position = avatar.GetIKPosition(AvatarIKGoal.LeftFoot);

                leftFootObj.rotation = avatar.GetIKRotation(AvatarIKGoal.LeftFoot);

            }



            if (rightFootObj != null)
            {

                rightFootObj.position = avatar.GetIKPosition(AvatarIKGoal.RightFoot);

                rightFootObj.rotation = avatar.GetIKRotation(AvatarIKGoal.RightFoot);

            }



            if (leftHandObj != null)
            {

                leftHandObj.position = avatar.GetIKPosition(AvatarIKGoal.LeftHand);

                leftHandObj.rotation = avatar.GetIKRotation(AvatarIKGoal.LeftHand);

            }



            if (rightHandObj != null)
            {

                rightHandObj.position = avatar.GetIKPosition(AvatarIKGoal.RightHand);

                rightHandObj.rotation = avatar.GetIKRotation(AvatarIKGoal.RightHand);

            }



            if (lookAtObj != null)
            {

                lookAtObj.position = avatar.bodyPosition + avatar.bodyRotation * new Vector3(0, 0.5f, 1);

            }



            if (rightElbowobj != null)
            {

                rightElbowobj.position = avatar.GetIKHintPosition(AvatarIKHint.RightElbow);


            }

            if (leftElbowobj != null)
            {

                leftElbowobj.position = avatar.GetIKHintPosition(AvatarIKHint.LeftElbow);


            }

            if (rightKneeobj != null)
            {

                rightKneeobj.position = avatar.GetIKHintPosition(AvatarIKHint.RightKnee);


            }

            if (leftKneeobj != null)
            {

                leftKneeobj.position = avatar.GetIKHintPosition(AvatarIKHint.LeftKnee);


            }

        }



    }


    /// <summary>
    /// IK 动画的控制专用函数
    /// </summary>
    /// <param name="layerIndex">动画层</param>
    void OnAnimatorIK(int layerIndex)
    {
        // 动画机为空,返回
        if (avatar == null)

            return;


        // 激活 IK 
        //1、 各部分权重赋值 1
        //2、 各部分位置赋值
        //3、 部分旋转赋值
        if (ikActive)
        {

            avatar.SetIKPositionWeight(AvatarIKGoal.LeftFoot, 1.0f);

            avatar.SetIKRotationWeight(AvatarIKGoal.LeftFoot, 1.0f);



            avatar.SetIKPositionWeight(AvatarIKGoal.RightFoot, 1.0f);

            avatar.SetIKRotationWeight(AvatarIKGoal.RightFoot, 1.0f);



            avatar.SetIKPositionWeight(AvatarIKGoal.LeftHand, 1.0f);

            avatar.SetIKRotationWeight(AvatarIKGoal.LeftHand, 1.0f);



            avatar.SetIKPositionWeight(AvatarIKGoal.RightHand, 1.0f);

            avatar.SetIKRotationWeight(AvatarIKGoal.RightHand, 1.0f);



            avatar.SetLookAtWeight(1.0f, 0.3f, 0.6f, 1.0f, 0.5f);


            avatar.SetIKHintPositionWeight(AvatarIKHint.RightElbow, 1.0f);
            avatar.SetIKHintPositionWeight(AvatarIKHint.LeftElbow, 1.0f);
            avatar.SetIKHintPositionWeight(AvatarIKHint.RightKnee, 1.0f);
            avatar.SetIKHintPositionWeight(AvatarIKHint.LeftKnee, 1.0f);


            if (bodyObj != null)
            {

                avatar.bodyPosition = bodyObj.position;

                avatar.bodyRotation = bodyObj.rotation;

            }



            if (leftFootObj != null)
            {

                avatar.SetIKPosition(AvatarIKGoal.LeftFoot, leftFootObj.position);

                avatar.SetIKRotation(AvatarIKGoal.LeftFoot, leftFootObj.rotation);

            }



            if (rightFootObj != null)
            {

                avatar.SetIKPosition(AvatarIKGoal.RightFoot, rightFootObj.position);

                avatar.SetIKRotation(AvatarIKGoal.RightFoot, rightFootObj.rotation);

            }



            if (leftHandObj != null)
            {

                avatar.SetIKPosition(AvatarIKGoal.LeftHand, leftHandObj.position);

                avatar.SetIKRotation(AvatarIKGoal.LeftHand, leftHandObj.rotation);

            }



            if (rightHandObj != null)

            {

                avatar.SetIKPosition(AvatarIKGoal.RightHand, rightHandObj.position);

                avatar.SetIKRotation(AvatarIKGoal.RightHand, rightHandObj.rotation);

            }



            if (lookAtObj != null)

            {

                avatar.SetLookAtPosition(lookAtObj.position);

            }

            if (rightElbowobj != null)

            {

                avatar.SetIKHintPosition(AvatarIKHint.RightElbow, rightElbowobj.position);



            }

            if (leftElbowobj != null)

            {

                avatar.SetIKHintPosition(AvatarIKHint.LeftElbow, leftElbowobj.position);



            }

            if (rightKneeobj != null)

            {

                avatar.SetIKHintPosition(AvatarIKHint.RightKnee, rightKneeobj.position);



            }

            if (leftKneeobj != null)

            {

                avatar.SetIKHintPosition(AvatarIKHint.LeftKnee, leftKneeobj.position);



            }

        }

        // 不激活 IK 
        //1、 各部分权重赋值 0    
        else
        {

            avatar.SetIKPositionWeight(AvatarIKGoal.LeftFoot, 0);

            avatar.SetIKRotationWeight(AvatarIKGoal.LeftFoot, 0);



            avatar.SetIKPositionWeight(AvatarIKGoal.RightFoot, 0);

            avatar.SetIKRotationWeight(AvatarIKGoal.RightFoot, 0);



            avatar.SetIKPositionWeight(AvatarIKGoal.LeftHand, 0);

            avatar.SetIKRotationWeight(AvatarIKGoal.LeftHand, 0);



            avatar.SetIKPositionWeight(AvatarIKGoal.RightHand, 0);

            avatar.SetIKRotationWeight(AvatarIKGoal.RightHand, 0);



            avatar.SetLookAtWeight(0.0f);


            avatar.SetIKHintPositionWeight(AvatarIKHint.RightElbow, 0);
            avatar.SetIKHintPositionWeight(AvatarIKHint.LeftElbow, 0);
            avatar.SetIKHintPositionWeight(AvatarIKHint.RightKnee, 0);
            avatar.SetIKHintPositionWeight(AvatarIKHint.LeftKnee, 0);

        }

    }


    /// <summary>
    /// UI 
    /// </summary>
    void OnGUI()
    {

        GUILayout.Label("激活IK然后在场景中移动Effector对象观察效果");

        ikActive = GUILayout.Toggle(ikActive, "激活IK");

    }
}

八、IK使用推荐插件:FinalIK

该插件是对Unity本身的IK的优化和增强,可以模拟出更加真实的效果,有兴趣可以看一看。