Animation 机制¶
前面所有的 FadeIn、Transform 都是 Animation 的子孙类。本节拆开 Animation 本身:它有哪些可调的要素、生命周期怎么走、play() 在背后做了什么,以及两个进阶机制——prepare_animation 与 override_animation。掌握这些,你才能在组合动画与自定义动画时不踩坑。
Animation¶
Animation(mobject=None, *args, use_override=True, **kwargs) 官方文档 ↗
Animation(mobject, ...) 是一切动画的基类。它把"一段时间内的状态变化"抽象成统一接口:渲染器每帧调用 interpolate(alpha),由子类决定如何把进度 alpha(0 到 1)翻译成物体的状态。
构造参数(五要素全展开)¶
name |
type |
default |
desc |
|---|---|---|---|
|
Mobject | None |
— |
被动画驱动的物体; |
|
float |
1.0 |
动画时长(秒) |
|
Callable |
|
速率函数,把线性时间映射为动画进度,见 Rate Functions 一节 |
|
float |
0.0 |
子对象错峰比例:第 n 个子对象比前一个晚 |
|
bool |
False |
True 时动画结束后把物体从场景移除( |
|
bool |
False |
反转速率函数;不影响 |
|
str | None |
None |
渲染日志中显示的动画名,默认 |
|
bool |
True |
播放期间是否挂起物体自身的 updater |
|
bool |
False |
True 时动画开始时把物体加入场景( |
常被称作"五要素"的是前五个:mobject、run_time、rate_func、lag_ratio、remover。introducer 与 remover 是一对镜像开关,分别管"开头进场"和"结尾离场"。
生命周期¶
每个动画在播放时依次经历:
begin()—— 记录起始状态(多数动画会先把物体拷贝一份作为"起点")逐帧
update_mobjects(dt)+interpolate(alpha)finish()—— 确保落在终点状态clean_up_from_scene(scene)—— 处理remover(离场)或把目标物留在场景中
.animate 构造器¶
m.animate 不是动画,而是一个动画构造器(_AnimationBuilder):访问 m.animate 时它立即对 m 调用 generate_target(),之后的每个方法调用都作用在目标副本上,最后由 play() 内部的 prepare_animation 调 build() 生成真正的 _MethodAnimation。
square.animate.shift(LEFT).scale(2) # 返回构造器,链式记录方法
self.play(square.animate(run_time=2, rate_func=linear).shift(LEFT)) # 动画参数要放在方法之前,且只能传一次
常见错误
m.animate.shift(LEFT)的返回值是构造器而不是m,不能继续当物体用(例如.next_to(...)会构造出错误的动画)。动画参数(
run_time、rate_func等)必须通过m.animate(...)在访问任何方法之前传入,且只能传一次;之后再传会抛ValueError。
run_time/rate_func 控制单个动画,lag_ratio 让多个动画错峰;Add 与 Wait 也是 Animation 家族成员。
查看源码 anim_mechanism.py
"""AnimMechanism: run_time / rate_func / lag_ratio / Add / Wait."""
from manim import *
class AnimMechanism(Scene):
def construct(self):
square = Square().set_fill(BLUE_E, opacity=0.6)
# run_time 与 rate_func 控制单个动画的时长与节奏
self.play(DrawBorderThenFill(square), run_time=1.2, rate_func=double_smooth)
# lag_ratio 让同一 play 里的多个动画错峰开始
dot_l = Dot(LEFT * 2 + DOWN)
dot_r = Dot(RIGHT * 2 + DOWN)
self.play(FadeIn(dot_l), FadeIn(dot_r), lag_ratio=0.5, run_time=1.5)
# Add 也是动画:默认 run_time=0,单独播放会报错,
# 必须显式给一个正的 run_time(相当于“出现后再静止这么久”)
self.play(Add(Text("Add 瞬间出现", font_size=32).next_to(square, UP)), run_time=0.5)
# Wait 让画面静止
self.wait(0.5)
Add¶
Add(*mobjects, run_time=0.0, **kwargs) 官方文档 ↗
Add(*mobjects, run_time=0.0) 把物体瞬间放进场景——相当于 Scene.add() 的动画版,好处是可以塞进 AnimationGroup/Succession 里与其他动画编排。默认 run_time=0 意味着"加入后不再额外停留"。
常见错误
单独播放 Add 会抛 ValueError: ... has a total run_time of 0 <= 0 seconds which Manim cannot render(本机 v0.21.0 实测)。因为整个 play() 的总时长为 0。修法:显式给一个正的 run_time(语义是"出现后静止这么久"),或把它和别的动画放进同一个 play()/Succession。
Wait¶
Wait(run_time=1, stop_condition=None, frozen_frame=None, rate_func=<function linear at 0…, **kwargs) 官方文档 ↗
Wait 是"空操作"动画:不改变任何物体,只消耗时间,常用来穿插停顿。
name |
type |
default |
desc |
|---|---|---|---|
|
float |
1 |
静止时长(秒) |
|
Callable | None |
None |
每帧求值,返回真值即提前结束;不能与 |
|
bool | None |
None |
是否按静止帧处理;None 时由 |
|
Callable |
|
速率函数(通常无需改动) |
Scene.wait(...) 本质就是 self.play(Wait(...))。有 updater 时 Wait 默认逐帧推进 updater;若确定画面静止,传 frozen_frame=True 可按单帧延展、加快渲染。
prepare_animation¶
prepare_animation(anim) 是 play() 的入口转换器(manim.animation.animation 模块):
传入
_AnimationBuilder→ 调build()生成_MethodAnimation传入
Animation→ 原样返回其他任何东西 → 抛
TypeError: Object ... cannot be converted to an animation
prepare_animation(FadeIn(square)) # FadeIn(Square)
prepare_animation(square.animate.shift(LEFT)) # _MethodAnimation(Square)
prepare_animation(42) # TypeError
Scene.play 内部对每个参数都调用它,因此裸 Mobject 会在这一步被拒绝:
常见错误
self.play(Square()) 会抛 TypeError: Unexpected argument Square passed to Scene.play().(本机 v0.21.0 实测,源自内部的 prepare_animation)。正确写法是 self.play(FadeIn(Square())) 或 self.play(Square().animate.shift(LEFT))——play 只认动画和 .animate 构造器。
override_animation¶
override_animation(animation_class) 官方文档 ↗
override_animation(animation_class) 是装饰器:为某个 Mobject 子类指定"当播放某类动画时,实际改播什么"。被标记的方法会在 m.animate 构造器的 build() 阶段被调用,返回替代动画。
class MySquare(Square):
@override_animation(FadeIn)
def _fade_in_override(self, **kwargs):
return Create(self, **kwargs)
class Demo(Scene):
def construct(self):
self.play(FadeIn(MySquare())) # 实际播放 Create(MySquare())
要点:
只修饰
Mobject子类的方法;覆写会被该物体的子类继承,但不会覆写动画类的子类(例如覆写FadeIn不影响FadeInFrom之类)。覆写后的动画不支持方法链式:
m.animate.shift(...).scale(...)中若命中覆写方法,构造器会抛NotImplementedError。传入的
anim_args(即m.animate(...)的参数)会以关键字形式交给覆写方法。
自测¶
✏️ 练习
self.play(Add(m)) 报错 total run_time of 0 <= 0 seconds,为什么?怎么改?
✅ 参考答案
Add 默认 run_time=0,单独一个零时长动画让整个 play() 总时长为 0,渲染器拒绝渲染。改法:给它显式正的时长(self.play(Add(m), run_time=0.5),含义是出现后再静止 0.5 秒),或与其他动画同播 / 放进 Succession。
✏️ 练习
text.animate.set_color(RED).scale(2) 与 text.animate(run_time=2).set_color(RED).scale(2) 有什么区别?第二行的 run_time 写到最后会怎样?
✅ 参考答案
前者用默认 run_time=1.0、rate_func=smooth;后者把 run_time=2 交给构造器,build() 时写进生成的动画。若把 run_time 写在方法之后(如 text.animate.set_color(RED)(run_time=2)),构造器已锁定参数,会抛 ValueError: Animation arguments must be passed before accessing methods and can only be passed once。
下一步¶
机制清楚了,接下来按家族认识具体的进场动画:先讲"画出来"的创建家族——Create、Write、打字机与逐字出场。