设想你是一位后厨主厨:备菜时手起刀落,炒菜时颠勺翻锅,炖煮时守在灶前看火——每一种“任务”都有一套专属动作。角色动画里的状态机,就是给角色发一张后厨“工作流卡”:它决定角色此刻该播放哪段动画,并在条件满足时切换到下一个任务,比如“菜切完了才下锅”。而动画混合,则像你放下菜刀、擦净手、再拿起锅铲那一串连贯动作的“慢镜头”——角色不会瞬间瞬移到新动作,而是把新旧两段动画按权重“勾兑”在一起,如同调酱汁一般平滑融合。 这篇文章将带你把后厨里的这张工作流卡拆开来看:从状态机的基本部件,到转移时长的平滑控制,再到混合权重、混合树、加法动画、骨骼重定向,最后落到性能优化。读完你就能理解主流游戏引擎里角色动画的底层逻辑,也能看懂为什么同一个“走”的动作在不同游戏里观感天差地别。

岗位关联

掌握动画状态机与混合,是游戏动画、引擎与 AI 行为联动方向从业者的基本功,也是把动捕数据变成“活角色”的关键一步。

目录
  1. 可视化演示
  2. 状态机基础
  3. 转移机制
  4. 混合原理
  5. 混合树
  6. 高级混合
  7. 骨骼与重定向
  8. 性能与优化
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示动画状态机(idle/wlk/run/jmp/atk)的状态转换、2D混合树(fwd×spd)的动画插值及两组动画A/B的交叉淡入淡出混合过程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

动画状态机(ASM) 5个状态节点:idle/walk/run/jump/attack 状态间按条件转换,高亮当前激活状态 FSM: state + transition + condition 状态机管理角色动画流转

状态机基础

后厨里流传着一张“任务卡”:待机时整理台面,备菜时手起刀落,炒菜时颠勺翻锅,炖煮时守在灶前看火。每一种任务都绑定一套固定动作,厨师不可能同时“切菜又炒菜”。角色动画的状态机,就是给角色发一张同样的任务卡:一个状态对应一段动画(例如 idle、walk、jump),角色在任意时刻必须且只能处于一个状态。

状态不是随意堆叠的,它们靠“转移”连成一张有向网络。后厨里“菜没切完不能下锅”,游戏里“角色没落地不能起跳”——这些前置条件就是转移条件。我们把整张网络抽象成一个标准模型,它由三个部件构成:状态集合、转移函数、初始状态。

M=(S,T,s0),T:S×C→S\displaystyle \mathcal{M} = (S, \mathcal{T}, s_0), \qquad \mathcal{T}: S \times C \to S

1. 状态集合 S:角色所有可能处于的动画状态的集合,例如待机、行走、奔跑。

S={idle, walk, run}\displaystyle S = \{ \text{idle},\ \text{walk},\ \text{run} \}

2. 条件集合 C:来自输入按键或动画参数的布尔条件,例如“移动速度大于 0.1”。

C={c1,c2,…},ci∈{0,1}\displaystyle C = \{ c_1, c_2, \ldots \}, \qquad c_i \in \{0, 1\}

3. 转移函数 T:把“当前状态 + 满足的条件”映射为“下一个状态”。

T:S×C→S\displaystyle \mathcal{T}: S \times C \to S

4. 初始状态 s₀:角色出生时进入的状态,通常是待机,保证每帧都有一个确定起点。

s0∈S\displaystyle s_0 \in S

回到后厨:每天早上开工,厨师先进入“待机”,看一眼订单再按条件逐条切换任务。状态机就是这张永不“越权”的工作流卡——它从底层保证了角色动画始终有且只有一个“当前动作”,逻辑上可预测、可调试。游戏引擎里绝大多数角色行为,都是从这张小小的卡片开始的。

转移机制

细心观察后厨会发现,厨师切换任务从不是“啪”的一下瞬移:放下菜刀需要零点几秒,擦手、转身、拿起锅铲,动作是连贯的。如果让“正在切菜”的画面瞬间变成“正在颠勺”,观感就像视频跳帧一样恐怖。动画转移同样需要一小段时间窗——这就是转移时长。

转移时长的本质,是给“旧动画让位、新动画登场”一个渐进过程。工程上先定义一个归一化进度 s(t):s=0 时完全由旧动画主导,s=1 时完全进入新动画。为了让过渡避免匀速切换的生硬感,还会把线性进度加工成一条两头慢、中间快的平滑曲线。

s(t)=t−t0d,w=s2(3−2s)\displaystyle s(t) = \frac{t - t_0}{d}, \qquad w = s^{2}(3 - 2s)

1. 归一化进度 s:表示转移完成了多少,从 0 开始增长到 1。

s=t−t0d\displaystyle s = \frac{t - t_0}{d}

2. 转移时长 d:新旧动画完全切换所需的秒数,游戏里通常取 0.1 到 0.5 秒。

d>0\displaystyle d > 0

3. smoothstep 变换:把线性进度变成两头平滑、中间快速的曲线。

w=s2(3−2s)\displaystyle w = s^{2}(3 - 2s)

4. 混合权重 w:w 从 0 连续变化到 1,作为后续混合阶段的输入。

厨师的经验在于“什么时候该收刀、什么时候该握铲”——动画系统也一样:旧动画播放到某个时间点才允许转移,这个时间点叫退出时间;同时,更紧急的动作(如受击硬直)可以打断当前正在进行的转移,就像后厨突然来了加急订单,厨师立刻放下手里的活。转移时长 + 平滑曲线 + 中断规则,三者共同决定“切换手感”。

混合原理

调一碟蘸料:两勺辣酱加三勺甜面酱,勺数加起来永远是五勺。动画混合的本质就是“勾兑”——新旧两段动画各取一个混合权重,加权合成最终姿态。权重之和为 1,保证最终姿态的“分量”不会凭空变大或缩小。其中最常见的操作叫交叉淡化(crossfade):在转移期间,把旧动画的姿态和新动画的姿态逐骨骼插值。

所谓的“姿态”,是把每个骨骼的旋转、位移拼成的一个大向量。混合就是对两个姿态向量做加权平均,计算上非常轻量,却是角色动画丝滑感的全部秘密。

P=w1P1+w2P2,w1+w2=1\displaystyle P = w_1 P_1 + w_2 P_2, \qquad w_1 + w_2 = 1

1. 姿态向量 P:把每个骨骼的旋转与位移拼接成的一个高维向量,代表角色某个瞬间的完整动作。

P∈Rb×k\displaystyle P \in \mathbb{R}^{b \times k}

2. 旧姿态 P₁ 与新姿态 P₂:交叉淡化期间同时播放的两段动画在当前帧的姿态。

P1, P2\displaystyle P_1,\ P_2

3. 权重 w₁ 与 w₂:各自占比,随转移进度从 0 到 1 连续变化。

w1+w2=1\displaystyle w_1 + w_2 = 1

4. 权重归一化约束:保证混合后姿态的整体“幅度”不漂移,这是勾兑酱汁时“总勺数不变”的数学表达。

P=w1P1+(1−w1)P2\displaystyle P = w_1 P_1 + (1 - w_1) P_2

调酱汁时手要稳,比例稍微一晃成品就偏了。动画系统也一样:权重曲线(例如上一节的 smoothstep)决定了哪一刻以谁为主,工程上称之为“淡入淡出曲线”。厨师靠手感匀速倾倒,动画系统靠权重函数精确控制,殊途同归。

混合树

客人点“微辣”,不是按一个二值开关,而是拧一个连续旋钮:辣椒量从 0 到 10 克连续变化。混合树就是动画版的“调味旋钮”——它把多个动画挂在一根参数轴上(比如角色移动速度),参数连续变化时,各子节点动画按权重平滑混合,避免“速度一超阈值就硬切奔跑”的尴尬。

一维混合树中,每个子节点在参数轴上有一个锚点位置,例如待机锚定在 0 m/s、行走锚定在 2 m/s、奔跑锚定在 5 m/s。当参数落在某个位置时,离它越近的锚点贡献越大,权重由参数与锚点的距离决定,最后做归一化。

wi=11+∣p−ci∣,w^i=wi∑j=1nwj\displaystyle w_i = \frac{1}{1 + |p - c_i|}, \qquad \hat{w}_i = \frac{w_i}{\sum_{j=1}^{n} w_j}

1. 混合参数 p:外部输入,例如角色当前移动速度、转向角度或高度差。

p∈R\displaystyle p \in \mathbb{R}

2. 锚点位置 c_i:第 i 个动画在参数轴上的归属位置。

ci∈R\displaystyle c_i \in \mathbb{R}

3. 距离反比权重:参数与锚点距离越近,未归一化的权重越大。

wi=11+∣p−ci∣\displaystyle w_i = \frac{1}{1 + |p - c_i|}

4. 权重归一化:所有子节点的权重加起来等于 1,保证姿态幅值稳定。

w^i=wi∑j=1nwj\displaystyle \hat{w}_i = \frac{w_i}{\sum_{j=1}^{n} w_j}

混合树的妙处还在于根运动:它不但混合姿态,还混合位移速度——参数越大,角色前进越快,就像厨师根据“翻锅频率旋钮”同时调整颠勺的幅度与节奏。用一簇混合树替换一堆互相硬切的状态,是大型游戏角色动画的标配做法。

高级混合

一道白切鸡,撒上蒜蓉就是蒜蓉白切鸡,但鸡肉本身没变。加法动画就是这个思路:在基础姿态(走路)之上叠加一个“增量姿态”(如呼吸起伏、受伤颤抖),最终效果是“带情绪的走路”,却不用为每种情绪重录一整段走路动画。增量只记录与基础姿态的差值,因此不会破坏主体动作。

加法动画只是高级混合的起点。二维混合空间是一张“调味平面”:横轴辣度、纵轴咸度,两个参数同时决定混合结果,适合表达转向 + 速度这类双因素动作。而分层混合借助骨骼遮罩把身体拆开——上半身举枪瞄准用一套动画,下半身走路用另一套,谁听谁的由遮罩权重决定。

Pfinal=Pbase+w⋅Padd\displaystyle P_{\text{final}} = P_{\text{base}} + w \cdot P_{\text{add}}

1. 基础姿态 P_base:主体动画,例如正常走路循环。

Pbase\displaystyle P_{\text{base}}

2. 增量姿态 P_add:只记录“变化量”的动画,例如胸口起伏或受伤颤抖。

Padd\displaystyle P_{\text{add}}

3. 叠加强度 w:0 到 1 之间的系数,控制增量对最终姿态的贡献。

w∈[0,1]\displaystyle w \in [0, 1]

4. 最终姿态 P_final:把增量加回基础姿态,得到“带情绪的主体动作”。

Pfinal=Pbase+w⋅Padd\displaystyle P_{\text{final}} = P_{\text{base}} + w \cdot P_{\text{add}}

厨师最拿手的“一菜两吃”:鸡腿肉和鸡胸肉分别按各自火候处理,最后装盘成一个整体。分层混合与加法动画,正是动画师眼里的“一菜两吃”——上半身与下半身各司其职,再通过遮罩合成,让移动射击、爬坡踉跄这类复合动作不靠堆状态也能自然呈现。

骨骼与重定向

同一道菜谱,换一口更大的锅、换一个更高的灶台,依然要做出一样的味道。动画重定向想解决的问题正是“换锅”:把动捕演员录好的动画,播放到身材比例完全不同的游戏角色身上。如果直接照搬,角色会“穿模”或“浮空”。

首先要做骨骼映射:源骨骼的“左上臂”对应目标骨骼的“左上臂”,名字或层级不一致时靠人工或算法对齐。其次,由于目标角色的骨骼更长或更短,关节位移不能直接照抄,而要按骨骼长度比例缩放,旋转则只做坐标系映射、不按比例缩放。

r=ltargetlsource,p⃗target=r⋅p⃗source\displaystyle r = \frac{l_{\text{target}}}{l_{\text{source}}}, \qquad \vec{p}_{\text{target}} = r \cdot \vec{p}_{\text{source}}

1. 源骨骼长度 l_source:录制动画的角色对应骨骼的长度。

lsource\displaystyle l_{\text{source}}

2. 目标骨骼长度 l_target:播放动画的角色对应骨骼的长度。

ltarget\displaystyle l_{\text{target}}

3. 长度比例 r:目标与源的长度比值,决定位移缩放倍数。

r=ltargetlsource\displaystyle r = \frac{l_{\text{target}}}{l_{\text{source}}}

4. 位置映射:源关节位置乘以 r,平移到目标骨骼空间;旋转保持相对姿态不变。

p⃗target=r⋅p⃗source\displaystyle \vec{p}_{\text{target}} = r \cdot \vec{p}_{\text{source}}

大厨出差到陌生后厨,先看灶台火力和锅的大小,再调整翻炒幅度——重定向就是动画的“看锅下菜”。映射 + 长度缩放,让同一段动捕在 2 米巨人和 1 米侏儒身上都显得自然。这也是动作类游戏能复用同一套动捕资产、批量生产 NPC 动画的技术基石。

性能与优化

晚市高峰,后厨同时开十几口锅:每道菜的每个配菜都要按标准处理,还得提前备菜、流水线分工,否则出菜必然混乱。游戏动画也一样:每个角色每帧都要按骨骼采样动画数据,混合层数越多,开销越大。

动画系统的主要成本在采样:要更新的角色数 × 平均骨骼数 × 平均混合层数,大致就是每帧花在动画上的计算量。降低成本的常用手段有:动画压缩(只存关键帧、量化旋转数据)、细节层级 LOD(远处角色低频采样、简化骨骼),以及预算控制(限制同屏播放动画的角色数量)。

C=N×J×L\displaystyle C = N \times J \times L

1. 角色数 N:场景中同时播放动画的角色数量,是成本的第一乘数。

N\displaystyle N

2. 平均骨骼数 J:每个角色采样时要更新的骨骼数量。

J\displaystyle J

3. 混合层数 L:交叉淡化、加法动画、分层混合带来的重复采样倍数。

L\displaystyle L

4. 总开销 C:每帧动画系统的采样计算量,直接决定动画预算的上限。

C=N×J×L\displaystyle C = N \times J \times L

后厨的“提前备菜”对应动画的预烘焙,远处客人看不清摆盘细节对应 LOD,同一口锅多份同炒对应共享动画采样。厨师不会在每道菜上倾注全部精致工序,动画系统也不会让每个角色都跑最高精度的混合计算——预算有限,好钢用在刀刃上,这才是量产游戏动画的生存法则。

AI 应用场景

开放世界中的第三人称角色操控

阶段:动画状态控制(状态机 + 混合树)

解决的问题:传统“剪辑切换”模式让角色动作生硬、速度突变,玩家推摇杆从静止到奔跑像“换碟”一样跳跃。用状态机管理待机/走/跑的大类,再用混合树根据摇杆推入程度连续混合,动作丝滑且与移动速度严格同步。

具体案例:在 Unity 的 Animator 中设置一个 Speed 浮点参数,混合树下挂 idle、walk、run 三个剪辑,摇杆推入幅度直接驱动参数与转移条件。玩家轻推摇杆时角色小步挪动,推到底时大步奔跑,中间没有任何可见的切换断层。

动作捕捉数据重定向到异形角色

阶段:动画数据处理与重定向

解决的问题:动捕演员与游戏角色体型、骨骼长度差异巨大,直接套用旋转会穿模或浮空。重定向负责骨骼映射与长度比例缩放,让同一份动捕数据适配所有体型的角色。

具体案例:录制一段“跨越障碍物”的动捕,先后重定向到 2 米高的巨人和 1 米高的侏儒 NPC。系统自动建立骨骼对应关系,按腿长比例缩放跨步位移,两个角色看起来都像“真的跨过了障碍”。

移动射击中的分层混合

阶段:动画分层合成

解决的问题:角色既要走路又要举枪射击,单一状态无法同时表达两种意图。用骨骼遮罩把上下半身拆开,下半身走混合树、上半身走持枪状态,叠加呼吸起伏的加法动画。

具体案例:吃鸡类游戏中角色边跑边开镜,上半身稳定持枪瞄准,下半身随摇杆速度自然行走。紧急受击时,高优先级状态直接打断当前转移,硬直结束后再平滑回到战斗姿态。

Python 演示

python119 行
import math  class AnimClip:    """一段动画剪辑, 根据本地时间返回一组骨骼旋转标量"""    def sample(self, time):        raise NotImplementedError  class SineClip(AnimClip):    """用正弦函数模拟循环动画, 不同状态频率/幅度不同"""    def __init__(self, name, amp, freq):        self.name = name        self.amp = amp        self.freq = freq     def sample(self, time):        return [            self.amp * math.sin(time * self.freq),            self.amp * math.cos(time * self.freq * 0.5),            self.amp * 0.3 * math.sin(time * self.freq * 0.25)        ]  class AnimState:    """一个动画状态: 绑定剪辑与移动速度"""    def __init__(self, name, clip, move_speed):        self.name = name        self.clip = clip        self.move_speed = move_speed        self.local_time = 0.0  class StateMachine:    def __init__(self):        self.states = {}        self.transitions = []        self.current = None        self.old = None        self.blend_t = 0.0        self.blend_dur = 0.3     def add_state(self, state):        self.states[state.name] = state     def add_transition(self, src, dst, condition):        self.transitions.append((src, dst, condition))     def update(self, dt, inputs):        if self.current is None:            self.current = list(self.states.values())[0]        for src, dst, cond in self.transitions:            if self.current.name == src and cond(inputs):                self.old = self.current                self.current = self.states[dst]                self.blend_t = 0.0                break        self.blend_t = min(self.blend_t + dt, self.blend_dur)        self.current.local_time += dt        if self.old is not None and self.blend_t >= self.blend_dur:            self.old = None        if self.old is None:            return 0.0        mix = self.blend_t / self.blend_dur        return mix     def sample_pose(self, mix):        new_pose = self.current.clip.sample(self.current.local_time)        new_speed = self.current.move_speed        if self.old is None or mix >= 1.0:            return new_pose, new_speed        old_pose = self.old.clip.sample(self.old.local_time)        old_speed = self.old.move_speed        pose = []        for a, b in zip(old_pose, new_pose):            pose.append(a * (1 - mix) + b * mix)        speed = old_speed * (1 - mix) + new_speed * mix        return pose, speed  def speed_input(keys):    if 'run' in keys:        return 2    if 'walk' in keys:        return 1    return 0  def main():    sm = StateMachine()    sm.add_state(AnimState('idle', SineClip('idle', 0.1, 1.0), 0.0))    sm.add_state(AnimState('walk', SineClip('walk', 0.2, 3.0), 1.4))    sm.add_state(AnimState('run', SineClip('run', 0.35, 6.0), 4.2))     sm.add_transition('idle', 'walk', lambda k: speed_input(k) == 1)    sm.add_transition('walk', 'run', lambda k: speed_input(k) == 2)    sm.add_transition('run', 'walk', lambda k: speed_input(k) == 1)    sm.add_transition('walk', 'idle', lambda k: speed_input(k) == 0)    sm.add_transition('run', 'idle', lambda k: speed_input(k) == 0)     script = [(0.0, []), (0.5, ['walk']), (2.0, ['run']),              (4.0, ['walk']), (5.5, []), (7.0, [])]    idx = 0    t = 0.0    dt = 1.0 / 10.0    while t <= 8.0:        while idx < len(script) - 1 and t >= script[idx + 1][0]:            idx += 1        keys = script[idx][1]        mix = sm.update(dt, keys)        pose, speed = sm.sample_pose(mix)        print('t=%.1f state=%-4s mix=%.2f speed=%.2f pose=%s'              % (t, sm.current.name, mix, speed,                 '[' + ' '.join('%.2f' % v for v in pose) + ']'))        t += dt  if __name__ == '__main__':    main()
目的:用 Python 模拟一个最简角色动画状态机(待机/走/跑),并实现交叉淡化混合,输出每一帧的状态、混合权重、移动速度与骨骼姿态。
  1. self.transitions.append((src, dst, condition))记录一条“从某状态到某状态”的转移,condition 是触发条件。
  2. if self.current.name == src and cond(inputs):只有当前状态匹配且条件成立,才允许发生转移。
  3. self.old = self.current把旧状态暂存为混合源,交叉淡化期间需要它的姿态。
  4. mix = self.blend_t / self.blend_dur计算混合进度,从 0 线性增长到 1。
  5. if self.old is None or mix >= 1.0:混合结束则只输出新状态姿态,不再做双重采样。
  6. pose.append(a * (1 - mix) + b * mix)对每个骨骼的旋转值做线性插值,这就是 crossfade 的本质。
  7. speed = old_speed * (1 - mix) + new_speed * mix混合时连移动速度一起插值,避免角色速度跳变。

常见误区

误区很多初学者看到“切换动画”四个字,就以为状态一到,立即用新动画替换旧动画,最多换一个过渡文件,不存在时间过程。

正解交叉淡化是在一段时间内,用权重把新旧两个姿态逐骨骼插值,权重从 0 平滑到 1(常用 smoothstep)。它是“一段连续的勾兑过程”,不是“换一帧图”。

为何常见:“切换”在直觉里是离散动作,忽略了动画天然具有时间维度;加上引擎默认帮你做了过渡,反而让人看不出背后有混合计算。

误区觉得只要把走、跑、跳、蹲、翻滚、贴墙全部建成独立状态,角色就一定生动自然,状态数越多越专业。

正解状态过多会导致转移条件爆炸:每个状态都要考虑“能从谁来、能到谁去”,调试难度指数上升,而且动作之间仍然存在硬切。更优做法是少量状态 + 混合树/参数驱动连续混合,像旋钮一样连续变化。

为何常见:把“真实感”错误等价于“状态数量”,忽略了真实感的来源其实是过渡的连续性。

误区认为多加一层加法动画、多叠一个遮罩层不影响性能,反正角色就那么几个。

正解每一层混合都对应一次额外的姿态采样与插值,总成本按“角色数 × 骨骼数 × 混合层数”增长。角色一多、层数一叠,动画系统就会吃光帧预算,必须用 LOD 与压缩控制成本。

为何常见:在只有一两个角色的小场景 demo 里,性能瓶颈完全不暴露,容易让人误以为混合没有代价。

误区觉得两个角色骨骼名字差不多,旋转数据直接复制就能播,顶多改改绑定。

正解重定向至少包含骨骼映射(源到目标的对应关系)和长度比例缩放(位移按骨骼长度比调整),还要处理单位、轴向差异,否则短腿角色会穿模、长腿角色会滑步。

为何常见:想当然认为“同名同构”就等于“可以直接用”,忽略了体型差异对位移的放大效应。

面试题

Q1. 关于动画状态机中的“状态”,最准确的理解是?

单选题 · easy

  • A.一段可循环播放的动画片段← 正确答案
  • B.当前帧的骨骼旋转值集合
  • C.角色身上的全部贴图材质
  • D.玩家按下按键的时间戳

答案:A。状态机里的“状态”对应“此刻该播哪段动画”,就像后厨任务卡上的“备菜/炒菜”。骨骼旋转值是采样结果,贴图材质与按键时间戳都与状态定义无关,因此 B、C、D 均错。

Q2. 状态机中“转移条件”的作用是?

单选题 · easy

  • A.决定角色外观的显示样式
  • B.决定何时从 A 状态切到 B 状态← 正确答案
  • C.决定动画文件的压缩比率
  • D.决定场景中光源的亮度数值

答案:B。转移条件就是“菜切完了才下锅”这类前置规则,只有条件成立,状态机才允许从 A 切到 B。它不管外观、压缩比或光照,因此选 B。

Q3. 交叉淡化(crossfade)的核心运算是?

单选题 · mid

  • A.删除旧动画中的全部关键帧
  • B.用硬切换覆盖当前姿态
  • C.两段姿态按权重插值合成← 正确答案
  • D.降低动画的采样帧率

答案:C。交叉淡化像勾兑酱汁,新旧两段姿态按权重加权平均,权重和为 1。删除关键帧、硬切换、降帧率都不是混合的本质,故选 C。

Q4. 一维混合树中,某子节点的权重主要取决于?

单选题 · mid

  • A.子节点动画的总时长
  • B.混合参数与节点位置的距离← 正确答案
  • C.骨骼数量的多少
  • D.动画文件的大小

答案:B。一维混合树中,权重由参数与锚点位置的距离反比决定,越接近的动画贡献越大,和时长、骨骼数、文件大小无关,故选 B。

Q5. 加法动画(additive)的正确理解是?

单选题 · mid

  • A.把两段动画的关键帧直接相加
  • B.在基础姿态上叠加增量姿态← 正确答案
  • C.让动画播放速度变大一倍
  • D.用新动画替换全部旧骨骼

答案:B。加法动画是在基础姿态上加一个“增量”,比如白切鸡加蒜蓉,主体不变、风味叠加。它不是关键帧相加、不是变速、更不是全量替换,故选 B。

Q6. 动画重定向中最难处理的环节是?

单选题 · extreme

  • A.源骨骼旋转直接套用到目标骨骼← 正确答案
  • B.目标模型贴图分辨率过低
  • C.场景中的动态光源数量过多
  • D.动画剪辑的文件体积偏大

答案:A。骨骼长度不同时,直接套用旋转会穿模或浮空,必须先做骨骼映射与长度比例缩放。贴图、光源、文件体积都不属于重定向的核心难点,故选 A。

Q7. 关于转移中断,下列说法正确的是?

单选题 · mid

  • A.转移一旦开始就不可被中断
  • B.高优先级转移可打断低优先级← 正确答案
  • C.中断只会发生在掉帧的时候
  • D.所有转移都必须完整播完

答案:B。就像后厨来了加急订单可以打断手头工作,紧急动作(如受击)允许打断低优先级转移。转移可被中断、与掉帧无关,故选 B。

Q8. 分层混合(layered blending)常用于实现?

多选题 · hard

  • A.上半身射击同时下半身行走← 正确答案
  • B.不同坡度下脚步姿态适配← 正确答案
  • C.待机动作叠加呼吸起伏← 正确答案
  • D.直接调整场景光照强度

答案:ABC。分层混合通过骨骼遮罩把身体拆开:上半身射击、下半身走路是标准案例;脚步按坡度适配是下方身体层的局部混合;呼吸起伏可做到上半身叠加层。光照不属于动画系统,故选 A、B、C。

Q9. 动画性能优化的常用手段包括?

多选题 · hard

  • A.只保留关键帧并重建中间帧← 正确答案
  • B.对旋转数据做量化与差值编码← 正确答案
  • C.远处角色降低采样帧率← 正确答案
  • D.将骨骼数量随机减少一半

答案:ABC。动画压缩涉及关键帧提取与量化编码;LOD 让远处角色低频采样。随机减少骨骼会破坏骨骼层级完整性,属于暴力做法,故选 A、B、C。

Q10. 以下哪些是状态机的必需要素?

多选题 · easy

  • A.动画状态集合← 正确答案
  • B.初始状态← 正确答案
  • C.转移条件与转移目标← 正确答案
  • D.场景贴图材质列表

答案:ABC。状态机由状态集合、初始状态、转移函数(条件 + 目标)组成,对应后厨任务卡的“任务项、开工状态、切换规则”。贴图材质不属于状态机结构,故选 A、B、C。