Description
What problem does this solve or what need does it fill?
Currently, we can control the relative position of 2D sprites by manually setting their z-value. This technically works, but requires setting and updating magic numbers scattered throughout the codebase.
When creating a game, you'll often want to specify things like "all in-game UI elements occur on top" or "units are drawn above terrain". Layers provide a sensible, comprehensible way to think about this in a single place.
What solution would you like?
To me, a good solution to this has the following properties:
- A single, central source of truth which controls the relative positions of different varieties of sprites.
- Easy initialization of freshly created sprites on a specific layer.
- Ability to move sprites between layers. You should be able to move sprites to a specific layer, up / down one layer, and to the top or bottom-most layer.
- Support for nested sublayers.
- Changing the position of one layer doesn't require changes to any other layer.
- Layer names must be unique.
I'm not exactly sure what such a solution would look like in practice; I plan to explore it in the comments below. Others are very welcome to comment with proposed solutions as well.
In addition, we need explicit, well-documented rules for what happens to sibling sprites that overlap and share a z-value.
What alternative(s) have you considered?
If a good-enough solution can be created without touching engine code, adding an example to the examples folder would likely be entirely sufficient to resolve this issue.
Additional context
This could be useful for customizing the ordering of various UI elements as well (see #254), depending on the exact pattern we use for constructing it. A builder pattern can obviate the need for this (especially if we have a single rooted tree), but 2D graphics can be used to create sprites in much more diverse fashions.
Activity
alice-i-cecile commentedon Jan 21, 2021
Enum-based layers
The natural starting point is an
enum, as we have a fixed number of cases to handle. If we deriveOrd, we get a nice automatic ordering that follows the order laid out in the code. Add in a nice function to convert that ordering to a z coordinate value and I think we have a basic solution.This implies that we want a
Layerstrait, that users can derive for their own enum.Working through the criteria:
get_zmethod to theLayerstrait, or have aFromorIntoimpl to handle the conversion.layer_above,layer_below,top_layerandbottom_layermethod. We could use theDoubleEndedItertrait for this, which would get us all that functionality and more.Conclusions
alice-i-cecile commentedon Jan 21, 2021
Double-ended struct layers
Building on the idea that most of the functionality that we want is covered by the
DoubleEndedItertrait, we could instead use aLayersstruct that uses something likeVecDequefor its internal storage.Criteria:
Layersstruct with global visibility (probably as a resource?), and then manually construct the list with string literals.VecDeque, but that's not too bad since performance concerns are completely negligible.Conclusions
2dLayersandUiLayersstructs though.alice-i-cecile commentedon Jan 21, 2021
HashMap-based layers
The idea is obvious: store a HashMap from
Layertof32(the z value), then look it up when you want to use it.Criteria:
Conclusions
alice-i-cecile commentedon Jan 21, 2021
This is discussed further in #1211, with a working implementation for UI only.
tigregalis commentedon Jan 21, 2021
I made a proof of concept that involved automatic subdivision of Z-space evenly among its children (and their children, and so on).
The API was a
Layercomponent to which you gave it a specific "thickness" (defined in terms of its "front" and "back" Z coordinates in world space), and a "depth" (its order relative to its siblings), and used theParent/Childrenrelationships to subdivide that thickness among each of its children, and the order defined by the depth for the children, to update the entity'sGlobalTransformcomponent.Something like:
An illustration of this (before I tried implementing it, so not exactly what I made):

Cons are that it wasn't particularly ergonomic (I might have avoided using
Parent/Childrelationships and instead defined my own relationships), doesn't easily address each of your 6 criteria (but could probably be adapted to), and you'll eventually run into floating point precision issues with enough nesting of layers (maybe around 20 levels deep?), plus I wasn't sure of the performance.However, I'm wondering now whether this can simply be data passed to and processed by the shader instead, i.e. just provide the metadata and let the GPU paint the layers correctly. I think this is feasible, and would be more performant than the other solutions, and may inform a possible API.
Davier commentedon Jan 21, 2021
That was tried in #1211 and the floating points precision issues already appeared for about 10 layers (with 100 to 150 entities). I don't think it's viable, unfortunately.
tigregalis commentedon Jan 21, 2021
What approach do you make use of now?
Davier commentedon Jan 22, 2021
Entities are part of stacking contexts. In the default case a context contains an entity and its children, but it can get arbitrarily complex using the
ZIndex::Autoproperty. Stacking contexts are cached, and rebuild/sorted when any entity that is part of it changes. When any context is changed, the z transform of all UI entities is reset, each at a constant interval over the previous. I could not find a simple way to make incremental updates to transforms.Kolsky commentedon Mar 15, 2021
May I ask how nested sublayers are laid out? Can they be nested many levels deep or only one level, what relations exist between children and parent except additional convenient separation, may a parent be a logical element of its children set or it should have a special treatment? Sorry for dumb questions, I'm not on good terms with UI, but they're important for me in implementing enum-based PoC.
alice-i-cecile commentedon Mar 15, 2021
@Kolsky these are great questions! Let me think about the options:
As a heads up, I expect that relations (vaguely described in #1627 and #1527) will serve as an excellent tool for implementing this, although prototypes are very welcome to see if it's worth pursuing. In discussion with @BoxyUwU, the general approach would be:
Layermarker component and a niceLayerLabelfield (see e.g.SystemLabelfor how this should be done).Spritecomponent also has anInLayerrelation that points to a specific layer.RootLayermarker component, marking the root of the layer tree.Layerentity such asTransparencyto set properties of all entities associated with that layer, and extend this behavior with your own systems.For now, you'd be able to comfortably implement the same design using components that wrap an
Entity(see Bevy's parent-child code for an example), rather than the yet-to-be-writtenRelationtype, and easily migrate once Relations land.Kolsky commentedon Mar 19, 2021
So I've made a procedural macro for enums, it's available at https://github.com/Kolsky/syn_derive_layers.
You can nest it as much as you need to by deriving
Layers, which can be applied to enums with unit (has the name only) or unnamed 1-tuple variants containing otherLayersenum, and the trait itself is unsafe to implement manually. Also there is aRootmarker trait, it can only be applied at the top level. The first enum variant, if any, can only be the unit one, if enum isn'tRoot. With that saying:Layersautomatically getsfnsto_numandtry_from_num: they assign ordinal number based on field order, as you would expect from generalized numeral system. Note that, however,A::C(C::E).to_num()fromenum A { B, C(C), D }isn't equal toC::E.to_num()fromenum C { E, F }, as there is no assumption whetherCis contained inAor any other enum.Tisn't marked asRoot, its first variant is a parent layer which can be constructed withT::try_from_num(0).unwrap().T: !is exception, though it cannot be constructed anyway.Layersfns are essentially pure and the size is constant, array mapping can be easily built if you would requireCopyfor it.get_zis fairly trivial withf32::from_bits. Compile error messages may be a bit cryptic at times, but I've tried to make them clear enough. The rest is also trivial, as @alice-i-cecile have said already. Hope this helps.Davier commentedon Mar 19, 2021
I think using
enums is problematic since they are static, we should probably support a dynamic number of layers. I would even argue for a total ordering between every Sprite entity to ensure there is no z fighting.Do we want to use the same z ordering system for sprites and UI? If so an implementation of the z-index property like in #1211 is a good candidate.
9 remaining items
colepoirier commentedon Mar 29, 2022
I came across a user-facing API design I really like here:
Upon seeing this I immediately thought that this would in fact be the optimal Layer/z-index ordering design for our user facing API. What do you think?
I'm not convinced that
otheris the optimal method name; my strong positive sentiment here is focused on the simpleAbove|Belowrelative ordering API.jomala commentedon Oct 27, 2022
Is there not also a requirement that I don't think has been mentioned, that the z allocated to the layer should not change between frames unless the layers is moved (or removed)? If that's right, I don't think that the allocation of values is the issue - the question is just how to set up the interface so that it's easy to work with, which is probably as hard/easy as other problems like handling named phases.
The one extra problem what to do in really offbeat scenarios when you're getting close to the smallest differences that f32 can represent, but doing an emergency reallocation of depths in that case is probably sufficient.
arnemileswinter commentedon Nov 9, 2022
The way i solved Pokémon-gen-3-style Y-sorting in 2D was the following:
Decide which layers you have, for me it was
GROUND_LAYER = 0
UNIT_LAYER = 1
SKY_LAYER = 2
UI_LAYER = 3
Notice that theres a maximum of 8 (maybe even less due to visual debuggers / UI)
now create a camera for each of them. Make sure to add a RenderLayer::layer(XXX_LAYER) Component for each camera.
Adjust the camera's priority to be the same as your XXX_LAYER so that the images stack upon another.
Make sure that your non-0 priority cameras have no ClearColorConfig, else you'll only see the highest-priority camera.
Now my floor tiles also get the RenderLayer::layer(GROUND_LAYER) component, units and sky objects accordingly.
For Y-Sorting on my UNIT_LAYER i simply created a system that aligns the translation's z axis with the y axis. something like:
Query<&mut Transform, (Changed<Transform>, With<YSorted>)>. When querying them, set your entity'stransform.translation.z = transform.translation.y.Ygg01 commentedon Dec 28, 2022
Has there been any ideas how to solve this issues? Was hoping to use this for isometric 2d game.
magras commentedon Aug 10, 2023
Not sure what relation it describes. There is neither equality (for total order) nor incomparability (for partial order). I think this will work only in the simplest cases.
I'm not an expert, but after some research I believe there are two main techniques to draw a complex sprite scene:
Total order doesn't work because sometimes you need a third sprite, which will tell that it's behind this one and in front of that one, to order two directly incomparable sprites.
deifactor commentedon Aug 14, 2023
Being a little cheeky here and plugging my extol_sprite_layer crate, which lets you specify the layer as a type that's convertible to an
f32and also optionally does y-sorting.That being said, I'd like to see something land in Bevy itself for this. Rather than designing the one layer system to rule them all, I'd like to see one that handles one particular case well and see how that goes. I'm biased because of my use case, of course, but I think handling the 2d graphics sprite layer is simple: you don't need to arbitrarily subdivide layers or support runtime layer creation, so the simple enum-based approach works fine.
deifactor commentedon Aug 14, 2023
Also, re @magras, I'm not sure what you mean about a partial order creating cycles. A partial order can't have cycles by definition. The sequence produced by a topological sort can be non-unique if there are incomparable elements, but it'll still satisfy the property that if a < b, then a appears before b in the sort. If your sprite layers do have cycles then I don't think there's anything sensible to do.
magras commentedon Aug 14, 2023
@deifactor, yes, I was wrong. Didn't notice partial order requires transitivity too. Thank you for the correction.
I wanted to say that it's possible to describe a cyclic relation like red > green > blue > red which will break topological sort. That will require some handling in the engine and probably will create questions from unqualified users like me. You could say that the situation is no different from the total order, but I believe that it's well known that violating transitivity for total order will break sorting algorithms.
I'm not sure if topological sort and depth maps should be a part of the core, but I'm frustrated that what I imagined as a simple sprite game turned into tinkering with renderer, assets and manual sorting of sprites.
feelingsonice commentedon Dec 8, 2023
Any updates to this?
pablo-lua commentedon Feb 8, 2024
I'm thinking about this, and I think that might be good if we can set the Layer to be hidden or be show at anytime, simply by disabling the Layer somehow
like, hide(layer) would hide all the sprites that are in the given layer for example.
NthTensor commentedon Feb 14, 2024
Entity-Backed Layers
Layers are hard for the same reason that using 3d transforms for 2d games feels weird. My thesis is that we should introduce layers at the same time that we transition to a
Transform2d(see #8268). This opens up a big class of possibilities relating to how we even represent the location of entities in the first place.Here is my proposal: A layer is an entity with a component
depthindicates the z-distance between this layer and the one on top of it. Layers can have other layers as children, or entities with aTransform2dcomponent.The z-layout system walks down the layer tree and determines the actual z-depth of each entity:
Depth(f32)component that gets updated (much likeTransform/GlobalTransform).layer.depth.total_depth := self.depth + children.map(|c| c.total_depth()).sum().A separate system can then go through to propagate
Transform2dand exposeDepthin theGlobalTransform. If more than one root layer is detected we can emit a warning.Criteria
Yes. It's the ECS.
You use something like
layer.push_children(entity)You use something like
entity.set_parent(layer)Yes, it is tree-structured.
Yes, the z-layout system will move everything for you automatically.
This one is kinda iffy. Layers are uniquely identified by their entity, and you can attach whatever components you want to identify them. Use enums, use
Name. Its flexible.tbillington commentedon Feb 14, 2024
Being pedantic but I think this needs better specification, "All entities" is way too broad imo. eg Would this be restricted to entities with a
Transform2d, or both aTransform2dandVisibilityetc.NthTensor commentedon Feb 15, 2024
Fair point. I was envisioning the z-layout system would only operate on nodes with depth and layer/transform2d. We can use a similar thing to how transform works to ensure all ancestors of an entity with a depth also have a depth.
MushineLament commentedon Nov 30, 2024
I hope he can control the display order of all 'images', including' nodes', to replace the cumbersome 'Index'. And as a 'Component', it facilitates the modification of display order. (Machine Translation)
maybeLucas6y6 commentedon Sep 28, 2025
Hello :) any updates on this? Or can anyone recommend a temporary solution until this is merged?