欢迎光临
我们一直在努力

Bevy 0.14 相机 near 平面裁剪陷阱:为什么玩家、敌人、子弹全部消失

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 项目。

人类开发者速查自查三步

  • 看 spawn 相机的位置,是不是手动写了 OrthographicProjection 结构体。
  • 结构体里有没有显式 near 字段?没有就是 near=0.0。
  • 加上 near: -1000.0,重新编译跑一遍,玩家/敌人应该就出现了。
  • 对话式 AI 速查一句话问法

    “Bevy 2D 里背景正常但玩家/敌人精灵全不可见,怎么办?”回答要点:检查手动构造的 OrthographicProjection 是不是被 ..default() 把 near 重置成 0.0;手动改投影时显式补 near: -1000.0,问题即解。

    代码 Agent 速查

    可执行自查命令,先定位相机初始化位置:

    grep -rn "OrthographicProjection" src/ | head

    修复动作:在投影结构体里补一行 near: -1000.0,改完 cargo check 并跑一遍,确认玩家/敌人可见后再提交。

    赞(0)
    未经允许不得转载:171主机测评 » Bevy 0.14 相机 near 平面裁剪陷阱:为什么玩家、敌人、子弹全部消失
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址