你在製作Manim動畫時,是否遇到過這樣的困境?
“代碼寫得天衣無縫,運行流暢,出來的動畫卻總覺得哪裏不對勁?”
雖然物體確實從 A 移動到了 B,但看起來就像是老舊的工業機器人在幹活——僵硬、死板,甚至有點無聊。
其實,你的動畫離 “絲滑” 和 “專業”,往往只差這一個參數的距離:rate_func (速率函數)。
今天,我們就來聊聊 Manim 中這個不起眼但至關重要的參數,看看如何通過控制 “時間的流速”,讓你的數學動畫不僅能動,而且動得有節奏、有靈魂。
1. 什麼是 Rate Function?(給時間的進度條)
在 Manim 中,當你寫下 .animate.shift(RIGHT) 時,默認發生了什麼?
如果你覺得動畫只是簡單的“在 Run Time 時間內移動距離 RIGHT”,那隻對了一半。Rate Function 本質上是動畫完成度與時間的關係。
想象一下你在看視頻時的進度條:
- 輸入 ($ t $):當前時間過去了多少(從 0 到 1,代表 0% 到 100% 的時間)。
- 輸出 ($ f(t) $):動畫實際上完成了多少(從 0 到 1,代表 0% 到 100% 的進度)。
默認的魔法:Smooth
Manim 的默認 rate_func 是 smooth。
這符合物理世界的慣性定律:起步時慢(加速),中間快,快結束時慢(減速)。
這就是為什麼默認的動畫看起來比較自然。
如果我們把它換成 linear(線性),物體就會瞬間以最大速度啓動,最後瞬間急停,看起來就會很像 “PPT 動畫”。
2. 常用函數圖鑑:選對“調味料”
Manim內置了一大堆寫好的函數,位於 manim.utils.rate_functions。
我們可以把它們看作是給動畫調味的香料。
為了方便演示,我們假設我們要移動一個小球。
2.1. 基礎三劍客
linear(勻速)- 效果:機械感強,速度恆定。
- 適用場景:旋轉的齒輪、循環滾動的背景、勻速掃描的雷達。
smooth(默認)- 效果:兩頭慢,中間快。
- 適用場景:絕大多數物體的移動、縮放。
rush_into/rush_from- 效果:
rush_into: 越走越快,最後“砰”地撞線(只有加速)。rush_from: 一開始很快,慢慢停下來(只有減速)。
- 適用場景:連續動作的銜接。比如小球飛入畫面停下(
rush_from),或者發射出去(rush_into)。
- 效果:
2.2. 動感特效組
there_and_back(往返)- 效果:走到終點,又原路返回起點。
- 適用場景:強調某個東西。比如把公式放大一下再縮回去,告訴觀眾“看這裏!”。
wiggle(擺動)- 效果:像果凍一樣左右晃動一下。
- 適用場景:表示“錯誤”、“拒絕”或者引起注意。
running_start(助跑)- 效果:先向後退一點點,然後猛地向前衝。
- 適用場景:想要表現物體很有力量感,或者像卡通片裏的衝刺效果。
2.3. 物理模擬組
ease_out_bounce(落地反彈)- 效果:像籃球落地一樣,到底部後彈跳幾次再停下。
- 適用場景:文字掉落、物體自由落體。
3. 動手寫個 Demo
光説不練假把式。下面的示例代碼可以直觀感受不同函數的區別:
from manim import *
class RateFuncComparison(Scene):
def construct(self):
# 定義我們想對比的函數
funcs = [
linear,
smooth,
rush_from,
rush_into,
there_and_back,
rate_functions.ease_out_bounce,
]
labels = [
"Linear",
"Smooth",
"Rush Into",
"Rush From",
"There & Back",
"Bounce",
]
# 創建圓點和文字
group = VGroup()
for i, (func, label_text) in enumerate(zip(funcs, labels)):
dot = Dot(color=TEAL)
label = Text(label_text, font_size=20).next_to(dot, LEFT)
row = VGroup(label, dot)
group.add(row)
# 豎直排列
group.arrange(DOWN, buff=0.5).to_edge(LEFT)
self.add(group)
# 製作動畫:讓所有點同時向右移動
# 注意:我們在這裏分別指定了不同的 rate_func
anims = []
for item, func in zip(group, funcs):
dot = item[1] # 獲取組裏的 Dot
anims.append(dot.animate(rate_func=func, run_time=3).shift(RIGHT * 4))
self.play(*anims)
運行後你會發現,雖然大家的 run_time 都是3秒,移動距離一樣,但“性格”截然不同。
4. 進階:自定義與時間扭曲
作為會 Python 的老手,如果內置函數滿足不了你怎麼辦?
4.1. 自定義函數 (Lambda大法)
rate_func 接受任何一個 Python 函數。
比如,你想做一個簡單的“先慢後快”的加速效果,可以直接用 Lambda:
# y = x^2,典型的加速曲線
class CustomRateFuncDemo(Scene):
def construct(self):
# 創建一個圓
circle = Circle(radius=0.5, color=BLUE).shift(LEFT * 2)
self.add(circle)
# 使用自定義 rate_func(t**2)讓圓向右移動
self.play(circle.animate(rate_func=lambda t: t**2).shift(RIGHT * 4), run_time=3)
self.wait()
4.2. 時間擠壓 (Squish Rate Func)
這是 Manim 中最強大的黑科技之一:squish_rate_func。
假設你寫了一個 run_time=6 的動畫,但你希望某個特定的變換(比如變色),
在第 1.2 秒到第 3 秒之間(即整個進度的 0.2 到 0.5)由白色變成紅色;
在第 3 秒到第 4.8 秒之間(即整個進度的 0.5 到 0.8)由紅色變成綠色。
你不需要把動畫拆成多段寫,只需要:
class SquishRateFuncDemo(Scene):
def construct(self):
# 創建一個圓點
dot = Dot(color=WHITE).shift(LEFT * 2)
self.add(dot)
# 使用UpdateFromAlphaFunc來同時控制位置和顏色變化
def update_dot(obj, alpha):
# 位置變化 - 使用默認的linear速率
obj.move_to(LEFT * 2 + RIGHT * 5 * alpha)
# 顏色變化 - 使用squish_rate_func控制變色的時間段
squished_alpha = squish_rate_func(smooth, 0.2, 0.5)(alpha)
squished_alpha2 = squish_rate_func(smooth, 0.5, 0.8)(alpha)
if alpha < 0.5:
obj.set_color(interpolate_color(WHITE, RED, squished_alpha))
else:
obj.set_color(interpolate_color(RED, GREEN, squished_alpha2))
self.play(
UpdateFromAlphaFunc(dot, update_dot),
run_time=6,
)
self.wait()
這個技巧在製作複雜的多重同步動畫時非常有效!
5. 總結
Manim 不僅僅是動畫工具,它更像是一個導演工具。
linear是為了表現機械、循環。smooth是為了表現自然、物理。there_and_back/wiggle是為了引導觀眾的注意力。squish_rate_func是為了精準控制時間軸。
下次當你的動畫看起來略顯生硬時,不妨停下來想一想:“這個動作的節奏對嗎?是不是該換個 rate function 了?”