前言
在平常的学习、刷视频时,一些科普或算法视频中经常会看到一些非常炫酷的动画效果,例如:
一些复杂数学公式和数学图像:
又或者一些简单的数学推理或物理动画:
还有B站大佬编写的 dijkstra 算法和本文即将带大家实现的冒泡排序的演示动画
甚至还可以实现 3D 效果
更多的案例可以参考Manim 社区
那么类似的动画都是如何实现的那?借助开源库 Manim。
Manim 是一个用于创建精确程序化动画的引擎,特别适用于制作解释性数学视频,借助它,可以实现打造动起来的数学世界和物理世界,小时候枯燥的课本冷冰冰的公式可以变得活能活现,理解不通的算法题让它动起来,形象的告诉背后的原理。
Manim 构建演示视频的功能特别强大,生成的质量也特别高,唯一的缺点 Manim 程序需要借助 python 来进行实现,如果大家从未学过或很少使用 python,会存在一定的门槛。但是也不必慌,当下有 MarsCode、Cursor、Windsurf 等诸多 AI 编程助手,只要会合理的运用 AI 编程助手,任何编程都阻挡不了我们,Manim 也一样。
在本文中:
带领大家使用 MarsCode 编写 Manim 案例,边生成代码,边学习收获
实现一个冒泡排序的演示动画
一起体验一下如何与 MarsCode 进行交互,以及遇到问题又该如何处理
Manim 快速入门
首先带大家使用 MarsCode 编写一下 Manim 官方提供快速入门中的案例,初步的熟悉一下 Manim,同样也建立起对 MarsCode 使用的基本思路。使用编程助手切记要注意的事项:不要太粗暴的描述需求,要尽可能的细致,在最开始的时候,不知道如何写提示词,可以借助豆包啊、gpt 来辅助生成。
快速入门中,案例演示了一个圆的绘制过程,在强调一下,使用 MarsCode 时,不要一次性输入太多信息,尽可能的描述清晰。在这个准则的基础上,输入:帮我绘制一个 pink 颜色,透明度为 0.5 的圆,展示绘制过程,使用 manim 程序进行实现。
可以使用 MarsCode 提供的命令行代码执行,也可以使用 Manim Slideview 插件提供的 run 图标,注意如果使用后者,需要在刚才的代码里面补充 scene 的绘制
from manim import *
class DrawCircle(Scene):
def construct(self):
# 创建一个圆
circle = Circle(color=PINK, fill_opacity=0.5)
# 将圆添加到场景中
self.add(circle)
self.wait(1)
# 需要补充
scene = DrawCircle()
scene.render()
复制代码
上述生成的视频是没有达到要求的,没有体现出绘制过程,继续交互:没有体现出圆的绘制过程。
from manim import *
class DrawCircle(Scene):
def construct(self):
# 创建一个圆
circle = Circle(color=PINK, fill_opacity=0.5)
# 使用Create动画来展示圆的绘制过程
self.play(Create(circle))
self.wait(1)
scene = DrawCircle()
scene.render()
复制代码
实现效果如下:
本着不学白不学的想法,继续想 MarsCode 输入指令:帮我解释一下所写代码。
增加动画的难度,继续交互:增加一些需求,最开始先绘制一个 square,然后变换成 pink 圆,最后圆淡出。
from manim import *
class DrawCircle(Scene):
def construct(self):
# 创建一个正方形
square = Square(color=BLUE, fill_opacity=0.5)
self.add(square)
self.wait(1)
# 将正方形变换成圆
circle = Circle(color=PINK, fill_opacity=0.5)
self.play(Transform(square, circle))
self.wait(1)
# 圆淡出
self.play(FadeOut(circle))
self.wait(1)
scene = DrawCircle()
scene.render()
复制代码
最后的圆是没有淡出,这会是什么问题那,没事,交给 MarsCode 来解决,这里顺便删除掉中间转换的等待:不需要中间的 wait,并且圆没有淡出效果
from manim import *
class DrawCircle(Scene):
def construct(self):
# 创建一个正方形
square = Square(color=BLUE, fill_opacity=0.5)
self.add(square)
# 将正方形变换成圆
circle = Circle(color=PINK, fill_opacity=0.5)
self.play(Transform(square, circle))
# 圆淡出
self.play(FadeOut(circle))
scene = DrawCircle()
scene.render()
复制代码
可以看到,没能成功实现 circle 淡出。一般这种情形下,建议是可以尝试再次和 MarsCode 进行交互,如果依旧未达成需求,一种可能 MarsCode 陷入了自证循环,就得想办法略过这个话题;另一种可能是使用的专业名词有误。
这种情况下可以借助其他的大模型来辅助,当时借助了豆包模型(不小心删除了会话,找不到了),这个问题有点离谱,如果自己独立解决的话,需要一定的 Manim 基础,还好有豆包。
在官方文档中,有这样一段话,大意是:基于 transform 方法进行的变化,其实物体本质没变,只是属性变了。通俗讲,给 MarsCode 的提示词应该是 square 淡出。
from manim import *
class DrawCircle(Scene):
def construct(self):
# 创建一个正方形
square = Square(color=BLUE, fill_opacity=0.5)
self.add(square)
# 将正方形变换成圆
circle = Circle(color=PINK, fill_opacity=0.5)
self.play(Transform(square, circle))
# 正方形淡出
self.play(FadeOut(square))
scene = DrawCircle()
scene.render()
复制代码
比如后续又增加了 square 的旋转操作,类似的一步一步的写 MarsCode 提示词即可,最终获得的代码如下:
from manim import *
class DrawCircle(Scene):
def construct(self):
# 创建一个正方形
square = Square(color=BLUE, fill_opacity=0.5)
# 使用Create动画来展示正方形的绘制过程
self.play(Create(square))
# 旋转正方形
self.play(Rotate(square, angle=PI/4))
# 将正方形变换成圆
circle = Circle(color=PINK, fill_opacity=0.5)
self.play(Transform(square, circle))
# 正方形淡出
self.play(FadeOut(square))
# 圆颜色渐变
self.play(circle.animate.set_color(PINK))
scene = DrawCircle()
scene.render()
复制代码
通过这部分快速入门案例的训练,大致可以总结出下面要点
MarsCode 交互时描述需求要清晰
如果生成存在问题,可以围绕问题继续通过 MarsCode 进行交互,也可以借助其他大模型工具进行辅助
如果在交互的过程中,可能由于提示词给予的不佳,MarsCode 还会记忆上下文,此时就很难再生成有效的内容,我的建议是关掉这个项目,重新打开一个
另外不得不赞叹一下 MarsCode 生成代码的水平还是很高的
冒泡排序演示动画
准备过程
下面咱们来一起做一个复杂案例:冒泡排序演示动画,具体效果上面已经展示过了,冒泡排序演示动画的核心流程:
首先尽可能清晰的输入指令:
from manim import *
import random
class BubbleSort(Scene):
def construct(self):
# 生成随机数组
arr = [random.randint(1, 20) for _ in range(10)]
# 创建矩形并初始化位置
rectangles = []
for i, num in enumerate(arr):
rect = Rectangle(width=0.5, height=num/10)
rect.align_to(RIGHT, DOWN)
rect.shift(LEFT * i)
rectangles.append(rect)
# 将矩形添加到场景中
self.add(*rectangles)
self.wait(1)
# 冒泡排序
for i in range(len(arr)):
for j in range(0, len(arr) - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
rectangles[j].become(rectangles[j + 1])
rectangles[j + 1].become(rectangles[j])
self.wait(0.1)
self.wait(2)
scene = BubbleSort()
scene.render()
复制代码
当时运行出结果后,直接人就有点昏啊,后来总结来看,其实就是需求还是不够清晰,一次性输入了太多的要求,切描述的不够清晰。
没办法,自己生成的代码,再苦也要咽下去,想办法尝试是否有优化空间,于是进行了一系列交互
可以发现,矩形位置是偏移的,一个好的构图,核心内容应该处于中间,反复沟通了多轮,下面可以见证一下破防时刻(这还只是部分,太惨了)
这里的问题其实很明显,因为完全不懂 Manim,使用的提示词非常不专业,导致 Marscode 错误理解,由于记忆性上下文的存在,也就是已经先入为主了,AI 目前没有这种跳出能力,陷入了僵局。
那这时候怎么办呐?理智的想法就是借助其他大模型进行辅助。于是又借助了豆包模型:要确保创建的矩形位于页面中间,你可以在创建矩形后,将它们作为一个整体进行居中对齐。
MarsCode 进行如下修改,实现需求。
# 将矩形添加到场景中
rectangles_group = VGroup(*rectangles)
rectangles_group.arrange(RIGHT, aligned_edge=DOWN)
rectangles_group.move_to(ORIGIN)
self.add(rectangles_group)
self.wait(1)
复制代码
实现到这里,冒泡排序基本工作就完成,下面进入核心的动画部分。
核心流程
元素的交换又踩了一个大坑,顺序进行了下面的交互,结果是一塌糊涂啊。
多次尝试依旧失败,那么解决方案就很单一了,help me,豆包。给豆包描述一下当前场景,豆包给出一个示例代码,喂给 Marscode。
def cyc_move(self,vm1,vm2):
vm1.generate_target()
vm1.target.next_to(vm2,ORIGIN,aligned_edge=DOWN)
vm2.generate_target()
vm2.target.next_to(vm1,ORIGIN,aligned_edge=DOWN)
self.play(MoveToTarget(vm1),MoveToTarget(vm2))
self.wait()
复制代码
冒泡排序的核心功能就实现了,后续又做了一些样式调整,这里就不赘述了,贴一下最后的完整代码。
from manim import *
import random
class BubbleSort(Scene):
def construct(self):
# 生成随机数组
arr = [random.randint(1, 20) for _ in range(10)]
# 创建矩形并初始化位置
rectangles = []
colors = [BLUE, GREEN, RED, YELLOW, PURPLE] # 定义一个颜色列表
for i, num in enumerate(arr):
# 为每个矩形赋予一个唯一的颜色
color = colors[i % len(colors)] # 循环使用颜色列表中的颜色
rect = Rectangle(width=1, height=num/5, fill_color=color, fill_opacity=0.8)
rect.align_to(RIGHT, DOWN)
rect.shift(LEFT * i)
# 将数组元素的值渲染到矩形的中心
text = Text(str(num), font_size=24, color=WHITE)
text.next_to(rect, DOWN, buff=0.1)
rectangles.append(VGroup(rect, text))
# 将矩形添加到场景中
rectangles_group = VGroup(*rectangles)
rectangles_group.arrange(RIGHT, aligned_edge=DOWN)
rectangles_group.move_to(ORIGIN)
self.add(rectangles_group)
self.wait(1)
# 冒泡排序
for i in range(len(arr)):
for j in range(0, len(arr) - i - 1):
# 对正在比较的元素添加放大缩小的呼吸效果
self.play(ApplyMethod(rectangles[j].scale, 1.2), ApplyMethod(rectangles[j + 1].scale, 1.2), run_time=0.3)
self.play(ApplyMethod(rectangles[j].scale, 1/1.2), ApplyMethod(rectangles[j + 1].scale, 1/1.2), run_time=0.3)
if arr[j] > arr[j + 1]:
self.wait(0.2)
arr[j], arr[j + 1] = arr[j + 1], arr[j]
# 交换矩形的位置,但不改变它们的颜色
rectangles[j], rectangles[j + 1] = rectangles[j + 1], rectangles[j]
self.wait(0.1)
# 交换矩形的显示位置,并保持在同一水平线上
self.cyc_move(rectangles[j], rectangles[j + 1])
self.wait(2)
def cyc_move(self, vm1, vm2):
vm1.generate_target()
vm1.target.next_to(vm2, ORIGIN, aligned_edge=DOWN)
vm2.generate_target()
vm2.target.next_to(vm1, ORIGIN, aligned_edge=DOWN)
# 在移动过程中不再添加呼吸效果
self.play(MoveToTarget(vm1), MoveToTarget(vm2), run_time=0.2)
# 这里不需要传入 Scene 实例
scene = BubbleSort()
scene.render()
复制代码
选择排序与冒泡排序有一定的相似性,于是做一个尝试,是否此时可以顺遂的完成:根据上述的思路,你能为我写一个选择排序的演示动画吗
整体实现其实是对的,下面的动画看起来有点怪,因为当时在冒泡排序的时候,约定了比较元素呼吸的顺序,微调一下即可。
接着又尝试了一下插入排序,插入排序思想与冒泡差距很大,没有办法推理得出,得出的演示动画非常不符合预期。总体 MarsCode 的生成过程还是非常让人满意的,毕竟光给一句笼统的实现插入排序提示词有些过分简易。
总结
本文借助 MarsCode 生成 Manim 程序,完成了快速入门案例和冒泡/选择排序演示动画,文章完整的展示整体使用 Marscode 的一些方法和过程,有一些心得,下面粗略的总结一下:
MarsCode 生成代码的质量是可以保证的,Manim 这种方式不算大众,能生成感觉已经很强了,通过一些提示词的引导,能完成较为不错的效果,点赞好评
对于存在一定专业性的程序,例如 Manim 这种,还是需要一些上下文知识,否则写出的提示词容易比较业余,从而导致 MarsCode 的误解
输入 MarsCode 提示词时,要清晰的描述需求,尽可能不要一次给予太多指令。比如最开始尝试:帮我生成一个冒泡排序的 Manim 程序,效果那就是一团糟
学会借助其他大模型的力量,AGI 是一系列产品,要把它们连结起来,共同使用
如果 MarsCode 持续进行交互,效果依旧不好,关闭该项目,重启一个
借助 MarsCode,可以降低程序的实现难度,下一个想要尝试使用 MarsCode + 豆包实现一个小米 su7 的展示,使用 Threejs 知识实现,敬请期待。
评论