首页 / 文档中心 / 进阶

Manim Updater 与 ValueTracker:让图形随参数实时变化

发布于 2026-11-02 · 进阶系列 · 约 4 分钟读完 · 中文正文 + English TL;DR
▶ 渲染效果实录(manim 渲染输出,代码见正文,可直接复现)
English TL;DR

Updaters make Mobjects re-evaluate every frame, which is how Manim does "reactive" graphics without keyframes. The pattern: a ValueTracker holds a number; an always_redraw(lambda: ...) rebuilds a Mobject from that number each frame; playing tracker.animate.set_value(...) animates the number, and everything derived from it follows. This chapter shows the four building blocks — dt-based add_updater for per-frame logic (rotations, velocity physics), updater functions that read other objects' state, always_redraw for full re-construction, and TracedPath for comet-tail trajectories. Full examples: a dot sweeping a sine curve with a live tangent line, a circle whose radius slider drives area and circumference labels, and a tangent-vector field on a parametric curve. Also the housekeeping you must not skip: clear_updaters() before transforming an updated object, and suspending updaters while capturing static frames.

响应式思维:参数 → 图形

普通动画是"关键帧"思维(从状态 A 到状态 B),而 updater 是"响应式"思维:图形是参数的函数,动画只动参数。核心三件套:

from manim import *

class TrackerTour(Scene):
    def construct(self):
        axes = Axes(x_range=[0, 8, 2], y_range=[-2, 2, 1],
                    x_length=10, y_length=4)
        curve = axes.plot(lambda x: np.sin(x), color=BLUE)

        # 1) 参数(一个数)放进 ValueTracker
        t = ValueTracker(0)
        # 2) 图形声明为参数的函数, always_redraw 每帧重建
        dot = always_redraw(
            lambda: Dot(axes.c2p(t.get_value(),
                                 np.sin(t.get_value())),
                        color=YELLOW))
        tangent = always_redraw(
            lambda: Line(
                axes.c2p(t.get_value() - 0.4,
                         np.sin(t.get_value() - 0.4)),
                axes.c2p(t.get_value() + 0.4,
                         np.sin(t.get_value() + 0.4)),
                color=ORANGE, stroke_width=6))

        self.add(axes, curve, dot, tangent)
        # 3) 动画只动参数, 点和切线自动跟随
        self.play(t.animate.set_value(8), run_time=5, rate_func=linear)

add_updater:每帧执行的自定义逻辑

add_updater(func) 每帧调用 func,签名是 (mob, dt),dt 是帧时长。适合连续逻辑(旋转、物理):

arrow = Arrow(ORIGIN, [1, 0, 0], buff=0, color=RED)
label = MathTex("\theta").next_to(arrow, UP)

def swing(m, dt):
    m.rotate(0.8 * dt)          # 每秒转 0.8 弧度
    label.next_to(m, UP)        # 让标签跟着箭头走

arrow.add_updater(swing)
self.add(arrow, label)
self.wait(4)

状态读取型 updater:让 B 盯着 A

dot = Dot(color=YELLOW)
dot.add_updater(lambda m: m.move_to(
    np.array([2 * np.sin(0.5 * self.time),
              1.5 * np.cos(0.7 * self.time), 0])))
# self.time 是场景时间, 上面是李萨如轨迹

注意写法差异:需要 dt 用双参函数,只需要每帧回调用 lambda m: ...。

TracedPath:彗星尾巴

from manim import TracedPath

dot = Dot(color=YELLOW)
path = TracedPath(dot.get_center, stroke_color=YELLOW,
                  stroke_width=4, fading_fraction=0.6)
dot.add_updater(lambda m: m.move_to(
    np.array([2 * np.sin(self.time), 2 * np.sin(3 * self.time), 0])))
self.add(path, dot)
self.wait(5)

TracedPath 内部就是个 updater,持续记录 dot.get_center 的轨迹。fading_fraction 让尾巴渐隐,非常适合"运动轨迹"教学。

滑杆交互:一张图讲透圆的面积

r = ValueTracker(1)
circle = always_redraw(
    lambda: Circle(radius=r.get_value(), color=BLUE))
formula = always_redraw(
    lambda: MathTex("S = \pi r^2 = %.2f" % (np.pi * r.get_value()**2))
    .to_edge(UP))

self.add(circle, formula)
self.play(r.animate.set_value(2.2), run_time=3)
self.play(r.animate.set_value(0.6), run_time=2)

一次动画改动半径,圆与面积公式同步更新——这就是 updater 的叙事威力(完整版见复刻系列)。

必须记住的清理动作

  • 变换前先 clear_updaters():带 updater 的对象直接 play(Transform(...)) 会每帧被 updater 覆盖,看起来"动画没生效"。dot.clear_updaters() 后再变换。
  • 暂停响应:dot.suspend_updating() / resume_updating() 用于拍"定妆照"。
  • always_redraw 的对象别重复 add:它返回的就是带 updater 的普通对象,self.add 一次即可。