边学边做Manim中动点沿路径运动

在Manim中,ValueTracker 是一种用于动态追踪和更新对象位置或属性的强大工具,一般情况下,我们可以用它来做一个动点的动态效果,例如把某个函数图像当作运动路径,在这个函数图像上做路径运动,先来看下面的动态效果:

以下是实现上面动画效果的代码,我们可以仔细的分析其中的过程,先从简单的代码说起。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from manim import *

class DotOnGraph(Scene):
def construct(self):
# 定义一个坐标系
axes = Axes()
self.play(Create(axes))
self.wait(2)

# 定义一个二次函数
graph = axes.plot(lambda x: x**2-3, color=BLUE)
self.play(Create(graph))
self.wait(2)

# 定义一个随 x 改变而移动的 Dot
x = ValueTracker(-2.5)
dot = always_redraw(lambda: Dot().move_to(axes.c2p(x.get_value(), x.get_value()**2-3)))
self.play(Create(dot))
self.wait(2)

self.play(x.animate.set_value(2), run_time=4)
self.wait()

创建一个简陋的坐标系,在这里我没有对坐标系进行过多的美化,毕竟是做一个学习测试,代码如下

1
2
3
4
# 定义一个坐标系   
axes = Axes()
self.play(Create(axes))
self.wait(2)

然后是定义一个二次函数图像,用来作为动点运动轨迹,没有太多值得分析的地方,

1
2
3
4
# 定义一个二次函数
graph = axes.plot(lambda x: x**2-3, color=BLUE)
self.play(Create(graph))
self.wait(2)

其中的axes.plot用于在二维坐标系上绘制函数曲线,它接收一个可调用对象Mobject,如

1
lambda x: x**2-3
1
lambda 参数: 表达式

并生成与该函数在指定区间内的图像一致的VMobject 曲线,可直接参与动画与组合,典型调用形式:

1
curve = axes.plot(func, x_range=[x_min, x_max], style_kwargs)

其中的curve的英文含义是曲线/弧线的含义,在这里其实curve虽然有曲线/弧线的含义,但更多的是一个VMobject,是一个对象。x_range=[x_min, x_max]其实就是函数自变量的取值范围,最大值和最小值的问题。

1
curve = axes.plot(lambda x: np.sin(x), color=BLUE)

其实含义就是生成正弦函数的曲线当作一个路径线,下面添加了自变量的取值范围,画出的图像就是截取一部分。

1
graph = ax.plot(lambda x: x**2-3, x_range=[0.2, 3], color=YELLOW)

到这里,我基本上算是理解了创建函数图像的问题。以下是关于如何使用 ValueTracker 的关键信息,首先是创建ValueTracker对象,通过

1
ValueTracker(initial_value)

进行初始化,initial_value 为追踪的起始值(默认为0),绑定对象属性

使用 add_updater 方法将 ValueTracker 与目标对象的属性关联。例如,更新文本位置:

1
2
v = ValueTracker(0)
a = Text("A").add_updater(lambda x: x.set_x(v.get_value()))

上述代码将文本 “A” 的水平位置绑定到 ValueTracker 的值,实现动态移动。我们回到代码,会发现

1
dot = always_redraw(lambda: Dot().move_to(axes.c2p(x.get_value(), x.get_value()**2-3)))

其中always_redrawmanim 中的一个函数,用于创建一个始终重新绘制的Mobject,我写了一个类似的代码

1
a = always_redraw(lambda: Text("A").move_to(axes.c2p(x.get_value(), x.get_value()**2-3)).next_to(dot, LEFT, buff=0.2))

给这个动点添加了一个标签,这个标签和动点保持同步,看来这段代码的核心思想,还是通过坐标系返回的数值,始终重新绘制点的轨迹。至于其他的问题,改天在学习编写心得,新的代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from manim import *

class DotOnGraph(Scene):
def construct(self):
# 定义一个坐标系
axes = Axes()
self.play(Create(axes))
self.wait(2)

# 定义一个二次函数
graph = axes.plot(lambda x: x**2-3,color=BLUE)
self.play(Create(graph))
self.wait(2)

# 定义一个随 x 改变而移动的 Dot
x = ValueTracker(-2)
dot = always_redraw(lambda: Dot().move_to(axes.c2p(x.get_value(), x.get_value()**2-3)))
a = always_redraw(lambda: Text("A").move_to(axes.c2p(x.get_value(), x.get_value()**2-3)).next_to(dot, LEFT, buff=0.2))
self.play(Create(dot),Create(a))
self.wait(2)

self.play(x.animate.set_value(2), run_time=4)
self.wait()

新的动画效果如下: