动画

动画状态机

简介

动画状态机(AnimatorStateMachine)是一种用于控制和管理动画切换的工具,你可以用它添加各种动画状态及其之间的转换规则,使角色或动画对象能够在不同动作之间自然的切换。

主要组成部分

动画状态(AnimatorState

代表动画状态机中的一个单独状态,即某一时刻动画系统中播放的动画。,比如“站立”、“跑步”或“跳跃”。每个状态都有一个对应的动画片段。

属性功能说明
Name修改 AnimatorState 的名字,名字在所在的层要是唯一的。
AnimationClip用于绑定 AnimationClip 资产,AnimationClip 存储了模型的动画数据。
WrapModeAnimatorState 是循环播放还是播放一次,默认为 Once 即播放一次。
SpeedAnimatorState 的播放速度,默认值为 1.0 ,值越大动画速度越快
StartTimeAnimatorStateAnimationClip 的哪个时间开始播放,时间为相对 AnimationClip 时长的归一化时间。默认值为 0 ,即从头开始播放。 例如:值为 1.0 ,则是 AnimationClip 的最后一帧状态。
EndTimeAnimatorState 播放到 AnimationClip 的哪个时间结束播放,时间为相对 AnimationClip 时长的归一化时间。默认值为 1.0 ,即播放到最后。
StateMachineScripts允许开发者编写自定义的脚本逻辑,以在动画状态机的不同事件(如状态进入、退出、更新等)中执行特定的代码。它类似于 Script,但专门用于动画状态机。
在编辑器中有三个特殊的动画状态

entry: 用于表示动画状态机的进入点。进入动画状态机时,总是先进入 entry,然后根据定义的转换条件跳转到其他状态。 entry 本身不会播放动画,它主要用于连接状态机的起点到初始动画状态。通常情况,你应该将它连接到角色或动画对象的默认状态,比如角色的 Idle(站立) 状态。

any:允许动画状态机中任意状态发生特定条件时跳转到目标状态,这对于处理全局事件或紧急动画(如受伤、死亡)非常有用。

any 状态具有最高的优先级,因此使用 any 需要谨慎,因为它可以打破当前动画的正常流转,可能会导致动画过渡不自然的情况。开发者需要确保只有在明确的条件下才使用 any 进行转换。

exit:表示动画状态机的退出点。当状态机进入 exit 时,通常意味着状态机结束。状态机将重新进入 entry 状态。

动画过渡(AnimatorStateTransition

用于定义动画状态机中两个状态之间的转换规则和条件。它决定了何时以及如何从一个 AnimatorState 切换到另一个 AnimatorState。

属性功能说明
Duration过渡时长,时间为相对目标状态的归一化时间,默认值为 0.25
Offset目标状态向前的偏移时间,时间为相对目标状态的归一化时间,默认值为 0
ExitTime起始状态过渡开始时间,时间为相对起始状态的归一化时间,默认值为 0.75
Solo使选定的动画过渡成为唯一活动的状态。其它动画过渡将被忽略。
Mute禁用选定的动画过渡

Solo 和 Mute 通常用于调试,用于帮助开发者更高效地测试和调试动画状态机。

使用动画状态机

默认播放

编辑器使用

将动画状态(AnimatorState)连接到entry上你导出的项目运行时就会自动播放其上的动画,而不需再调用 animator.play。点击动画控制组件(Animator)绑定的实体,即可预览动画。

alt text

脚本使用

你可以通过 animatorStateMachine.addEntryStateTransition 方法让动画状态连接动画状态机的 entry

const animatorStateMachine = animator.animatorController.layers[0].stateMachine;
animatorStateMachine.addEntryStateTransition('Idle');

动画过渡

编辑器使用

将两个想要过渡的 AnimatorState 连接即可实现动画过渡的效果, 点击两个动画间的连线,可以修改动画过渡的参数调整效果。

alt text

点击连线你可以设置动画过渡(AnimatorStateTransition)的参数,如添加条件:

alt text

若添加多个条件,只有所有条件都满足时,才会开始过渡。

脚本使用

你可以通过为 AnimatorState 添加 AnimatorTransition 实现动画状态间的过渡。

const walkState = animatorStateMachine.addState('walk');
walkState.clip = walkClip;
const runState = animatorStateMachine.addState('run');
runState.clip = runClip;
const transition = new AnimatorStateTransition();
transition.duration = 1;
transition.offset = 0;
transition.exitTime = 0.5;
transition.destinationState = runState;
walkState.addTransition(transition);
animator.play("walk");

通过这样的方式你之后每次在该动画状态机所在的层播放 walk 动画时都会在播放一半时开始过渡到 run 动画。

为动画过渡添加条件

你可以通过 animatorStateTransition.addCondition 方法为动画过渡添加条件

const idleState = animatorStateMachine.addState('idle');
idleState.clip = idleClip;
const walkState = animatorStateMachine.findStateByName('walk');
const transition = new AnimatorStateTransition();
 
transition.addCondition(AnimatorConditionMode.Greater, 'speed', 0);
 
transition.destinationState = walkState;
idleState.addTransition(transition);

通过这样的方式你之后每次在该动画状态机所在的层播放 idle 动画时并且 speed 参数大于 0 时,动画将过渡到 walk 动画。

添加参数/删除参数

你可以通过 animatorController.addParameter 方法来添加参数,animatorController.removeParameter 方法来添加参数

const animatorController = animator.animatorController;
animatorController.addParameter('speed', 0);
// 删除参数
animatorController.removeParameter('speed');
设置参数值

你可以通过 animator.setParameterValue 方法来设置参数的值

class AnimationScriptExample extends Script {
  animator: Animator;
  onStart() {
    this.animator = this.entity.getComponent(Animator);
  }
 
  onUpdate(deltaTime: number) {
    const inputManager = this.engine.inputManager;
 
    // 如果用户按下 W 键,则设置 speed 参数值为 1, 符合切换到走路状态的条件,动画切换到走路
    if (inputManager.isKeyHeldDown(Keys.KeyW)) {
      this.animator.setParameterValue('speed', 1);
    }
    // 如果用户松开 W 键,则设置 speed 参数值为 0, 符合切换到站立状态的条件,动画切换到站立
    if (inputManager.isKeyUp(Keys.KeyW)) {
      this.animator.setParameterValue('speed', 0);
    }
  }
}
获取参数值

你可以通过 animator.getParameterValue 方法来获取参数的值:

class AnimationScriptExample extends Script {
  animator: Animator;
 
  onStart() {
    this.animator = this.entity.getComponent(Animator);
  }
 
  onUpdate(deltaTime: number) {
    const speed = this.animator.getParameterValue('speed');
    if (speed === 1) {
      console.log("The player is walking")
    }
  }
}
获取参数对象

你可以通过 animator.getParameter 方法来获取参数对象:

class AnimationScriptExample extends Script {
  animator: Animator;
 
  onStart() {
    this.animator = this.entity.getComponent(Animator);
    const speedParameter = this.animator.getParameter('speed');
    // 设置 speed 的默认值
    speedParameter.defaultValue = 0;
  }
}

动画状态机动画循环

将动画状态连接到 exit 状态,状态机会退出并重新进入 entry,使得整体流程循环 alt text

脚本使用

你可以通过 state.addExitTransition 方法让动画状态连接动画状态机的 exit

const runState = animatorStateMachine.addState('run');
runState.addExitTransition();

通过这样的方式你之后每次在该动画状态机所在的层播放 Run 动画时都会播放结束就会进入 exit 状态之后重新从 entry 开始。

状态机脚本

你可以为每个动画状态添加状态机脚本,它可以让你在动画状态机的不同事件(如状态进入、退出、更新等)中接收到回调详见

alt text

alt text

脚本使用

状态机脚本提供了三个动画状态周期:

  • onStateEnter:动画状态开始播放时回调。
  • onStateUpdate:动画状态更新时回调。
  • onStateExit:动画状态结束时回调。
class theScript extends StateMachineScript {
  /**
   * onStateEnter is called when a transition starts and the state machine starts to evaluate this state.
   * @param animator - The animator
   * @param animatorState - The state be evaluated
   * @param layerIndex - The index of the layer where the state is located
   */
  onStateEnter(animator: Animator, animatorState: AnimatorState, layerIndex: number): void {
    console.log(`Enter ${animatorState.name}`)
  }
 
  /**
   * onStateUpdate is called on each Update frame between onStateEnter and onStateExit callbacks.
   * @param animator - The animator
   * @param animatorState - The state be evaluated
   * @param layerIndex - The index of the layer where the state is located
   */
  onStateUpdate(animator: Animator, animatorState: AnimatorState, layerIndex: number): void {
    console.log(`Update ${animatorState.name}`)
  }
 
  /**
   * onStateExit is called when a transition ends and the state machine finishes evaluating this state.
   * @param animator - The animator
   * @param animatorState - The state be evaluated
   * @param layerIndex - The index of the layer where the state is located
   */
  onStateExit(animator: Animator, animatorState: AnimatorState, layerIndex: number): void {
    console.log(`Exit ${animatorState.name}`)
  }
}
 
animatorState.addStateMachineScript(theScript)

如果你的脚本不用复用的话你也可以这么写:

state.addStateMachineScript(
  class extends StateMachineScript {
    onStateEnter(
      animator: Animator,
      animatorState: AnimatorState,
      layerIndex: number
    ): void {
      console.log("onStateEnter: ", animatorState);
    }
  }
);

多层动画状态机混合

每个动画层(AnimatorControllerLayer)都有一个状态机,动画最终的表现由多个层进行混合而成,如何使用动画层(AnimatorControllerLayer)详见

这篇文档对您有帮助吗?