动画管线与动作状态机
Animation Pipeline & Action State Machines
低层动画管线把片段和混合规格变成局部姿势、全局姿势和蒙皮矩阵调色板;其上的动作状态机 (ASM) 给游戏代码提供"按状态驱动动画"的接口。每个状态是任意复杂的片段混合,跟踪当前所有片段的方式有扁平加权平均和混合树两种;过渡用数据驱动的过渡矩阵描述,控制参数通过节点搜索、命名变量或控制结构体暴露给代码(GEA4 卷 II 13.9–13.10)。
动画管线的六个阶段
对每个动画中的角色或物体(卷 II 13.9):
- 片段解压与姿势提取:每个片段在当前时间提取一个局部姿势,可以是全身、部分(关节子集)或差分姿势。
- 姿势混合:全身 lerp、部分骨骼 lerp、叠加混合,输出一个完整的局部姿势;只有一个片段时跳过。见 动画混合。
- 全局姿势生成:遍历层级连乘局部姿势。
- 后处理(可选):IK、布娃娃、程序化动画,见 动画约束与后处理。
- 重算全局姿势:很多后处理以全局姿势为输入、输出局部姿势,之后需重算;不需要全局姿势的后处理可以放在 2 和 3 之间,省掉重算。
- 矩阵调色板生成:每个关节的全局姿势乘缓存的逆绑定姿势,得到交给渲染器的蒙皮矩阵,见 顶点混合与蒙皮。
flowchart LR IN["输入:局部时钟、片段、骨骼、混合规格"] --> D["解压与姿势提取"] D --> B["姿势混合"] B --> G["全局姿势计算"] G --> P["后处理 IK/布娃娃"] P --> G2["重算全局姿势"] G2 --> K["蒙皮矩阵计算"] K --> OUT["输出:局部姿势、全局姿势→玩法系统;矩阵调色板→渲染引擎"]
上图按 GEA 图 13.48 的阶段顺序绘制;后处理钩子位于全局姿势之后、调色板生成之前。
动作状态机
角色的动作(站、走、跑、跳)最好用有限状态机建模,即动作状态机 (ASM)。它位于动画管线之上,几乎所有高层游戏代码都通过它驱动动画(卷 II 13.10)。通用 FSM 见 状态模式与有限状态机。
- 每个状态对应一个任意复杂的混合:idle 可以只是一个全身片段;"跑"是半圆方向混合;"边跑边射击"再加瞄准用的叠加或部分混合、看向用的眼头肩混合、控制步态和脚距的叠加,以及增加"人味"的随机变化。
- 状态 A→B 过渡时,两个状态的输出姿势通常交叉淡入淡出。
- 状态层:身体不同部分可同时做不同的事(跑、瞄准射击、面部说台词),而且彼此不完全同步:头先转,然后是肩、髋,最后是腿,传统动画称之为预备动作 (anticipation)。做法是多个独立状态机各占一层,各层输出再混合成最终姿势。GEA 图 13.49 的例子:基础层负责全身姿态和移动,变化层叠加差分片段增加多样性,两个手势层(一个叠加、一个部分混合)负责瞄准和指向。
扁平加权平均
引擎维护一张当前所有权重非零片段的扁平列表,对每个关节的平移、四元数(当作四维向量)、缩放做 N 点加权平均(卷 II 13.10.1):
权重归一化后就是 ;N=2 时退化为 lerp。
- 补充(GEA 此处只说把四元数“当作四维向量”平均):加权和一般不是单位四元数,平均后要重新归一化(即 nlerp 的推广);并且 与 表示同一旋转,求和前需把各四元数翻到同一半球(与参考四元数点积为负就取反),否则会抵消甚至得到接近零的结果。见 四元数 和 动画混合 中的 slerp 符号说明。这种平均只在各旋转彼此接近时是好的近似。
- OGRE:
Ogre::Entity聚合AnimationStateSet,其中每个AnimationState记片段名、局部时钟mTimePos、权重mWeight、是否启用、是否循环。OGRE 没有播放速率概念,只能靠缩放传给addTime()的时间来变速(卷 II 13.10.1.1)。 - Granny:同样是扁平加权平均并自动归一化权重;长处在时间处理,使用全局时钟、支持任意次数循环和负速率倒放(卷 II 13.10.1.2)。
- 交叉淡入淡出:简单情况就是升 、降 。从一个复合混合过渡到另一个(如走 = ABC 三路平均,跳 = DE 两路)时,要保持组内相对权重:源组权重乘 ,目标组乘 ,例如 …(式 13.20)。这要求在扁平数组之外额外维护片段分组的元数据(卷 II 13.10.1.3)。
混合树
混合树是编译原理中的表达式树(语法树):内部节点是混合运算(非终结符),叶节点是片段(终结符)(卷 II 13.10.2)。
| 节点 | 说明 |
|---|---|
| 二元 lerp | 两个输入、一个 |
| 一维通用混合 | n 个输入;对给定 总能化成相邻两个片段的二元 lerp |
| 二维 lerp | 级联的二元 lerp |
| 叠加 | 二元,但输入不可交换:一个是普通姿势,另一个是差分姿势,结果是普通姿势;所以叠加输入必须是叶节点,叠多个差分要级联 |
| 分层 | 各层当前状态的树合并成一棵"超级树 (über tree)" |
| 交叉淡入淡出 | 在源、目标两棵树的根之间临时插一个二元 lerp 节点, 从 0 到 1,完成后撤掉该节点和源树 |
和扁平加权平均相比,混合树上的交叉淡入淡出更直观:无论是片段到片段还是复合混合到复合混合,做法都一样(卷 II 13.10.2.6)。
状态与混合树的数据驱动规格
- 目标是快速迭代:增删改状态并很快看到结果。原子节点只需四种:片段、二元 lerp、二元叠加,可能还有三元(三角形)lerp;常用组合可预定义为复合节点(N 维线性混合、足球运球、射击、格斗招式等)。GEA 作者认为用文本还是图形编辑器对最终质量影响不大,关键是改了能很快看到效果(卷 II 13.10.3)。
- Naughty Dog(《神秘海域》《最后生还者》):用定制的 Scheme 写状态(与公司的 Lisp 渊源有关),分 simple(单片段)和 complex(任意 lerp/叠加树)两类:
(define-state complex
:name "move-b-to-f"
:tree (anim-node-lerp
(anim-node-additive
(anim-node-additive (anim-node-clip "move-f") (anim-node-clip "move-f-look-lr"))
(anim-node-clip "move-f-look-ud"))
(anim-node-additive
(anim-node-additive (anim-node-clip "move-b") (anim-node-clip "move-b-look-lr"))
(anim-node-clip "move-b-look-ud"))))
快速迭代靠四个工具:游戏内动画查看器;命令行工具热重编、热重载动画脚本;记录每个角色最近几秒的全部状态转换,可暂停后倒带排查;以及在 Maya 里改动画几乎即时更新到游戏里的实时更新工具(卷 II 13.10.3.1)。数据定义语言的背景见 引擎配置系统,游戏内工具见 游戏内调试工具。
- UE5:GEA 列出骨架编辑器(绑定、给关节加插槽 socket,即其他引擎的挂接点)、骨骼网格体编辑器、动画编辑器(导入、压缩、片段时间设置,组合混合空间,创建动画蒙太奇)、动画蓝图编辑器(用蓝图控制动画状态机)和物理编辑器(布娃娃刚体层级)(卷 II 13.10.3.2)。详见 动画蓝图与动画系统。
过渡
- 种类:源末姿势与目标首姿势完全一致时可直接切换;否则交叉淡入淡出;交叉淡入淡出做不出来的(从躺地到站起)用过渡状态,它只在过渡中使用、从不作为稳态节点,但作为完整状态可以包含任意混合树(卷 II 13.10.4.1)。
- 参数:源与目标状态、类型(立即 / 交叉淡入淡出 / 过渡状态)、时长、缓入缓出曲线类型、过渡窗口(只在源动画局部时间的某段区间内允许,例如出拳挥到后半段才能接受击反应)(卷 II 13.10.4.2)。
- 过渡矩阵:n 个状态最多 种过渡,实际矩阵很稀疏(死亡态不能转出,开车不能直接转游泳),而且一个过渡规格可被许多状态对复用(卷 II 13.10.4.3)。
- 《荣誉勋章:血战太平洋》的通配符过渡:源、目标名可含
*,先定义全局默认from="*" to="*",再按类别细化,直到具体状态对(卷 II 13.10.4.4):
<trans from="*" to="*" type=frozen duration=0.2>
<trans from="walk*" to="run*" type=smooth duration=0.15>
<trans from="*prone" to="*get-up" type=smooth duration=0.1 window-start=2.0 window-end=7.5>
- 《神秘海域》的一等公民过渡:如果高层代码按目标状态名请求过渡,就得知道状态名和哪些过渡合法。Naughty Dog 把过渡变成有名字的一等实体:每个状态列出合法过渡,名字标准化(叫 "walk" 的过渡总是去某种走路状态),代码按过渡名请求,找不到就失败。
transition-group复用一组过渡,transition-end指定局部时间到头时的过渡。好处是状态图结构可以改,C++ 不用动:十种走路状态都定义叫 "jump" 的过渡,先都指向一个通用跳跃状态,再逐步改成各自的跳跃或插入过渡状态,只要过渡名不变(卷 II 13.10.4.4)。
控制参数
- 扁平加权平均:接口简单,但控制代码要按名字查每个片段并亲自调权重(比如为斜向跑去调 StrafeLeft、RunForward、StrafeRight、RunBackward 四个权重),繁琐难读。
- 混合树:片段天然按功能分组,但参数埋在树里,代码需要先验地知道树结构。
- 常见解法:节点搜索(给关键节点起名如 "HorizAim",按名查找)、命名变量(参数按名字查)、控制结构体(一个浮点数组或 C 结构体存全部参数,节点按成员、名字或索引绑定)(卷 II 13.10.5)。
与 UE5 的对照
| GEA 概念 | UE5 对应 | 差异 |
|---|---|---|
| 状态层 | 动画蓝图中的插槽 (Slot) 与上下半身分层 | UE5 文档用插槽组让上下半身同时播放不同蒙太奇(Animation Montage) |
| 过渡参数与稀疏矩阵 | 状态机转换规则、导管、状态别名 | UE5 转换是单向连线;状态别名让一组状态共享同一套规则和混合时长(State Machines),作用上接近 MOHPA 的通配符默认过渡 |
| 过渡状态 | 状态机中的普通状态 | UE5 文档未单列"过渡状态"类型(这一对应是归纳) |
| 命名变量 / 控制结构体 | 事件图更新、动画图读取的变量 | 见 动画蓝图与动画系统 |
GEA 原书中的问题
- 13.10.3.2 的标题是 Unreal Engine 5,但文中说片段"UE4 称为 Sequences",配图也是"Unreal Engine 4 animation Blueprints editor",是从旧版沿用的文字和截图。
- 同节说动画蒙太奇用来定义游戏内过场 (in-game cinematics)。UE5 文档把蒙太奇描述为把多个动画序列组合成一个资产并由蓝图控制播放、可分段跳转(用于连招、换弹等玩法动作)(Animation Montage);UE5 中制作过场的工具是 Sequencer 过场动画(Unreal Engine Sequencer Movie Tool Overview)。两种说法并列,以引擎文档为准。
出处
- Game_Engine_Architecture_4th_Edtion_Volume_2_-_Jason_Gregory.pdf
- Jason Gregory《Game Engine Architecture》第 4 版(2026)卷 II 第 13 章 13.9–13.10 节
- State Machines
- Animation Montage
- Unreal Engine Sequencer Movie Tool Overview