你的第一个场景¶
欢迎来到 Manim 社区版(ManimCE)教程。本节带你写出第一个可渲染的场景,并理解三个最核心的概念:Scene、play() 与 wait()。
Scene¶
Scene(renderer=None, camera_class=<class 'manim.camera.…, always_update_mobjects=False, random_seed=None, skip_animations=False) 官方文档 ↗
Scene 是 Manim 中一切场景的基类:编写场景就是继承 Scene 并实现 construct() 方法。渲染器负责实例化场景,并依次调用生命周期方法 setup() → construct() → tear_down(),其中 construct() 是你写动画逻辑的地方。渲染入口是 render(preview=False),通常由 CLI 自动调用,不必手动执行。
注意 Scene 与摄像机是组合关系而非继承关系:场景通过构造函数持有 camera_class(默认 Camera)实例,访问方式为 self.camera。
name |
type |
default |
desc |
|---|---|---|---|
|
CairoRenderer | OpenGLRenderer | None |
None |
渲染器;None 时按 |
|
type[Camera] |
Camera |
场景使用的摄像机类 |
|
bool |
False |
为 True 时即使无动画也每帧更新 mobject |
|
int | None |
None |
随机种子;设置后随机效果可复现 |
|
bool |
False |
跳过所有动画,只走逻辑(配合 |
最小可运行示例:
from manim import *
class MyScene(Scene):
def construct(self):
self.play(FadeIn(Text("你好,Manim!")))
用 CLI 渲染它:
manim -pqm scene.py MyScene
约定与注意事项:
每个
.py示例文件只包含一个场景类,类名就是输出视频的文件名。construct()由渲染器自动调用,不需要手动执行。文件中必须有
from manim import *(或等价的显式导入)。
construct 方法¶
construct(self) 是场景的入口。场景中所有的 self.add(...)、self.play(...)、self.wait(...) 都写在这里。
self 是什么¶
self 就是当前场景实例。通过它访问场景上的方法,例如 self.camera.frame_center、self.mobjects 等。
下面这个示例演示了 Scene 的两组基础操作——无动画的 add / remove 与有动画的 play / wait:
add 与 remove 瞬间改变画面,不产生任何动画;play 才有逐帧的动画过程。
查看源码 scene_basics.py
"""SceneBasics: add / play / wait / remove on a single Scene."""
from manim import *
class SceneBasics(Scene):
def construct(self):
square = Square()
square.set_fill(BLUE_E, opacity=0.6)
label = Text("play 才有动画", font_size=36)
label.next_to(square, DOWN)
# add 不播放动画,物体瞬间出现在画面里
self.add(square)
self.wait(0.5)
# play 才有逐帧的动画过程
self.play(FadeIn(label), run_time=1.0)
self.play(square.animate.shift(LEFT * 2), run_time=1.0)
self.play(FadeOut(label), run_time=0.5)
# remove 与 add 一样没有动画,物体瞬间消失
self.remove(square)
self.wait(0.5)
play¶
self.play() 用来播放动画。签名中 *args 接收 Animation 或 .animate 构造器(_AnimationBuilder),多个动画默认同时开始:
self.play(FadeIn(circle))—— 播放单个动画self.play(FadeIn(circle), Write(label))—— 两个动画同时播放self.play(circle.animate.shift(LEFT))—— 由.animate生成方法动画用
lag_ratio可以让多个动画错峰开始
play 的参数¶
name |
type |
default |
desc |
|---|---|---|---|
|
Animation | _AnimationBuilder |
— |
要播放的动画,可传入多个 |
|
float |
1.0 |
动画时长(秒) |
|
function |
|
速率函数,见 Rate Functions 章节 |
|
float |
0.0 |
多动画之间的错峰比例 |
|
str | None |
None |
播放期间写入的字幕内容(输出 .srt) |
wait¶
self.wait() 让画面静止一段时间,常用来给观众留出阅读时间,例如 self.wait(2) 停顿 2 秒。
wait 的参数¶
name |
type |
default |
desc |
|---|---|---|---|
|
float |
1.0 |
静止时长(秒),可以是小数 |
|
callable | None |
None |
返回 True 时提前结束等待 |
|
bool | None |
None |
强制按“静止帧”处理;None 时自动判断 |
相关便捷方法:
self.pause(duration=1.0)——wait的别名,语义完全相同self.wait_until(stop_condition, max_time=60)—— 等到条件满足为止,最多等max_time秒
静止等待默认只写一帧再按时长延展,几乎不占渲染时间;如果场景里有 updater,默认会逐帧模拟,此时传 frozen_frame=True 可强制按静止帧处理(代价是 updater 不会运行)。
下面是一个完整的官方风格示例——正方形变换成圆,随后停顿:
Transform 让正方形逐步变形为圆,最后的 wait() 让成品画面停留一秒。
查看源码 square_to_circle.py
"""SquareToCircle: the classic first Manim scene.
Rendered by scripts/render_examples.py (one Scene class per file).
"""
from manim import *
class SquareToCircle(Scene):
def construct(self):
circle = Circle()
circle.set_fill(PINK, opacity=0.5)
square = Square()
square.flip(RIGHT)
square.rotate(-3 * TAU / 8)
circle.set_stroke(BLUE, width=4)
self.play(Create(square))
self.play(Transform(square, circle))
self.play(FadeOut(square))
self.wait()
同一个示例也可以用不带扩展名的路径引用,二者等价:
CircleIntro 演示了 FadeIn 与 Write 的组合。
查看源码 circle_intro.py
"""CircleIntro: a Circle with a FadeIn entrance and a text label."""
from manim import *
class CircleIntro(Scene):
def construct(self):
circle = Circle(radius=1.5)
circle.set_fill(BLUE_E, opacity=0.6)
circle.set_stroke(BLUE, width=4)
label = Text("Circle", font_size=36)
label.next_to(circle, DOWN)
self.play(FadeIn(circle))
self.play(Write(label))
self.wait(2)
渲染质量对比¶
不同质量标志对应的输出:
标志 |
分辨率 |
帧率 |
用途 |
|---|---|---|---|
|
854×480 |
15 |
快速预览 |
|
1280×720 |
30 |
日常调试 |
|
1920×1080 |
60 |
正式发布 |
普通管道表格(不在 ::: 容器中)同样有效:
命令 |
作用 |
|---|---|
|
渲染结束后预览 |
|
渲染结束后打开输出目录 |
常见错误与建议¶
常见错误
construct() 只能有 self 一个参数。写 def construct(self, t): 之类的签名,渲染时会被静默忽略或报错——需要参数时请改用 self.time、updater 或构造时把值存到实例属性上。
常见错误
不要把 Mobject 直接传给 play():self.play(Square()) 会抛 TypeError: Unexpected argument。正确写法是 self.play(FadeIn(Square())) 或 self.play(Square().animate.shift(LEFT))。旧版支持的“把 mobject 方法当参数传”也已被移除,统一使用 .animate。
提示
wait() 只是“画面静止”,updater 与 self.time 仍在推进。若想让某个带随机效果的场景每次渲染结果一致,用 CLI --seed 或构造参数 Scene(random_seed=42) 固定随机种子。
版本说明
本教程所有示例均基于 manim 0.21.0,与其他大版本的 API 可能存在差异。
废弃提醒
旧教程中的 self.add_foreground_mobjects 已被 self.add_foreground_mobject 取代,请使用新写法。
自测¶
✏️ 练习
如何让一个动画播放完毕后停顿 2 秒,再播放下一个动画?
✅ 参考答案
在两次 play 之间调用 self.wait(2)。wait 的参数单位是秒,可以是小数。
✏️ 练习
self.play(a, b) 与先后写两行 self.play(a)、self.play(b) 有什么区别?
✅ 参考答案
前者两个动画同时开始(总时长取两者较大值);后者串行执行,总时长为两者之和。
✏️ 练习
场景里有一个 updater 在不停移动圆点,此时 self.wait(2, frozen_frame=True) 会发生什么?
✅ 参考答案
这 2 秒会按静止帧处理:圆点停在原地、不随 updater 移动,但视频时长仍然延长 2 秒。想要 updater 生效就不要传 frozen_frame=True。
场景类的继承关系¶
常见场景子类的两条继承链:
这些子类会在“3D 与摄像机”板块详细介绍。
下一步¶
到这里你已经掌握了最小工作流。下一节将拆解一次渲染背后的完整流程与输出目录结构。