godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.13k stars 93 forks source link

Add "Sorting layers" in addition to z-index #3971

Open Wokarol opened 2 years ago

Wokarol commented 2 years ago

Describe the project you are working on

Space farming game with 2D top down graphics

Describe the problem or limitation you are having in your project

let's start with a potential scenario showing this issue:

Let's assume we have a platformer with side view and a map split into chunks. The map is mainly composed of background elements, middleground elements and foreground elements. Each chunk of the map is a separate scene we can load as needed. Currently, the game has 3 chunks loaded which is visible in the tree like so:

World
    Blacksmith (scene)
        Background Machines
        Middleground Anvils
        Foreground Pipes
    Outdoors (scene)
        Background Hills
        Middleground Houses
        Foreground Bushes
    Forest (scene)
        Background Trees
        Middleground Stumps
        Foreground Leaves

We should first render background, then middleground, then foreground elements. Currently this can be achieved by either using

Mainly, z indexes are problematic when we want to change order of elements. If we, for example, give all the background elements z index of -1, it all works fine. But when we decide to add a layer that goes between Middleground and Background, we have to go back to all the chunks and change that.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

This proposal solves this problem by adding layers. They won't change the way objects are sorted on z axis but allow for creating groups of object infinitely far on the z axis. Allowing for simple change of layers' order and similar.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

To make that possible, instead of current "Z Relative" toggle, There would be option to select what the index is relative to. User will be able to choose "Base" layer, a built-in layer that cannot be removed. "Parent" which will inherit layer and z index from the parent or any other custom layer. Optionally there could also be "Paren't Layer" which inherit layer but not the z index but I don't see the practical reason yet.

Then when comparing, instead of checking which of the objects has bigger z index, first the engine will check their layers.

If this enhancement will not be used often, can it be worked around with a few lines of script?

Partially. There is a possibility of creating a script with similar functionality but such script will provide annoying at best editor workflow so this is far from ideal workaround.

It can also be worked around by using z-index with bigger spacing, like choosing -100 instead of -1 for a background. But that can only go so far and will still be prone to the same issues after a while.

Is there a reason why this should be core and not an add-on in the asset library?

Making it a core feature would provide the best editor workflow and would replace the current editor elements related to z index. Doing the same with an add-on or asset would be problematic, if even possible.

The solution also does not bloat the editor much.

neikeq commented 2 years ago

I was just writing a proposal about a similar thing (although mine is about Sprite3D).

Sorting layers would really improve usability of Z-index. Unity has similar settings:

Screenshot from 2022-02-17 03-21-55

"Order in Layer" there would be the equivalent of Z-index, although theirs range from -32768 to 32767.

Other than what's already mentioned here, another advantage is the ability to easily re-order these layers. This would be a very exhausting task with Z-index.

https://user-images.githubusercontent.com/7718100/154395203-36e6031f-0ca4-41a7-8132-045842126546.mp4

It can also be worked around by using z-index with bigger spacing, like choosing -100 instead of -1 for a background. But that can only go so far and will still be prone to the same issues after a while.

Not only that, but Z-index can only range from -4096 to 4096. That may seem more than enough, but for games with a lot of small assets that make bigger ones, you're going to run out of indices.

golddotasksquestions commented 2 years ago

I don't get it. What you are describing sounds to me exactly like the existing ParallaxBackground+ParallaxLayer / CanvasLayers. (ParallaxBackground is just a CanvasLayer node with added functionality)

While I was reading this proposal, I constantly thought "What's wrong with using PrarallaxLayers or CanvasLayers?" You gave this answer:

Canvas Layers, which are not a good option in this case due to scene structure

Which for me explains nothing at all. Why are CanvasLayers not a good option "due to scene structure"? In which case is a scene structure any hindrance to any of this?

To me it sounds like CanvasLayers do exactly what you are proposing here.

zindex has a different purpose. Just to reiterate, 2D sorting in Godot is handled as follows: CanvasLayers > overrules_ > zindex > overrules > Ysort > overrules_ > scene tree hierarchy

You still can use zindex or Ysort or scene tree hierarchy within_ CanvasLayers to do more granular sorting.

Wokarol commented 2 years ago

Why are CanvasLayers not a good option "due to scene structure"?

Okay, let me explain this a bit more @golddotasksquestions. To make this work with Canvas Layers, the scene would have to be structured like so:

World
    Background # CanvasLayer
        Background Machines
        Background Hills
        Background Trees
    Middleground # CanvasLayer
        Middleground Anvils
        Middleground Houses
        Middleground Stumps
    Foreground # CanvasLayer
        Foreground Pipes
        Foreground Bushes
        Foreground Leaves

Now, taking into consideration that Machines, Anvils and Pipes are part of a single scene (a single file) you would have to either create 3 files blacksmith_background_chunk.tscn, blacksmith_middleground_chunk.tscn, blacksmith_foreground_chunk.tscn which would be awful for the workflow as you would have to work with those 3 scenes separately making level creation harder. Or you would have to create a script which would load a scene, split it into layers and parent to corresponding CanvasLayer nodes.

golddotasksquestions commented 2 years ago

@Wokarol This is where you are mistaken. You can have exactly the scene structure you want. All of this is already supported. Just instance your chunks as you please one after another:


World
    Blacksmith (scene)
        Background Machines (on CanvasLayer -1)
        Middleground Anvils (on CanvasLayer 0. No need to add a as a node since everything is by default on CanvasLayer 0)
        Foreground Pipes (on CanvasLayer 1)
    Outdoors (scene)
        Background Hills (on CanvasLayer -1)
        Middleground Houses (on CanvasLayer 0. No need to add a as a node since everything is by default on CanvasLayer 0)
        Foreground Bushes (on CanvasLayer 1)
    Forest (scene)
        Background Trees (on CanvasLayer -1)
        Middleground Stumps (on CanvasLayer 0. No need to add a as a node since everything is by default on CanvasLayer 0)
        Foreground Leaves (on CanvasLayer 1)
Zireael07 commented 2 years ago

@golddotasksquestions: Looks like CanvasLayers are another thing that seems to be not documented enough...

Wokarol commented 2 years ago

@golddotasksquestions, on first glance that might look like a solution. But consider this simpler example:

World
    Blacksmith
        Background Machines # CanvasLayer 1
            Machine # Z-index 0
            Pipe # Z-index -2
    Outdoors 
        Background Hills # CanvasLayer 1
            Some Bush # Z-index -1
            Far Hill # Z-index -3

The expected rendering order for me would be:

  1. Machine
  2. Some bush
  3. Pipe
  4. Far Hill

    Which is not the case because Canvas Layers render "at once" as in, you cannot have two Canvas Layers that render between each other.

    On top of that, Canvas Layers don't inherit from Node2D which means that if I move the Outdoors chunk, the bush and hill won't follow that.

Wokarol commented 2 years ago

Also, that being said. I could not find a rule when it comes to which of the two Canvas Layers render first. And I cannot find anything about it in the documentation.

golddotasksquestions commented 2 years ago

@Wokarol Oh you are indeed correct, I can replicate your result! I think this is a bug though. If you sort two or more objects on the same Layer I would expect them to be sorted together.

Wokarol commented 2 years ago

If you sort two or more objects on the same Layer I would expect them to be sorted together

@golddotasksquestions, while this would solve some problems. It still does not take into the consideration passing the transformation further. If there was some CanvasLayer but as a 2D Node, I would take a step back and turn this proposal into "please add editor-modified enum to Canvas Layer over the integer it currently uses".

eerbin13 commented 1 year ago

On top of that, Canvas Layers don't inherit from Node2D which means that if I move the Outdoors chunk, the bush and hill won't follow that.

Exactly the issue I just ran into. Trying to do level chunking and my chunks each have TileMaps on their own CanvasLayers.

So each chunk has multiple CanvasLayers. And since it doesn't inherit Node2D, changing the chunk position does not position the child CanvasLayers or their TileMaps.

I'm guessing I'll have to either make my level loading code break chunks up and instance different TileMaps on different "main scene" CanvasLayers as was a suggested solution (needless complexity IMO) , or somehow do without them entirely.

I'm leaning towards the latter. Any thoughts? What was your solution, @Wokarol ?

Wokarol commented 1 year ago

@eerbin13, well, as an experienced hobbyist I switched the project before it became a really problematic issue.

Jokes aside, I would probably lean towards having a single Position2D node under CanvasLayer node for moving and a script that follows a parent's parent with relative offset. Or something similar. Especially that in your case it looks like a runtime issue and not one you have in editor.

Alternative is to use z index "far far away", both solutions are kinda... meh.

With my limited experience I don't see much more you could do to work around this problem.

dalexeev commented 1 year ago

The problem with z_index is that it is global within the CanvasLayer. The RenderingServer (VisualServer in 3.x) has a draw index that works like z_index but within the parent. However, this is a more low-level abstraction layer than the scene system: changes to draw index via script are reset when the order of nodes in the parent is changed.

Related:

stephanbogner commented 3 months ago

@Wokarol This is where you are mistaken. You can have exactly the scene structure you want. All of this is already supported. Just instance your chunks as you please one after another:


World
    Blacksmith (scene)
        Background Machines (on CanvasLayer -1)
        Middleground Anvils (on CanvasLayer 0. No need to add a as a node since everything is by default on CanvasLayer 0)
        Foreground Pipes (on CanvasLayer 1)
    Outdoors (scene)
        Background Hills (on CanvasLayer -1)
        Middleground Houses (on CanvasLayer 0. No need to add a as a node since everything is by default on CanvasLayer 0)
        Foreground Bushes (on CanvasLayer 1)
    Forest (scene)
        Background Trees (on CanvasLayer -1)
        Middleground Stumps (on CanvasLayer 0. No need to add a as a node since everything is by default on CanvasLayer 0)
        Foreground Leaves (on CanvasLayer 1)

@golddotasksquestions Your first reply sounded quite mean tbh but thanks for this one. It explains exceptionally well how to structure more complex scenes and also explains the layer concept really well 👍❤️

AThousandShips commented 3 months ago

Those comments aren't by Wokarol, you're tagging the wrong person

stephanbogner commented 3 months ago

Those comments aren't by Wokarol, you're tagging the wrong person

@AThousandShips Thanks for the info, fixed it now.

Wokarol commented 3 months ago

@stephanbogner, keep in mind that as seen in the discussion after that message, this solution does not exactly solve the problem because two Canvas Layers won't mix together even if they have the same index