Hi !
During Ludum Dare #43 I had the pleasure to work on Abortion Boy, a pretty gore game that we had so much fun to imagine and create ! Though it's not the main feature of the game, Abortion Boy has a procedural level generation system that allows you to play the game over and over and never play the same level twice.
The system I used is pretty simple so seasoned programmers won't learn anything, but I know there are a lot of very beginners devs here and I believe this breakdown can be a good way to show them it's not so hard doing things that can seem impossible to do in a few hours !

What is a procedurally generated level ?
Procedural generation is used to create a lot of data randomly using algorithms. In video games, this is obviously a great way to do things since it will make the game random at each generation and give it a less predictable gameplay.
Let's get into it !
So, what I did is a simple maze only made of rooms of the same exact size, only the exits will change. What we want is generate a grid of indexes through code, then use these indexes to instantiate rooms.
But how do we represent a maze in code ? Well, a good way to do it is to work with a two-dimensional array of integers. Each cell will represent a room for the game. So let's declare it.

Since we are talking about a game jam and you can't really know what will be the rooms size, how many of them there will be in the game, let's declare some variables to make the system more modular.

Now, what do we do with this ?
Well, now let me explain the process which is very easy ! We're going to generate a main path (the first cell being our spawning point, the last one being the level end), then we will add rooms on empty cells to make the whole level less linear. Let's do the main path first !
It will look something like this :

First, we want to choose a first cell. Let's just declare two values : one for the x, and one for the y. The y will at first be 0, and we want the x to be random. With that, the first cell will be a random one in the first line. We can now say that, in our array of integers, the first cell has a given index (all the cells being 0 by default).

You can determine the room to be whatever you want. My index 1 means the room will have exits on left and right. Knowing that, we can randomly choose where the path will now go. If it goes left, then we want to substract 1 to our cellX variable. If it's going right, then we want to add 1 to it. When you're done, you can generate a second cell with correct exits : if the previous cell is on left, then you want the new one to have a left opening.
Do the exact same thing for the cellY variable. If your random path decides to go down, then do not change the cellX value but add 1 to cellY. * Be careful ! Arrays can be tricky because you can't see what is going on inside of them, but you need to add and not substract* to go down (note that it also depends on the generation system you're making, but in my case, it's working by going down). You can also use Unity debug methods to visualize your array in console, something like this :

We want to stop the calculations when our path has reached the last line and can't go down anymore. To do this, just use a while loop, whose condition will be some boolean like "hasReachedEnd" or "mainPathGenerated".

Yeah ! We now have our main path !
The next step is actually pretty easy. We're simply going to fill the level where the main path didn't go. To do so, just loop through all of your array cells, and whenever the cell index is a 0 (which means it's not on a main path, since default int value is 0), just set a random index ; since it's not the main path, it's not a problem if there are dead ends.

You're now actually done with your level generation, but there are still some issues to fix : first, there are exits that leads out of the map, second, there are exits that lead into closed walls.

And now you will understand why working with array of integers is very useful ! What I did is simply create two methods : one to close the out of the map openings, and another one to close dead ends. Each method just loop through the array, and compare each case with its 4 neighbors. Knowing my indexes, I know that if a room with an index of 2 is up to a room with an index of 6 for example, then I need to change if for a room of index 1 to close the dead end.
It takes some time setting up this system, and I did it in a very badly optimised way since I just wrote an endless stack of if statements, but, since I'm just comparing and changing integers, the code runs very fast and there's absolutely no latency during the indexes generation. Using Unity diagnostics library, we can see that the system takes 0.018 seconds to generate 2500 indexes!

Of course this is just generating the indexes, it will take time if you want to generate 2500 rooms with environment, props and everything haha, but you get the idea.
And we're done !
There's nothing more to do ! You now have a simple but very useful generation system you can use to create nice little games, and of course, once you've clearly understood your code and your system, you can get on more complex things like having different room sizes, or maybe use a three-dimensional array to build a level on x, y and z axis !
Keep in mind that I didn't mean to do a tutorial, I simply wanted to give my feeling about procedural generation, and try to show it is not necessarily as hard as it can seem to be. Once I had my system working, I only had to change the offset between rooms to match with the assets I got from my friends, then I added empty GameObjects all over the game to spawn random objects at each generation. Each level also had a different size, different game assets, changing colors and visual effects. Those little steps are ridiculously easy and are not 100% procedural but they give the whole thing a very nice random feeling.
Thanks for reading this post, I hope you enjoyed it !
Don't hesitate to try out Abortion Boy and tell us what you think of it ! ;)