godotengine / godot-proposals

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

An Improved 2D TileMapPattern System for Traditional Roguelike and Platformer devs #7591

Open MendicantNinja opened 1 year ago

MendicantNinja commented 1 year ago

Describe the project you are working on

A traditional roguelike where I plan to have dungeon generation similar to something like Zorbus: https://dungeon.zorbus.net/ . There is a list of specific types of rooms (patterns) that are placed at random to build a complex and most importantly, !FUN! dungeon. Here's an example of the process in psuedocode:

1) I place a square "starting" room with doors/exits on each of the 4 sides of the square. I reach into my bag of 100 rooms (patterns) and pick one of them at random to attach to the east side wall exit. These rooms all have varying sizes, shapes, and layouts. This room also has its own exits to attach rooms to.

2) I check if this room fits and won't overlap with any already existing rooms. If it does fit I place it and repeat the room placement process on another exit, if not I pick a different pattern to place. Try 10 times and then move on to the next exit if nothing fits. If nothing fits for the next 3 exits end dungeon generation.

3) Repeat for all the room exits until the dungeon is filled with rooms and the 3 exits failure condition is met.

Describe the problem or limitation you are having in your project

1) The current system used to organize patterns is not rearrangeable or organizable, this makes using the pattern system difficult especially with a large number of patterns. What if I want to have "types" of patterns like corridors, rooms, boss rooms, and treasure rooms as seperate pattern "categories" I can draw from and place? What if I decide a corridor should now be placed as a room? If I have pattern number 76 and pattern number 1, the only way to swap them is to delete all my patterns and start from scratch. No good!

2) The current system can't save a pattern with multiple layers. Suppose this common scenario for 2D RPG or JRPG developers: I have a village house with a roof layer with transparency/no occlusion when the player looks inside, a wall/furniture layer with collision and y-sorting so my player sprite is in-front of them, and a floor layer. Currently I can only save 1 layer of the house (wall, floor, roof) as a pattern and can't save the whole house as a pattern. Meaning that I can't do something extremely useful and simple like placing a house without serious effort, let alone procedurally generate a village full of houses and shops.

3) If I'm selecting my rooms/patterns at random, and I don't know which room I'm going to get, then how can I make sure the exits and entrances of the rooms line up when I place? I can't use mypattern.position +/- a y or x coordinate because I don't know which room I'm going to get.

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

An improved pattern system capable of organizing patterns, handling patterns with multiple layers, and custom hinge/exit/entrance points that the developer can place on an individual pattern, then access with a method during placement/procedural generation to help position them.

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

I am admittedly new to software development and just learned C++ specifically to make this feature and help my game come to life. So this will be light on specific code and focus more on general data structures and such.

1) Make an array of arrays. An array of pattern categories (room types), which itself contains an array of patterns (rooms).

2) Make it so that multiple layers can be saved to a pattern. I'm sure this can be done, but I'm not sure exactly how.

3) Designate hinge/exit/entrance points (tiles) that the developer can place on an individual pattern, then access with a method: e.g tilemappattern.place(patterngroup,patternname, exit_tile, entrance_tile) which in practice would be something like this tilemappattern.place (crypt_rooms, crypt_room17, east_wall_exit, west_wall_entrance).

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

It will be used often and is necessary for my project and those of a few others I've talked to. I don't think it can be worked around with a few lines of script.

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

1) Because other engines like Unity and Unreal already have stuff like "pre-fabs" if I'm not mistaken. So this seems to be industry standard. Godot is still seen as a "2D Engine" and having an intuitive and easy-to-use pattern system would attract lots of 2D devs who want to make platformers or roguelike or anything else with lots of patterns.

2) Because this isn't just a useful addition, but necessary for the type of game I and many others want to make. An engine should be capable of making any game "out of the box" I think. But I suppose this is just a subjective ideal. Godot can make roguelikes, and many devs in the r/roguelikedev discord already use Godot for development.

3) It meets the basic guidelines set out here for merging something. https://godotengine.org/article/will-your-contribution-be-merged-heres-how-tell/ Feel free to add your own thoughts in the comments.

KoBeWi commented 1 year ago

Make an array of arrays. An array of pattern categories (room types), which itself contains an array of patterns (rooms). Make it so that multiple layers can be saved to a pattern. I'm sure this can be done, but I'm not sure exactly how.

I think these 2 make sense. It's a general improvement for the pattern system.

Designate hinge/exit/entrance points (tiles) that the developer can place on an individual pattern

This is too specific and can already be achieved with special tiles and metadata.

btw you should make it more obvious that this proposal is about TileMap patterns. People unfamiliar with TileMaps will get confused on what is this about.

SlugFiller commented 1 year ago

Thinking about this, can't the original requirement be better achieved using waveform collapse? Instead of patterns, you can simply set constraints that force the entire house once a single tile from it is placed. "Hinges" are achieve by simply using regular constraints.

The area I can see this approach being a problem is the empty space, since waveform collapse is space filling. It's possible to use a second pass that picks the largest connected area, and removes any unreachable areas from the map. But this could, conversely, remove most of the map, because it doesn't try to achieve maximum connectivity.

Maybe there's a way to add a connectivity constraint to waveform collapse. Usually waveform collapse uses local constraints, and connectivity is non-local. So I'm not sure how well that may or may not work. But I do know that 2D maze generators use a system similar to waveform collapse, with connectivity as the only constraint. And those work.

awardell commented 1 year ago

If I have pattern https://github.com/godotengine/godot-proposals/issues/76 and pattern https://github.com/godotengine/godot-proposals/issues/1, the only way to swap them is to delete all my patterns and start from scratch. No good!

I'm very confused by these links...

Edit: Ahh, in my own comment I see they're displayed as numbers 😂 I think if you edit your proposal you could remove the hyperlinks generated by those numbers, and that would be helpful.

Zireael07 commented 1 year ago

@awardell those hyperlinks are auto-generated and will come back. He needs not to use the hash symbol, just numbers

btw you should make it more obvious that this proposal is about TileMap patterns. People unfamiliar with TileMaps will get confused on what is this about.

Exactly.

MendicantNinja commented 1 year ago

If I have pattern #76 and pattern #1, the only way to swap them is to delete all my patterns and start from scratch. No good!

I'm very confused by these links...

Edit: Ahh, in my own comment I see they're displayed as numbers 😂 I think if you edit your proposal you could remove the hyperlinks generated by those numbers, and that would be helpful.

I'm pretty new to Github. Fixed!

MendicantNinja commented 1 year ago

Thinking about this, can't the original requirement be better achieved using waveform collapse? Instead of patterns, you can simply set constraints that force the entire house once a single tile from it is placed. "Hinges" are achieve by simply using regular constraints.

The area I can see this approach being a problem is the empty space, since waveform collapse is space filling. It's possible to use a second pass that picks the largest connected area, and removes any unreachable areas from the map. But this could, conversely, remove most of the map, because it doesn't try to achieve maximum connectivity.

Maybe there's a way to add a connectivity constraint to waveform collapse. Usually waveform collapse uses local constraints, and connectivity is non-local. So I'm not sure how well that may or may not work. But I do know that 2D maze generators use a system similar to waveform collapse, with connectivity as the only constraint. And those work.

Wavefunction collapse has been used very well in Roguelikes like Caves of Qud before. But with complex tilesets that have 3 tile-high walls or multi-tile props (e.g. statues) it's more difficult. WFC doesn't quite have the level of fine detail and control as doing it with hand-built patterns stitched together.

MendicantNinja commented 1 year ago

Make an array of arrays. An array of pattern categories (room types), which itself contains an array of patterns (rooms). Make it so that multiple layers can be saved to a pattern. I'm sure this can be done, but I'm not sure exactly how.

I think these 2 make sense. It's a general improvement for the pattern system.

Designate hinge/exit/entrance points (tiles) that the developer can place on an individual pattern

This is too specific and can already be achieved with special tiles and metadata.

btw you should make it more obvious that this proposal is about TileMap patterns. People unfamiliar with TileMaps will get confused on what is this about.

Ok, I'll see if I can start by doing points 1 and 2, they seem easier to implement than 3 anyway.

Also fixed the naming issue. I'm so used to working with Tilemaps and patterns I forgot that other people might not be familiar with them.