Manim 数学动画三大基石Mobject、Animation 与 Scene 完全指南【免费下载链接】manimA community-maintained Python framework for creating mathematical animations.项目地址: https://gitcode.com/GitHub_Trending/man/manim本指南围绕 ManimCommunity 版官方教程 building_blocks.rst 展开系统讲解制作数学动画必须掌握的三大核心概念数学对象Mobject、动画Animation与场景Scene。读完本文你将掌握如何创建与摆放 mobject、为其设置样式与图层顺序、用内置动画或自定义动画驱动变化并理解Scene.construct中编排这一切的完整流程从而独立产出自己的数学动画视频。建议在阅读本文前先完成 quickstart 与 output_and_config 两个教程以便熟悉最基本的渲染命令与配置方式。三大核心概念一览Manim 将制作数学动画的能力抽象为三个可协同编排的概念每个概念都对应一个独立的类概念类职责数学对象mobjectMobject屏幕上可显示的对象如圆形、箭头、矩形乃至坐标系、函数图像、柱状图动画AnimationAnimation在两类状态之间进行插值的过程如淡入、旋转、变换场景SceneScene编排的“连接组织”负责显示/移除 mobject、播放动画、控制无动画的时间间隔三者对应的实现分别位于 manim/mobject/mobject.py、manim/animation/animation.py 与 manim/scene/scene.py下文将结合源码逐一展开。Mobjects一切可视对象的基础mobject是所有 Manim 动画的基本构件。任何派生自Mobject的类都代表一个可以显示在屏幕上的对象简单形状如Circle、Arrow、Rectangle是 mobject复杂的构造如Axes、FunctionGraph、BarChart同样是 mobject。需要特别说明直接实例化Mobject显示在屏幕上只会得到一个空白帧——因为Mobject是所有 mobject 的抽象基类本身没有预定的可视形状只是“可以被显示的东西”的骨架因此实际开发中几乎不会直接使用裸的Mobject实例。真正常用的是它的派生类VMobjectV 代表 Vectorized矢量化的 mobject它使用矢量图形进行显示。绝大多数时候你处理的都是 vmobject但文档与社区习惯仍统称为 “mobject”。从源码结构看manim/mobject/geometry 目录下的形状类大多继承自VMobject。注意任何能显示在屏幕上的对象都是 “mobject”即便它并不具有数学性质。创建与显示 mobject与 quickstart 中介绍的一致Manim 脚本的核心代码都写在Scene派生类的construct方法中。显示一个 mobject 需要调用所在场景的add方法移除则调用removeclass CreatingMobjects(Scene): def construct(self): circle Circle() self.add(circle) self.wait(1) self.remove(circle) self.wait(1)从源码看Scene.add 会将传入的 mobject 追加进场景的mobjects列表Scene.remove 则将其从列表中移除add的 docstring 明确说明“mobjects 将按添加顺序从背景到前景显示”。放置 mobject坐标系统与定位方法默认情况下mobject 在创建时位于坐标原点并被赋予默认颜色。下面的Shapes场景用shift方法摆放三个图形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)其中正方形相对原点向UP方向移动一个单位圆形与三角形分别向LEFT和RIGHT移动一个单位。注意与其他图形软件不同Manim 将坐标原点放在屏幕正中心正竖直方向为上UP正水平方向为右RIGHT。方向常量ORIGIN、UP、DOWN、LEFT、RIGHT等均定义在 manim/constants.py例如UP np.array((0.0, 1.0, 0.0))、ORIGIN np.array((0.0, 0.0, 0.0))。除了shiftManim 还提供move_to、next_to、align_to等放置方法。下面的MobjectPlacement场景用到了全部三种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)三者语义的差异是关键move_to使用绝对单位相对ORIGIN度量next_to使用相对单位以第一个参数传入的 mobject 为基准度量align_to中的LEFT不是度量单位而是指定要对齐的边界。mobject 的边界坐标通过其外围的虚拟包围盒bounding box确定。提示Manim 中很多方法支持链式调用。例如square Square()加square.shift(LEFT)两行可合并为square Square().shift(LEFT)。这之所以可行是因为大多数方法调用会返回被修改后的 mobject 本身源码中这些方法均以return self结尾例如 Mobject.shift。样式设置set_stroke 与 set_fillMobjectStyling场景演示了如何改变 mobject 的默认外观class MobjectStyling(Scene): def construct(self): circle Circle().shift(LEFT) square Square().shift(UP) triangle Triangle().shift(RIGHT) circle.set_stroke(colorGREEN, width20) square.set_fill(YELLOW, opacity1.0) triangle.set_fill(PINK, opacity0.5) self.add(circle, square, triangle) self.wait(1)这里用到两个最核心的样式方法set_stroke修改 mobject 的边框样式。从 VMobject.set_stroke 的源码可见它支持color、width、opacity、background、family等参数最终写入stroke_rgbas、stroke_width、stroke_opacity等属性set_fill修改 mobject 的内部填充样式参数为color与opacityVMobject.set_fill写入fill_rgbas与fill_opacity。默认情况下大多数 mobject 的内部是完全透明的因此必须显式指定opacity才能显示填充色1.0表示完全不透明0.0表示完全透明。此外set_stroke与set_fill只在VMobject及其派生类上实现裸Mobject实现的是set_colorMobject.set_color。由于绝大多数预定义类都派生自VMobject通常可以放心使用set_stroke与set_fill。屏幕图层顺序Z 轴顺序MobjectZOrder场景与MobjectStyling几乎完全相同唯一区别在于add的参数顺序class MobjectZOrder(Scene): def construct(self): circle Circle().shift(LEFT) square Square().shift(UP) triangle Triangle().shift(RIGHT) circle.set_stroke(colorGREEN, width20) square.set_fill(YELLOW, opacity1.0) triangle.set_fill(PINK, opacity0.5) self.add(triangle, square, circle) self.wait(1)可以看到add的参数顺序决定了对象在屏幕上的显示顺序最左侧的参数位于最底层背景。MobjectStyling中add(circle, square, triangle)将圆放在最底而MobjectZOrder中add(triangle, square, circle)将三角形放在最底。这也与 Scene.add 的 docstring 相互印证。Animations动画的本质是插值动画是 Manim 的核心。将动画加入场景统一通过Scene.play方法完成Scene.play内部将参数转交渲染管理器处理class SomeAnimations(Scene): def construct(self): square Square() # 一些动画负责显示 mobject…… self.play(FadeIn(square)) # ……一些动画移动或旋转 mobject…… self.play(Rotate(square, PI/4)) # 一些动画将 mobject 移出屏幕 self.play(FadeOut(square)) self.wait(1)简单来说动画是在两个 mobject 状态之间进行插值的过程。例如FadeIn(square)从square的完全透明版本开始、到完全不透明版本结束通过逐渐增大透明度完成插值FadeOut恰好相反从完全不透明插值到完全透明。Rotate则从传入的 mobject 开始以同一对象旋转一定角度结束——这次插值的是角度而非透明度。用 .animate 动画化任意方法任何能改变 mobject 属性的方法都可以被动画化途径是animate属性class AnimateExample(Scene): def construct(self): square Square().set_fill(RED, opacity1.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)animate是所有 mobject 的一个属性实现见 Mobject.animate它会将后面调用的方法包装成动画square.set_fill(WHITE)直接设置填充色而square.animate.set_fill(WHITE)则把这一动作变成动画。多个方法可通过链式调用合并在一个动画中如square.animate.shift(UP).rotate(PI / 3)同时完成位移与旋转。源码提示animate的 docstring 特别提醒——对同一个mobject不建议在同一次play中传入多个独立动画例如self.play(my_mobject.animate.shift(RIGHT), my_mobject.animate.rotate(PI))这很可能无法正常工作应改用方法链。同时play的关键字参数如rate_func、run_time也可直接放在.animate(...)中例如mobject1.animate(run_time2).rotate(PI)。控制动画时长run_time默认情况下传给play的任意动画恰好持续 1 秒。可以通过run_time参数控制时长class RunTime(Scene): def construct(self): square Square() self.add(square) self.play(square.animate.shift(UP), run_time3) self.wait(1)上述场景中正方形向上移动的动画被拉长为 3 秒。创建自定义动画继承 Animation 并实现 interpolate_mobject虽然 Manim 内置了大量动画但当你需要将某个 mobject 从一种状态平滑地动画到另一种状态、而内置动画无法满足时可以定义自己的动画。做法是继承Animation类并重写interpolate_mobject方法。interpolate_mobject接收一个alpha参数它从 0 开始、随动画进程不断变化。你只需要在方法内根据alpha值操纵self.mobject即可之后便能免费获得Animation的全部能力——例如为动画指定不同run_time或使用不同rate_func速率函数。从源码看Animation.interpolate 每帧被调用一次并转调interpolate_mobjectAnimation.interpolate_mobject它进一步通过get_sub_alpha将 alpha 按lag_ratio分配给各子对象。案例自定义数字计数动画 Count假设你想把某个数字平滑地从起点递增到终点。FadeTransform只能淡出起始数字、淡入目标数字而更直观的计数方式是平滑地逐数递增。为此可以自定义一个Count动画动画逻辑设计alpha的取值范围为 01代表当前动画进度0 为开始、0.5 为中途、1 为结束。假设从 50 数到 100alpha 0 时数值应为 50alpha 0.5 时数值应为 75alpha 1 时数值应为 100。通式即为50 alpha * (100 - 50)从起始值出发按 alpha 比例加上待递增的部分。完整代码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 (self.rate_func(alpha) * (self.end - self.start)) self.mobject.set_value(value) class CountingScene(Scene): def construct(self): # 创建 DecimalNumber 并加入场景 number DecimalNumber().set_color(WHITE).scale(5) # 添加 updater让数值变化时始终保持在原点居中 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_time4, rate_funclinear) self.wait()要点说明Count的构造函数接收一个DecimalNumbermobject、start与end三个参数将DecimalNumber传给父类Animation构造器并保存起止值唯一的自定义逻辑在interpolate_mobject中按alpha计算当前值再调用 DecimalNumber.set_value 写入数值通过number.add_updater(...)添加更新器保证数字位数变化导致宽度改变时仍居中于原点self.play(Count(number, 0, 100), run_time4, rate_funclinear)展示自定义动画的可复用性任意时长、任意DecimalNumber、任意速率函数都可直接套用。注意自定义动画若想使用rate_func必须显式将self.rate_func(alpha)应用到正在动画的参数上如上面代码中的self.rate_func(alpha) * (self.end - self.start)。可以尝试把速率函数换成rate_functions.there_and_back观察计数行为如何随之改变先增后减、再回到起点。获取 mobject 的坐标mobject 包含定义其边界的点。这些点可用来让其他 mobject 相对彼此摆放例如get_center、get_top、get_start等方法相关实现见 Mobject.get_center、Mobject.get_start、Mobject.point_from_proportion。下面是一个展示关键坐标的完整示例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(fa.get_start() {np.round(point_start,2).tolist()}, font_size24).to_edge(UR).set_color(YELLOW)) self.add(Text(fa.get_end() {np.round(point_end,2).tolist()}, font_size24).next_to(self.mobjects[-1],DOWN).set_color(RED)) self.add(Text(fa.get_center() {np.round(point_center,2).tolist()}, font_size24).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、get_top、get_bottom、point_from_proportion(0.5)等获取关键点并以不同颜色的Dot标记最后把a.points中每个原始点也画成小圆点直观展示 mobject 内部点集与边界坐标的关系。其中point_from_proportion(alpha)返回沿 mobject 路径按比例alpha01处的点0.5即路径中点。将 mobject 变换为其他 mobjectmobject 之间可以互相变换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 的点排列方向不一致例如一个顺时针、一个逆时针时变换可能出现怪异表现。此时可以借助flip函数或使用 numpy 的roll重新排列点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), axis0) m2a.points points self.play(Transform(m1a,m1b),Transform(m2a,m2b), run_time1)这里通过np.roll将m2a的点沿第 0 轴滚动四分之一长度使左右两组的点排列方向一致从而让两组变换的旋转方向协调统一。Scenes一切编排的连接组织Scene类是 Manim 的“连接组织”connective tissue每个 mobject 必须被add到场景中才会显示remove后停止显示每个动画必须由场景play出来每个没有动画的时间间隔由wait决定视频的所有代码都必须写在派生自Scene的类的construct方法中。construct在 Scene.construct 中定义为空实现pass等待子类覆写其 docstring 同时建议初始化代码放setup收尾代码放tear_down。wait的默认时长为DEFAULT_WAIT_TIME定义于 manim/constants.py值为 1.0 秒Scene.wait 内部实际上是播放一个Wait动画还支持stop_condition与frozen_frame等进阶参数。一个典型的 Manim 脚本结构如下class MyScene(Scene): def construct(self): self.play(Write(Text(Hello World!)))此外单个文件中可以包含多个Scene子类以便在同一次渲染中同时渲染多个场景。渲染时只需在命令行指定场景类名例如manim render -p 文件名.py MyScene具体参数与输出配置可参考 output_and_config。小结从三大基石到完整动画将三个概念串联起来一条完整的 Manim 工作流即创建 mobject实例化VMobject体系下的形状Circle、Square、Triangle、Line、DecimalNumber等摆放与美化用shift/move_to/next_to/align_to定位用set_stroke/set_fill/set_color设置样式编排动画用play 内置动画FadeIn、FadeOut、Rotate、Transform或mobject.animate.xxx()驱动变化必要时自定义Animation子类控制节奏用run_time控制动画时长用wait填补停顿用rate_func调整缓动组织场景在Scene.construct中按顺序编排上述全部动作一个文件可承载多个场景。每一步的核心实现都能在 manim/mobject/mobject.py、manim/mobject/types/vectorized_mobject.py、manim/animation/animation.py、manim/scene/scene.py 与 manim/constants.py 中找到对应的类与方法。掌握这三个基石你就拥有了制作数学动画所需的所有工具。【免费下载链接】manimA community-maintained Python framework for creating mathematical animations.项目地址: https://gitcode.com/GitHub_Trending/man/manim创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考