z_index是 Manim 里每个物体都有的一个数字,用来定重叠时谁在上面,默认是 0。mob.set_z_index(2)把它改成 2,数字大的盖住数字小的,可以是负数和小数。两个物体z_index相同的时候才轮到添加顺序:后self.add的在上面。对VGroup调set_z_index,组里每个成员都会被设上;self.bring_to_front只改添加顺序,压不过一个z_index更高的物体。
先交代一下版本:下面的代码和结论出自 Manim Community 0.21.0,这是 2026 年的最新版本,我们是 2026 年 10 月在上面逐条渲出画面、读重叠处的像素颜色核对的。Manim 本身是一个 Python 动画库,3Blue1Brown 的数学视频用的就是它,图形和公式都拿代码来摆;背景知识可以看这篇介绍。极坐标⋅XYZ 上还有一套 2026 年最新的 Manim 中文视频教程,这篇讲的z_index是其中一节。
下面三段完整代码可以直接贴进极坐标⋅XYZ 的工作区点运行,里面装的也是 Manim 0.21.0,本机不用装 Python 和 LaTeX。
三个图形叠在一起,先不设,再设
一个红圆、一个青色三角、一个紫色方块,位置互相重叠。先按这个顺序加进场景,停一秒,再给圆和三角设上z_index:
from manim import * class WhoIsOnTop(Scene): def construct(self): circle = Circle(radius=1.2, color=RED).set_fill(RED, 1).move_to([-1, 0, 0]) triangle = Triangle(color=TEAL).set_fill(TEAL, 1).scale(1.6) square = Square(side_length=2.2, color=PURPLE).set_fill(PURPLE, 1).move_to([1, 0, 0]) # 三个都没设 z_index(默认 0),后 add 的盖住先 add 的 self.add(circle, triangle, square) self.wait() # 不动添加顺序,只改 z_index:数字大的在上面 circle.set_z_index(2) triangle.set_z_index(1) self.wait()前一秒里,方块在最上面,三角压着圆。我们在画面上取了三个点看颜色:圆和三角重叠的地方是青色,三角和方块重叠的地方是紫色,三个图形都盖到的那一点也是紫色。
后一秒整个反过来。同样三个点,颜色变成了红、青、红:圆在最上面,三角居中,方块到了最底下。这中间没有重新add,也没有把谁移走,只改了两个数字。
set_z_index可以在add之前调,也可以像上面这样在add之后调,下一帧就按新的数字画。数字不一定是正整数,我们试过 0.5 和 -1 都认。
背后只有一次排序
Manim 0.21.0 画每一帧之前,会把场景里的东西整理成一张表,这段逻辑在manim/utils/family.py里,去掉无关的部分大致是:
extracted_mobjects = remove_list_redundancies( list(it.chain(*(m.get_family() for m in mobjects))), ) if use_z_index: return sorted(extracted_mobjects, key=lambda m: m.z_index)第一步是摊平。mobjects是场景里的物体,按添加顺序排着。每个物体连同它的成员、成员的成员(Manim 管这叫 family)依次展开,接成一张长表。一个VGroup里装着三个方块,展开后就是组自己加三个方块,一共四项。
第二步是按z_index从小到大排序。表里靠前的先画,靠后的后画,后画的盖住先画的。
Python 的sorted是稳定排序,z_index相同的项,排完之后彼此的先后和排之前一样。所以「z_index相同看添加顺序」其实是这次排序自带的结果,Manim 没有为它另写规则。
要注意的是,排的是摊平之后的表。组在这张表里只是其中一项,组里的每个成员各自带着自己的z_index参加排序,不会因为同属一个组就挨在一起。我们试过这样一个场景:g1 = VGroup(红, 橙),g2 = VGroup(蓝, 绿),只给红设了z_index = 1,然后self.add(g1, g2)。打印出来的绘制顺序是橙、蓝、绿、红。红是最先加进来的,却画在最后,压住了后加的g2。
想自己看这张表,在construct里加一行:
print([(type(m).__name__, m.z_index) for m in self.get_mobject_family_members()])后进场的底框,用负数垫下去
z_index最常见的用处是这种情况:文字已经在画面上了,后面想给它加一个实心的底框。底框是后进场的,按添加顺序它在上面,会把文字整个盖掉。
from manim import * class BoxBehindText(Scene): def construct(self): left = Text("z_index = 0").move_to([-3.3, 0, 0]) right = Text("z_index = -1").move_to([3.3, 0, 0]) self.add(left, right) self.wait(0.5) # 两个实心底框都比文字后进场,区别只在右边那个设了 z_index=-1 box_l = SurroundingRectangle(left, buff=0.3).set_fill(BLUE_E, 1) box_r = SurroundingRectangle(right, buff=0.3).set_fill(BLUE_E, 1).set_z_index(-1) self.play(FadeIn(box_l), FadeIn(box_r)) self.wait()播完之后,左边只剩一块黄边、深蓝底的框,字看不见了。我们按 1280×720 渲出来数过左半边的白色像素,底框进场前是 1764 个,进场后是 0 个。右边的字还在,白色像素前后是 1651 和 1658,底框垫在了字的下面。
背景网格、坐标系后面的色块、给公式加的高亮底色,都是同一个写法:给要垫底的那个东西一个负的z_index,什么时候加进场景就不用管了。反过来,一个标签要一直浮在所有东西上面,给它一个比别人都大的数。
z_index在动画进行的过程中也一直算数。上面的FadeIn淡入期间,右边的字始终在框上面。我们另外试过:红色方块z_index = 1停着不动,一个后加的蓝色方块滑过来叠到它身上,重叠处从头到尾都是红色。
组的 z_index,设的是每个成员
对一个VGroup调set_z_index(1),Manim 会把组里每个成员、成员的成员都设成 1。参数里有个family=True,默认就是这样。
这是调用那一刻做的事,组不会记住「我的成员都该是 1」。之后再加进组的成员,z_index还是它自己原来的值:
from manim import * class LateMember(Scene): def construct(self): plate = RoundedRectangle(width=5, height=2, corner_radius=0.2, color=BLUE).set_fill(BLUE_E, 1) card = VGroup(plate).set_z_index(1) label = Text("label") card.add(label) # 后加进组的成员,z_index 还是 0 self.add(card) self.wait() card.set_z_index(1) # 再设一次,这回 label 也是 1 了 self.wait()前一秒画面上只有一块蓝色的底板,label这几个字被它盖住了,卡片范围内一个白色像素都没有。label明明是后加进组的,可它的z_index是 0,底板是 1,排序之后它排到了底板前面,先画。后一秒字出来了,因为又调了一次card.set_z_index(1),两个成员都是 1,回到按组内顺序排,label在后。
所以往一个设过z_index的组里加东西,要么加完再对整个组调一次set_z_index,要么给新成员单独设。
动画里什么时候换层
z_index是物体身上的一个属性,改了就改了,没有中间值。放进动画里,有两个地方和直觉不太一样。
第一个是.animate.set_z_index(1)。它能用,但换层发生在动画播完的时候,播的过程中不变。我们让下面的红方块self.play(red.animate.set_z_index(1), run_time=1),这一秒里重叠处一直是蓝色,播完才跳成红色。想让它在某个时刻换层,直接在两个play之间写red.set_z_index(1)更清楚,.animate在这里没有额外的好处。
第二个是变形。Transform(a, b)把a变成b的样子,场景里留下的还是a,z_index也还是a原来的值,b身上设的z_index带不过来。ReplacementTransform(a, b)播完后场景里换成了b,从那一刻起用的是b的z_index。如果两个数不一样,最后一帧会跳一下。我们试的是红方块z_index = 3变成一个没设z_index的黄方块,旁边有个蓝方块和它重叠:变形过程中它一直在蓝方块上面,颜色从红渐变到黄,播完的瞬间被蓝方块盖住了。
target = Square(color=YELLOW).set_fill(YELLOW, 1) target.set_z_index(a.z_index) # 目标和原物体用同一个 z_index,播完就不会跳 self.play(ReplacementTransform(a, target))什么时候用哪个
| 你想做的事 | 写法 |
|---|---|
| 让一个物体压在所有东西上面 | mob.set_z_index(10),比别人都大就行 |
| 让底框、背景网格垫到最底下 | mob.set_z_index(-1) |
都没设z_index,把其中一个提到最上面 | self.bring_to_front(mob)或再self.add(mob)一次 |
都没设z_index,把其中一个压到最下面 | self.bring_to_back(mob) |
| 整个组一起换层 | group.set_z_index(1),成员都会被设上 |
| 复制出来的物体 | mob.copy()会带着原来的z_index |
| 查现在的绘制顺序 | self.get_mobject_family_members() |
常见写错的地方
用 bring_to_front 去压一个 z_index 更高的物体
blue.set_z_index(1) self.add(red, blue) self.bring_to_front(red) # 红还是在蓝下面bring_to_front在 0.21.0 里的函数体只有一句self.add(*mobjects),也就是把物体重新加一次,让它排到添加顺序的最后。它改的是添加顺序,而添加顺序只在z_index相同的时候才起作用。蓝是 1,红是 0,红加多少次都在蓝下面。
两个物体都没设z_index的时候,bring_to_front(red)是管用的,我们试过,红到了上面。bring_to_back同理。add_foreground_mobject也压不过:红用它加成前景物体,蓝的z_index是 1,重叠处还是蓝色。
场景里只要有人用了z_index,后面调层次就都用z_index,别两套混着来。
把 z_index 写在 VGroup 的括号里
card = VGroup(plate, label, z_index=5)这样写不报错,也没有效果。构造函数里的z_index=5只设给了组自己,plate和label还是 0。组本身没有可画的东西,它排在哪都不影响画面。我们拿这样一个组和一个后加的蓝方块比,蓝方块照样盖在上面;打印绘制顺序,两个方块的z_index都是 0,只有VGroup那一项是 5。
改成VGroup(plate, label).set_z_index(5)。set_z_index(5, family=False)和写在括号里是一个效果,同样不起作用。
想用 shift(OUT) 把物体抬到前面
red.shift(OUT * 5)普通的Scene是从正面平着看的,物体的 z 坐标不参与遮挡。我们把红方块往外挪了 5 个单位,后加的蓝方块还是盖在它上面。
如果手里的物体本来就带着 z 坐标,想按它来定层次,可以调red.set_z_index_by_z_Point3D(),它会把物体中心的 z 坐标抄给z_index。上面那个红方块调完之后z_index是 5.0,就到蓝方块上面了。
常见追问(FAQ)
Q:z_index的默认值是多少?能用负数和小数吗?A:默认是 0。负数和小数都能用,我们试过set_z_index(-1)和set_z_index(0.5),都按大小正常排。想在 0 和 1 之间再插一层,不用把别的数字全改一遍,给 0.5 就行。
Q:物体已经add进场景了,再调set_z_index还管用吗?A:管用。Manim 每画一帧都重新排一次,改完之后的下一帧就按新的数字来。本文第一段代码就是先add后设的。
Q:always_redraw生成的物体,z_index写在哪?A:写在lambda里面或者外面都行。我们两种都试了:always_redraw(lambda: Square().set_z_index(1)),以及先always_redraw(...)再对返回的物体调set_z_index(1),播了几帧之后z_index都还是 1,遮挡关系也对。
Q:a.become(b)会把b的z_index带过来吗?A:不会。我们让一个默认的方块become一个z_index = 4的方块,之后它的z_index还是 0。copy()是带的,复制出来的物体z_index也是 4。
Q:3D 场景里z_index还管用吗?A:平面图形之间管用。我们在ThreeDScene里从正上方看两个重叠的方块,红的z_index = 1,蓝的后加,红在上面。球面、曲面这类带明暗的 3D 物体,Manim 的 3D 相机还会按它们离镜头的远近再排一次,那是另一套规则,这篇不展开。
Q:能不能让 Manim 完全不看z_index?A:能,在construct开头写self.camera.use_z_index = False,之后只按添加顺序画。我们这样设了之后,z_index = 1的红方块被后加的蓝方块盖住了。一般用不到,接手别人的场景、想先排除z_index的影响时可以临时开一下。
z_index和添加顺序在教程《布局(下)· 尺寸 / 包围 / 编组 / 深度 + 实战》的第四节「深度方向布局 z_index」里有视频讲解,旁边的代码可以边看边改。同一课第三节讲VGroup,文字版是《Manim 怎么把一组物体排成一行或网格?》。
来源:极坐标⋅XYZ · jizuobiao.xyz · 更新于 2026-10-06 · 基于 Manim Community 0.21.0