用Manim做出点在直线上的运动过程

今天我们来学习,使用Manim做出点在直线上运动的过程,这个动画效果还是很普遍的,一些初中数学几何作图问题,都会用到这个效果,估计很多朋友看到这篇文章也是因为想着学习点在直线上的运动的知识,不多说来看下面的代码

1.代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from manim import *

class SimpleMovingPoint(Scene):
def construct(self):
# 创建A点和B点
A = LEFT * 3
B = RIGHT * 3

# 创建线段AB
line_AB = Line(A, B, color=BLUE)

# 创建标签
label_A = Text("A").next_to(A, DOWN)
label_B = Text("B").next_to(B, DOWN)

# 创建动点P
P = Dot(color=RED)
label_P = Text("P", color=RED).next_to(P, UP, buff=0.1)

# 让标签跟随P点
label_P.add_updater(lambda m: m.next_to(P, UP, buff=0.1))

# 显示所有元素
self.add(line_AB, label_A, label_B, P, label_P)
self.wait(0.5)

# 让P点在线段AB上运动
self.play(
MoveAlongPath(P, line_AB),
run_time=3
)

self.wait()

来看效果演示视频

2.代码分析

2.1创建点的坐标

1
2
A = LEFT * 3
B = RIGHT * 3

首先需要创建两个点,在ManimLEFT 是一个表示方向的向量常量,具体值为 [-1, 0, 0],也就是笛卡尔坐标系中的(-1, 0)

1
LEFT * 3 = 坐标(-3, 0, 0)

Manim中,常用的方向常量有

1
2
3
4
5
6
LEFT    = np.array([-1, 0, 0])    # 左 (-1, 0)
RIGHT = np.array([1, 0, 0]) # 右 (1, 0)
UP = np.array([0, 1, 0]) # 上 (0, 1)
DOWN = np.array([0, -1, 0]) # 下 (0, -1)
OUT = np.array([0, 0, 1]) # 向屏幕外
IN = np.array([0, 0, -1]) # 向屏幕内

还有对角线方向的常量

1
2
3
4
UL = UP + LEFT    # 左上 (-1, 1)
UR = UP + RIGHT # 右上 (1, 1)
DL = DOWN + LEFT # 左下 (-1, -1)
DR = DOWN + RIGHT # 右下 (1, -1)

2.2创建直线

第二行代码的表示很清楚,就是用来创建一条直线

1
line_AB = Line(A, B, color=BLUE)

2.3创建标签

1
2
label_A = Text("A").next_to(A, DOWN)
label_B = Text("B").next_to(B, DOWN)

2.4创建动点和标签

1
2
P = Dot(color=RED)
label_P = Text("P", color=RED).next_to(P, UP, buff=0.1)

2.5动点标签跟随

1
label_P.add_updater(lambda m: m.next_to(P, UP, buff=0.1))

其中add_updater(func)作用是给一个Mobject(图形对象)添加一个更新函数,每次场景刷新时,都会调用这个函数更新对象;lambda m:这是一个匿名函数(lambda函数)m 是函数参数,指向调用add_updater的那个对象本身,在这里,m 就是 label_P 这个文本标签对象,注意以下内容:

  • lambda m: 中的 m 总是代表调用add_updater的那个对象本身
  • 更新器函数会在每一帧被调用
  • 常用于创建跟随效果、动态连线、实时显示数值等
  • 记得在不需要时清理更新器,避免性能问题

可以尝试着理解一下代码,标签始终在圆形的右边

1
2
3
circle = Circle()
label = Text("圆形")
label.add_updater(lambda m: m.next_to(circle, RIGHT))

创建一个圆和圆对应的标签,让添加的标签始终跟随着圆。

2.6.路径运动

1
2
3
4
self.play(
MoveAlongPath(P, line_AB),
run_time=3
)
2.6.1表达形式

MoveAlongPathManim中让物体沿着指定路径运动的动画类,常用的格式是MoveAlongPath(对象, Mobject)的样式,其中路径可以是任何Mobject,例如Line - 直线,Circle - 圆形,Arc - 圆弧,Polygon - 多边形,尝试着理解下面的代码

1
2
3
# 圆形路径
circle_path = Circle(radius=2)
self.play(MoveAlongPath(dot, circle_path), run_time=3)
1
2
3
# 三角形路径
triangle_path = Polygon(LEFT, UP, RIGHT)
self.play(MoveAlongPath(dot, triangle_path), run_time=3)
2.6.2.常见参数
1
2
3
4
# 正向运动(默认)
self.play(MoveAlongPath(dot, path))
# 或明确指定
self.play(MoveAlongPath(dot, path, forward_direction=True))
1
2
3
4
# 反向运动
self.play(MoveAlongPath(dot, path.reverse_direction()))
# 或
self.play(MoveAlongPath(dot, path, forward_direction=False))

2.6.3调控速度

1
2
3
4
5
6
7
8
9
10
11
self.play(
MoveAlongPath(dot, path),
rate_func=linear, # 匀速运动(默认)
run_time=3
)

# 其他速度函数:
# rate_func=smooth # 平滑加减速
# rate_func=there_and_back # 往复运动
# rate_func=slow_into # 缓慢开始
# rate_func=there_and_back, # 关键参数:往复运动

3.重新测试

理解了上面的知识,我们可以重新测试代码,让点P往复运动一次,看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from manim import *

class SimpleMovingPointTwo(Scene):
def construct(self):
# 创建A点和B点
A = LEFT * 3
B = RIGHT * 3

# 创建线段AB
line_AB = Line(A, B, color=BLUE)

# 创建标签
label_A = Text("A").next_to(A, DOWN)
label_B = Text("B").next_to(B, DOWN)

# 创建动点P
P = Dot(color=RED)
label_P = Text("P", color=RED).next_to(P, UP, buff=0.1)

# 让标签跟随P点
label_P.add_updater(lambda m: m.next_to(P, UP, buff=0.1))

# 显示所有元素
self.add(line_AB, label_A, label_B, P, label_P)
self.wait(0.5)

# 让P点在线段AB上运动
self.play(
MoveAlongPath(P, line_AB),
run_time=6,
rate_func=there_and_back,

)

self.wait()

在我这里我们仅仅是修改了一句代码

1
rate_func=there_and_back

就会形成下面的往复运动效果

4.多次循环

如果想着让点P在线段AB上多次循环,就需要使用Python的循环语句,来看下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from manim import *

class SimpleMovingPointThree(Scene):
def construct(self):
# 创建A点和B点
A = LEFT * 3
B = RIGHT * 3

# 创建线段AB
line_AB = Line(A, B, color=BLUE)

# 创建标签
label_A = Text("A").next_to(A, DOWN)
label_B = Text("B").next_to(B, DOWN)

# 创建动点P
P = Dot(color=RED)
label_P = Text("P", color=RED).next_to(P, UP, buff=0.1)

# 让标签跟随P点
label_P.add_updater(lambda m: m.next_to(P, UP, buff=0.1))

# 显示所有元素
self.add(line_AB, label_A, label_B, P, label_P)
self.wait(0.5)

# 让P点在线段AB上运动
for i in range(3):
# 正向运动
self.play(
MoveAlongPath(P, line_AB),
rate_func=there_and_back,
run_time=6
)

self.wait()

其中有一段代码

1
2
3
4
5
6
for i in range(3):  # 让下面的动画重复执行3次
self.play(
MoveAlongPath(P, line_AB),
rate_func=there_and_back,
run_time=6,
)

或许下面的解释正容易理解

1
2
3
4
5
range(3) 生成一个序列:[0, 1, 2]
第一次循环:i = 0
第二次循环:i = 1
第三次循环:i = 2
循环共执行 3 次
1
2
3
4
5
6
7
8
第一次循环(i=0):
P点:A → B → A (一次完整的来回)

第二次循环(i=1):
P点:A → B → A (第二次来回)

第三次循环(i=2):
P点:A → B → A (第三次来回)

如果不适用循环,也可以使用下面的代码

1
2
3
4
5
6
7
8
# 第一次来回
self.play(MoveAlongPath(P, line_AB), rate_func=there_and_back, run_time=6)

# 第二次来回
self.play(MoveAlongPath(P, line_AB), rate_func=there_and_back, run_time=6)

# 第三次来回
self.play(MoveAlongPath(P, line_AB), rate_func=there_and_back, run_time=6)

简单来说for i in range(3): 就是你代码中的”重复按钮”,按一下它,下面的动画就会自动重复播放3次。来看代码演示的效果

好了,今天的代码就写到这里,给自己做一个记录。