运动变形

本节处理两类"自定义运动":MoveAlongPath 让物体贴着任意曲线走;Homotopy 家族让你写出任意空间形变函数,对物体每个点做个性化映射。最后补充 manim.utils.paths 的路径函数,它们决定 Transform 系动画中点如何从起点走到终点。

MoveAlongPath

MoveAlongPath(mobject, path, suspend_mobject_updating=False, **kwargs) 官方文档 ↗

MoveAlongPath(mobject, path) 让物体沿一条 VMobject 路径运动,方向始终沿路径切线,进度由 rate_func 控制。

digraph G { graph [rankdir=LR, bgcolor="transparent", nodesep="0.25", ranksep="0.35"]; node [shape=box, style="rounded,filled", fontname="Helvetica,sans-serif", fontsize=11, height=0.3, color="#8b93a1", fillcolor="#f2f4f7", fontcolor="#1f2733"]; edge [color="#a6adba", arrowsize=0.6, penwidth=1.1]; "MoveAlongPath" -> "Animation"; "Animation" -> "object"; }

MoveAlongPath(mobject: Mobject, path: VMobject, suspend_mobject_updating: bool = False, **kwargs)

name

type

default

desc

path

VMobject

—

运动路径(Circle、ArcBetweenPoints、自定义曲线等)

suspend_mobject_updating

bool

False

播放期间是否挂起物体自身 updater

Homotopy

Homotopy(homotopy, mobject, run_time=3, apply_function_kwargs=None, **kwargs) 官方文档 ↗

Homotopy(homotopy, mobject, run_time=3) 是按函数逐点形变的动画:homotopy(x, y, z, t) 把 t 时刻空间点 (x, y, z) 映射到新位置。t 从 0(原状)到 1(终态)。

digraph G { graph [rankdir=LR, bgcolor="transparent", nodesep="0.25", ranksep="0.35"]; node [shape=box, style="rounded,filled", fontname="Helvetica,sans-serif", fontsize=11, height=0.3, color="#8b93a1", fillcolor="#f2f4f7", fontcolor="#1f2733"]; edge [color="#a6adba", arrowsize=0.6, penwidth=1.1]; "Homotopy" -> "Animation"; "Animation" -> "object"; }

Homotopy(
    homotopy: Callable[[float, float, float, float], tuple[float, float, float]],
    mobject: Mobject,
    run_time: float = 3,
    apply_function_kwargs: dict | None = None,
    **kwargs,
)

两个子类覆盖两种常见特化:

SmoothedVectorizedHomotopy

SmoothedVectorizedHomotopy(homotopy, mobject, run_time=3, apply_function_kwargs=None, **kwargs) 官方文档 ↗

SmoothedVectorizedHomotopy(homotopy, mobject, run_time=3, ...) 是 Homotopy 的子类:在通用逐点映射之外,对 VMobject 相邻点之间的位移做平滑插值,缓解逐点独立映射导致的边缘撕裂,形变视觉上更顺滑。

digraph G { graph [rankdir=LR, bgcolor="transparent", nodesep="0.25", ranksep="0.35"]; node [shape=box, style="rounded,filled", fontname="Helvetica,sans-serif", fontsize=11, height=0.3, color="#8b93a1", fillcolor="#f2f4f7", fontcolor="#1f2733"]; edge [color="#a6adba", arrowsize=0.6, penwidth=1.1]; "SmoothedVectorizedHomotopy" -> "Homotopy"; "Homotopy" -> "Animation"; "Animation" -> "object"; }

ComplexHomotopy

ComplexHomotopy(complex_homotopy, mobject, **kwargs) 官方文档 ↗

ComplexHomotopy(complex_homotopy, mobject, **kwargs) 也是 Homotopy 的子类:形变函数用复数形式 f(z: complex, t: float) -> complex 描述,写共形映射(如 z * np.exp(t * PI * 1j) 的整体旋转缩放)比三元组形式简洁得多。

digraph G { graph [rankdir=LR, bgcolor="transparent", nodesep="0.25", ranksep="0.35"]; node [shape=box, style="rounded,filled", fontname="Helvetica,sans-serif", fontsize=11, height=0.3, color="#8b93a1", fillcolor="#f2f4f7", fontcolor="#1f2733"]; edge [color="#a6adba", arrowsize=0.6, penwidth=1.1]; "ComplexHomotopy" -> "Homotopy"; "Homotopy" -> "Animation"; "Animation" -> "object"; }

PhaseFlow

PhaseFlow(function, mobject, virtual_time=1, suspend_mobject_updating=False, rate_func=<function linear at 0…, **kwargs) 官方文档 ↗

PhaseFlow(function, mobject, virtual_time=1) 沿向量场推进物体:每一点按 function(point) 给出的速度方向运动 virtual_time 个虚拟秒,默认 rate_func=linear。常用来可视化微分方程的流。

digraph G { graph [rankdir=LR, bgcolor="transparent", nodesep="0.25", ranksep="0.35"]; node [shape=box, style="rounded,filled", fontname="Helvetica,sans-serif", fontsize=11, height=0.3, color="#8b93a1", fillcolor="#f2f4f7", fontcolor="#1f2733"]; edge [color="#a6adba", arrowsize=0.6, penwidth=1.1]; "PhaseFlow" -> "Animation"; "Animation" -> "object"; }

PhaseFlow(
    function: Callable[[np.ndarray], np.ndarray],
    mobject: Mobject,
    virtual_time: float = 1,
    suspend_mobject_updating: bool = False,
    rate_func: RateFunction = linear,
    **kwargs,
)

manim.utils.paths 路径函数

这组函数不是动画,而是 Transform 系动画的 path_func 参数:它决定每个点从起点到终点走什么路线。全部为工厂函数,调用后返回真正的路径函数。

函数

签名

路线形状

straight_path()

—

直线(默认)

path_along_arc(arc_angle, axis=OUT)

弧度,轴向

绕指定轴沿弧线走 arc_angle

clockwise_path()

—

顺时针半圆弧(等价 path_along_arc(-PI))

counterclockwise_path()

—

逆时针半圆弧(等价 path_along_arc(PI))

path_along_circles(arc_angle, circles_centers, axis=OUT)

两段圆心

各点分别绕给定圆心转 arc_angle

spiral_path(angle, axis=OUT)

弧度

螺旋线,旋转的同时收缩

典型用法:

self.play(
    Transform(a, b, path_func=path_along_arc(PI / 2)),
    run_time=2,
)

圆点沿下半圆弧 MoveAlongPath 到右侧;随后正方形被自定义 Homotopy 纵向压扁并带正弦波纹。

查看源码 motion.py
"""MotionAndHomotopy: MoveAlongPath 沿路径运动,Homotopy 自定义形变。"""

from manim import *


class MotionAndHomotopy(Scene):
    def construct(self):
        dot = Dot(LEFT * 2.5)
        self.add(dot)
        self.play(
            MoveAlongPath(
                dot,
                ArcBetweenPoints(LEFT * 2.5, RIGHT * 2.5, angle=-PI / 2),
            ),
            run_time=2,
        )

        # homotopy: 对空间中的每个点 (x, y, z),按时间 t 映射到新位置
        def squash(x, y, z, t):
            return (x, y * (1 - t) + t * 0.3 * np.sin(3 * PI * x), z)

        square = Square().shift(LEFT * 2.5)
        self.add(square)
        self.play(Homotopy(squash, square, run_time=2))
        self.wait(0.5)

常见错误

Transform 的 path_func 影响的是运动路线,不改变起止状态:无论绕多大的弧,动画结束物体都精确落在目标状态。另外 clockwise_path() / counterclockwise_path() 是无参工厂,注意写 path_func=counterclockwise_path() 而不是传函数本身时漏了调用。

自测

✏️ 练习

想写"正方形被压成一条水平波浪线"的效果,应该用哪个动画、函数怎么写?

✅ 参考答案

用 Homotopy:函数返回 (x, y * (1 - t) + t * A * np.sin(k * x), z),t 从 0 到 1 时 y 坐标被逐渐压到正弦曲线上。

✏️ 练习

MoveAlongPath 与给物体加 updater lambda m, dt: m.shift(...) 手写运动有什么区别?

✅ 参考答案

MoveAlongPath 保证物体严格贴路径、进度与 rate_func 绑定、播完停在路径终点;手写 updater 需要自己积分速度和方向,容易偏离曲线,但适合不受固定路径约束的运动。

下一步

路径和形变解决了"怎么走",下一节回到最基本的刚体运动——旋转:Rotate 与 Rotating 只有一词之差,行为却大不相同。