4 weeks half time
I set out to make a specialized level generator in Unity
I observed thoroughly the level generation found in games like Terraria, Spelunky, and Spelunky 2.
I've played those games more hours than I would like to admit, and I was impressed with the consistency and level of design that is possible with their level generation system.
I started by making a system that would place "rooms" in the world, and place tiles based on the tiles in that room.
I wanted the generation to have weighted randomness, because that gives one more control over the results. I created a generic class with a value and a weight, and extension functions that give values based on the weights. This serves as the base for almost all the randomness in the game, from rooms to the path through the levels to individual tiles.
During level generation, rooms have to be placed in the world. We begin by placing a border of impassable blocks around, and follow it up by placing rooms in the middle. Every time you try to place a room, there are some conditions. There can't already be a room where you're trying to place a room, and you have to satisfy the opening constraints.
The list of rooms available to choose from is provided by the level designer when creating the level object from which the stage is created.
A path through the level
A level can be any size, but I wanted to guarantee that the levels would always be traversible. One way of ensuring this is to generate a continuous path and to make sure that the rooms that connect those paths always have an opening in the required directions.
Every room has to decide whether its side is open or closed, and that way we can decide if a room can be placed in a certain spot or not.
Up until this point every room was populated using code for testing purposes, but I wanted to be able to edit the rooms manually to allow for further design and control. I wanted the editor to be fluid and intuitive, because that's where you'll spend most of your time using the editor. So I got to work.
I used the unity custom editor for all of the ui in this project. For the editor, I got the mouse position and converted it to tile space to get the tile that should be edited currently. Tiles can be selected and edited directly, and in paint mode you can drag to paint and color pick with middle click.
The rooms are saved as unity ScriptableObjects, and can be assigned to a Level ScriptableObjects.
I ran into a huge issue with the serialization, where no room data could be saved, and I had no idea why. I was stuck on this for about two days, and it was a little scary at that point because I didn't know how long it would be until the bug would be fixed. In the end, I managed to find the source of the bug by testing the serialization of every variable until I found the one that couldn't be saved. When I found the source, a multidimensional array, I fixed it by making it single-dimensional and adding functions to acess the tiles with a twodimensional indexing.
A level is essentially a collection of rooms, a tileset, and some generation settings. The terrain generator takes a level as an argument, and uses the dimensions and the rooms to generate a path and place room objects. The tile map that is created is then passed to the level spawner, along with the tileset to be spawned and placed into the world. The tileset is a list of prefabs, which means that they can essentially do anything they want after they're placed. This allows for an insane amount of freedom when it comes to the actual mechanics of the game.
There is a way to guarantee that certain rooms and objects are placed in the level. The guaranteed rooms will be placed in the world, in a room that is not intersected by the path. If the guaranteed room has a position, it will instead be avoided by the path. Guaranteed objects will be placed in the available tiles that are placed in the world, and only one of each.
There's a different weighted list of rooms for entrances, exits, and regular rooms. There is a few unspoken requirements on a level, suchj as there having to be enough rooms with opening variations to fill most variations of path directions and tile configurations. For example, if there's a room that is in a corner and a path enters it from above, then the level must have a room that has an opening upwards and no openings downwards or towards the wall.
In the case that no room can be found when generating a level, a completely filled room will be placed there, just to be clear about what has happened. There will never be a moment in the finished product where a completely filled room will be in the middle of a stage, as one is supposed to test every level a lot before calling it done.
I made many minor but still substantial changes and optimizations made during the course of the project that either improve the quality of the tools or makes the game better.
There's a streamlined and efficient way to create new rooms
Of course, I wanted to be able to traverse the levels that are created somehow, so I spent some time creating game mechanics that I could design the levels around.
The player was taken from an older project, and the rest was created during this project. It's not much, but adding gold and bombs to the game just made sense. Besides, it's a lot of fun blowing up worlds you spent 3 weeks making.
Here's another look at the editor and level generation
There are a lot of areas where I consider the project to be lacking, and that I would have fixed if there was the time to. For example, you can't undo while editing rooms. That's something that would be a deal-breaker for almost everyone using the tool.
I had a level designer test out the tool and have a look at the game, and I got a lot of valuable feedback and insight. Aside from some minor and obvious bugs, I learned that the different scriptable object files were hard to tell apart, aside from naming and storage structure. It would also be nice to display the percentage chance for a weighted object when displayed in a list. Also, some buttons for adding and removing objects from lists would be great. And maybe a sort of palette for the most recently used blocks in the room editor, so you don't have to recreate your favourite tiles over and over.
Ultimately, there wasn't any time to add many of those features. What I really would have liked is to have it be easier to select rooms, some sort of grid structure with the rooms displayed with all of their names. I would also have liked to be able to mark rooms as being flippable, so you can have more variation with less work.
Most importantly, I've tried to make the code as flexible and open as possible, to make room for future improvements. If you wanted to add branching paths, that would be easy. If you wanted to add some additional data to rooms, like how wide the openings are to the other directions, that could be done.