Hello there and welcome to my first legitimate post for The Blood Archives! This post focuses on procedural generations, my experiences with it, and how I've incorporated it into games in various ways.
I remember when I first started playing around with procedural generation, it seemed really complicated, and I didn't know where to start, which is probably why it took me so long to start playing with it. If only I had known that the term "procedural generation" actually captures a very wide range of complexities, from the simplest map generation to the complex algorithm that guides games like Minecraft's world generation.
Today I'm here to help show you how this awesome aspect of game development can be used, coded, and incorporated into your games. Just something to keep in mind, however: I'm not an expert at procedural generation. My knowledge is limited and thus I will keep attempting to learn. Right now, though, my goal is to help you get started with procedural generation.
Alright, we're past all the talking and explanations that you probably just skimmed anyway. Let's actually get started with an algorithm.
Identify what aspects of the generation should be random. A great example of this is generating a shape; what about the shape should be random? Will it be a square, or will the shape be completely random? Any entrances (for a building, perhaps) or features that should be consistent will all shapes (perhaps a carpet covers the floor that isn't contacting walls)? Some things need to be coded by hand (such as the carpet or entrance size), while others can have an aspect of randomness (entrance position and side lengths). Usually, the best procedurally generated features have a mix of the two.
Okay, you've identified what you want to be randomized. Now, for most projects, you are going to want to seed your project. For what this means is, well, look no farther than Minecraft's seed. The same seed produces the same world. How I accomplish this task is by using a combination of variable incrementation and random seeds.
Random seeds are a feature available to any decent language that allows the programmer to set the seed of the project's RNG (random number generator). For example, if you set the random seed of the project to 2, and you tell it to generate you a number in between 1 and 1000, no matter how you run the program, that number will always result in 515. Unfortunately, this does raise an issue. Because all RNGs with the parameters 1,1000 will always result in the exact same number, how can you generate complex worlds without them being boring?
Well, this is where project seeding comes in handy. What you'll want to do is define a variable, lets say, "seed" as the seed you want the project to use. Then, create a function (a.k.a. a method) called, for example, "setRandSeed()". This method will then take "seed", as defined above, and add one to it. Then it will tell the program to set the random seed to the new seed. Then, every time you access a random number in your program, call "setRandSeed()", or whatever you named it, after you use the number. I'll show you how I implemented it in one of my games (using Lua 5.1 in LÖVE):
You can see how after I access any random number, to prevent the same values appearing, I reseed the program. The reason behind this is, as I previously explained, the program's seed is algorithmically changed during the generation, causing randomness, but also causing the map to remain the same given the same seed.
- Taking a random number, creating a box (also known as a "landmass" in my code), and creating other landmasses that originate off of the original landmass' corners. As the landmasses run out of available corners, the oldest landmass with an available corner is selected as the new origin. The process repeats until a certain number of landmasses are created.
- Taking 1-Dimensional simplex noise (explained later) and turning the value returned by the noise into terrain height, resulting in terraria-esque terrain.
- Carving out holes in rock, then connecting them based off a "bucket-fill" program - if the box doesn't fill all the other holes with a bucket fill, it's not connected and thus a tunnel is generated to another open space.
It's literally a bunch of blobs. However, it has great uses in terrain generation, due to the fact that it's randomness is smoothed out, which results in much smoother terrain - you're not going to get spikes of height. Rather, you're going to get "hills" of terrain. LÖVE has a great built-in function for this, which is what I made use of in this code:
It may look complex, but rest assured, it isn't actually too complicated. I'll explain, step-by-step, what it's doing.
- The for-loop: looping through every x-coordinate of the map (every integer between 1 and 2000)
- Getting terrain height: this is the line where simplex is used. First, the simplex noise is sampled using the x-coordinate * 0.001. The *0.001 simply decreases terrain variation, since our x-value is moving much slower through the simplex noise. The (x+seed) is used so not all the maps look the same. Simplex returns a value between 0 and 1. We don't want extremely short terrain, so we multiply it by 35 (thus the terrain can vary by 35 tiles). Then, we add 265 so that the shortest the terrain can be is 265. Finally, as we are running a tile-based map (there can't be .5 of a tile), we round the value up using math.ceil().
- Make a stone column going from the bottom of the map to the height we defined above.
- The setRandomSeed line simply ensures good variation across dirt depth.
- The next line defines dirt height as an integer between 3 and 5. Obviously we want the dirt to seem as if it had been generated with the stone, not just sprinkled on top of the stone. So we start at the y-coordinate above our stone column, and loop our way down from there using the dirt height. Thus the dirt "eats into" the stone rather than just sits on top of it.
- The final segment just adds grass to the dirt, then reseeds so as not to get the same values.
No comments:
Post a Comment