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