Manim 的 bring_to_front 为什么不起作用?它只改添加顺序,压不过 z_index
Posted on
2026-10-07 01:41
云南老表
阅读(2)
评论(0)
收藏
举报
利益相关:本文作者与极坐标⋅XYZ(jizuobiao.xyz)团队有关联。下面的结论是我们在 Manim Community 0.21.0(2026 年的最新版本)上渲出画面、读像素颜色核对的,代码都贴在文里,结论请自行验证。
self.bring_to_front(mob) 调了之后物体还是被盖着,基本都是同一个原因:盖住它的那个东西 z_index 比它高。Manim 画一帧的时候先按 z_index 排,数字相同的再按添加顺序排。bring_to_front 改的只是添加顺序,轮不到它说话。改法是给要置顶的物体一个更大的 z_index,比如 mob.set_z_index(2)。
Manim 是用 Python 代码做数学动画的开源引擎,3Blue1Brown 的视频就是它做的。它没有图层面板,重叠的物体谁在上面,靠的是两样东西:物体加进场景的先后,和每个物体身上一个叫 z_index 的数字,默认是 0。
先复现
两个重叠的方块,蓝的设了 z_index = 1,然后想把红的提到上面:
from manim import *
class BringToFrontFails(Scene):
def construct(self):
red = Square(side_length=2, color=RED).set_fill(RED, 1).move_to([-0.5, 0, 0])
blue = Square(side_length=2, color=BLUE).set_fill(BLUE, 1).move_to([0.5, 0, 0])
blue.set_z_index(1)
self.add(red, blue)
self.bring_to_front(red)
self.wait()
渲出来,两个方块重叠的地方是蓝色。我们把 bring_to_front(red) 连着调了三次,还是蓝色。
把 blue.set_z_index(1) 那一行删掉再跑,重叠处就是红色了,bring_to_front 正常工作。

bring_to_front 到底做了什么
0.21.0 的源码里,它的函数体只有两行:
def bring_to_front(self, *mobjects):
self.add(*mobjects)
return self
self.add 对一个已经在场景里的物体,会先把它从列表里拿掉,再接到末尾。所以上面那段代码调完之后,self.mobjects 是 [blue, red],红确实排到了最后。
可是画的时候,Manim 还要对这张表按 z_index 排一次序。我们把排完的结果打印出来:
print([(m.color, m.z_index) for m in self.get_mobject_family_members()])
# 红 0,蓝 1
红是 0,蓝是 1,蓝排在后面,后画,盖住红。不管红在 self.mobjects 里排第几,这一步都会把它排回蓝的前面。
bring_to_back 是同样的道理,它把物体挪到列表开头,同样压不住一个 z_index 更低的东西。add_foreground_mobject 我们也试了:红用它加成前景物体,蓝的 z_index 是 1,重叠处还是蓝色。
自己没设过 z_index,也会碰到
有些 Manim 自带的对象,内部已经设了 z_index。Graph 是一个:它的顶点是 0,边是 -1,这样边永远在顶点下面。
副作用是,场景里任何一个 z_index 为 0 的实心物体,只要位置和边重叠,都会把边盖住,和谁先加进场景无关:
plate = Rectangle(width=6, height=3, color=BLUE).set_fill(BLUE, 1)
g = Graph([1, 2], [(1, 2)], layout={1: [-2, 0, 0], 2: [2, 0, 0]})
self.add(plate, g) # 底板先加,图后加
self.bring_to_front(g) # 再提一次
渲出来两个顶点看得见,中间那条边看不见,被底板挡住了。底板是先加的,图是后加的,还 bring_to_front 了一次,都没用,因为边是 -1,底板是 0。
把底板降到 -2,边就出来了:
plate.set_z_index(-2)
碰到「明明后加的,却被先加的盖住」,先看一眼被盖的东西是不是哪个库对象的一部分。
三种改法
给要置顶的物体一个更大的数
知道对手是几,就比它大一点。不知道的话,问场景要当前的最大值:
top = max(m.z_index for m in self.get_mobject_family_members())
red.set_z_index(top + 1)
我们在一个 z_index 最大是 7 的场景里这样写,红被设成 8,到了最上面。
把挡路的那个降下去
像上面底板的例子,该垫底的东西给负数,比把别的东西一个个抬高省事。
全部清零,回到只看添加顺序
接手一个到处是 z_index 的场景,想先弄清楚画面时可以这样:
for m in self.get_mobject_family_members():
m.z_index = 0
清完之后 bring_to_front(red) 就管用了,我们试过,重叠处变回红色。还有一个开关 self.camera.use_z_index = False,效果一样,区别是它不改物体身上的数字。
平时写场景,我们的习惯是二选一:要么全程不碰 z_index,只靠添加顺序和 bring_to_front;要么只要用了一处 z_index,后面调层次就都用它。
顺带一个副作用:别对组里的成员 bring_to_front
bring_to_front 是重新 add,这在物体属于某个组的时候会改掉场景的结构。
g = VGroup(red, blue, green)
self.add(g)
print(self.mobjects) # [VGroup]
self.bring_to_front(red)
print(self.mobjects) # [Square(蓝), Square(绿), Square(红)]
调之前,场景列表里只有 g 一项。调之后,g 从列表里消失了,换成了三个散着的方块。g 这个对象还在,里面还是三个成员,但场景已经不把它当成一个整体记着。
接下来 self.remove(g) 就不灵了。我们试了,调完之后三个方块还在画面上,self.mobjects 一个没少。remove 找的是列表里的 g,而列表里已经没有它。
要把组里的一个成员提到上面,用 red.set_z_index(1),它不动场景列表。
小结
| 情况 | bring_to_front(mob) |
mob.set_z_index(n) |
|---|---|---|
场景里都没设过 z_index |
管用 | 管用 |
挡住它的物体 z_index 更高 |
没用 | 设成更大的数就行 |
被挡的是 Graph 的边这类自带负 z_index 的东西 |
没用 | 把挡路的降到更低 |
| 物体是某个组的成员 | 会把组在场景列表里拆散 | 不影响场景列表 |
之后又有新物体 add 或 FadeIn 进来 |
新来的又到它上面 | 不受影响 |
最后一行我们也试了:bring_to_front(red) 之后 self.play(FadeIn(green)),绿在红上面。bring_to_front 只是那一刻排到最后,后面进场的照样排在它后头。
z_index 的完整规则,包括组的 z_index 怎么传给成员、ReplacementTransform 播完为什么会跳层、3D 场景里还管不管用,在极坐标⋅XYZ 的这篇文章里写得更细;配套视频在教程《布局(下)· 尺寸 / 包围 / 编组 / 深度 + 实战》的第四节。
实测环境:Manim Community 0.21.0 · 2026 年 10 月
浙公网安备 33010602011771号