ManimCE教程(3)Manim 基本构成要素

本质上,manim 提供了三个不同的“概念”,您可以将它们组合在一起以生成数学动画:数学物体(mathematical object,简称 moject )、动画( animation )和场景( scene )。在接下来的介绍中可以看到,这三个概念中的每一个都作为一个单独的“类”被独立的执行。这三个类是:MobjectAnimationScene

Mobject

Mobject 是 manim 动画中基本的构造块(building block)。每一个从 Mobject 派生出的类代表着一个可以被放置在屏幕上的物体。例如,CircleArrowRectangle 等简单形状都是 mojects 。更复杂的构造如 AxesFunctionGraphBarChart 也是mojects。

如果你试图在屏幕上显示 Moject 的一个实例,你只会看到一个空的框架,原因是 Mobject 类是所有其他 Mobject 的抽象基类,即它没有任何可以在屏幕上显示的预先确定的视觉形状。它只是一个可以展示的东西的骨架,因此,您很少需要使用 Moject 的普通实例;相反,您很可能会创建其派生类的实例。其中一个派生类是 VMobject。V代表矢量化Mobject。从本质上讲,vmobject 是一个使用矢量图形进行显示的 mobject。大多数时候,您将处理 vmobject,尽管我们将继续使用术语 “moject” 来指代可以在屏幕上显示的形状类,因为它更为通用。

创建和显示 mojects

正如ManimCE教程(1)快速入门中所解释的,通常 manim 脚本中的所有代码都放在 Scene 类的 construct( ) 方法中。要在屏幕上显示 mobject ,请调用包含 Sceneadd( ) 方法。这是在屏幕上显示未设置动画的 moject 的主要方式。要从屏幕上删除 moject ,只需从包含的 Scene 中调用 remove( ) 方法。

from manim import *

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

ManimCE教程(3)Manim 基本构成要素_第1张图片

放置 mojects

让我们定义一个名为 Shapes 的新 Scene ,并在其中添加一些 mojects。这个脚本生成一个静态图片,显示一个圆、一个正方形和一个三角形:

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)

ManimCE教程(3)Manim 基本构成要素_第2张图片
默认情况下,第一次创建移动对象时,移动对象被放置在坐标或原点的中心。它们也被赋予了一些默认的颜色。此外,Shapes 场景通过使用 shift( ) 方法放置 mojects。正方形从原点向上移动一个单位,而圆和三角形分别向左和向右移动一个单元。

还有许多其他可能的方法可以将 mojects 放置在屏幕上,例如 move_to( )next_to( )align_to( )
ManimCE教程(3)Manim 基本构成要素_第3张图片
move_to( ) 方法使用绝对单位(相对于 origin 测量),而 next_to( ) 使用相对单位(从作为第一个参数传递的 moject 测量)。align_to( ) 使用 LEFT 不是作为测量单位,而是作为确定用于对齐的边界的一种方式。移动对象的边界坐标是使用其周围的假想边界框来确定的。

设置 mojects 的样式

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)

ManimCE教程(3)Manim 基本构成要素_第4张图片
此场景使用两个主要函数来更改 mobject 的视觉样式:set_stroke( )set_fill( )。前者改变了 moject 边界的视觉样式,而后者改变了内部的视觉样式。默认情况下,大多数 mojects 的内部都是完全透明的,因此必须指定不透明度参数才能显示颜色。不透明度为1.0表示完全不透明,而0.0表示完全透明。

只有 VMobject 的实例实现 set_stroke( )set_fill( )Moject 的实例实现了 set_color( )。绝大多数预定义的类都是从 VMobject 派生的,因此通常可以安全地假设您有权访问set_stroke( )set_fill( )

Moject 显示顺序

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)

ManimCE教程(3)Manim 基本构成要素_第5张图片
这里唯一的区别是将 mojects 添加到场景中的顺序。在 MojectStyling 中,我们将它们添加为 add(circle, square, triangle),而在 MojectZOrde r中,我们添加它们为 add(triangle, square, circle)

动画

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

from manim import *

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

        # some animations display mobjects, ...
        self.play(FadeIn(square))

        # ... some move or rotate mobjects around...
        self.play(Rotate(square, PI/4))

        # some animations remove mobjects from the screen
        self.play(FadeOut(square))

        self.wait(1)

ManimCE教程(3)Manim 基本构成要素_第6张图片
简单地说,动画是在两个 mojects 之间进行插值的过程。例如,FadeIn(square) 以正方形的完全透明开始,以完全不透明结束,通过逐渐增加不透明度在它们之间进行插值。FadeOut 以相反的方式工作。另一个例子是,Rotate 从作为参数传递给它的移动对象开始,以相同的对象结束,但旋转了一定的量,这一次是插值移动对象的角度,而不是其不透明度。

动画方法

mobject 的任何可以更改的属性都可以设置动画。事实上,通过使用 animate( ),任何更改 mobject 属性的方法都可以用作动画。

from manim import *

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

        # animate the change of color
        self.play(square.animate.set_fill(WHITE))
        self.wait(1)

        # animate the change of position and the rotation at the same time
        self.play(square.animate.shift(UP).rotate(PI / 3))
        self.wait(1)

ManimCE教程(3)Manim 基本构成要素_第7张图片
==animate( )==是所有 mojects 的一个属性,用于为后面的方法设置动画。例如,==square.set_fill(WHITE)==设置正方形的填充颜色,而 ==square.animate.set_filll(WHITE)==则设置此动作的动画。

动画持续时间

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

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)

ManimCE教程(3)Manim 基本构成要素_第8张图片

创建自定义动画

即使 Manim 有许多内置动画,您也会发现有时需要从 Moject 的一种状态平滑地到另一种状态,因此需要自定义动画。首先扩展 Animation 类并覆盖其 interpolate_mobject( )interpolate_mobject( ) 方法接收 alpha 作为一个参数,该参数从0开始,并在整个动画中更改。因此,您只需要根据其 interpolate_mobject 方法中的 alpha 值在 Animation 内部操纵 self.object。然后,您可以获得动画的所有好处,例如在不同的运行时间播放动画或使用不同的速率函数。

假设您从一个数字开始,并希望创建一个将其变换为目标数字的 Transform 动画。您可以使用 FadeTransform 来执行此操作。但是,当我们考虑将一个数字从一个转换为另一个时,一种直观的方法是平稳地递增或递减。Manim 有一个功能,允许您通过定义自己的自定义动画来自定义此行为。

您可以从创建自己的 Count 类开始,该类扩展了 Animation。该类可以有一个带有三个参数的构造函数,即 DecimalNumberMojectstartend。构造函数将 DecimalNumber Moject传递给超级构造函数(在本例中为Animation构造函数),并设置开始和结束。

唯一需要做的就是定义动画每一步的外观。Manim 根据视频的帧速率、速率函数和播放的动画的运行时间,在 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+α*(100-50)。一旦设置了 DecimalNumber 的计算值,就完成了。
定义 Count 动画后,可以在 Scene 中使用任何速率函数为任何 DecimalNumber 播放所需的任何持续时间。

from manim import *

class Count(Animation):
    def __init__(self, number: DecimalNumber, start: float, end: float, **kwargs) -> None:
        # Pass number as the mobject of the animation
        super().__init__(number,  **kwargs)
        # Set start and end
        self.start = start
        self.end = end

    def interpolate_mobject(self, alpha: float) -> None:
        # Set value of DecimalNumber according to alpha
        value = self.start + (alpha * (self.end - self.start))
        self.mobject.set_value(value)


class CountingScene(Scene):
    def construct(self):
        # Create Decimal Number and add it to scene
        number = DecimalNumber().set_color(WHITE).scale(5)
        # Add an updater to keep the DecimalNumber centered as its value changes
        number.add_updater(lambda number: number.move_to(ORIGIN))

        self.add(number)

        self.wait()

        # Play the Count Animation to count from 0 to 100 in 4 seconds
        self.play(Count(number, 0, 100), run_time=4, rate_func=linear)

        self.wait()

使用移动对象的坐标

Mojects 包含定义其边界的点,这些点可以用于将其他 mojects 分别添加到彼此,例如通过get_center( )get_top( )get_start( ) 等方法。

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)

ManimCE教程(3)Manim 基本构成要素_第9张图片

使用移动对象的坐标将移动对象转换为其他移动对象

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))

ManimCE教程(3)Manim 基本构成要素_第10张图片
Transform 函数将上一个 moject 的点映射到下一个 mobject 的点。这可能会导致奇怪的行为,例如,当一个对象的点按顺时针方向排列,而其他点按逆时针方向排列时。在这里,使用翻转功能并通过 numpy 的滚动功能重新定位点可能会有所帮助:

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)

场景

Scene 类是 manim 的连接结构,每个 moject 都必须添加到要显示的场景中,或者从中删除以停止显示。每个动画都必须由一个场景播放,每个没有动画发生的时间间隔都由对 ==wait( )==的调用决定。视频的所有代码都必须包含在从 Scene 派生的类的 ==construct( )==方法中。最后,如果要同时渲染多个场景,则单个文件可能包含多个场景子类。

你可能感兴趣的:(ManimCE教程,python)