在Unity游戏开发中,Inverse Kinematics(IK)是创建逼真角色动画的强大工具。同时,能够在适当的时候切换到布偶物理状态来实现死亡动画等效果,可以极大地增强游戏的视觉体验。本文将详细介绍如何在Unity中利用IK实现常规动画,并在需要时切换到布偶状态以展示死亡动画。

一、IK动画基础

1.1 设置Animator和IK目标

首先,我们需要为角色添加Animator组件,它将负责管理角色的动画状态机。同时,确定IK目标(查看 Animation Rigging教程),例如对于一个双足角色,我们通常会有左右脚和头部等IK目标。这些目标可以是空的游戏对象,它们的位置和旋转将通过IK算法来影响角色的骨骼。 从而实现更自然的动画效果。

以下是相关的代码声明:

// 用于控制角色动画的Animator组件
public Animator animator; 
// 左脚IK目标的Transform,用于设置左脚在动画中的目标位置和旋转
public Transform leftFootIKTarget; 
// 右脚IK目标的Transform
public Transform rightFootIKTarget; 
// 头部IK目标的Transform,这里只是示例,可根据需要添加更多IK目标,比如手部等
public Transform headIKTarget; 

1.2 在代码中启用IK

为了让Animator组件根据我们设置的IK目标工作,我们需要在脚本中重写OnAnimatorIK方法。这个方法用于设置IK权重和目标位置/旋转。以下是一个简单的示例,用于设置脚部和头部的IK目标:

// 当Animator处理IK时调用此方法,layerIndex表示当前动画层索引
void OnAnimatorIK(int layerIndex)
{
    // 首先确保Animator组件存在,否则无法进行IK设置
    if (animator)
    {
        // 设置左脚IK的位置权重为1,表示完全应用IK位置计算。AvatarIKGoal.LeftFoot是Unity定义的表示左脚IK目标的枚举值
        animator.SetIKPositionWeight(AvatarIKGoal.LeftFoot, 1f); 
        // 设置左脚IK目标的位置,animator会根据这个位置来调整角色左脚的位置
        animator.SetIKPosition(AvatarIKGoal.LeftFoot, leftFootIKTarget.position); 
        // 设置左脚IK的旋转权重为1,表示完全应用IK旋转计算
        animator.SetIKRotationWeight(AvatarIKGoal.LeftFoot, 1f); 
        // 设置左脚IK目标的旋转,animator会根据这个旋转来调整角色左脚的旋转
        animator.SetIKRotation(AvatarIKGoal.LeftFoot, leftFootIKTarget.rotation); 

        // 右脚IK的设置,与左脚类似
        animator.SetIKPositionWeight(AvatarIKGoal.RightFoot, 1f);
        animator.SetIKPosition(AvatarIKGoal.RightFoot, rightFootIKTarget.position);
        animator.SetIKRotationWeight(AvatarIKGoal.RightFoot, 1f);
        animator.SetIKRotation(AvatarIKGoal.RightFoot, rightFootIKTarget.rotation);
  
    }
}

通过这样的设置,角色的脚部和头部就会根据我们设定的IK目标来动态调整位置和旋转,从而实现更自然的动画效果,比如行走、站立、转头等动画。

二、基于IK的常规动画实现

2.1 行走动画示例

对于行走动画,我们可以通过改变IK目标的位置来模拟脚步的移动。这里我们假设已经有一个walkSpeed参数来控制行走速度。
在这里插入图片描述

  using UnityEngine;
using UnityEngine.EventSystems;

public class BodyIKAnimationController : MonoBehaviour
{
    // 用于控制角色动画的Animator组件
    public Animator animator;


    // 行走速度,用于控制角色行走动画的速度,值越大,单位时间内行走的距离越远
    [SerializeField]
    private float walkSpeed = 2.0f;

    // 角色每一步抬起的高度,用于在行走动画中实现抬脚的效果,值越大,抬脚越高
    [SerializeField]
    private float stepHeight = 0.5f;



    // 左脚IK目标的Transform,用于设置左脚在动画中的目标位置和旋转
    public Transform leftFootIKTarget;

    // 记录左脚的初始位置,在动画过程中用于计算当前位置
    private Vector3 leftFootInitialPosition;

    // 记录左脚行走步骤的进度,范围从0到stepTime,表示当前左脚行走步骤的完成程度,初始值为0
    private float leftFootStepProgress = 0;

    // 右脚IK目标的Transform
    public Transform rightFootIKTarget;


    // 记录左脚的初始位置,在动画过程中用于计算当前位置
    private Vector3 rightFootInitialPosition;

    // 记录左脚行走步骤的进度,范围从0到stepTime,表示当前左脚行走步骤的完成程度,初始值为0
    private float rightFootStepProgress = 0;


    // 表示左脚是否正在移动的标志,初始化为false
    private bool isLeftFootMoving = true; 

    // 角色的行走方向,用于控制八个方向的移动,需归一化处理(长度为1)
    public Vector3 moveDirection;
    // 当Animator处理IK时调用此方法,layerIndex表示当前动画层索引
    void Start()
    {
        // 首先确保Animator组件存在,否则无法进行IK设置
        if (animator == null)
        {
            animator = GetComponent<Animator>();
        }

        // 设置左脚IK的位置权重为1,表示完全应用IK位置计算。AvatarIKGoal.LeftFoot是Unity定义的表示左脚IK目标的枚举值
        animator.SetIKPositionWeight(AvatarIKGoal.LeftFoot, 1f);
            // 设置左脚IK目标的位置,animator会根据这个位置来调整角色左脚的位置
            animator.SetIKPosition(AvatarIKGoal.LeftFoot, leftFootIKTarget.position);
            // 设置左脚IK的旋转权重为1,表示完全应用IK旋转计算
            animator.SetIKRotationWeight(AvatarIKGoal.LeftFoot, 1f);
            // 设置左脚IK目标的旋转,animator会根据这个旋转来调整角色左脚的旋转
            animator.SetIKRotation(AvatarIKGoal.LeftFoot, leftFootIKTarget.rotation);

            // 右脚IK的设置,与左脚类似
          

        // 获取左脚和右脚的初始位置
        leftFootInitialPosition = leftFootIKTarget.position;
        rightFootInitialPosition = rightFootIKTarget.position;



    }

    void Update()
    {

        Debug.Log("leftFootInitialPosition:" + leftFootInitialPosition + "   rightFootInitialPosition:" + rightFootInitialPosition);
        // 根据行走速度计算每一步所需的时间,用于控制脚步移动的节奏
        if (Input.GetKey(KeyCode.W)) { 
            isLeftFootMoving = true;
         }
        /*
          Vector3.left:表示指向世界空间中 X 轴负方向的单位向量,即(-1, 0, 0)。这是获取 “左” 方向的简便方式。
          Vector3.right:指向世界空间中 X 轴正方向的单位向量,即(1, 0, 0),用于 “右” 方向。
          Vector3.forward:指向世界空间中 Z 轴正方向的单位向量,即(0, 0, 1),代表 “前” 方向。
          Vector3.back:指向世界空间中 Z 轴负方向的单位向量,即(0, 0, - 1),表示 “后” 方向。
          左前方向向量: (Vector3.left + Vector3.forward).normalized;
          左后方向向量: (Vector3.left+Vector3.back).normalized;
          右前方向向量: (Vector3.right + Vector3.forward).normalized;
          右后方向向量: (Vector3.right+Vector3.back).normalized;
       */
        //moveDirection =   (Vector3.left + Vector3.back).normalized;
        moveDirection = Vector3.forward;
        IKTargetUpdate(moveDirection);


        //左脚旋转操作 
        //目标旋转角度
        Quaternion FootControllerrotation = Quaternion.Euler(0, 0, 0)
        // 计算旋转差值(四元数表示)
        Quaternion rotationDifference = Quaternion.Euler(0, 90, -90);
        FootRotation(FootControllerrotation, leftFootIKTarget, rotationDifference)
        //右脚旋转操作 
        Quaternion rightrotationDifference = Quaternion.Euler(0, 90, 90);
        FootRotation(FootControllerrotation, rightFootIKTarget, rightrotationDifference); 


    }

    void IKTargetUpdate(Vector3 moveDirection)
    {  
        float stepTime = 1 / walkSpeed;
        // 左脚的动画逻辑
        if (isLeftFootMoving)
        {
            // 增加左脚行走步骤的进度,根据每帧的时间增量(Time.deltaTime)来更新。Time.deltaTime是Unity提供的表示上一帧到当前帧的时间间隔
            leftFootStepProgress += 0.01f;

            // 如果左脚行走步骤的进度超过了一步所需的时间(stepTime),表示这一步已经完成
            if (leftFootStepProgress >= stepTime)
            {
                //Debug.Log("leftFootStepProgress:" + leftFootStepProgress + "   stepTime:" + stepTime);
                leftFootStepProgress = 0;
                // 设置左脚不再移动,后续需要有相应的逻辑来重新触发移动
                isLeftFootMoving = false; 


            }

            // 计算当前左脚行走步骤的插值因子t,用于计算当前左脚的目标位置,t的范围是0到1,用于实现脚步移动的平滑过渡
            float t = leftFootStepProgress / stepTime;
            // 计算垂直方向的位置变化,用于抬脚和落脚效果
            float verticalOffset = Mathf.Sin(t * Mathf.PI) * stepHeight;

            // 根据行走方向计算水平方向的偏移量
            Vector3 horizontalOffset = moveDirection * stepTime * t;

            // 计算左脚在当前帧的目标位置,考虑八个方向的移动
            Vector3 targetPosition = leftFootInitialPosition + new Vector3(horizontalOffset.x, verticalOffset, horizontalOffset.z);

            // 设置左脚IK目标的位置,驱动动画系统更新左脚的位置,使角色的左脚根据计算出的位置移动
            leftFootIKTarget.position = targetPosition;

        }  
        // 右脚的动画逻辑,与左脚类似
        if (isLeftFootMoving == false)
        {
            rightFootStepProgress += 0.01f;
            if (rightFootStepProgress >= stepTime)
            { 
                rightFootStepProgress = 0;
                isLeftFootMoving = true;
            } 
            float t = rightFootStepProgress / stepTime; 
            float verticalOffset = Mathf.Sin(t * Mathf.PI) * stepHeight; 
            Vector3 horizontalOffset = moveDirection * stepTime * t; 
            Vector3 targetPosition = rightFootInitialPosition + new Vector3(horizontalOffset.x, verticalOffset, horizontalOffset.z); 
            rightFootIKTarget.position = targetPosition;
        } 
    }

    void FootRotation(Quaternion FootControllerrotation, Transform Foor, Quaternion rotationDifference)
    {

    
        // 获取手控制器的旋转(四元数表示)
        Quaternion aRotationQuaternion = FootControllerrotation;



        // 应用差值到手控制器的旋转并获取新的四元数
        Quaternion bRotationQuaternion = aRotationQuaternion * rotationDifference;

        // 将新的四元数设置给左手的旋转
        Foor.rotation = bRotationQuaternion;
    } 
     
}

通过上述代码,我们利用IK目标位置的改变和时间控制,实现了基本的行走动画效果,角色的双脚会根据设定的速度和高度参数进行抬脚和落脚的动作。

三、布偶状态与死亡动画

3.1 设置布偶物理组件

要实现布偶状态,我们需要为角色添加适当的物理组件。首先是Rigidbody,它赋予角色物理属性,如重力、质量等。同时,对于每个骨骼,我们可以添加CharacterJointConfigurableJoint等关节组件来模拟物理连接,使角色在物理模拟下呈现出类似布偶的效果。
在这里插入图片描述

以下是相关的代码示例:

// 用于布偶物理模拟的根Rigidbody组件,它是整个布偶物理系统的基础,控制角色整体的物理行为
public Rigidbody rootRigidbody; 
// 角色各个关节的CharacterJoint数组,CharacterJoint用于模拟关节的物理连接,这里可用于布偶物理效果,每个关节都有其特定的物理属性设置
public CharacterJoint[] joints; 

void Start()
{
    // 初始时关闭根Rigidbody的物理模拟,使角色处于动画控制状态,不受物理引擎的影响,这样可以先让角色执行IK动画
    rootRigidbody.isKinematic = true; 
    // 遍历所有关节,关闭关节预处理。这一步可以在初始化时避免一些不必要的物理计算,在切换到布偶状态时再重新启用
    foreach (var joint in joints)
    {
        joint.enablePreprocessing = false; 
    }
}

3.2 切换到布偶状态实现死亡动画

当角色死亡时,我们可以通过以下步骤切换到布偶状态:

// 用于触发角色死亡并切换到布偶状态的函数
public void Die()
{
    // 禁用Animator组件,停止基于动画状态机的动画播放,角色将不再受动画状态机控制,而是转为受物理引擎控制
    animator.enabled = false; 
    // 启用根Rigidbody的物理模拟,使角色进入布偶物理状态,开始受物理引擎的各种物理规则影响,如重力、碰撞等
    rootRigidbody.isKinematic = false; 
    // 遍历所有关节,启用关节预处理,使关节在物理模拟中正常工作,模拟布偶的物理效果,关节会根据物理作用力做出相应的动作
    foreach (var joint in joints)
    {
        joint.enablePreprocessing = true; 
    }
    // 给根Rigidbody添加一个向下的力,模拟死亡后的倒下效果。这里的力的大小和方向可以根据实际情况调整,ForceMode.Impulse表示瞬间施加一个力
    rootRigidbody.AddForce(Vector3.down * 5f, ForceMode.Impulse); 
}

通过这样的切换,角色就会从基于IK的动画状态切换到布偶物理状态,模拟出死亡后的倒下等效果,给玩家更真实的视觉感受。

四、动画状态切换的管理

在游戏中,需要合理地管理动画状态的切换。可以使用状态机或脚本逻辑来决定何时从正常的IK动画切换到死亡的布偶状态。例如,可以通过检测角色的生命值,当生命值为0时触发Die函数。这种方式可以根据游戏的具体逻辑来灵活调整,使动画过渡更加自然和符合游戏情境。

通过以上步骤,我们可以在Unity中实现基于IK的常规动画,并在合适的时机切换到布偶状态来展示死亡动画,为游戏角色带来更丰富和逼真的动画表现。希望本文对您在Unity动画开发方面有所帮助。

Logo

分享前沿Unity技术干货和开发经验,精彩的Unity活动和社区相关信息

更多推荐