#bevy #layer #sprite #gamedev #sorting #order #transform

extol_sprite_layer

为Bevy显式定义的精灵层,包括自动y排序

6个版本 (破坏性)

0.6.0 2024年7月7日
0.5.0 2024年4月5日
0.4.0 2024年2月18日
0.3.0 2024年1月16日
0.1.0 2023年4月30日

157游戏开发 中排名

Download history 1/week @ 2024-05-17 11/week @ 2024-06-21 7/week @ 2024-06-28 162/week @ 2024-07-05 6/week @ 2024-07-12

每月386次 下载

MIT/Apache

38KB
259

extol_sprite_layer 允许您使用单独的组件来指定 Bevy 游戏中精灵的绘制顺序,而不是通过变换的z坐标。

如何使用

use bevy::prelude::*;
use extol_sprite_layer::{LayerIndex, SpriteLayerPlugin, SpriteLayerOptions};

// Define a type to represent your layers. All the traits here other than Copy
// are mandatory.
#[derive(Debug, Copy, Clone, Component, PartialEq, Eq, Hash)]
enum SpriteLayer {
    Background,
    Object,
    Enemy,
    Player,
    Ui,
}

impl LayerIndex for SpriteLayer {
    // Convert your type to an actual z-coordinate.
    fn as_z_coordinate(&self) -> f32 {
        use SpriteLayer::*;
        match *self {
            // Note that the z-coordinates must be at least 1 apart...
            Background => 0.,
            Object => 1.,
            Enemy => 2.,
            // ... but can be more than that.
            Player => 990.,
            Ui => 995.
        }
    }
}

let mut app = App::new();
// Then, add the plugin to your app.
app
  .add_plugins(DefaultPlugins)
  .add_plugins(SpriteLayerPlugin::<SpriteLayer>::default());

// Now just use SpriteLayer as a component and don't set the z-component on any
// of your transforms.

// To disable y-sorting, do
app.insert_resource(SpriteLayerOptions { y_sort: false });

注意事项

总的来说,它执行以下操作

  1. Last 调度中,它为具有层的每个实体(及其后代)在 GlobalTransform 上设置z坐标(而不是 Transform
  2. First 调度中,它重新将它们的 GlobalTransform 的z坐标重置为零。
  3. 在这两种情况下,它都会 跳过变更检测

这是因为子应用都在您的应用程序的主 Main 调度之后运行。

这很丑陋,可能会以微妙的方式破坏事物,但它具有以下属性

  • 精灵层可以继承
  • 您的应用程序代码可以计算距离,而无需截断位移的z坐标(如果您不将z坐标重置为零,您就必须这样做)

动机

当在 bevy 中制作2D游戏时,z坐标实际上被用作层索引:z坐标较高的东西被渲染在z坐标较低的东西之上。这很有效,但也有一些问题

z坐标对于距离或归一化等事物并不相关。下面的代码有细微的错误

use bevy::prelude::*;

/// Get a unit vector pointing from the position of `source` to `target`, or
/// zero if they're close.
fn get_normal_direction(query: Query<&GlobalTransform>, source: Entity, target: Entity) -> Vec2 {
    let from_pos = query.get(source).unwrap().translation();
    let to_pos = query.get(target).unwrap().translation();
    (from_pos - to_pos).normalize_or_zero().truncate()
}

错误在于我们在截断之前进行归一化,因此z坐标仍然“计算”长度。所以如果源在 (0, 0, 0) 和目标在 (0, 1, 100),那么我们将返回 (0, 0.001)

同一层的实体以任意顺序绘制,这个顺序可能会在帧之间改变。如果你的游戏允许同一层的实体相互重叠,这意味着实体会在前后“翻转”。这很令人分心。常见的解决方案是Y排序,即同一层的实体按其Y坐标排序,所以屏幕上较低的敌人会先绘制。以下是我正在制作的游戏tengoku的例子,这也促使我开发了这个库。在这两张图片中,所有敌人(蓝色“士兵”)都在同一层。在第一张中,敌人按出生顺序大致绘制,这使得堆叠看起来杂乱无章。第二张是Y排序,结果是一个看起来干净得多的堆叠。(中间的红色机器人代表玩家,在两种情况下都在较高的层。)

non-y-sorted enemies piled up in a disorderly way y-sorted enemies in a much cleaner pile

性能

如果启用了Y排序(默认设置),则此插件的时间复杂度为O(N log N),其中N是具有精灵层的实体数量。在我的个人电脑(System76 Lemur Pro 10)上的基准测试中(10000个精灵),启用Y排序时,插件增加了大约600us的开销;禁用rayon功能(或运行在单线程运行时)大致将此数值加倍。

如果未启用Y排序,则开销为O(N)且并不足以担心。

已知问题

  • 出于性能考虑,Y排序会一次性对所有实体进行排序。这意味着,如果某一层的z坐标与精灵数量的乘积大于2^23左右,可能会遇到浮点精度问题。

帮助

请随时在Bevy Discord服务器的任一频道中联系我;我是@Sera。

依赖项

~23MB
~421K SLoC