Manim动画的基本组成元素#

本文档介绍Manim动画的基本组成元素,并提供了开始制作自己的视频所需的所有必要工具。

本质上说, Manim提供了三个不同的概念,您可以将它们组合在一起来制作数学动画。: 第一 mathematical object(数学组件) (或者说 mobject 可动态化对象), 第二: animation, 第三: scene。正如我们将在接下来的章节中看到的那样,Manim中的每个概念都被实现为一个单独的类: Mobject类、 Animation类和Scene类。

Note

建议在阅读本页面之前先阅读Quickstart快速入门Manim输出设置教程。

Mobjects#

Mobjects是所有Manim动画的基本构建模块。 从 Mobject 派生的每个类都代表了可以显示在屏幕上的对象。 例如,简单的形状,如Circle圆形Arrow箭头Rectangle矩形,都是Mobjects。 更复杂的构造,如Axes坐标轴FunctionGraph函数图像BarChart条形图也是Mobjects。,

Mobject 是指Manim中的“可动态化对象”, 是用于组合和操作的基本元素。如果您只是创建了一个Mobject的实例,而没有对其进行任何绘制或操作, 那么在屏幕上将只会看到一个空的框架,没有任何可见的内容。 原因在于Mobject 类是所有其他Mobjects的抽象基类,即它没有任何预定义的可视化形状,无法直接在屏幕上显示。 它只是一个能够被显示的事物的骨架。 因此,您很少需要使用纯粹的Mobject实例; 相反,您很可能会创建其派生类的实例。其中一个派生类是VMobject(向量化Mobject)V 表示VMobject是Mobject的一种特定类型,是矢量化的, 这意味着它是通过一系列点和曲线的数学表示vector graphics(矢量图)而不是栅格化图像表示的。 大多数情况下,您将处理vmobject,但我们将继续使用术语“mobject”来指代可以在屏幕上显示的形状类,因为它更加通用。

Note

任何可以在屏幕上显示的对象都是一个 mobject,即使它不一定具有数学性质。

Tip

要查看从Mobject派生的类的示例,请查看geometry模块。 实际上,这些类中的大多数也是从VMobject派生的。

创建并显示Mobjects#

Quickstart(快速入门)中所解释的那样,通常将manim脚本中的所有代码都放在Scene类的construct()方法中。 为了将一个 mobject 放到 screen 上, 需要调用Sceneadd() 方法。 这是不动的 mobject 放到 screen 上的方法。remove() 方法可以将物体从场景中移走。 Scene.

Example: CreatingMobjects(创建物体)

from manim import *

class CreatingMobjects(Scene):
    def construct(self):
        circle = Circle()
        self.add(circle)
        self.wait(1)
        self.remove(circle)
        self.wait(1)

mobject们之间的位置关系#

这个例子中我们定义了一个新的Scene(场景), 并且创造了一些Shapes(几何图形), 通过调用 add() 方法,件这些 mobjects 加入进去。这段程序生成了一个circle(圆), 一个square(正方形), 和一个triangle(三角形):

Example: Shapes (图形)

from manim import *

class Shapes(Scene):
    def construct(self):
        circle = Circle()
        square = Square()
        triangle = Triangle()

        circle.shift(LEFT)
        square.shift(UP)
        triangle.shift(RIGHT)

        self.add(circle, square, triangle)
        self.wait(1)

默认情况下, 一个或多个物体被放置在坐标轴的交点,也就是坐标系的origin(原点),即 (0, 0) 的位置。也会给物体默认的颜色。 此外,使用shift()方法可以将 Shapes 精确放在 scene 中相对位置上。 正方形从原点沿UP移动一个单位, 而圆形和三角形分别向LEFT 和向RIGHT移动一个单位。

注意

Manim 库相对于其他图形软件的一个不同之处。在 Manim 库中,坐标系的中心点被放置在屏幕的中心位置。 垂直向上的方向被定义为正方向,水平向右的方向也被定义为正方向。这与一些其他的图形软件可能采用不同的坐标系约定有所不同。 此外,Manim 库中常用的一些常量,如 ORIGIN(坐标系原点)UP(向上的单位向量)DOWN(向下的单位向量)LEFT(向左的单位向量)RIGHT(向右的单位向量)等等。 这些常量是在 Manim 库的constants 模块中定义的,可以在编写代码时方便地引用和使用。

Manim 库中,除了使用 shift() 方法外,还有其他多种方法可以用来控制 mobjects 的位置和摆放方式,比如: move_to(), next_to(), 和 align_to(). 下面这个 scene的类 MobjectPlacement 就用到了他们三个。

Example: MobjectPlacement

from manim import *

class MobjectPlacement(Scene):
    def construct(self):
        circle = Circle()
        square = Square()
        triangle = Triangle()
.
        # 圆放在离origin(原点)左边两个单位的地方
        circle.move_to(LEFT * 2)
        # 把square(正方形)放在 circle(圆) 的左边 
        square.next_to(circle, LEFT)
        # 三角形的左边缘(边框)与圆形的左边缘(边框)对齐
        triangle.align_to(circle, LEFT)

        self.add(circle, square, triangle)
        self.wait(1)

move_to() 方法提供了绝对单位来指定物体的位置。以 ORIGIN(原点)作为参照系, 而 next_to() 使用相对位置(以第一 mobject 作为参照对象)。 align_to() 类似于word中的对齐方式,使用LEFT表示相对于另一个物体的 left 来确定对齐,并使用该物体的边界来进行对齐。 在计算一个物体的边框(边界)坐标时,会使用一个虚拟的包围盒(bounding box)来确定它的位置。

提示:

在 manim 中有许多方法能被连在一起写,比如下面这行。 lines

square = Square()
square.shift(LEFT)

can be replaced by

square = Square().shift(LEFT)

技术上说,该方法通常会以某种方式修改mobject,然后将修改后的mobject作为输出返回.

样式Mobjects#

这段代码改变了mobjects的默认外观。

Example: MobjectStyling

from manim import *

class MobjectStyling(Scene):
    def construct(self):
        circle = Circle().shift(LEFT)
        square = Square().shift(UP)
        triangle = Triangle().shift(RIGHT)

        circle.set_stroke(color=GREEN, width=20)
        square.set_fill(YELLOW, opacity=1.0)
        triangle.set_fill(PINK, opacity=0.5)

        self.add(circle, square, triangle)
        self.wait(1)

这个场景使用了两个主要函数来改变mobject的视觉样式: set_stroke()set_fill()。 前者改变了mobject边框的视觉样式,而后者改变了内部的样式。 默认情况下,大多数mobject的内部是完全透明的,因此您必须指定不透明度参数才能显示颜色。 不透明度为1.0表示完全不透明,而0.0表示完全透明。

只有VMobject的实例实现了set_stroke()和set_fill()方法。Mobject的实例则实现了set_color()方法。绝大多数预定义的类都是从VMobject派生而来的,因此通常可以安全地假定您可以访问set_stroke()和set_fill()方法。 在Manim中,Mobject表示可变化的数学对象,例如线、圆、曲线等。它们通常具有边框和填充,因此可以使用set_stroke()set_fill()方法来更改它们的外观属性。 而Mobject则表示一般的可视化对象,例如文本、图片、点等。它们通常不具有边框和填充,因此使用set_color()方法来更改它们的颜色属性。 由于MobjectMobject的子类,因此大多数预定义的类都是从Mobject派生而来的,因此它们通常都具有边框和填充,可以使用set_stroke()和set_fill()方法来更改它们的外观属性。因此,在使用Manim时,通常可以安全地假定您可以访问set_stroke()set_fill()方法。

Mobject 出现在 screen 的顺序#

下一个场景与上一节中的MobjectStyling场景完全相同,除了一行代码以外。

Example: MobjectZOrder

from manim import *

class MobjectZOrder(Scene):
    def construct(self):
        circle = Circle().shift(LEFT)
        square = Square().shift(UP)
        triangle = Triangle().shift(RIGHT)

        circle.set_stroke(color=GREEN, width=20)
        square.set_fill(YELLOW, opacity=1.0)
        triangle.set_fill(PINK, opacity=0.5)

        self.add(triangle, square, circle)
        self.wait(1)

这里唯一的区别(除了场景名称)是向场景添加mobjects的顺序不同。在MobjectStyling场景中,我们按照add(circle, square, triangle)的顺序添加它们,而在MobjectZOrder场景中,我们按照add(triangle, square, circle)的顺序添加它们。

正如您可以看到的,add()方法的参数顺序决定了mobjects在屏幕上的显示顺序,最左边的参数被放在最底层。

动画#

在Manim的核心是动画。通常,您可以通过调用play()方法向场景中添加动画。

Example: SomeAnimations

from manim import *

class SomeAnimations(Scene):
    def construct(self):
        square = Square()

        # 播放一些动画, ...
        self.play(FadeIn(square))

        # ... 在Manim中对mobject进行平移或旋转的操作。...
        self.play(Rotate(square, PI/4))

        # 一些动画会从屏幕上删除mobjects。
        self.play(FadeOut(square))

        self.wait(1)

简单来说,动画是介于两个mobject之间变换的过程。例如, FadeIn(square)从一个完全透明的square开始,以一个完全不透明的版本结束,通过逐渐增加不透明度来变换。 FadeOut的工作方式相反:它从完全不透明变换到完全透明。另一个例子是Rotate,它从传递给它的mobject开始,以同一个对象,但旋转了一定角度结束,这次变换的是mobject的角度而不是其不透明度。

动画方法#

在Manim中,任何可以更改的mobject属性都可以被动画化。实际上,通过使用 animate()方法,任何更改mobject属性的方法都可以用作动画。

Example: AnimateExample

from manim import *

class AnimateExample(Scene):
    def construct(self):
        square = Square().set_fill(RED, opacity=1.0)
        self.add(square)

        # 改变颜色的动画
        self.play(square.animate.set_fill(WHITE))
        self.wait(1)

        # 同时改变位置,同时旋转的动画
        self.play(square.animate.shift(UP).rotate(PI / 3))
        self.wait(1)

References: Animation

在Manim中,animate()是所有mobjects的属性,它可以将接下来的方法动画化。例如,square.set_fill(WHITE)将设置square对象的填充颜色,而square.animate.set_fill(WHITE)将对此操作进行动画化。

动画运行时长#

默认情况下,传递给play()方法的任何动画都持续一秒钟。您可以使用run_time参数来控制动画的持续时间。

Example: RunTime

from manim import *

class RunTime(Scene):
    def construct(self):
        square = Square()
        self.add(square)
        self.play(square.animate.shift(UP), run_time=3)
        self.wait(1)

创建一个定制的动画#

尽管Manim具有许多内置的动画,但有时您需要平滑地从Mobject的一个状态过渡到另一个状态。 如果您发现自己处于这种情况下,那么您可以定义自己的自定义动画。 您可以通过扩展Animation 类并覆盖其interpolate_mobject()方法来实现自定义动画。 interpolate_mobject()方法接收alpha作为参数,该参数从0开始并在动画过程中发生变化。 因此,您只需根据interpolate_mobject()方法中的alpha值来操作self.mobject。 然后,您可以获得Animation的所有优势,例如将其运行时间设置为不同的值或使用不同的rate函数。

假设您从一个数字开始,并希望创建一个Transform动画,将其转换为目标数字。 您可以使用FadeTransform来实现,它将淡出起始数字并淡入目标数字。 但是,当我们考虑将数字从一个转换为另一个时,一种直观的方法是通过平滑地增加或减少它来实现。 Manim具有一项功能,允许您通过定义自己的自定义动画来自定义此行为。

您可以通过创建自己的Count类来实现数字计数动画,该类扩展了Animation类。 该类可以具有带有三个参数(DecimalNumber Mobject、start和end)的构造函数。 构造函数将DecimalNumber Mobject传递给超类构造函数(在本例中为Animation构造函数),并设置start和end属性。

您可以使用interpolate_mobject()方法来定义自定义动画的外观。该方法接收一个alpha参数,该参数表示当前正在播放的动画的步骤。alpha参数的值在0到1之间,表示动画的当前进度。例如,0表示动画的开始,0.5表示动画的一半,1表示动画的结束。

在数字Count动画的情况下, 您只需要找出一种方法来确定在给定alpha值时要显示的数字, 然后在Count动画的interpolate_mobject()方法中设置该值。 假设您从50开始并在动画结束时递增,直到DecimalNumber达到100。

  • 如果 alpha 是 0, 你想要的值就是 50.

  • 如果 alpha 是 0.5, 你想要的值就是 75.

  • 如果 alpha 是 1, 你想要的值就是 100.

通常,您从起始数字开始,并根据alpha值仅添加要增加的值的一部分。 因此,在每个步骤中计算要显示的数字的逻辑将是50 + alpha * (100 - 50)。 一旦为DecimalNumber设置了计算出的值,您就完成了数字计数动画。

一旦您定义了自己的Count动画, 您就可以在您的Scene中为任何DecimalNumber对象播放它,并以任何速率函数设置任何持续时间。

Example: CountingScene

from manim import *

class Count(Animation):
    def __init__(self, number: DecimalNumber, start: float, end: float, **kwargs) -> None:
        # 传递number作为动画对象初始化
        super().__init__(number,  **kwargs)
        # 设置开始和结束
        self.start = start
        self.end = end

    def interpolate_mobject(self, alpha: float) -> None:
        # 根据alpha设置DecimalNumber的值
        value = self.start + (alpha * (self.end - self.start))
        self.mobject.set_value(value)


class CountingScene(Scene):
    def construct(self):
        # 创建Decimal Number并将其添加到场景中
        number = DecimalNumber().set_color(WHITE).scale(5)
        # 您可以add_Updater函数来创建自定义的更新函数
        number.add_updater(lambda number: number.move_to(ORIGIN))

        self.add(number)

        self.wait()

        # 从0到100播放4秒。
        self.play(Count(number, 0, 100), run_time=4, rate_func=linear)

        self.wait()

References: Animation DecimalNumber interpolate_mobject() play()

使用对象的坐标#

Mobjects包含定义其边界的点。这些点可以用于相互添加其他Mobjects,例如通过get_center()get_top()get_start()等方法。以下是一些重要坐标的示例:

Example: MobjectExample

../_images/MobjectExample-1.png
from manim import *

class MobjectExample(Scene):
    def construct(self):
        p1= [-1,-1,0]
        p2= [1,-1,0]
        p3= [1,1,0]
        p4= [-1,1,0]
        a = Line(p1,p2).append_points(Line(p2,p3).points).append_points(Line(p3,p4).points)
        point_start= a.get_start()
        point_end  = a.get_end()
        point_center = a.get_center()
        self.add(Text(f"a.get_start() = {np.round(point_start,2).tolist()}", font_size=24).to_edge(UR).set_color(YELLOW))
        self.add(Text(f"a.get_end() = {np.round(point_end,2).tolist()}", font_size=24).next_to(self.mobjects[-1],DOWN).set_color(RED))
        self.add(Text(f"a.get_center() = {np.round(point_center,2).tolist()}", font_size=24).next_to(self.mobjects[-1],DOWN).set_color(BLUE))

        self.add(Dot(a.get_start()).set_color(YELLOW).scale(2))
        self.add(Dot(a.get_end()).set_color(RED).scale(2))
        self.add(Dot(a.get_top()).set_color(GREEN_A).scale(2))
        self.add(Dot(a.get_bottom()).set_color(GREEN_D).scale(2))
        self.add(Dot(a.get_center()).set_color(BLUE).scale(2))
        self.add(Dot(a.point_from_proportion(0.5)).set_color(ORANGE).scale(2))
        self.add(*[Dot(x) for x in a.points])
        self.add(a)

Transforming mobjects into other mobjects#

It is also possible to transform a mobject into another mobject like this:

Example: ExampleTransform

from manim import *

class ExampleTransform(Scene):
    def construct(self):
        self.camera.background_color = WHITE
        m1 = Square().set_color(RED)
        m2 = Rectangle().set_color(RED).rotate(0.2)
        self.play(Transform(m1,m2))

The Transform function maps points of the previous mobject to the points of the next mobject. This might result in strange behaviour, e.g. when the dots of one mobject are arranged clockwise and the other points are arranged counterclockwise. Here it might help to use the flip function and reposition the points via the roll function of numpy:

Example: ExampleRotation

from manim import *

class ExampleRotation(Scene):
    def construct(self):
        self.camera.background_color = WHITE
        m1a = Square().set_color(RED).shift(LEFT)
        m1b = Circle().set_color(RED).shift(LEFT)
        m2a= Square().set_color(BLUE).shift(RIGHT)
        m2b= Circle().set_color(BLUE).shift(RIGHT)

        points = m2a.points
        points = np.roll(points, int(len(points)/4), axis=0)
        m2a.points = points

        self.play(Transform(m1a,m1b),Transform(m2a,m2b), run_time=1)

Scenes#

The Scene class is the connective tissue of manim. Every mobject has to be added to a scene to be displayed, or removed from it to cease being displayed. Every animation has to be played by a scene, and every time interval where no animation occurs is determined by a call to wait(). All of the code of your video must be contained in the construct() method of a class that derives from Scene. Finally, a single file may contain multiple Scene subclasses if multiple scenes are to be rendered at the same time.