Manim 怎么把一组物体排成一行或网格?VGroup 的 arrange 与 arrange_in_grid
2026 年 10 月 5 日 · 极坐标⋅XYZ
Manim 怎么把一组物体排成一行或网格?VGroup 的 arrange 与 arrange_in_grid
把物体装进一个 VGroup,调 group.arrange(RIGHT, buff=0.5),它们就从左到右排成一行,相邻两个之间留 0.5;方向换成 DOWN 是一列。要排成几行几列,用 group.arrange_in_grid(rows=2, cols=3)。这两个方法只挪成员的位置,不改大小。有一处差别最好先知道:arrange 排完会把整组挪到画面正中,arrange_in_grid 排完整组还在原来的位置。
Manim 是写代码做数学动画的 Python 库,3Blue1Brown 那些讲线性代数、微积分的视频就是用它做的,作者 Grant Sanderson 后来把它开源了。画面上的每个图形、每行公式都是一个 Python 对象,位置靠调方法来定,没有能用鼠标拖的画布,所以「怎么排整齐」最后都要落到某个方法上。现在大家装的一般是社区版 Manim Community,2026 年的最新版本是 0.21.0,本文的代码和坐标是 2026 年 10 月在这一版上跑出来的。第一次听说 Manim 的话,可以先看这篇介绍。
下面三段完整代码可以在极坐标⋅XYZ 的工作区里直接运行,点代码块上面的「在工作区中打开」就行。工作区里也是 Manim 0.21.0,电脑上不用装 Python 和 LaTeX。
先排一行,再排成两行三列
六个方块建出来的时候都在原点,叠成了一个。先让它们排成一行,再改成两行三列:
from manim import *
class RowAndGrid(Scene):
def construct(self):
colors = [RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE]
boxes = VGroup(*[Square(side_length=1, color=c).set_fill(c, 0.6) for c in colors])
self.add(boxes) # 六个方块都在原点,叠成一个
self.wait(0.5)
# 一行:从左往右,相邻两个之间留 0.5
self.play(boxes.animate.arrange(RIGHT, buff=0.5))
self.wait(0.5)
# 两行三列
self.play(boxes.animate.arrange_in_grid(rows=2, cols=3, buff=0.5))
self.wait()
第一步播完,六个方块的中心 x 依次是 -3.75、-2.25、-0.75、0.75、2.25、3.75,y 都是 0。方块边长 1,间隙 0.5,所以相邻两个的中心隔 1.5,整排以原点为中心左右对称。
第二步播完,上面一行是红、橙、黄,下面一行是绿、蓝、紫,x 是 -1.5、0、1.5,y 是 0.75 和 -0.75。成员按加进 VGroup 的顺序填格子,先填满第一行,再填第二行。
buff 是相邻两个成员之间的空隙,两个方法不写的话都是 0.25。arrange 的第一个参数是方向,RIGHT、DOWN 之外也可以写 LEFT、UP,顺序就反过来。斜着排也行,我们试过三个边长 1 的方块 arrange(DR, buff=0.2),中心落在 (-1.2, 1.2)、(0, 0)、(1.2, -1.2),是一条往右下走的台阶。
arrange 是一串 next_to
Manim 0.21.0 里 arrange 的函数体只有几行,去掉类型标注大致是这样:
def arrange(self, direction=RIGHT, buff=0.25, center=True, **kwargs):
for m1, m2 in zip(self.submobjects[:-1], self.submobjects[1:]):
m2.next_to(m1, direction, buff, **kwargs)
if center:
self.center()
return self
第一个成员不动,第二个 next_to 到第一个旁边,第三个 next_to 到第二个旁边,一路接下去。接完之后,整组用 center() 挪到原点。
next_to 管的是两个物体边到边的距离,所以 arrange 保证的是间隙相等。成员一样大的时候看不出区别,大小不一就能看出来了。我们拿宽度是 0.6、2、1、3 的四个图形 arrange(RIGHT, buff=0.5),量出来三段间隙都是 0.5,相邻中心的距离是 1.8、2.0、2.5。

arrange 不认识的参数会原样交给 next_to,最常用的是 aligned_edge。往下排的时候,默认是每个成员的中心对中心。三个宽度是 1、3、2 的长条 arrange(DOWN, buff=0.3),左边缘的 x 是 -0.5、-1.5、-1.0,参差不齐。加上 aligned_edge=LEFT,三个左边缘都在 -1.5。几行文字要左对齐、一排高矮不同的图形要底边对齐(aligned_edge=DOWN),都是这么写。
最后那个 center() 可以关掉。arrange(RIGHT, buff=0.5, center=False) 只做前面那串 next_to,第一个成员在哪,整排就从哪开始。我们把第一个方块先放到 (-4, 1) 再这样排,三个方块落在 (-4, 1)、(-2.5, 1)、(-1, 1)。
arrange_in_grid 先量格子,再往里放
要是只用 arrange 排两行,得先把每行各排一次,再把两行上下叠起来。成员大小一样时这么做没问题,大小不一的话,两行各排各的,上下就对不成列。下面左边是这种写法,右边是同样六个方块交给 arrange_in_grid:
from manim import *
class UnevenSizes(Scene):
def construct(self):
sides = [0.5, 2, 0.5, 1, 0.5, 1.5]
def make(color):
return [Square(side_length=s, color=color).set_fill(color, 0.5) for s in sides]
# 左边:两行各自 arrange,再把两行上下叠起来
m = make(BLUE)
row1 = VGroup(*m[:3]).arrange(RIGHT, buff=0.2)
row2 = VGroup(*m[3:]).arrange(RIGHT, buff=0.2)
nested = VGroup(row1, row2).arrange(DOWN, buff=0.2).move_to([-3.5, 0, 0])
# 右边:同样六个方块,交给 arrange_in_grid
grid = VGroup(*make(YELLOW)).arrange_in_grid(rows=2, cols=3, buff=0.2).move_to([3.5, 0, 0])
self.add(nested, grid)
self.wait()
以各自那一组的中心为零点,左边第一行三个方块的 x 是 -1.45、0、1.45,第二行是 -1.2、-0.25、0.95,六个中心落在六个不同的 x 上。右边两行都是 -1.95、-0.25、1.7。

arrange_in_grid 的做法和 arrange 不一样。它先把成员按行列分好,量出每一列里最宽的那个、每一行里最高的那个,当作这一列的宽和这一行的高,这样就有了一张格子。然后把每个成员放进自己的格子,默认摆在格子正中。所以同一列的成员中心在一条竖线上,同一行的在一条横线上。
想让格子里的东西靠边,有两个参数。cell_alignment=LEFT 让所有成员贴着格子左边,上面那六个方块这样排,每列的左边缘是 -2.45、-1.25、0.95。col_alignments="lcr" 可以一列一列地指定,三个字母对应三列的左、中、右;行的方向是 row_alignments,字母用 u、c、d。
行数和列数可以只写一个,另一个它自己算。七个方块写 cols=3 排出来是 3、3、1,写 rows=2 是 4、3。两个都不写,列数取成员个数开平方再向上取整:六个排成 3、3,七个是 3、3、1,十个是 4、4、2。
排完之后,整组在哪
arrange 的最后一步是 center(),整组回到原点。arrange_in_grid 开头先记下了整组的中心,最后一行是 self.move_to(start_pos),把排好的网格挪回那个中心。同样是「把三个方块排成一行」,写法不同,落点就不同:
from manim import *
class WhereDoesItGo(Scene):
def construct(self):
plane = NumberPlane(background_line_style={"stroke_opacity": 0.3})
a = VGroup(*[Square(side_length=0.8, color=BLUE).set_fill(BLUE, 0.5) for _ in range(3)])
b = VGroup(*[Square(side_length=0.8, color=YELLOW).set_fill(YELLOW, 0.5) for _ in range(3)])
a.move_to([-4, 2, 0])
b.move_to([4, -2, 0])
self.add(plane, a, b)
self.wait(0.5)
# 两组都是「排成一行」,写法不同
self.play(a.animate.arrange(RIGHT), b.animate.arrange_in_grid(rows=1), run_time=2)
self.wait(0.5)
# arrange 后面接一个 move_to,把整组送回去
self.play(a.animate.arrange(RIGHT).move_to([-4, 2, 0]), run_time=2)
self.wait()
第一个 play 播完,蓝色那组的中心在 (0, 0),它一边散开一边从左上角滑到了画面正中。黄色那组的中心还是 (4, -2),在原地散开。

第二个 play 把 arrange 和 move_to 串在一条 .animate 后面,蓝色那组回到 (-4, 2),三个方块在 (-5.05, 2)、(-4, 2)、(-2.95, 2)。
我们自己写场景时,arrange 后面基本都会跟一个定位:.arrange(RIGHT).to_edge(UP)、.arrange(DOWN).next_to(title, DOWN)、.arrange(RIGHT).move_to(...)。对整个组调 move_to,成员是一起平移的,排好的间距不会变,这一点上一篇讲 shift 和 move_to 时试过。
什么时候用哪个
| 你想做的事 | 写法 |
|---|---|
| 排成一行 | group.arrange(RIGHT, buff=0.5) |
| 排成一列,左边对齐 | group.arrange(DOWN, buff=0.3, aligned_edge=LEFT) |
| 一排高矮不同的图形,底边对齐 | group.arrange(RIGHT, aligned_edge=DOWN) |
| 排成两行三列 | group.arrange_in_grid(rows=2, cols=3) |
| 每行固定 4 个,行数随个数变 | group.arrange_in_grid(cols=4) |
| 横向间隙 0.2,纵向间隙 1 | group.arrange_in_grid(cols=3, buff=(0.2, 1)) |
| 从上往下一列一列地填 | group.arrange_in_grid(rows=2, flow_order="dr") |
| 排好之后放到指定位置 | 在后面接 .move_to(...)、.to_edge(...) 或 .next_to(...) |
常见写错的地方
先定位,后 arrange
row = VGroup(a, b, c).to_edge(UP).arrange(RIGHT)
这一行想的是「放到画面顶上,再排开」,结果整排在画面正中。arrange 最后那步 center() 把前面的 to_edge(UP) 盖掉了。我们试过 move_to([3, 2, 0]) 之后再 arrange(RIGHT),组的中心是 (0, 0);两步调个顺序,中心是 (3, 2)。
row = VGroup(a, b, c).arrange(RIGHT).to_edge(UP)
组里只有一个成员时也会这样。一个在 (2, 2) 的方块单独装进 VGroup 再 arrange(RIGHT),没有东西可排,它照样被挪到了原点。成员个数是循环算出来的时候,容易碰上这种情况。
排完之后又加成员、又改大小
arrange 只在被调用的那一刻摆一次位置,之后组里再发生什么它都不管。
排好三个方块之后往组里 add 第四个,新来的那个还在它自己原来的位置。我们试的时候它在 (5, 3),离那一排很远,整个组的外接框被它撑大,中心变成了 (2, 1.75)。
排好之后把中间那个 scale(2),原本 0.5 的间隙变成了 0,三个方块贴在一起。
这两种情况都是再调一次 arrange,然后把定位那一步也重做一遍。这和 next_to 摆好的标签不会跟着物体走是同一个道理,那一篇里讲过。
rows × cols 装不下
VGroup(*[Square() for _ in range(7)]).arrange_in_grid(rows=2, cols=3)
七个成员放不进 2×3 的格子,0.21.0 直接报 ValueError: Too few rows and columns to fit all submobjetcs.(最后一个词是源码里的拼写,照抄的)。成员个数不固定的时候,rows 和 cols 只写一个,让另一个自己算。
往 VGroup 里放图片
VGroup(Square(), ImageMobject("logo.png"))
报的是 TypeError: Only values of type VMobject can be added as submobjects of VGroup, but the value ImageMobject (at index 0 of parameter 1) is of type ImageMobject. You can try adding this value into a Group instead.
VGroup 只收矢量图形,图片是位图。报错信息最后一句已经给了改法,换成 Group 就行。arrange 和 arrange_in_grid 是定义在 Mobject 上的,Group 一样能调。我们把一个方块和一张图片装进 Group 再 arrange(RIGHT, buff=0.5),两个正常排开了。
常见追问(FAQ)
Q:buff 不写的话,间距是多少?
A:arrange 和 arrange_in_grid 的默认值都是 0.25 个画布单位。默认画布高 8 个单位,0.25 大概是画面高度的 3%。
Q:网格的横向和纵向间距想设得不一样,怎么写?
A:buff 传一个元组。我们量过 buff=(0.2, 1.0):同一行里相邻两个的间隙是 0.2,上下两行之间是 1.0,也就是第一个数管横向,第二个数管纵向。0.21.0 的文档字符串把这个元组写成 (row, col),容易看反,以实际跑出来的为准。
Q:网格能不能从上往下一列一列地填?
A:能,加 flow_order="dr",意思是先往下(down)再往右(right)。默认是 "rd",先往右再往下。六个方块排 2×3,"dr" 的结果是第 1、2 个在第一列,第 3、4 个在第二列。
Q:arrange 能做成动画吗?
A:能,self.play(group.animate.arrange(RIGHT)),每个成员从现在的位置沿直线滑到排好的位置,本文第一段代码就是这么写的。后面还可以接着串,比如 group.animate.arrange(RIGHT).to_edge(UP)。
Q:VGroup 里面还套着 VGroup,arrange 会把里面那层也打散吗?
A:不会。arrange 只挪直接成员,里面那个组被当成一整块。我们把一个上下摞着两个方块的小组和另外两个图形一起 arrange(RIGHT),小组里两个方块还是上下摞着,相对位置没变。
Q:对一个 Text 调 arrange 会怎样?
A:会把字拆开重排。Text("ABC") 本身就是一个组,三个字母是它的三个成员,调 arrange(RIGHT, buff=0.5) 之后字距被拉开,我们量到宽度从 1.34 变成了 2.25。要排的是几行文字的话,把几个 Text 装进一个 VGroup,对这个 VGroup 调 arrange(DOWN, aligned_edge=LEFT)。
VGroup、arrange 和 arrange_in_grid 在教程《布局(下)· 尺寸 / 包围 / 编组 / 深度 + 实战》的第三节「编组与排列」里有视频讲解,旁边的代码可以边看边改;next_to 和 aligned_edge 在前一课《布局(上)· 描述关系不算坐标》里。
来源:极坐标⋅XYZ · jizuobiao.xyz · 更新于 2026-10-05 · 基于 Manim Community 0.21.0