This post will outline an approach to making a dungeon generator mixing pre-made rooms and procedural rooms, which will allow arbitrary sizes and shapes of rooms. Better than telling is showing, so just look at the beautiful animation below!
Linked to this are two complexities. The first is that the position of the tiles in the generated world should be different than in the designed world, so that a room can be created at any arbitrary position. To this end, each designed room holds information about the coordinates of its corners, and thus also where its centre is. Further, this allows one to rotate or mirror the rooms. In the above example, rooms can be mirrored horizontally or flipped vertically - due to limits of the tileset, rotations are off the table. Still, this (somewhat) quadruples the amount of rooms the generator can pull from.
The next is that the rooms should not connect to each-other arbitrarily. Each designed room holds a list of potential doors. I decided to go for a limited amount of door-spaces rather than allowing any non-obstructed wall to make a door - this gives some extra design possibilities when it comes to symmetric rooms, etc. Anyway, when a new room is added, it finds an old, unused door-space to connect one of its own doors to. This is how the position offset described above is found - the place where the two potential doors overlap. Before the room is brought into the world, every single tile has to be checked for overlap with other rooms.
However, apart from the predesigned rooms, the generator also sports two wholly procedural room types.
Hallways, here highlighted within the grey rings, are generated procedurally to connect potential doors between rooms that would be difficult to make connect otherwise. There are basically three types - straight corridors, three part corridors (as shown) and corner corridors. The two first connect doors that face each-other, while the corner corridor connects perpendicular doors.
The hallways are pretty simple, but have an important purpose. The generation algorithm starts with a seed room in the middle and then generates rooms in random directions. This, however, tends to lead to radial arms that stretch in different directions and never touch. In that way, the whole dungeon would be a linear tree where all rooms have just a single path to one another. The hallways, with their unending flexibility, make sure that there are circular paths through the dungeon.
Last, corridors also create potential door spaces, and are thus able to connect with one another, as seen in the above example. I have spent quite a bit of time tuning the chance of creating hallways so that gigantic cities of interconnected hallways do not spawn.
Finally, caves are completely procedurally generated rooms that spawn almost the same way as predesigned rooms. They can be seen in the rightmost half of the above picture, with the brown walls. Their shape is determined by Perlin noise, specifically a contour map with the value at the origin door as comparison value. Some extra stuff is done to make sure that no unreachable islands are included. This same algorithm also finds out the dimensions of the room and where to place potential doors. Finally, the room is filled with random decals.
And I guess, the more I look these rooms over, the more I see how much work there still is to do. The bottom-most cave is kind of okay, but the top one, which just randomly happened to only have one sort of floor tile, looks unfinished. The decals also seem strewn about haphazardly, and tile repetitions really ruin the atmosphere. I think I ought to look them over again, perhaps replacing random chance with Perlin noise, and expand the available decals quite a bit, too. Unfortunately, the tileset I am using does not have a lot to offer when it comes to rubble.
However, I think the cave bits offer a fine amount of diversity as long as they are kept rare and small. It mostly seems to be a problem when they are big and all over the place.
Two last things I want to mention. First, adding dynamic lighting really made the dungeons feel a lot more unique. However, I think I could still work on some faint light-sources in the darker areas so that they do not end up completely uniformly dark. Currently only torches act as light, which are all equally luminant.
Second, if you look at the above picture, you might notice that the different parts of the dungeon are coloured differently. The original tileset offered a single colour swap, but when working with procgen, colour swap is pretty straightforward to implement on a larger scale. Thus, each new room deviates slightly in colour from the previous, meaning that as you explore one arm of the dungeon, the walls might become green, and in another, red. A lot of tuning went into making this difference noticeable yet not too strong. Perhaps I am not quite done, either.
If you're curious how the designed rooms were made, well, mostly with a lot of auto-tiling! I designed one gigantic dungeon, then had an algorithm cut out individual rooms. I also use noise as inspiration for this design, as described in the previous post. The default dungeon looks like this:
All of this is WIP, but you can expect further updates in not too long - though about a topic a bit different than dungeon generation. What is a dungeon without a thousand hapless adventurers running around, steered by neural networks, most often to their demise by slime?
The tileset was purchased from https://pita.itch.io/rpg-dungeon-tileset and is a lot better than what I'm showcasing here in these early prototypes. Though it could use a bit more rubble methinks.
This comment has been removed by the author.
ReplyDelete"Hi can I steal your code plsthx"
DeleteHey Samishal, sorry for not answering earlier, I just did not really have time to, as you asked, go in-depth with the code. The short of the long is that it's a very convoluted process. The script for generating a room is 283 lines of code, another 154 lines to render it to screen.
DeleteNow seeing what Anonymous has said, well... If you do want my code, I will actually hand it over free of charge. It just won't be much help, since I don't quite have time to comment it all through.
If there's something specific you were wondering, it'd be easier to answer.
Hi Benjamin,
DeleteThanks for getting back to me, I don't want your code as the comment by 'Anonymous' implied, I just find articles like yours easier to understand when a few snippets of pseudo code are used to explain some of the concepts rather than a big paragraph. That's not to say that I didn't like your article, I found it very interesting and I liked the way that you've approached the issue using preset rooms. It's something I'm working on myself a roguelike project so I think having the full code would be cheating.
One thing I'd like to ask though: what data structure do you use to store your rooms? I'm currently at a point in my game where I use a set of premade game objects and mutate them when the world is constructed to generate a wider set of monsters & items. I've yet to find a decent way of storing the core set of game objects and dungeon generation in the manner you've displayed here is the next item on my long long list.
Sam
The data structures, it's a bit of a mess, to be honest. I use several grids (2d-arrays) to store, for each tile: Is it a wall, what kind of floor is there, and what room is it part of. Then I use a list to store each entity - furniture, boulders, doors too! This list just has the tile-ID and x and y positions. Finally, a similar list for torches and other light-sources (though right now torches are the only ones).
DeleteI think the most important thing to say is that the three grids are not used for drawing onto screen - rather, a different script goes back post-generation and finds the right tile to be drawn for each square. Basically auto-tiling. This allows me to flip rooms around, and for doors to be created anywhere - the tiles around them will adapt afterwards.
I'm not quite sure to what you are referring with "in the manner you've displayed here is the next item on my long long list".
If you'd like to see the relation between the preset rooms and the generation, you can try out a live demo here: https://1drv.ms/u/s!AqbyjsU9LS-JhY9DUAGnE2PPsAAGHg. Some other people are quite excited about this program for RPing, D&D and such, so I might continue developing a better interface and such. Right now, it is prone to crashing if using manual generation, beware!
If you have a blog or something, I'd love to see how your own generation is going.
Thanks for the explanation. I figured it might be something like that, basically separating the geometry data from the visual and mutating that in order to rotate or flip etc.
Delete"I'm not quite sure to what you are referring with "in the manner you've displayed here is the next item on my long long list"." - Sorry was pretty bushed from work when I wrote that. The current state of my game is that a cave system is generated at run time however I'm planning to move to a dungeon generated similar to yours where a mix of procedural generation and pre-fabricated rooms are used.
I've built a map editor of sorts that allows me to 'draw' the rooms using the tile sets and then save them as an xml file which is then loaded by the game but the project was stalled a bit when I couldn't figure out the best way of organizing the room objects.
If your interested in the map editor or the cave generation I can share what I have with you, however I'm a little shy about my code so I've sent you a Google Hangouts link rather than publishing it here. I also have a library for generating world maps.
P.S. I'm using Java... for my sins.
Thanks for the great article Benjamin! Lots of good ideas here.
ReplyDeleteI am also working on a procedural dungeon gereator with pre-designed rooms, and I came here seeking for an answer of which data structure to use for the whole map. I must say that after reading the comments, I am still unsure of which data structure do you use in your game.
My question is: When introducing a new room in the "world grid", how do you know that that rooms fits within the current state of the world?
My problem is that, as the rooms have different sizes, a newly introduced one can overlap with the previously placed ones.
Could you please share with us how is the calculation to check wether the room fits or not? It will definitely help to understand your idea.
Bro, can you share code for this please?
ReplyDelete