Manim 的基础构建模块 _ 汉化|manim内置的基础mobject的了解


本文档解释了 Manim 的构建模块,并将为你提供开始制作自己视频所需的所有工具。

基本上,Manim 提供了三个不同的概念,你可以将它们组合在一起以制作数学动画:数学对象(简称 mobject),动画(Animation),以及场景(Scene)。如我们将在后续部分中看到的,每个概念在 Manim 中都作为一个单独的类来实现:Mobject 类、Animation 类和 Scene 类。

注意 : 建议在阅读本页之前先阅读教程《快速入门》和《Manim 的输出设置》。

Mobjects

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

如果你尝试在屏幕上显示一个 Mobject 的实例,你只会看到一个空框架。原因是 Mobject 类是所有其他 mobjects 的抽象基类,也就是说它没有任何预先确定的视觉形状可以显示在屏幕上。它只是一个可以显示事物的骨架。因此,你很少需要使用纯粹的 Mobject 实例;相反,你更可能会创建它的派生类的实例。其中一个派生类是 VMobject。V代表向量化 Mobject。本质上,VMobject 是使用矢量图形显示的 mobject。大多数情况下,你将处理 VMobjects,尽管我们将继续使用 “mobject” 这一术语来指代可以显示在屏幕上的形状类,因为它更为通用。

注意 : 任何可以显示在屏幕上的对象都是 mobject,即使它不一定具有数学性质。
提示 :要查看从 Mobject 派生的类的示例,请参见几何模块。事实上,其中大多数也派生自 VMobject。

创建和显示 mobjects

正如 Quickstart 中所解释的,manim 脚本中的所有代码通常都放在场景类的 construct() 方法中。要在屏幕上显示一个 mobject,请调用包含场景的 add() 方法。这是在屏幕上显示未做动画的 mobject 的主要方法。要从屏幕上移除移动对象,只需调用包含场景的 remove() 方法。

from manim import *

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

放置移动对象mobjects

让我们定义一个名为 Shapes 的新场景,并向其中add()一些 mobjects。该脚本会生成一张静态图片,显示一个圆形、一个正方形和一个三角形:

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)

默认情况下,mobjects 在首次创建时会放置在坐标中心或原点。它们还被赋予了一些默认颜色。此外,"形状 "场景会使用 shift() 方法来放置 mobjects。正方形从原点向上移动一个单位,而圆形和三角形则分别向左和向右移动一个单位。

注意 : 与其他图形软件不同,manim 将坐标原点放置在屏幕的中心。正方向垂直向上,正方向水平向右。另见常量模块中的 ORIGIN、UP、DOWN、LEFT、RIGHT 等常量。

在屏幕上放置 mobjects(数学对象)还有许多其他可能的方法,例如 move_to()next_to()align_to()。下一个场景 MobjectPlacement 使用了这三种方法。

from manim import *

class MobjectPlacement(Scene):
    def construct(self):
        circle = Circle()    # 创建一个圆形
        square = Square()    # 创建一个正方形
        triangle = Triangle()# 创建一个三角形

        # 将圆形移动到距离原点左边两单位的位置
        circle.move_to(LEFT * 2)
        # 将正方形放置在圆形的左边
        square.next_to(circle, LEFT)
        # 将三角形的左边界与圆形的左边界对齐
        triangle.align_to(circle, LEFT)

        self.add(circle, square, triangle) # 将圆形、正方形和三角形添加到场景中
        self.wait(1)  # 等待 1 秒钟
  1. move_to():将一个 mobject 移动到指定的位置。在这里,circle.move_to(LEFT * 2) 将圆形移动到距离原点左边两单位的位置。

  2. next_to():将一个 mobject 放置在另一个 mobject 的旁边。square.next_to(circle, LEFT) 将正方形放在圆形的左边。

  3. align_to():将一个 mobject 的某个边界对齐到另一个 mobject 的相同边界。triangle.align_to(circle, LEFT) 将三角形的左边界与圆形的左边界对齐。

方法链式调用的技巧:
  • 方法链:在 Manim 中,许多方法可以链式调用。例如,以下两行代码:
    square = Square()
    square.shift(LEFT)
    
    可以简化为一行:
    square = Square().shift(LEFT)
    
    这是因为大多数方法在调用后会返回被修改的 mobject 本身。因此,你可以继续对返回的 mobject 调用其他方法,实现方法的链式调用。

这种链式调用不仅可以使代码更加简洁,还可以在一步中完成多个操作,使代码更易读和维护。

设计 mobjects

下面的场景更改了移动对象的默认美观效果。

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 的内部是完全透明的,因此必须指定 opacity(不透明度)参数才能显示颜色。opacity 值为 1.0 表示完全不透明,而 0.0 则表示完全透明。

只有 VMobject 的实例实现了 set_stroke()set_fill() 方法。而 Mobject 的实例则实现了 set_color() 方法。绝大多数预定义的类都是从 VMobject 派生的,所以通常可以安全地假设你可以使用 set_stroke()set_fill() 方法。

Mobject 屏幕显示顺序

接下来的场景与上一节的 MobjectStyling 场景完全相同,除了有一行代码不同。

示例: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)

这里唯一的区别(除了场景名称之外)是 mobject 添加到场景中的顺序。在 MobjectStyling 中,我们将它们以 add(circle, square, triangle) 的顺序添加,而在 MobjectZOrder 中,我们以 add(triangle, square, circle) 的顺序添加。

正如你所看到的,add() 方法的参数顺序决定了 mobjects 在屏幕上的显示顺序,左边的参数被放置在后面。

动画

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

示例:SomeAnimations

from manim import *

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

        # 一些动画显示 mobjects...
        self.play(FadeIn(square))

        # ...一些动画移动或旋转 mobjects...
        self.play(Rotate(square, PI/4))

        # 一些动画将 mobjects 从屏幕上移除
        self.play(FadeOut(square))

        self.wait(1)

简单来说,动画是指在两个 mobjects 之间插值的过程。例如,FadeIn(square) 从一个完全透明的方块开始,并以一个完全不透明的方块结束,通过逐渐增加透明度来插值它们之间的状态。FadeOut 则相反:它从完全不透明插值到完全透明。另一个例子是 Rotate,它以传递给它的 mobject 为起点,以旋转了一定角度后的同一对象为终点,这次插值的是 mobject 的角度而不是透明度。

动画方法

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

示例: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)

参考:Animation

animate() 是所有 mobjects 的一个属性,它能够将后续的方法动画化。例如,square.set_fill(WHITE) 设置方块的填充颜色为白色,而 square.animate.set_fill(WHITE) 则会将这个操作动画化。

动画运行时间

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

示例: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 逐渐变化到 1。你只需在 interpolate_mobject 方法中根据 alpha 的值来操纵 self.mobject。通过这种方式,你可以享受 Animation 提供的所有好处,例如在不同的运行时间内播放动画或使用不同的速率函数。

假设你有一个数字,想要创建一个从一个数字平滑过渡到另一个目标数字的动画。可以使用 FadeTransform 来实现这一点,它会淡出起始数字并淡入目标数字。但更直观的方式是通过平滑地递增或递减数字来实现这一点。Manim 允许你通过定义自己的自定义动画来实现这种行为。

你可以从创建一个继承自 AnimationCount 类开始。这个类的构造函数可以包含三个参数:DecimalNumber Mobject、起始值和结束值。构造函数会将 DecimalNumber Mobject 传递给父类构造函数(即 Animation 构造函数),并设置起始值和结束值。

你需要做的唯一事情是定义动画每一步该如何显示。在 interpolate_mobject() 方法中,Manim 会根据视频的帧率、速率函数和动画播放时间为你提供 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 播放此动画,持续时间和速率函数可以任意设置。

示例:CountingScene

from manim import *

class Count(Animation):
    def __init__(self, number: DecimalNumber, start: float, end: float, **kwargs) -> None:
        # 传递 number 作为动画的 mobject
        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):
        # 创建 DecimalNumber 并将其添加到场景中
        number = DecimalNumber().set_color(WHITE).scale(5)
        # 添加一个更新器,在数值变化时使 DecimalNumber 保持居中
        number.add_updater(lambda number: number.move_to(ORIGIN))

        self.add(number)

        self.wait()

        # 播放 Count 动画,在 4 秒钟内从 0 计数到 100
        self.play(Count(number, 0, 100), run_time=4, rate_func=linear)

        self.wait()

参考AnimationDecimalNumberinterpolate_mobject()play()

使用 mobject 的坐标

mobjects 包含定义其边界的点。这些点可以用来将其他 mobjects 以相应的方式添加到它们上面,例如通过 get_center()get_top()get_start() 等方法。以下是一些重要坐标的示例:

示例:MobjectExample

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)

在这个示例中,我们创建了一个由四个点组成的线段 a。我们使用 get_start()get_end()get_center() 方法来获取线段的起点、终点和中心点,并将这些点显示为彩色的圆点。我们还用 point_from_proportion() 方法在 a 上标记了中点。

将 mobjects 转换为其他 mobjects

你还可以将一个 mobject 转换为另一个 mobject,如下所示:

示例: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))

Transform 函数将前一个 mobject 的点映射到下一个 mobject 的点。这可能会导致奇怪的行为,例如当一个 mobject 的点按顺时针排列而另一个 mobject 的点按逆时针排列时。在这种情况下,可以使用 flip 函数和 numpyroll 函数来重新定位点:

示例: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)

在这个示例中,我们首先创建了两个不同的形状 m1am1b 以及 m2am2b。为了使 m2a 的点与 m2b 的点对齐,我们使用 numpy.roll() 来重新定位 m2a 的点。这确保了 Transform 动画在形状转换时表现得更加自然。

场景(Scenes)

Scene 类是 Manim 的核心部分,起到了连接和管理各个 mobject 和动画的作用。在 Manim 中,所有的 mobject 必须添加到一个场景中才能被显示,或者从场景中移除以停止显示。每个动画也必须由场景来播放,而每一个没有动画发生的时间段则由调用 wait() 方法来决定。你的视频的所有代码都必须包含在一个继承自 Scene 类的类的 construct() 方法中。最后,一个文件中可以包含多个 Scene 子类,以便同时渲染多个场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值