把一个嘴型元件拖到舞台上,在时间轴上拖动播放头,嘴型纹丝不动;按 Ctrl+Enter 测试影片,它却疯狂循环播放。同一个元件、同一段动画,编辑态和运行态给出两种完全不同的结果。这不是 bug,而是 Animate 里两类元件在时间轴求值方式上的根本差异——它决定了口型动画、角色动画和交互元件应该分别用哪一种元件。
元件不是”图层的容器”,而是一条独立的时间轴
Animate 里的元件(Symbol)保存在库中,舞台上出现的是它的实例(Instance)。每个元件内部都有自己完整的时间轴:图层、关键帧、嵌套的其它元件实例。舞台上的实例只保存”引用 + 实例级属性”:变换矩阵、色彩效果、混合模式、滤镜,以及——这是关键——这个实例的时间轴该如何取帧。
库里改元件,所有实例一起变;实例级属性只影响那一个实例。真正让人踩坑的是最后那一项:取帧规则在 Movie Clip 和 Graphic 之间完全不同。
Graphic 的帧号是父时间轴的函数
Graphic 元件没有自己的播放头。它显示第几帧,是由父时间轴当前帧号直接算出来的。设:
P = 父时间轴当前帧号
S = 该实例在父时间轴上的起始帧
F0 = 实例属性里的 "First"(起始帧偏移,1-based)
L = 元件自身时间轴长度(总帧数)
d = P - S // 实例已经存在了多少帧
三种循环模式给出三个不同的取帧函数:
Loop: frame = ((d + F0 - 1) mod L) + 1
Play Once: frame = min(d + F0, L)
Single Frame: frame = F0 // 与 d 无关,永远静止
因为 frame 是 P 的纯函数,Animate 在编辑态就能算出任意父帧对应的子帧,于是你拖动父时间轴的播放头,嵌套动画会跟着走;导出逐帧视频或序列图时,每一帧也都能算出正确的子帧。
举个能手算的例子:元件长度 L = 8,实例从父时间轴第 S = 20 帧开始,First 设为 F0 = 3,Loop 模式。父帧 P = 26 时:
d = 26 - 20 = 6
frame = ((6 + 3 - 1) mod 8) + 1 = (8 mod 8) + 1 = 1
父帧 27 时得到第 2 帧,父帧 25 时得到第 8 帧。First 改成 1,同样的父帧 26 会得到第 7 帧——这就是”口型对不上、整体错开几帧”最常见的来源:不是关键帧画错了,而是某个实例的 First 值被无意改动过。
Movie Clip 的帧号跟父时间轴无关
Movie Clip 有自己的播放头,由运行时的帧循环驱动,与父时间轴当前停在哪一帧没有关系。父时间轴停住,Movie Clip 照样播;父时间轴倒着拖,Movie Clip 不会倒着走。
直接后果是:在编辑态的舞台上,Movie Clip 实例永远只显示它自己的第 1 帧。Animate 无法在编辑态”预知”运行时播放头会停在哪,所以它只能画第一帧。你必须测试影片才能看到它动。
这解释了开头那个现象,也解释了另一个更麻烦的情况:把 Movie Clip 嵌套进一个按逐帧方式渲染的时间轴(比如导出 PNG 序列或视频)时,导出结果里那段嵌套动画往往每一帧都停在第 1 帧。渲染器是按父帧号求值的,而 Movie Clip 根本不接受父帧号作为输入。
为什么口型和角色动画几乎都用 Graphic
电视动画流水线里,角色的嘴、眼、手基本都做成 Graphic 元件,原因正是上面那个”帧号是父帧的函数”:
- 可以逐帧对位。把嘴型元件设成
Single Frame,然后在每个关键帧上直接改First值,就等于”这一帧用第几号嘴型”。配音波形摆在下面,拖着播放头就能校准,所见即所得。 - 导出可靠。逐帧渲染时子帧可计算,不会出现只导出第一帧的情况。
- 可以倒放和 scrub。父时间轴往回拖,画面正确回退,方便检查时间节奏。
反过来,Movie Clip 的优势在运行态:它有实例名,可以被脚本寻址;支持滤镜、混合模式和 3D 变换(Graphic 不支持滤镜);适合做循环背景、粒子、UI 元件这类”不需要和父时间轴精确对齐”的东西。
嵌套时变换是矩阵连乘,色彩效果也是
实例的变换沿元件层级逐级相乘,和骨骼层级完全同构:
M_world(instance) = M_world(parent) · M_local(instance)
所以把一个内部已有缩放的元件再放进一个缩放过的父元件,最终缩放是两者相乘,不是相加。这也是”元件套了三层之后线条粗细看起来不对”的原因:描边宽度会被累计缩放影响,除非把描边设为不随缩放变化。
色彩效果(Alpha、Tint、Brightness、Advanced)同样沿层级复合。父实例 Alpha 50%、子实例 Alpha 50%,最终不透明度是 25%。想让整组元素统一淡出,应该在最外层加一次 Alpha,而不是给每个子元件各加一次——后者的结果是非线性的,而且没法用一条补间控制。
在 Animate 里怎么验证这些结论
下面这套检查不依赖任何插件,版本差异也不影响,建议实际做一遍再采信本文的公式:
- 验证 Graphic 取帧公式:做一个 8 帧元件,每帧画上数字 1–8。拖到舞台,设 Loop、
First = 3,把父时间轴拉长到 30 帧。逐帧拖动播放头,记录舞台上显示的数字,和上面的公式对照。 - 验证 Movie Clip 不受父帧影响:同一个元件复制一份,元件类型改成 Movie Clip,放在下面一层。拖播放头时它应该始终显示 1;Ctrl+Enter 后它开始独立循环。
- 验证嵌套导出行为:把两个实例都放在时间轴上,导出 PNG 序列,检查 Movie Clip 那一路是不是每张图都停在第 1 帧。
- 验证 Alpha 复合:父子各设 50% Alpha,用取色器量最终像素值,应接近 25% 而不是 0% 或 50%。
不同 Animate 版本的面板位置和选项名称会有出入(”循环””Loop””图形选项”等叫法随版本和语言包变化),但取帧规则本身多年未变。如果你的实测结果和公式不符,优先怀疑 First 的 1-based/0-based 约定和实例起始帧 S 的取值,而不是循环模式本身。
选型时真正该问的问题
不要用”这个要不要动”来选元件类型,那会同时选中两类。真正的判据只有两条:
第一,这段动画需不需要和父时间轴精确对齐。需要(口型、角色动作、任何要导出成逐帧素材的东西)就用 Graphic。第二,需不需要脚本寻址、滤镜或混合模式。需要就必须用 Movie Clip,并接受它在编辑态不可预览、在逐帧导出中不展开的代价。
两个条件同时成立时(既要精确对齐又要滤镜),常见做法是把滤镜移到外层容器,或者干脆把该段动画预渲染成序列图再导入——用 Movie Clip 硬扛通常会在导出环节出问题。