Explicitly set sprite layers for sprites in Bevy games.

Overview

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.

Motivation

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)!

If you have entities that are children of other entities, the parent's transform propagates to the child. Sometimes this is what you want, but if it isn't, it leads to things being on the completely wrong layer.

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.)

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

How to use

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_plugin(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 });

Performance

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.

Known issues

  • Currently this only supports sprites (i.e., not meshes).
  • For performance reasons, y-sorting sorts all entities at once. This means that if the product of a layer's z-coordinate with the number of sprites is larger than 2^23 or so, you can run into floating point precision issues.
  • Sprite layers do not propagate.

Help

Feel free to ping me in one of the channels on the Bevy Discord server; I'm @Sera.

You might also like...
Bevy virtual Joystick for mobile games (Works with mouse on desktop)
Bevy virtual Joystick for mobile games (Works with mouse on desktop)

Bevy Virtual Joystick Create and use a Virtual Joystick in a UI for bevy Game Engine. Versions Aviable and compatible versions bevy VirtualJoystick 0.

Minecraft-esque voxel engine prototype made with the bevy game engine. Pending bevy 0.6 release to undergo a full rewrite.
Minecraft-esque voxel engine prototype made with the bevy game engine. Pending bevy 0.6 release to undergo a full rewrite.

vx_bevy A voxel engine prototype made using the Bevy game engine. Goals and features Very basic worldgen Animated chunk loading (ala cube world) Optim

bevy-hikari is an implementation of voxel cone tracing global illumination with anisotropic mip-mapping in Bevy
bevy-hikari is an implementation of voxel cone tracing global illumination with anisotropic mip-mapping in Bevy

Bevy Voxel Cone Tracing bevy-hikari is an implementation of voxel cone tracing global illumination with anisotropic mip-mapping in Bevy. Bevy Version

A simple extension for `bevy-editor-pls` to support tilemap editing right inside the bevy app.

What is this This is a simple tilemap editor plugin, that hooks right into bevy_editor_pls to work with bevy_ecs_tilemap. It works completely within i

Bevy Simple Portals is a Bevy game engine plugin aimed to create portals.
Bevy Simple Portals is a Bevy game engine plugin aimed to create portals.

Portals for Bevy Bevy Simple Portals is a Bevy game engine plugin aimed to create portals. Those portals are (for now) purely visual and can be used t

Minecraft using Bevy and Bevy-Meshem

minecraft_bevy Minecraft_bevy was built to showcase bevy_meshem. After a week of developing it has: Chunk loading / unloading each chunk's mesh is bei

A frontend to Assets purchased on Epic Games Store
A frontend to Assets purchased on Epic Games Store

Epic-Asset-Manager A frontend to Assets purchased on Epic Games Store Current Screenshot Install Arch Linux Use the AUR package Build flatpak meson _b

A single-threaded polling-based Rust async executor suitable for use in games, embedded systems or WASM.

simple async local executor An Enlightware® software. Overview A single-threaded polling-based executor suitable for use in games, embedded systems or

A framework for making games using Macroquad.

Omegaquad A framework for making games using Macroquad. After writing maybe 5 games and finding myself always going to the previous project to copy-pa

Comments
  • Generalize layer index computation beyond a single component

    Generalize layer index computation beyond a single component

    Example use cases include 'this is flying' or whatever. You can emulate this with a compound struct but an ECS approach might be cleaner. The most general version is anything that implements Query; unclear if I want to support that, and it makes the types harder to reason about. But they flying use case is kind of compelling.

    opened by deifactor 0
  • Support for Mesh2D

    Support for Mesh2D

    Just run after extract_mesh2d. Benchmark whether recomputing the inverse transform every time is worth it relative to possibly doing some fancy math (it probably isn't).

    opened by deifactor 0
Owner
Ash
robot girl // she/her
Ash
A light-weight Anchor-Offset based 2D sprite rendering system for the bevy engine.

Bevy AoUI A light-weight anchor-offset based 2D sprite layout system for the bevy engine. Bevy AoUI provides a light-weight rectangular anchor-offset

Mincong Lu 4 Nov 22, 2023
Materials for sprites in Bevy

bevy_sprite_material Materials for sprites in Bevy This bevy plugin changes the bevy_sprite implementation using a material. You might be interested i

Félix Lescaudey de Maneville 6 Aug 21, 2022
Use sprites in a 3d bevy scene.

bevy_sprite3d Use 2d sprites in a 3d bevy scene. This is a pretty common setup in other engines (unity, godot, etc). Useful for: 2d games using bevy's

null 46 Apr 24, 2023
🤹‍ 2D sprite rendering extension for the specs ECS system

specs-blit 2D sprite rendering extension for the Specs ECS system. All sprites are loaded onto a big array on the heap. Example // Setup the specs wor

Thomas Versteeg 8 Aug 14, 2022
A tool to generate inbetweens for animated sprites, written in godot-rust

Bitmapflow is a tool to help you generate inbetweens for animated sprites. In other words, it makes your animations smoother. It uses optical flow to

null 411 Dec 21, 2022
A Rust library for blitting 2D sprites

blit A Rust library for blitting 2D sprites Documentation Usage Add this to your Cargo.toml:

Thomas Versteeg 23 Dec 6, 2022
🎨 Procedurally generate 2D sprites

sprite (Executable) Run On Linux you need the gtk-rs dependencies to compile: cargo install sprite sprite This should produce the following window: s

Thomas Versteeg 68 Nov 25, 2022
An ergonomic physics API for bevy games.

Heron An ergonomic physics API for 2d and 3d bevy games. (powered by rapier) How it looks like fn main() { App::build() .add_plugins(DefaultPlug

Jonathan Cornaz 313 Dec 16, 2022
A simple camera for properly displaying tile-based low resolution pixel perfect 2D games in bevy.

Bevy Tiled Camera A simple camera for properly displaying low resolution pixel perfect 2D games in bevy. The camera will adjust the viewport to scale

sark 10 Oct 5, 2022
A framework for building adventure games in Bevy.

Bevy_adventure A framework for building 3d adventure games in Bevy. preview.mp4 Features Interactive trait is the backbone of the framework, allowing

Hank Jordan 9 Jan 5, 2023