写点什么

【万字长文】MarsCode 结合 Manim 打造炫酷的算法演示动画

作者:战场小包
  • 2024-12-18
    湖北
  • 本文字数:6112 字

    阅读完需:约 20 分钟

【万字长文】MarsCode结合 Manim 打造炫酷的算法演示动画

前言

在平常的学习、刷视频时,一些科普或算法视频中经常会看到一些非常炫酷的动画效果,例如:


一些复杂数学公式和数学图像:



又或者一些简单的数学推理或物理动画:




还有B站大佬编写的 dijkstra 算法和本文即将带大家实现的冒泡排序的演示动画




甚至还可以实现 3D 效果



更多的案例可以参考Manim 社区


那么类似的动画都是如何实现的那?借助开源库 Manim


Manim 是一个用于创建精确程序化动画的引擎,特别适用于制作解释性数学视频,借助它,可以实现打造动起来的数学世界和物理世界,小时候枯燥的课本冷冰冰的公式可以变得活能活现,理解不通的算法题让它动起来,形象的告诉背后的原理。


Manim 构建演示视频的功能特别强大,生成的质量也特别高,唯一的缺点 Manim 程序需要借助 python 来进行实现,如果大家从未学过或很少使用 python,会存在一定的门槛。但是也不必慌,当下有 MarsCode、Cursor、Windsurf 等诸多 AI 编程助手,只要会合理的运用 AI 编程助手,任何编程都阻挡不了我们,Manim 也一样。


在本文中:


  1. 带领大家使用 MarsCode 编写 Manim 案例,边生成代码,边学习收获

  2. 实现一个冒泡排序的演示动画

  3. 一起体验一下如何与 MarsCode 进行交互,以及遇到问题又该如何处理

Manim 快速入门

  • Manim 的具体下载文章中就不赘述了,网上有各种各样的教程,推荐参考官网的文档。如果大家使用 VSCode 进行开发,推荐安装 Manim Slideview 插件。

  • MarsCode 大家应该也都不陌生了,是一款依托于豆包大模型的智能编程助手,可以实现代码的生成、补全、错误检测等,目前有插件和 IDE 两种使用方式,本文是通过 VScode 中 MarsCode 插件实现的。



首先带大家使用 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()
复制代码


当时运行出结果后,直接人就有点昏啊,后来总结来看,其实就是需求还是不够清晰,一次性输入了太多的要求,切描述的不够清晰。



没办法,自己生成的代码,再苦也要咽下去,想办法尝试是否有优化空间,于是进行了一系列交互


  • 生成的矩形没有颜色且没有元素渲染到上面



  • 降低为 5 个元素



可以发现,矩形位置是偏移的,一个好的构图,核心内容应该处于中间,反复沟通了多轮,下面可以见证一下破防时刻(这还只是部分,太惨了)


这里的问题其实很明显,因为完全不懂 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 知识实现,敬请期待。

发布于: 刚刚阅读数: 5
用户头像

战场小包

关注

成长中的小前端,一起努力,一起进步 2021-09-23 加入

掘金签约作者、InfoQ签约作者、阿里云社区签约作者。公众号: 小包学前端

评论

发布
暂无评论
【万字长文】MarsCode结合 Manim 打造炫酷的算法演示动画_Python_战场小包_InfoQ写作社区