Manim 的 .animate 让物体转 180° 为什么不转?.animate 和 Rotate 的区别
2026 年 11 月 8 日 · 极坐标⋅XYZ
Manim 的 .animate 让物体转 180° 为什么不转?.animate 和 Rotate 的区别
mob.animate.rotate(PI) 看起来没转,是因为 .animate 只认起点和终点:它把终点的每个点都和起点对应的点连成直线,让物体沿直线过渡过去,从来不会让点绕着中心画弧。转 180° 时,每个点都是直线穿过中心奔向对面,物体先缩成一个点再长出来;转 360° 时终点和起点完全一样,什么都不会发生。想看到真正的旋转,把 self.play(mob.animate.rotate(PI)) 换成 self.play(Rotate(mob, PI)),Rotate 会让每个点沿圆弧走。
Manim 是一个用 Python 写数学动画的开源引擎,源头是 Grant Sanderson 为 3Blue1Brown 频道写的那一套代码。画面里的图形、公式、坐标轴都是 Python 对象,你描述它们长什么样、怎么变,引擎再逐帧渲染成视频。如今大家装的多是社区维护的 Manim Community,2026 年的最新版本是 0.21.0,本文说的行为以这一版为准。如果对 Manim 还不熟,先读这篇入门介绍;想按顺序系统学,2026 年最新的 Manim 中文教程在极坐标⋅XYZ 也有,从教程页进去。
下面两段完整代码都可以在极坐标⋅XYZ 的工作区里打开对照,点代码块上方的「在工作区中打开」即可,打开即用,不用在本机装 Python。
最小可运行的例子:左右各转 180°
两个一模一样的三角形,左边用 .animate.rotate(PI),右边用 Rotate(right, PI),参数、时长都相同,同时播放。
from manim import *
class AnimateVsRotate(Scene):
def construct(self):
left = Triangle(color=BLUE).set_fill(BLUE, 0.5).move_to(LEFT * 3)
right = Triangle(color=ORANGE).set_fill(ORANGE, 0.5).move_to(RIGHT * 3)
self.add(left, right)
self.wait(0.5)
self.play(
left.animate.rotate(PI),
Rotate(right, PI),
run_time=3,
)
self.wait()
左边的蓝色三角形没有转动的过程:它一路缩小,缩到几乎成一个点,再朝相反的朝向展开,最后尖角朝下。右边的橙色三角形绕自己的中心转了半圈,尖角朝下。两边的终点形状一样,区别全在过程里。
为什么是这样
.animate 的做法可以概括成两步。.animate 后面接的每个方法都不会立刻改动物体,而是记在一份目标副本上;self.play 执行时,再把这份副本当作终点,让物体从当前状态过渡过去。我们的课程里把它讲成「底层就是 MoveToTarget,只是帮你省了手写 generate_target 这一步」。
过渡用的是逐点的直线插值:物体上每个点都从它的起点出发,沿直线走向它在目标副本里对应的那个点。直线插值对位移、换颜色、缩放都很自然,对旋转就出问题了,因为旋转的轨迹本来是圆弧,而直线插值不管你中间想怎么转,只看两头。
设物体的中心在原点,转角为 θ,那么某个点 p 在动画进行到比例 t 时的位置是 (1 − t)·p + t·R(θ)p。在 t = 0.5 的时候,物体相对中心的大小变成原来的 |cos(θ/2)|:
| 旋转角度 | 动画中点时物体的大小 | 看起来像什么 |
|---|---|---|
PI / 4(45°) |
约 0.92 倍 | 基本是在转,略微缩一下 |
PI / 2(90°) |
约 0.71 倍 | 转得有点「缩水」 |
PI(180°) |
0 倍 | 缩成一个点再展开 |
TAU(360°) |
1 倍,且终点就是起点 | 完全不动 |
这是按公式推出来的,不是靠经验。角度越大,直线越「抄近路」,旋转感越弱。
Rotate 则是 Manim 内置的一个专门的动画类。它同样先准备一份旋转后的目标副本,但过渡时让每个点沿圆弧走,弧的角度就是你给的旋转角。点走的是圆弧,物体全程保持原来的大小,转几度都像在转。
所以这两个写法不是同一件事的两种写法,只是在小角度上长得像:
mob.animate.rotate(PI) |
Rotate(mob, PI) |
|
|---|---|---|
| 点的轨迹 | 起点到终点的直线 | 绕中心的圆弧 |
| 转 180° | 缩成一点再展开 | 正常转半圈 |
| 转 360° | 什么都不发生 | 正常转一整圈 |
| 适合 | 位移、变色、缩放、小角度旋转 | 任何角度的旋转 |
常见写错的地方
写 TAU 想转一圈,结果画面静止
self.play(sq.animate.rotate(TAU))
终点副本和起点完全重合,每个点的直线插值就是原地不动,所以这一行播放期间什么也看不到,时间却照样过去。改成 Rotate:
from manim import *
class FullTurn(Scene):
def construct(self):
tri = Triangle(color=GREEN).set_fill(GREEN, 0.5)
self.add(tri)
self.wait(0.5)
# 想看到转一整圈,用 Rotate
self.play(Rotate(tri, TAU), run_time=2)
self.wait()
转的不是自己的中心,而是别的点
Rotate 默认绕物体自己的中心转。想绕另一个点转,用 about_point;想绕物体的某个边或角转,用 about_edge:
self.play(Rotate(arm, PI / 2, about_point=ORIGIN))
.animate.rotate(...) 里也能传 about_point,但前面讲的直线插值问题依然存在,所以绕点转大角度还是用 Rotate。
把 rotate 直接丢进 play
self.play(sq.rotate(PI)) # 不要这样写
sq.rotate(PI) 在这一行就把方块转了,并且返回方块本身,play 收到的是一个物体,而不是动画,会报错。要么写成 sq.animate.rotate(PI),要么写成 Rotate(sq, PI)。
常见追问(FAQ)
Q:小角度旋转还能用 .animate.rotate 吗?
A:能。从上面的表可以看到,45° 时中途只缩一点点,肉眼基本看不出来。但角度一旦超过 90°,建议直接用 Rotate,省得再调。
Q:Rotate 的 rate_func 和 run_time 怎么改?
A:和其他动画一样,作为关键字参数传:Rotate(mob, PI, run_time=2, rate_func=linear)。
Q:链式 .animate.rotate(...).shift(...) 会怎样?
A:位移和旋转会合进同一份目标副本,一起做直线插值。课程里还提到,链式调用按书写顺序依次作用在副本上,几何变换(比如旋转和拉伸)不可交换,先后顺序不同,终点形状也不同。
Q:Rotate 之后物体的朝向会保留吗?
A:会。动画结束时物体停在旋转后的状态,下一个动作接着用。
Q:想让物体一直转下去呢?
A:转一次用 Rotate;想持续旋转,可以用 Rotating 动画,或者给物体加一个每帧转一点的 updater,写法见ValueTracker 与 Updater 那一课。
.animate 的链式调用、目标副本的原理,可以到教程《动画》一课了解;想直接动手,打开工作区,把上面的 Rotate 换成 .animate 对比看看。
来源:极坐标⋅XYZ · jizuobiao.xyz · 更新于 2026-11-08 · 基于 Manim Community 0.21.0