2D 角色动画的本质

Unity 的逐帧角色动画不是直接移动角色,而是按时间修改 SpriteRenderer 当前显示的 Sprite。
物理与动画要分开理解:
  • Rigidbody2D、Collider2D、Transform 决定人物真实位置和运动。
  • Animator、Animation Clip、SpriteRenderer 决定人物当前显示什么姿势。
动画用于表现运动状态,不应代替 Rigidbody2D 控制跳跃和下落。

Animation Clip:属性随时间发生变化

Animation Clip 是一组按时间记录的属性。逐帧角色动画通常修改 SpriteRenderer.Sprite。
例如:
Clip 没有生成新的图片,只是在不同时间把不同 Sprite 引用写入 SpriteRenderer。

Samples

Samples 表示动画每秒采样多少次。
例如 Samples = 10 时,每个相邻关键帧通常间隔 0.1 秒。动画速度由关键帧时间和 Samples 共同决定,不只取决于用了多少张 Sprite。

Loop Time

Loop Time 决定动画结束后是否回到第一帧。
循环动画必须满足首尾能够自然衔接。非循环动画可以表现一次性的姿势变化,播放完成后保持最后一帧,等待 Animator 切换到其他状态。

两种动画帧设计

保留 Loop

循环过程中人物主体的参考位置应保持稳定:
  • 身体中心和脚底高度基本一致。
  • 第一帧和最后一帧能够自然连接。
  • 可以让头发、披风、衣角、呼吸和武器挂件产生局部变化。
适合 Idle、Walk、Run、持续悬浮,以及专门设计成无缝循环的空中动作。

取消 Loop

允许人物身体姿势随动作阶段发生变化:
  • 起跳时身体展开。
  • 下落时身体逐渐收紧。
  • 攻击包含前摇、出手和收招。
  • 受击或死亡姿势逐渐变化。
适合 Jump、Fall、Attack、Hurt、Death 等一次性过程。动画播放完成后保持最后一帧,直到游戏状态发生变化。
Jump 和 Fall 的帧数不需要相同。两个 Clip 可以有不同帧数、Samples 和播放时长,因为它们分别表现两个不同阶段。

Animator 组件

Animator 读取 Animator Controller,并在运行时计算当前 State、Transition、Blend Tree 和动画结果。

Controller

指定当前对象使用的 Animator Controller。Controller 中保存参数、状态机、状态转换和混合树。

Avatar

主要用于 3D 人形骨骼重定向。普通 2D Sprite 逐帧动画通常不需要 Avatar。

Apply Root Motion

开启后允许动画中的根节点位移驱动 GameObject。使用 Rigidbody2D 控制角色时通常关闭,否则动画位移和物理位移可能同时修改角色位置。

Update Mode

  • Normal:随普通帧更新,受 Time.timeScale 影响,普通 2D 动画通常使用它。
  • Animate Physics:尽量配合物理更新,适合需要与物理节奏同步的动画。
  • Unscaled Time:不受 Time.timeScale 影响,常用于暂停界面和独立 UI 动画。

Culling Mode

  • Always Animate:即使不可见也继续计算动画。
  • Cull Update Transforms:不可见时减少部分更新。
  • Cull Completely:不可见时停止动画计算。
角色离开相机后是否还需要保持动画时间和状态,决定使用哪种模式。

Animator Parameter:把游戏状态传给动画

Animator 不会自动知道角色是否移动、是否落地或正在上升。脚本必须读取游戏状态并写入参数。
参数写入顺序通常不是关键;只要在同一帧完成,Animator 使用的是这一帧最终写入的值。

Bool

表示持续存在的二元状态,例如 isGrounded、isMoving 和 isDead。Bool 会保持当前值,直到脚本再次修改。

Float

表示连续变化的数值,例如 yVelocity、水平速度和移动速度。Float 适合驱动 Blend Tree。

Trigger

表示发生一次的事件,例如 Attack、Hurt 和 Dash。Trigger 被状态机使用后会被消耗,不适合表示持续状态。

Int

表示离散编号,例如武器类型、攻击段数或动作编号。

State:当前采用哪套动画逻辑

Animator State 表示当前正在执行的动画状态,例如 Idle、Walk 和 JumpAndFall。
State 的 Motion 可以是一个 Animation Clip,也可以是一个 Blend Tree。
State 不是动画文件本身,而是 Animator 使用动画的方式。State 还包含 Speed、Mirror、Cycle Offset 和 Write Defaults 等设置。

Transition:什么时候切换状态

Transition 保存从一个 State 到另一个 State 的条件与过渡方式。
跳跃案例中的关系是:

Conditions

Conditions 决定转换什么时候成立。isGrounded 适合决定是否进入或离开空中状态,yVelocity 适合区分上升与下落。

Has Exit Time

开启后,即使 Conditions 已经满足,也要等待源动画播放到指定进度才能离开。
它适合需要播放到关键阶段的 Attack、Hurt 和 Death;不适合要求立即响应的起跳、落地和移动切换。

Exit Time

使用标准化动画时间:0.5 表示播放到 50%,1 表示播放完一轮。只有使用 Has Exit Time 时,它才构成主要转换条件。

Transition Duration

表示状态过渡持续多久。骨骼和数值属性可以在过渡中插值,但两个 Sprite 引用不能真正融合成一张中间图片,最终仍然表现为离散 Sprite 的选择。因此逐帧 Jump、Fall 和 Idle 通常使用很短的时间或 0。

Fixed Duration

  • 开启:Transition Duration 使用秒。
  • 关闭:Transition Duration 使用相对当前动画长度的标准化比例。

Transition Offset

决定进入目标状态时从目标动画的哪个进度开始。普通 Jump 和 Fall 通常从 0 开始。

Interruption Source

决定过渡正在进行时,哪些状态能够打断它。它主要用于处理攻击、受击、死亡等状态的优先级。

Ordered Interruption

决定多个可打断 Transition 是否按照列表顺序评估。状态复杂时,Transition 的排列顺序会影响最终选择。

Can Transition To Self

决定当前状态能否再次转换到自己。
例如 Any State → JumpAndFall 的条件是 isGrounded == false。角色腾空期间这个条件会持续成立。如果允许 Transition To Self,已经处于 JumpAndFall 时仍可能重新进入同一状态,使动画时间重新开始,表现为重复播放或抽动。
普通 JumpAndFall 应关闭 Can Transition To Self。只有确实需要重新播放同一状态时才开启,例如使用 Trigger 重新触发 Hurt 或 Attack。

Any State

Any State 表示不论当前处于哪个 State,都可以尝试指定 Transition。
它适合 Hurt、Death、Stun 等必须立即打断当前动作的状态。
使用 Any State → JumpAndFall 时需要注意:
  • isGrounded == false 在整个腾空期间持续成立。
  • 应关闭 Can Transition To Self,避免重复进入 JumpAndFall。
  • 地面检测必须稳定,避免落地边缘反复 true / false。

Blend Tree:用连续参数组织动画

Blend Tree 根据一个或多个参数计算各个 Motion 的权重。

1D

使用一个 Float 参数,例如移动速度或 yVelocity。
适合根据单一连续数值区分慢走、跑步、上升和下落。

2D Simple Directional

使用两个参数表示方向,适合上、下、左、右等明确方向,并且每个方向通常只有一个 Motion。

2D Freeform Directional

同一方向可以存在不同强度,例如向前慢走与向前奔跑。

2D Freeform Cartesian

把两个参数看作普通坐标,不强调方向关系,适合两个相互独立数值共同控制动画。

Direct

每个子 Motion 使用独立参数直接控制权重,适合需要由脚本明确控制多个 Motion 权重的情况。

Threshold

Threshold 表示每个 Motion 对应的参数位置。参数位于两个 Threshold 之间时,Unity 会计算它们的权重。
Sprite 是离散图片引用,不能像骨骼姿势和数值属性一样生成真正的中间 Sprite。Blend Tree 在逐帧 Sprite 动画中主要用于根据连续数值组织动画选择,而不是把两张图片融合。

Automatic Thresholds

开启后由 Unity 根据 Motion 顺序自动分配 Threshold;关闭后可以手动指定每个 Motion 对应的参数值。

yVelocity 驱动 Jump 与 Fall

yVelocity 来自 Rigidbody2D 的真实竖直速度:
  • yVelocity > 0:角色正在上升。
  • yVelocity 接近 0:角色接近最高点。
  • yVelocity < 0:角色正在下落。
  • 落地后通常回到接近 0。
完整过程是:
Blend Tree 只根据参数选择动画,不能修正 Sprite 内人物高度不一致、Pivot 不合理或动画首尾无法衔接的问题。

本次下落抽动与反弹

真实物理反弹

录制问题视频时,角色使用的 Physics Material 2D 设置了:
人物带着负的 yVelocity 撞击地面后,一部分向下速度被转换成向上速度,因此 Rigidbody2D 发生真实回弹。
回弹还会让 yVelocity 出现:
Blend Tree 可能因此从 Fall 再偏向 Jump,然后又回到 Fall,使动画跳动更加明显。

动画帧造成的视觉抽动

下落动画选择了 Sprite 45~48,但不同帧中人物身体相对格子和 Pivot 的高度不一致,同时 Animation Clip 开启了 Loop。
这里变化的是 Sprite 内人物的视觉位置,不是 PlayerRoot、Rigidbody2D 或 Collider2D 的真实位置。
这次问题不是因为 Jump 与 Fall 使用的动画帧数量不同,也不是 Transition 配置本身造成。核心原因是 Physics Material 的 Bounciness,以及不满足循环条件的下落帧开启了 Loop。

对应选择

  • 需要 Loop:身体和脚底基准保持稳定,只让头发、披风等局部元素形成首尾连续的循环。
  • 不需要 Loop:允许身体姿势逐渐变化,播放一次后保持最后一帧,等待 yVelocity 或 isGrounded 切换状态。
普通角色的 Jump 和 Fall 使用第二种方式通常更自然。