Learning procedural generation methods I

Posted by Fabio on Aug. 8, 2012, 11:58 a.m.

Let me preface this post with a few thoughts:

There are many ways to achieve a 'procedurally generated' environment. For example, a method I won't be discussing–arranging pre-created rooms, linked together by random paths. That's a form of procedural generation. So, keep in mind that there are many ways to achieve this effect.

Secondly, I'm not claiming that my way is
the way. There are many other (possibly more efficient) methods of creating the type of generation I'm going to discuss. This is simply the method I use and by no means am I claiming it to be the best or only. I've found this method in particular is best for any type of platform game or top-down game.

I don't want to write this specifically for GameMaker 8 (as these principals can be applied to any and all forms of game making) but please be aware that this is the program I am using. I'm also
not going to provide an example or blocks of code for you to use–that's for you to figure out you lazy fuck. I'm simply going to tell you how to get it done.

Step one: the digger/miner

Don't let the name fool you–we aren't 'carving' or 'digging' out anything. Instead, our digger is going to create 'chunks' for us. The digger should be your first and primary object, outside of a control (which should have some sort of global timer on it for the creation process. You can set this to whatever you'd like after some tinkering). You want this motherfucker to be created smack dab in the center of your room/level.

You want your digger to keep choosing a random direction to move in constantly (in some sort of alarm or step event), as long as there is still time left on our control object's timer. Each time it moves to a new block (let's say 16x16) and there isn't already a chunk there, make one. Simple.

You also want there to be a chance for your digger to create a clone digger (I usually set it at around 6-12%, depending on how big I want the level to be and how fast I want it done). Make sure you have some sort of 'max digger' variable on your control so 10,000 diggers aren't being created. That shit will crash your computer, bitch.

Step two: ending your diggers' movement and creating solids

So once the timer on your control object runs out, you want each digger to stop. Destroy those bitches, get rid of them. Whatever. So now you've got a bunch of background and a bunch of these 'chunks'. Now what?

Well I'll tell you now what, bitch. Next thing we're going to do is create solids. These are going to fill in any necessary background space with a solid block, to give your character some kind of malleable environment to work with–think of it like a fence to the yard we just created.

To do this, you need to perform a check with each chunk (only once our control object's timer is up). We're going to check if there isn't a chunk on my (the chunk we're checking) left side, create a solid there. Then check the right side. Then above, then below. (For GameMaker users, the '!place_meeting(x,y,obj)' code comes in handy here.) So at the end of these checks, each little island in the map and the outside walls of your level will be a solid–nothing else. So there's no wasted processing power on 10,000 solid objects filling unnecessary background space.

Step three: clearing up the clutter (optional)

Now that the digging has stopped and you've created your solids, you're usually going to get a pretty 'messy' looking level. Little solo solids scattered every which way, tiny 1x2, 4x4, or 1x3 blocks floating about. It's going to depend on what you want, but I usually decide to get rid of these stragglers.

To sort of 'clear up' a lot of the singular blocks, all you have to do is perform a check on each solid to see if there is a 'chunk' surrounding each side of it. (Again, 'place_meeting(x,y,obj)' comes in handy here.) If this is true, destroy it. Simple.

Step four: adding pools and waterfalls (optional)

To create little waterfalls, all you have to do is pick a few random solids and perform a check to see if there is a chunk beneath it. If there is, create a waterfall. For my waterfall object, I have it move down (if there is a chunk and not a solid beneath it) and create a tile for water. If it can't go down any further, I perform a check to see if it can move left or right, then it'll keep going in that direction until it gets stopped up by a solid or there is a chunk underneath it–in which case we move down again.

For a pool, I pick the lowest chunk and create a 'source water' block there. Then, every chunk that's let's say, 20 above the source block, becomes water.

After this, we're done with our chunks. You can keep them, or destroy them and place a matching tile in their place to save processing. Doesn't matter much to me.

In conclusion

Remember that this is only one method to get your very own procedurally generated environment. There's so many ways to accomplish this–so now that I've shown you the groundwork, experiment! Trial and error is a huge component in this process.

Here's a few screenshots of some levels I generated using this method: (This is zoomed out, the tiny torches were also added randomly.)

Hope everything was described clearly enough. Thank you for reading.

Comments

Fabio 12 years, 3 months ago

Arrays is something I've heard of used quite a bit in procedural generation but it's something I've never experimented with. I think I'm just a creature of habit and have been doing this (which may be clunky) method that I just worked out on my own because I'm afraid of change.

Rob 12 years, 3 months ago

It's not because you're doing it procedurally, it's because you're doing it on a fixed grid, which is where static random access times can come in very useful. But sure, go do it 4,800+ times as slowly if you wish.

Fabio 12 years, 3 months ago

In my engine right now, the solid creation stage doesn't even take a fraction of a second. I could see it being a problem with a larger scale room possibly, but I don't have trouble with it at the moment. Array usage is something I do want to learn more about, however.

Fabio 12 years, 3 months ago

@Moikle:

In my engine, there is not an equal chance–I've made it so each digger is more likely to move left or right than up or down (to accommodate a platformer) but in the example pictures shown there is an equal chance.

@Ludamad:

That's a really good point and one that I hadn't though of before!

Rob 12 years, 3 months ago

It's not the creation, it's the checking. But yeah, it's more something that would affect a non-trivial sized world, ie something like Terraria.

Quote:
Array usage is something I do want to learn more about, however.

You uh… do know how to use arrays, right?

Fabio 12 years, 3 months ago

In a general sense, but I've never applied them to something of this nature.

Rob 12 years, 3 months ago

Just treat the level as a giant 2D array then. with the dimensions being [room_width / block.sprite_width, room_height / block.sprite_height]

Juju 12 years, 3 months ago

ITT: Brownian Motion