1. 问题现象
在 Bevy 0.14 的 2D 游戏(老游戏逆向重写项目)中,窗口为 640×480。相机使用 Camera2dBundle,但为了“分辨率无关”,手动构造 OrthographicProjection 将缩放模式改为 FixedVertical(600.0),即世界高度固定为 600 逻辑单位,宽度随窗口比例自适应。
实际表现是:主菜单和背景(森林、草地、视差层)渲染完全正常,但游戏内的玩家、敌人、护盾、子弹全部不显示。
值得注意的是,两个版本表现完全一致:
- headless 模式(Linux 掌机,无窗口渲染到 /dev/fb0)
- Mac 桌面窗口版
这条一致性很重要,说明问题与平台、渲染后端无关。
2. 逐条排除干扰项
在定位问题前,先排除以下常见干扰因素:
- 素材加载正常:日志里没有任何 Asset failed to load,所有图片 handle 都是好的。
- 没有 panic:游戏逻辑一直在跑,能听到音效、能按键切菜单、能进游戏。
- 背景在、玩家不在:不是“整个场景渲染坏了”,而是“特定 z 层的精灵没了”。
- 修改精灵大小、换纹理、换 atlas 都无效:说明问题不在精灵资源本身。
3. 谬误溯源:踩过的一串错误方向
以下排查方向都属于“公开说法/直觉猜测”,但最终都指向错误目标:
- 怀疑 z 轴极性:2D 里 z 正方向到底朝屏幕里还是屏幕外?以为玩家 z 写反、被背景挡住。
- 怀疑纹理 / atlas:sprite 的 texture handle 或图集 UV 有问题。
- 怀疑视锥剔除:给玩家加 NoFrustumCulling 组件,没用。
- 怀疑渲染顺序 / sorting:以为只是被别的层盖住。
4. 真正原因:相机的 near 平面(近裁剪面)
真正的原因不是上面任何一个,而是相机的 near 平面(近裁剪面)。
先看 Bevy 0.14 里两组默认值的差别,问题就出在这里:
- OrthographicProjection 自身的默认值:near 为 0.0(注意是 0)。
- Camera2dBundle 的默认值:near 为 -1000.0(Camera2dBundle 专门为 2D 场景覆盖了投影默认值)。
很多开发者以为手动写 OrthographicProjection { ..default() } 就等同于 Camera2dBundle 的默认投影,其实不然。..default() 填充的是 OrthographicProjection 自己的默认值,near 是 0.0,而不是 -1000。
常见的写法(会出现精灵不显示):
commands.spawn(Camera2dBundle {
projection: OrthographicProjection {
scaling_mode: ScalingMode::FixedVertical(600.0),
..default()
},
..default()
});
推荐的写法(显式给出 near):
commands.spawn(Camera2dBundle {
projection: OrthographicProjection {
scaling_mode: ScalingMode::FixedVertical(600.0),
near: -1000.0,
..default()
},
..default()
});
near=0.0 意味着近裁剪面正好压在屏幕平面 z=0 上,z≥0 的精灵(在屏幕平面“后方”)全部被裁掉。本项目的玩家、敌人、护盾、子弹都在 z≈+0.8~+1.0,所以全部不可见;背景 z<0,落在裁剪范围外,正常显示。
一句话总结:不是精灵的问题,是相机把 z≥0 的世界整个“看不见”了。
5. 解决方案
修复方式很简单:在手动构造 OrthographicProjection 时,显式设置 near 为负值,例如:
commands.spawn(Camera2dBundle {
projection: OrthographicProjection {
scaling_mode: ScalingMode::FixedVertical(600.0),
near: -1000.0,
..default()
},
..default()
});
这样近裁剪面回到 z=-1000,z≥0 的精灵就能正常渲染了。
6. 经验总结
这个问题的核心教训是:在使用 ..default() 构造投影时,务必检查 near/far 是否被重置为默认值。Bevy 的 Camera2dBundle::default() 和 OrthographicProjection::default() 的 near 值并不一致,前者是 -1000,后者是 0.0。手动构造投影时,默认值可能与你预期的相机行为不同。
排查过程分享:
- 用诊断网格在 z 轴不同深度摆放测试块(z 从 -2 到 +3,每隔 0.5 一层),观察哪些层能显示。
- 结果显示:z 在 -0.9 到 -0.1 之间的测试块正常显示,z 大于等于 0 的全部不显示。
- 给测试块加上 NoFrustumCulling 组件,排除了视锥剔除的干扰,现象不变,说明不是视锥剔除的问题。
- 阅读 Bevy 源码后发现 OrthographicProjection 的默认 near 就是 0.0,结构体文档里也提示过手动初始化投影要留意默认值。
- 把 near 改成 -1000.0 之后,z 在 +3.0 以内的精灵都能正常显示。
边界条件:
- 只有手动构造投影并且依赖 ..default() 时才会触发,直接使用 Camera2dBundle::default() 不会。
- near 为 0.0 时,屏幕平面(z=0)及之后的精灵会被近裁剪面裁掉,所以背景(z 小于 0)正常显示、前景精灵(z 大于等于 0)全部消失,这个现象很有辨识度,遇到基本可以立刻联想到相机 near 的问题。
建议在项目中统一封装相机创建函数,显式指定 near/far,避免类似问题再次发生。
7. 落地结论:可复用规则与速查清单
把这次踩坑沉淀成几条可以直接复用的规则,方便以后遇到类似问题快速定位。
可复用规则
- 在 Bevy 2D 里,只要手动构造 OrthographicProjection(比如改 scaling_mode、改 viewport、改投影边界),必须显式写 near: -1000.0(或任意负值)。不要指望 ..default() 帮你继承 Camera2dBundle 的 near。
- 一眼识别的症状:背景(z<0)在、玩家/敌人/子弹(z≥0)全不在,大概率就是相机 near 被重置成 0.0。
- 这个坑 headless(离屏渲染)和桌面窗口版都会踩,与平台无关。
适用范围
- Bevy 0.14(0.13 及更早版本默认值相同,同样适用)。
- 任意用 Camera2dBundle + 手动改投影的 2D 项目。
人类开发者速查自查三步
对话式 AI 速查一句话问法
“Bevy 2D 里背景正常但玩家/敌人精灵全不可见,怎么办?”回答要点:检查手动构造的 OrthographicProjection 是不是被 ..default() 把 near 重置成 0.0;手动改投影时显式补 near: -1000.0,问题即解。
代码 Agent 速查
可执行自查命令,先定位相机初始化位置:
grep -rn "OrthographicProjection" src/ | head
修复动作:在投影结构体里补一行 near: -1000.0,改完 cargo check 并跑一遍,确认玩家/敌人可见后再提交。

