从零玩转 Manim #2:阶段 1 实战 —— 11 个例子写出第一个"圆→方→三角"变身动画

从零玩转 Manim(2):阶段 1 实战 ------ 11 个例子写出第一个"圆→方→三角"变身动画

目录

  • 阶段 0:环境与定位(5 分钟)
  • 阶段 1.1:Scene ------ 一切动画的舞台
  • 阶段 1.2:Mobjects ------ 万物皆数学对象
  • 阶段 1.3:Creation Animations ------ 物体如何登场
  • 阶段 1.4:Animations ------ 如何让物体动起来
  • 产出:圆 → 方 → 三角变身动画
  • 我踩过的 5 个坑
  • 下一篇预告

阶段 0:环境与定位(5 分钟)

三件套 + pip install manim(上篇已详述)。这里只补充一个实战必踩的配置

macOS 项目根目录有 SIP 限制,manim 默认写 media/ 会失败。解法是项目根放一个 manim.cfg

ini 复制代码
[CLI]
quality = low_quality
preview = True

[output]
media_dir = /tmp/manim_media

之后所有渲染都进 /tmp/manim_media/videos/,稳。


阶段 1.1:Scene ------ 一切动画的舞台

核心认知 :Scene = 画布 + 时间线。每个动画必须写在一个继承 Scene 的类的 construct(self) 方法里。

4 个核心动作(必背)

方法 用途 时间
self.play(...) 推进时间,播放动画 默认 1 秒
self.add(...) 瞬时加入,无动画 0 秒
self.remove(...) 瞬时移除 0 秒
self.wait(t) 空白等待 t 秒 t 秒

01_minimal.py ------ 一切从这里开始:

python 复制代码
from manim import *

class MyScene(Scene):
    def construct(self):
        circle = Circle()
        self.play(Create(circle))
        self.wait(1)

渲染命令:manim 01_minimal.py MyScene

输出:/tmp/manim_media/videos/01_minimal/480p15/MyScene.mp4

把 4 个动作对照看

02_four_actions.py 把 play/add/remove/wait 都演示了一遍,特别演示了一个反直觉的点:add(circle)play(Create(circle)) 不会重画 ------因为对象已经在场景里了,Create 是"渐进画出"动画,对已在的对象无效。

这是 ManimCE 最容易踩的坑之一:Create 只对"第一次出现"的对象画路径

两个 Scene 子类

子类 何时用 关键能力
Scene 普通 2D 静态相机
ThreeDScene 3D set_camera_orientation(phi, theta)
MovingCameraScene 2D + 镜头运动 self.camera.frame.animate 缩放/平移

最小 3D 例子(03_subclass.py):

python 复制代码
class Minimal3D(ThreeDScene):
    def construct(self):
        self.set_camera_orientation(phi=75 * DEGREES, theta=-45 * DEGREES)
        axes = ThreeDAxes()
        sphere = Sphere(radius=1).set_color(BLUE)
        self.add(axes, sphere)
        self.wait()

phi 是从 z 轴俯视的角度,theta 是绕 z 轴旋转的角度------两个参数决定 3D 视角。


阶段 1.2:Mobjects ------ 万物皆数学对象

Mobject = Mathematical Object,屏幕上所有可见元素的基类。

继承树(记一个口诀)

scss 复制代码
Mobject(根)
├── VMobject(向量化的,贝塞尔曲线) ← 90% 时间打交道的就是它
│   ├── Circle / Square / Polygon / Line / Arrow
│   ├── Text / MathTex / Tex
│   ├── Axes / NumberPlane
│   └── VGroup
├── ImageMobject(位图)
└── Group(任意类型混合)

口诀 :能用 set_fill / set_stroke 的都是 VMobject(除了 Text 用 Pango);不能的就是 ImageMobjectPMobject

5 个最常用的 get_* 方法

方法 用途
get_center() 整体定位
get_top()/bottom()/left()/right() 边对齐
get_corner(UL/UR/DL/DR) 角落布局
get_width()/get_height() 对齐大小
get_start()/get_end() 路径端点(用于 Line/Arrow)

04_mobjects.py 验证坐标:

python 复制代码
rect = Rectangle(width=4, height=2)
print(rect.get_corner(UL))  # array([-2.,  1.,  0.])

输出确认:UL: x=-2.00, y=1.00UR: x=2.00, y=1.00------4×2 矩形的 4 个角确实在 (±2, ±1)。

避免共享引用:用 copy()

Python 赋值是引用传递。直接 same = rect 然后操作 same,原 rect 也会动 。永远用 copy()

python 复制代码
rect_copy = rect.copy()      # 副本,独立
rect_copy.set_fill(RED, opacity=0.3)
rect_copy.shift(RIGHT * 4)

方法链 ------ 一气呵成但别太长

python 复制代码
circle = (Circle(radius=0.5, color=YELLOW)
          .shift(LEFT * 3 + DOWN * 2)
          .set_fill(YELLOW, opacity=0.5)
          .rotate(PI / 4))

经验法则:3 步以上拆行更可读。

自定义 VMobject

05_custom_shape.py 演示两种定义方式:

python 复制代码
# 1. 直线连接:set_points_as_corners
star = VMobject()
star.set_points_as_corners([UP, UP+RIGHT, RIGHT+DOWN*0.2, ...])

# 2. 平滑曲线:set_points_smoothly
heart = VMobject()
heart.set_points_smoothly([np.array([np.cos(t), np.sin(t)-0.3, 0])
                            for t in np.linspace(0, 2*PI, 50)])

这两招在手,你就能画任意 2D 形状。


阶段 1.3:Creation Animations ------ 物体如何登场

creation-animations.md 列出了 10+ 种"出场方式"。记住这个决策树

scss 复制代码
登场一个对象,先问:它是几何形状还是文字?
│
├── 几何形状(Circle/Square/Line/Arrow)
│   └── 默认用 Create() --- 沿路径渐变画出
│
├── 文字 / 公式
│   └── 用 Write() --- 模拟手写
│
├── 想要"低调出现"
│   └── 用 FadeIn() --- 透明度过渡
│
├── 想要"重点强调形状边界"
│   └── 用 DrawBorderThenFill() --- 先描边再填色
│
└── 想要"从某点/边冒出"
    └── 用 GrowFromCenter / GrowFromPoint / GrowFromEdge

06_creation_kinds.py 一次性看 5 种

把 5 个相同大小的方块并排放,分别用 Create/Write/FadeIn/DrawBorderThenFill/GrowFromCenter 登场,肉眼对比差异。

特别值得记的是 FadeIn 的两个增强参数:

python 复制代码
FadeIn(mob, shift=UP)     # 边淡入边向上
FadeIn(mob, shift=LEFT)   # 边淡入边向左
FadeIn(mob, scale=2)      # 边淡入边放大

这是"低调出现但又有点动静"的最常用技巧。

出场/退场对称(黄金规则)

07_symmetry.py 演示了三组对称:

出场 退场
Create Uncreate
FadeIn FadeOut
GrowFromCenter ShrinkToCenter

入场用什么,退场就用什么------这条规则看着废话,但 80% 的"看起来不舒服"的动画都违反它。

验证坑:Create 对已存在对象无效

08_create_already_there.py 故意先 self.add(c)play(Create(c))------结果整个 3 秒视频完全静止。Play 计数 +1,但视觉上无事发生。

这就是为什么 Create 通常配合play 后构造的对象使用。


阶段 1.4:Animations ------ 如何让物体动起来

animations.md 的核心是 .animate 语法------这是 ManimCE 最常用的"动画制造机"。

.animate 的本质

.animate 不是一个新方法,它是一个属性。访问它返回的对象把"普通方法调用"变成"动画对象":

python 复制代码
circle.shift(RIGHT)            # 立即修改 circle(瞬时,0 秒)
circle.animate.shift(RIGHT)    # 返回"shift 到 RIGHT"的动画(要在 play 里播,默认 1 秒)

记住这条规则,剩下都是细节。

5 个常用动作

操作 例子
移动 mob.animate.shift(LEFT * 2)
缩放 mob.animate.scale(2)
旋转 mob.animate.rotate(PI / 4)
变色 mob.animate.set_color(RED)
链式 mob.animate.shift(RIGHT).rotate(PI/4).set_color(BLUE)同时进行)

run_timerate_func

每个 self.play() 可以加全局参数:

python 复制代码
self.play(
    mob.animate.shift(RIGHT),
    run_time=3,           # 持续 3 秒(默认 1 秒)
    rate_func=smooth,     # 缓动曲线(默认 smooth)
)
  • run_time 默认 1 秒
  • rate_func 默认 smooth(起步慢→中间快→结尾慢,最有"质感")
  • 其他常用:linear(匀速)、there_and_back(去再回来)

链式动作是"同时"进行

09_animate_basics.py 里这段:

python 复制代码
self.play(
    c.animate.shift(LEFT * 3)        # 同时左移
          .rotate(-PI / 3)            # 同时旋转
          .set_fill(GREEN, opacity=0.8)  # 同时变色
          .scale(0.5),                # 同时缩小
    run_time=2,
)

4 个动作 2 秒内同时完成。想让它们依次 进行?阶段 2 的 Succession

动画 vs 瞬时修改(最后一个易错点)

调用方式 效果
circle.set_color(RED) 立即变色(0 秒)
self.play(circle.animate.set_color(RED)) 1 秒渐变

mob.method(...)立刻调用self.play(mob.animate.method(...))动画化。这一字之差决定了你的代码"动不动"。

同时播放多个动画

10_multiple.py

python 复制代码
self.play(
    circle.animate.shift(DOWN),         # 圆下移
    square.animate.shift(UP),           # 方上移
    triangle.animate.set_color(YELLOW), # 三角变色
    run_time=2,
)

self.play() 的所有参数是同时 播放的(除非用 Succession,阶段 2 会讲)。


阶段 1 产出:圆 → 方 → 三角变身动画

11_final.py ------ 综合运用阶段 1 所有知识点,21 个动画一气呵成:

python 复制代码
"""
阶段 1 产出 - 圆 → 方 → 三角变身动画
综合运用:
- VGroup + arrange
- Create 登场
- Transform 变形
- .animate 链式动作
- FadeOut 对称退场
"""
from manim import *


class ShapeMorph(Scene):
    def construct(self):
        # ---- 1. SETUP ----
        circle = Circle(color=BLUE, fill_opacity=0.5)
        square = Square(color=RED, fill_opacity=0.5)
        triangle = Triangle(color=GREEN, fill_opacity=0.5)

        shapes = VGroup(circle, square, triangle).arrange(RIGHT, buff=1.5)
        title = Text("Shape Morph", font_size=42).to_edge(UP)

        # ---- 2. 标题登场 ----
        self.play(Write(title))
        self.wait(0.3)

        # ---- 3. 三个形状依次 Create ----
        self.play(Create(circle))
        self.wait(0.2)
        self.play(Create(square))
        self.wait(0.2)
        self.play(Create(triangle))
        self.wait(0.5)

        # ---- 4. 整体脉冲(链式 animate) ----
        self.play(
            shapes.animate.scale(0.8).set_fill(opacity=0.8),
            run_time=1,
        )
        self.play(
            shapes.animate.scale(1.25).set_fill(opacity=0.5),
            run_time=1,
        )
        self.wait(0.5)

        # ---- 5. 变身:圆 → 方 ----
        circle_target = Square(color=YELLOW, fill_opacity=0.5).move_to(circle)
        self.play(Transform(circle, circle_target), run_time=1.5)
        self.wait(0.3)

        # ---- 6. 变身:方 → 三角 ----
        square_target = Triangle(color=PURPLE, fill_opacity=0.5).move_to(square)
        self.play(Transform(square, square_target), run_time=1.5)
        self.wait(0.5)

        # ---- 7. 全部变蓝 ----
        self.play(shapes.animate.set_color(BLUE), run_time=1.5)
        self.wait(0.5)

        # ---- 8. 旋转离场 ----
        self.play(
            shapes.animate.rotate(PI).set_fill(WHITE, opacity=0.2),
            run_time=1.5,
        )
        self.wait(0.3)

        # ---- 9. CLEANUP:对称退场 ----
        self.play(FadeOut(shapes), FadeOut(title))
        self.wait(0.5)

渲染命令:manim 11_final.py ShapeMorph,约 30 秒出片。


我踩过的 5 个坑

  1. Create 对已存在对象无效 → 不要先 add()play(Create())
  2. self.add() 不推进时间 → 它只是瞬时插入
  3. Transform 后原变量还在场景 (阶段 2 会用 ReplacementTransform 解决)
  4. macOS SIP 限制 → 渲染输出要放到可写目录(用 manim.cfgmedia_dir
  5. 方法链别太长 → 3 步以上拆行更可读

下一篇预告

阶段 1 写的 Transform(circle, target) 后,circle 这个变量还在场景里------这是个坑。下一篇会解决它,并进入真正的"节奏感":

  • ReplacementTransform:原对象消失,新对象登场
  • LaggedStart / Succession:让多个形状像波浪一样依次登场
  • 完整 easing 矩阵ease_out_bounce 等 20+ 种缓动
  • positioning 5 件套move_to / shift / next_to / align_to / to_edge
相关推荐
johnny2331 天前
数据可视化平台:Flourish、Basedash、Datart、DataWrapper、Shaper、Edalitics
数据可视化
太平洋月光4 天前
Antv G2中自定义技巧📊
前端·数据可视化
那些年丶ny6 天前
颜色扩展库
前端·javascript·数据可视化
於 兔シ6 天前
Apache Superset 二次开发实践:接入 AI Agent,并确保数据权限不被绕过
ai·数据分析·agent·数据可视化·bi·superset·数据权限
极光代码工作室8 天前
基于Spark的日志监控与分析平台
大数据·hadoop·python·spark·数据可视化
慧都小妮子8 天前
ThingsBoard PE 智慧能源:打造企业数字化能源管理平台
运维·物联网·能源·数据采集·数据可视化·thingsboard·制造业数字化
databook9 天前
当散点图不够用时:用 t-SNE 可视化多维数据
python·数据分析·数据可视化
Strayer9 天前
拓扑管网 3D 可视化大屏demo:科技感(地图 + 拓扑 + 3D 空间)
前端·three.js·数据可视化
datagear9 天前
DataGear 6.0.0 新特性之本地图表
数据分析·html·数据可视化