godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.12k stars 69 forks source link

Tilemap functionality that allows setting multiple layers at once #5412

Open yhwhey opened 2 years ago

yhwhey commented 2 years ago

Describe the project you are working on

A 2D grid-based tactics game with heavy used of layered graphics to create depth (Godot 4 latest alpha)

Describe the problem or limitation you are having in your project

This is specifically a level design workflow problem although could be extended to be useful during game execution.

I have several tilemap layers for allowing entities to be sandwiched between background and foreground graphics layers For example some of the layers i have which are sorted by y offset: GROUND GROUND_DECORATION (e.g. long_grass_background) EFFECT <----- entities go here GROUND_DECORATION_FOREGROUND (e.g. long_grass_foreground) EFFECT_FOREGROUND

The tile sprites that are used for the background and foreground are related, they are always set together. What this means is if i want to place some long grass in a map, I need to manually set the GROUND_DECORATION layer with the background sprite and the GROUND_DECORATION_FOREGROUND layer with the foreground sprite. This is an extremely tedious and error prone process.

I have considered using the tileset custom data to associate the fore and back ground sprites and when the background sprite is set, use a tool script to automatically set the foreground sprite on the correct layer. However, as far as I can tell there is no facility for custom behaviour when setting a cell via the editor. I tried overriding the set_cell method but this is not possible see 65685

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

I'm not sure the best way to achieve this but I can see a couple of options. I'm sure there are others

  1. Provide a signal for when a cell is changed
  2. Allow overriding of the set_cell function

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

implement a tilemap_cell_set signal which is emitted when a cell is set. not sure how performant this would be if setting lots of tiles all at once

Or

Allow overriding the set_cell method

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

I have spent time investigating and researching how this could otherwise be achieve and I couldn't find a way to do it

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

This facility would be widely useful for triggering custom logic based on setting tiles

KoBeWi commented 2 years ago

You mean you want to place the same tile on 2 layers? But wouldn't one layer be fully covered by the higher one? Some images could be helpful.

Also your custom data approach is possible if you don't need the tiles in the editor or at least not immediately. You can iterate all placed tiles with a script and mirror them on another layer.

yhwhey commented 2 years ago

Screenshot 2022-09-13 at 10 24 13 AM

This screenshot should help illustrate what I mean. the leftmost tile is composed of 3 separate sprites across 3 layers. the entity sits between those. you can see the two separate sprites required. the two 'grass' sprites are always set on the same tile together and must be on different layers

KoBeWi commented 2 years ago

I came up with this script:

@tool
extends TileMap

var UPPER_LAYER = 1
var LOWER_LAYER = 0

var previous_used_cells: int

func _ready() -> void:
    if Engine.is_editor_hint():
        create_tween().set_loops().tween_callback(mirror_cells).set_delay(0.5) # Avoid updating too often.

func mirror_cells():
    var used_cells = get_used_cells(LOWER_LAYER)
    if used_cells.size() != previous_used_cells:
        clear_layer(UPPER_LAYER)

        previous_used_cells = used_cells.size()
        for cell in used_cells:
            var tile = get_cell_atlas_coords(LOWER_LAYER, cell)
            if tile == Vector2i(0, 0):
                set_cell(UPPER_LAYER, cell, 0, Vector2i(1, 0))

https://user-images.githubusercontent.com/2223172/189772722-439f73df-439c-4559-aaa7-ed7e8c78d407.mp4

yhwhey commented 2 years ago

This is a great solution thanks. Will definitely make use of it. It is a workaround though and I wonder if there is value in having a more obvious solution built into the tilemap node

groud commented 2 years ago

This proposal is interesting but seems like quite a specific need, and would be very tedious to implement. It's likely better as a plugin unless a lot of people ask for it.

yhwhey commented 2 years ago

My use case is quite specific for sure. Generalising it to: "enabling arbitrary code to be executed when a cell is set" would have broad usage imo

alazifk commented 1 year ago

@yhwhey Tilemap.changed is emitted when a cell is set (which its description does not make clear).

Not great performance wise, though. I think either a private function like "_on_set_cell" or emitting a signal would be good (maybe you want to do things like deleting or moving other nodes if a wall is placed on top of them). I'd like a signal that emits a list of changed cells best, because you can change multiple tiles per frame and that would be better performance-wise if you want to do something like custom auto-tiling.