Manim的积木
本文档解释了manim的组成部分,并将为您提供开始制作自己的视频所需的所有工具。
基本上,manim提供了三个不同的概念供您使用,您可以将它们编排在一起以生成数学动画:数学对象(简称mobject)、动画和场景。正如我们将在以下几节中看到的,这三个概念中的每一个都作为一个单独的类在manim中实现:Mobject
、Animation
和Scene
类。
笔记
在阅读本页之前,建议您阅读教程Quickstart和Manim的输出设置。
移动对象,Mobjects
mobject是所有manim动画的基本构建块。从Mobject
派生的每个类表示一个可以在屏幕上显示的对象。例如,圆Circle
、箭头Arrow
和矩形Rectangle
等简单形状都是mobject。更复杂的结构,如轴Axes
、函数图FunctionGraph
或条形图BarChart
也是mobject。
如果试图在屏幕上显示Mobject
的实例,则只能看到一个空帧。原因是Mobject
类是所有其他Mobject的抽象基类,即它没有任何可以在屏幕上显示的预定义视觉形状。它只是一个可以展示的东西的骨架。因此,您很少需要使用Mobject
的普通实例;相反,您很可能会创建其派生类的实例。其中一个派生类是VMobject
。V代表矢量化Mobject。本质上,vmobject是使用矢量图形vector graphics
进行显示的mobject。大多数时候,您将处理vmobjects,尽管我们将继续使用术语“mobject”来指代可以在屏幕上显示的形状类别,因为它更为通用。
笔记
任何可以在屏幕上显示的对象都是mobject
,即使它不一定是数学性质的。
提示
要查看从Mobject
派生的类的示例,请参阅几何体geometry
模块。其中大多数事实上也是从VMobject派生的。
创建和显示移动对象
如Quickstart中所述,通常manim脚本中的所有代码都放在场景类的construct()
方法中。要在屏幕上显示mobject,请调用包含场景Scene
的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)
放置移动对象
让我们定义一个名为Shapes的新场景Scene
,并在其中添加add()
一些mobject。该脚本生成一个静态图片,其中显示一个圆、一个正方形和一个三角形:
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)
默认情况下,首次创建移动对象时,将其放置在坐标中心或原点。它们还提供了一些默认颜色。此外,形状场景使用shift()
方法放置移动对象。正方形从原点向上移动一个单位,而圆和三角形分别左移LEFT
和右移RIGHT
一个单位。
注意
与其他图形软件不同,manim将坐标中心放置在屏幕的中心。垂直正方向向上,水平正方向向右。另请参阅常数constants
模块中定义的常数原点、上UP
、下DOWN
、左LEFT
、右RIGHT
和其他。
有许多其他可能的方法可以将mobject放置在屏幕上,例如move_to()
、next_to()
和align_to()
。下一个场景MobjectPlacement
使用了这三种方法。
from manim import *
class MobjectPlacement(Scene):
def construct(self):
circle = Circle()
square = Square()
triangle = Triangle()
# place the circle two units left from the origin
circle.move_to(LEFT * 2)
# place the square to the left of the circle
square.next_to(circle, LEFT)
# align the left border of the triangle to the left border of the circle
triangle.align_to(circle, LEFT)
self.add(circle, square, triangle)
self.wait(1)
move_to()
方法使用绝对单位(相对于原点测量),而next_to()
使用相对单位(从作为第一个参数传递的mobject测量)。align_to()
不使用LEFT
作为测量单位,而是作为确定用于对齐的边界的方法。移动对象边界的坐标是使用其周围的假想边界框确定的。
提示
manim中的许多方法可以链接在一起。例如,两条线square = Square() square.shift(LEFT)
可以替换为
square = Square().shift(LEFT)
从技术上讲,这是可能的,因为大多数方法调用返回修改后的mobject。
设置移动对象的样式,Styling 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)
此场景使用两个主要函数来更改移动对象的视觉样式:set_stroke()
和set_fill()
。前者改变了主体边界的视觉风格,而后者则改变了室内的风格。默认情况下,大多数移动对象具有完全透明的内部,因此必须指定不透明度参数以显示颜色。不透明度为1.0
表示完全不透明,而0.0
表示完全透明。
只有VMobject
的实例实现set_stroke()
和set_fill()
。Mobject
的实例实现set_color()
。绝大多数预定义类都是从VMobject
派生的,因此通常可以安全地假设您可以访问set_stroke()
和set_fill()
。
移动对象屏幕顺序
下一个场景与上一节中的MobjectStyleing
场景完全相同,只是只有一行。
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)
这里唯一的区别(除了场景名称)是将移动对象添加到场景的顺序。在MobjectStyleing
中,我们将它们添加为add(circle, square, triangle)
,而在MobjectZorder
中,我们添加它们为add(triangle, square, circle)
。
如您所见,add()
参数的顺序决定了mobject在屏幕上的显示顺序,最左边的参数放在后面。
动画,Animations
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)
简而言之,动画是在两个运动对象之间插值的过程。例如,FadeIn(square)
以完全透明的square
版本开始,以完全不透明的版本结束,通过逐渐增加不透明度在它们之间进行插值。淡出FadeOut
的工作方式相反:它从完全不透明插值到完全透明。另一个例子是,Rotate
从作为参数传递给它的mobject开始,以相同的对象结束,但旋转了一定量,这次是插值mobject的角度,而不是其不透明度。
动画制作方法,Animating methods
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)
参考:Animation
·animate()·是所有mobjects的属性,用于为后面的方法设置动画。例如,square.set_fill(WHITE)
设置正方形的填充颜色,而square.animate.set_fill(WHITE)
设置此动作的动画。
动画运行时,Animation run time
默认情况下,传递给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)
创建自定义动画,Creating a custom animation
尽管Manim有许多内置动画,但有时需要从一个Mobject
的一个状态平滑地设置到另一个状态。如果您发现自己处于这种情况,则可以定义自己的自定义动画。首先扩展Animation
类并覆盖其interpolate_mobject()
。interpolate_mobject()
方法将alpha作为参数接收,该参数从0开始,并在整个动画中更改。所以,你只需要操纵自己。根据其interpolate_mobject()
方法中的alpha值,在动画内部移动对象。然后,您可以获得动画Animation
的所有好处,例如为不同的运行时间播放动画或使用不同的速率函数。
假设您从一个数字开始,并希望创建将其转换Transform
为目标数字的变换动画。您可以使用FadeTransform
进行转换,它将淡出起始编号,淡入目标编号。但是,当我们考虑将一个数字转换为另一个数字时,一种直观的方法是平滑地增加或减少它。Manim有一个功能,允许您通过定义自己的自定义动画来自定义此行为。
您可以先创建自己的Count
类来扩展动画Animation
。该类可以有一个具有三个参数的构造函数,一个小数DecimalNumber
Mobject、start和end。构造函数将小数DecimalNumber
Mobject传递给超级构造函数(在本例中为动画构造函数),并设置start和end。
你需要做的唯一一件事就是定义你希望它在动画的每一步中的表现。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+alpha*(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()
参考:
Animation
DecimalNumber
interpolate_mobject()
play()
使用移动对象的坐标,Using coordinates of a mobject
移动对象包含定义其边界的点。这些点可以分别用于将其他mobject相互添加,例如通过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)
将移动对象转换为其他移动对象,Transforming mobjects into other mobjects
也可以将一个mobject转换为另一个mob,如下所示:
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))
变换函数将前一个mobject的点映射到下一个mob的点。这可能会导致奇怪的行为,例如,当一个对象的点按顺时针方向排列,而其他点按逆时针方向排列时。在这里,使用翻转功能并通过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)
场景,Scenes
场景Scene
类是manim的结缔组织。每个移动对象都必须添加added
到要显示的场景中,或者从中删除removed
以停止显示。每个动画都必须由一个场景播放played
,并且没有动画发生的每个时间间隔都由对wait()
的调用决定。视频的所有代码都必须包含在从场景派生的类的construct()
方法中。最后,如果要同时渲染多个场景Scene
,则单个文件可能包含多个场景Scene
子类。
前一篇:
Manim’s Output Settings
下一篇:
Thematic Guides