Crates.io | extol_sprite_layer |
lib.rs | extol_sprite_layer |
version | 0.6.0 |
source | src |
created_at | 2023-04-30 05:36:29.173651 |
updated_at | 2024-07-07 00:40:26.065832 |
description | Explicitly-defined sprite layers for Bevy, including automatic y-sorting. |
homepage | |
repository | https://github.com/deifactor/extol_sprite_layer |
max_upload_size | |
id | 852575 |
size | 133,946 |
extol_sprite_layer
lets you specify the drawing order for sprites in your Bevy game using a separate component rather than via the z-coordinate of your transforms.
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 });
Broadly speaking, it does the following:
Last
schedule, it Sets the z-coordinate on the GlobalTransform
(and not the Transform
) for every entity with a layer (and their descendants)First
schedule, it re-zeros their GlobalTransform
's z-coordinate.This works because subapps are all run after your main app's Main
schedule.
This is ugly, and may break things in subtle ways, but it has the properties that:
When making a 2D game in bevy, the z-coordinate is essentially used as a layer index: things with a higher z-coordinate are rendered on top of things with a lower z-coordinate. This works, but it has a few problems:
The z-coordinate isn't relevant for things like distance or normalization. The following code is subtly wrong:
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()
}
The bug is that we normalize before truncating, so the z-coordinate still 'counts' for purposes of length. So if the source is at (0, 0, 0)
and the target is at (0, 1, 100)
, then we'll return (0, 0.001)
!
Entities on the same layer are drawn in an effectively arbitrary order that can change between frames. If your game can have entities on the same layer overlap with each other, this means entities will 'flip-flop' back and forth. This is distracting. The usual solution is y-sorting, where entities on the same layer are sorted by their y-coordinate, so enemies lower on the screen are drawn on top. Here's an example from my WIP game tengoku, which is what led me to develop this crate. In both images, all the enemies (the blue 'soldiers') are on the same layer. In the first one, enemies are drawn roughly in spawn order, which makes the pile appear disorganized and unnatural. The second is y-sorted, resulting in a much cleaner-looking pile. (The red robot in the center, representing the player, is on a higher layer in both cases.)
If y-sorting is enabled (the default), this plugin is O(N log N)
, where N
is the number of entities with sprite layers. In benchmarks on my personal machine (a System76 Lemur Pro 10), with 10000 sprites, the plugin added about 600us of overhead with y-sorting; Disabling the rayon
feature (or running on a single-threaded runtime) roughly doubles this.
If y-sorting is not enabled then the overhead is O(N)
and not significant enough to worry about.
Feel free to ping me in one of the channels on the Bevy Discord server; I'm @Sera.