from manim import * class MovingAngle(Scene): def construct(self): rotation_center = LEFT theta_tracker = ValueTracker(110) line1 = Line(LEFT, RIGHT) line_moving = Line(LEFT, RIGHT) line_ref = line_moving.copy() line_moving.rotate( theta_tracker.get_value() * DEGREES, about_point=rotation_center ) a = Angle(line1, line_moving, radius=0.5, other_angle=False) tex = MathTex(r"\theta").move_to( Angle( line1, line_moving, radius=0.5 + 3 * SMALL_BUFF, other_angle=False ).point_from_proportion(0.5) ) self.add(line1, line_moving, a, tex) self.wait() line_moving.add_updater( lambda x: x.become(line_ref.copy()).rotate( theta_tracker.get_value() * DEGREES, about_point=rotation_center ) ) a.add_updater( lambda x: x.become(Angle(line1, line_moving, radius=0.5, other_angle=False)) ) tex.add_updater( lambda x: x.move_to( Angle( line1, line_moving, radius=0.5 + 3 * SMALL_BUFF, other_angle=False ).point_from_proportion(0.5) ) ) self.play(theta_tracker.animate.set_value(40)) self.play(theta_tracker.animate.increment_value(140)) self.play(tex.animate.set_color(RED), run_time=0.5) self.play(theta_tracker.animate.set_value(350))
Angle(line1, line_moving, radius=0.5, other_angle=False) 中的参数other_angle是什么意思?
在 Manim 的 Angle 类中,other_angle 参数决定了当两条线相交时,大括号(弧线)绘制在哪一个角度上。
1. 直观理解
当两条直线相交时,它们之间实际上形成了四个角(两对对顶角)。即便我们只看相邻的两个角,它们加起来也是 180°。
-
other_angle=False(默认值):绘制“常规”的那个角。通常是根据两条线的向量方向确定的主角。 -
other_angle=True:绘制该角的补角(即 180° 减去当前角的那一部分)。
2. 为什么需要它?
在数学动画中,你可能想标注两条线交点的“左侧角”或者“右侧角”。由于两条线的定义顺序和方向可能导致 Manim 默认选择了你不想要的那个角,other_angle 就成了一个快速切换开关。
3. 代码中的逻辑
在你的代码里:
a = Angle(line1, line_moving, radius=0.5, other_angle=False)
如果 line1 是指向右的,line_moving 是向上旋转 110° 的:
-
False:你会得到一个 110° 的角。
-
True:你会得到它旁边的那个角(即 $180^\circ - 110^\circ = 70^\circ$ 的那个角)。
4. 特别注意:它不是“优角”
很多初学者会把 other_angle 误认为是切换优角 (Reflex Angle)(即大于 180° 的那个角)。
-
如果你想要显示大于 180° 的角(比如 250°),你需要调整线段的定义方向,或者使用
radius为负值等技巧。 -
other_angle主要是用来在相邻的两个补角之间做切换。
5. 你的代码为什么要用它?
在你的 MovingAngle 场景中,theta_tracker 的值会一直变到 350°。
-
当角度超过 180° 时,
Angle的行为可能会变得违反直觉。 -
通过显式设置
other_angle=False,你确保了 Manim 始终尝试按照你传入的两条线的拓扑关系来绘制那个“主角度”,从而配合add_updater实现平滑的动画效果。
总结一句话:
other_angle 就是用来切换到隔壁那个补角的开关。如果默认出来的弧线位置不对,把它改成 True 往往就能解决问题。
Angle(line1, line_moving, radius=0.5, other_angle=False)中的参数radius是控制什么的?
在 Angle 类中,radius 参数控制的是表示角度的那段弧线的半径大小。
简单来说,它决定了那个弧线离顶点(两条线的交点)有多远。
1. 核心作用
-
视觉距离:
radius值越大,弧线就画得越大,离两条线的交点越远。 -
默认值:如果不指定,它的默认值通常是
0.5(Manim 单位)。
2. 在你代码中的具体体现
在你的代码里,radius 出现了两次,起到了不同的作用:
A. 绘制主弧线
a = Angle(line1, line_moving, radius=0.5, other_angle=False)
这里 radius=0.5 设定了你看到的那个黄色(或默认色)圆弧的大小。
B. 定位标签($\theta$)
tex = MathTex(r"\theta").move_to(
Angle(
line1, line_moving, radius=0.5 + 3 * SMALL_BUFF, other_angle=False
).point_from_proportion(0.5)
)
这是一个非常聪明的用法!为了让符号 $\theta$ 准确地放在弧线外面一点点,你创建了一个虚拟的、半径更大的 Angle:
-
这个虚拟角度的半径是
0.5 + 3 * SMALL_BUFF。 -
通过
point_from_proportion(0.5)找到这段大弧线的中点。 -
将文本 $\theta$ 移动到这个位置,从而实现了“标签始终跟随角度旋转并保持间距”的效果。
3. radius 的特殊技巧
-
正值与负值:
-
通常使用正值。
-
但在某些复杂的几何情况下,将
radius设为负值,可以强制让弧线转到交点的另一侧(起到类似对顶角的效果),这在某些版本的 Manim 中是一种实用的调试技巧。
-
-
配合
SMALL_BUFF:-
在定位文本时,通常会在
radius上加一个微小的偏移量(Buffer),防止文字直接压在弧线上。
-
总结:
radius 就是用来调节角度弧线“个头”大小的参数。个头越大,标注就越明显,但也越占地方。
lambda x: x.become(line_ref.copy())
become()是用来干什么的?
在 Manim 中,become() 是一个非常强大且特殊的方法。简单一句话概括:它能让一个物体“变身”成另一个物体的样子,但保持自己的身份不变。
1. 核心作用:状态替换
当你调用 A.become(B) 时:
-
外观克隆:A 会立即获得 B 的所有视觉属性,包括位置、颜色、形状、大小、旋转角度等。
-
身份保留:A 依然是原来的那个 Python 对象(内存地址没变)。这意味着之前所有绑定在 A 上的引用(比如它在某个
Group里,或者有其他的updater)依然有效。
2. 为什么在 Updater 里必须用 become()?
在你提供的代码中:
line_moving.add_updater(
lambda x: x.become(line_ref.copy()).rotate(...)
)
这是 become() 最经典的使用场景。
如果不使用 become() 的后果:
如果你直接在 updater 里写 x.rotate(...),由于 updater 是每一帧都在执行的,line_moving 会在上一帧已经旋转的基础上叠加旋转。结果就是线条会像风扇一样越转越快,最后飞出屏幕。
使用 become() 的逻辑:
-
重置:
x.become(line_ref.copy())。每一帧开始时,先把line_moving变回最初那根水平线的样子(line_ref)。 -
重新计算:然后再根据
theta_tracker当前的最新的值旋转一次。 -
循环:下一帧重复这个过程——先重置,再旋转。
这样就确保了物体的状态始终与 ValueTracker 的数值绝对同步。
3. become() vs 直接赋值
-
直接赋值 (
x = B):这只是把变量名指向了新对象。Manim 的渲染引擎并不知道你换了对象,原本在屏幕上的 A 不会发生任何变化。 -
使用
become():它是“整容手术”。物体 A 还在屏幕上,但它的每一条路径、每一个像素都变成了 B 的样子。
4. 常见使用场景
| 场景 | 描述 |
| 复杂动画同步 | 像你代码中那样,让一个复杂的几何图形(如 Angle)随变量实时刷新。 |
| 物体“变身” | 在动画中让一个圆圈无缝变成一个方块(虽然 ReplacementTransform 也能做,但 become 在 updater 中更受控)。 |
| 状态回滚 | 快速将物体恢复到之前保存的某个快照状态。 |
5. 关于 .copy()
在你的代码里写的是 x.become(line_ref.copy())。
-
为什么要 copy? 因为
become会修改目标对象的内部数据。为了保护原件line_ref不被意外修改,通常先克隆一份副本作为“样板”给become使用。
总结:
become() 是 Manim 实现动态响应式动画的基石。它解决了一个核心问题:如何在保持物体身份连续性的同时,每一帧都重新定义它的样子。

浙公网安备 33010602011771号