如何在Manim中实现不同动画速度同时播放的复杂动画效果?
- 内容介绍
- 相关推荐
本文共计778个文字,预计阅读时间需要4分钟。
原文:
在 Manim 中,self.play() 默认支持多动画同步启动,但所有动画共享同一时间轴——若直接传入多个 Transform 并设置不同 run_time(如原示例中 transform_point1.run_time=1 与 transform_point2.run_time=2),Manim 会忽略单个动画的 run_time,统一采用最长的 run_time 或报错,导致预期失效。
✅ 正确做法是:使用 .animate 链式语法为每个动画单独指定 run_time。例如:
from manim import * class TwoDotsWithDifferentSpeeds(Scene): def construct(self): point1 = Dot(point=[-3, 0, 0], radius=0.5, color=BLUE) point2 = Dot(point=[3, 0, 0], radius=0.5, color=RED) self.add(point1, point2) # 同时缩放,但 point1 耗时 1s,point2 耗时 2s self.play( point1.animate(run_time=1).scale(1.8), point2.animate(run_time=2).scale(0.3), )
⚠️ 注意:point1.animate(...) 返回的是一个 Animation 对象,而非 Mobject;因此不能在循环中反复调用 point1.animate.scale(...) 并累积到 VGroup——这会导致 animate 被多次触发而引发异常。self.play() 中每个 .animate 调用必须作用于当前帧下的原始 Mobject 状态。
? 对于周期性、持续变化的异步动画(如两个点以不同频率反复缩放),静态序列动画(self.play 多次调用)难以维护节奏,此时推荐使用 UpdateFromFunc ——它通过每帧回调函数实时更新属性,完全绕过 run_time 限制,实现真正独立的时序控制。
以下是一个专业示例:让 point1 每 1 秒完成一次缩放周期(快),point2 每 3 秒完成一次(慢),二者严格并行运行:
from manim import * import numpy as np class PeriodicAsyncScaling(Scene): def construct(self): point1 = Dot([-3, 0, 0], radius=0.5, color=BLUE) point2 = Dot([3, 0, 0], radius=0.5, color=RED) self.add(point1, point2) # 定义周期缩放函数:t 为动画总运行时间(秒) def update_scale_fast(mob, dt): t = self.renderer.time # 获取全局已运行时间(需确保场景已开始渲染) scale = 0.7 + 0.6 * np.sin(2 * np.pi * t / 1.0) # 周期=1s mob.set_width(2 * scale * 0.5).set_height(2 * scale * 0.5) def update_scale_slow(mob, dt): t = self.renderer.time scale = 0.7 + 0.6 * np.sin(2 * np.pi * t / 3.0) # 周期=3s mob.set_width(2 * scale * 0.5).set_height(2 * scale * 0.5) point1.add_updater(update_scale_fast) point2.add_updater(update_scale_slow) # 播放 6 秒,观察异步周期行为 self.wait(6.0) point1.clear_updaters() point2.clear_updaters()
? 关键要点总结:
- ✅ 基础并行变速:用 mobj.animate(run_time=T).method(),每个动画独立设速;
- ❌ 避免在 self.play() 中混用 animate 与手动构造的 Animation 实例(如 Transform);
- ✅ 周期/持续动画:优先选用 add_updater() + UpdateFromFunc 或自定义 updater 函数,通过 self.renderer.time 获取全局时间戳实现精准相位控制;
- ⚠️ 使用 updater 时,务必在结束前调用 clear_updaters(),防止后续操作意外触发。
掌握这两种范式,即可灵活驾驭 Manim 中任意复杂度的多轨异步动画编排。
本文共计778个文字,预计阅读时间需要4分钟。
原文:
在 Manim 中,self.play() 默认支持多动画同步启动,但所有动画共享同一时间轴——若直接传入多个 Transform 并设置不同 run_time(如原示例中 transform_point1.run_time=1 与 transform_point2.run_time=2),Manim 会忽略单个动画的 run_time,统一采用最长的 run_time 或报错,导致预期失效。
✅ 正确做法是:使用 .animate 链式语法为每个动画单独指定 run_time。例如:
from manim import * class TwoDotsWithDifferentSpeeds(Scene): def construct(self): point1 = Dot(point=[-3, 0, 0], radius=0.5, color=BLUE) point2 = Dot(point=[3, 0, 0], radius=0.5, color=RED) self.add(point1, point2) # 同时缩放,但 point1 耗时 1s,point2 耗时 2s self.play( point1.animate(run_time=1).scale(1.8), point2.animate(run_time=2).scale(0.3), )
⚠️ 注意:point1.animate(...) 返回的是一个 Animation 对象,而非 Mobject;因此不能在循环中反复调用 point1.animate.scale(...) 并累积到 VGroup——这会导致 animate 被多次触发而引发异常。self.play() 中每个 .animate 调用必须作用于当前帧下的原始 Mobject 状态。
? 对于周期性、持续变化的异步动画(如两个点以不同频率反复缩放),静态序列动画(self.play 多次调用)难以维护节奏,此时推荐使用 UpdateFromFunc ——它通过每帧回调函数实时更新属性,完全绕过 run_time 限制,实现真正独立的时序控制。
以下是一个专业示例:让 point1 每 1 秒完成一次缩放周期(快),point2 每 3 秒完成一次(慢),二者严格并行运行:
from manim import * import numpy as np class PeriodicAsyncScaling(Scene): def construct(self): point1 = Dot([-3, 0, 0], radius=0.5, color=BLUE) point2 = Dot([3, 0, 0], radius=0.5, color=RED) self.add(point1, point2) # 定义周期缩放函数:t 为动画总运行时间(秒) def update_scale_fast(mob, dt): t = self.renderer.time # 获取全局已运行时间(需确保场景已开始渲染) scale = 0.7 + 0.6 * np.sin(2 * np.pi * t / 1.0) # 周期=1s mob.set_width(2 * scale * 0.5).set_height(2 * scale * 0.5) def update_scale_slow(mob, dt): t = self.renderer.time scale = 0.7 + 0.6 * np.sin(2 * np.pi * t / 3.0) # 周期=3s mob.set_width(2 * scale * 0.5).set_height(2 * scale * 0.5) point1.add_updater(update_scale_fast) point2.add_updater(update_scale_slow) # 播放 6 秒,观察异步周期行为 self.wait(6.0) point1.clear_updaters() point2.clear_updaters()
? 关键要点总结:
- ✅ 基础并行变速:用 mobj.animate(run_time=T).method(),每个动画独立设速;
- ❌ 避免在 self.play() 中混用 animate 与手动构造的 Animation 实例(如 Transform);
- ✅ 周期/持续动画:优先选用 add_updater() + UpdateFromFunc 或自定义 updater 函数,通过 self.renderer.time 获取全局时间戳实现精准相位控制;
- ⚠️ 使用 updater 时,务必在结束前调用 clear_updaters(),防止后续操作意外触发。
掌握这两种范式,即可灵活驾驭 Manim 中任意复杂度的多轨异步动画编排。

