Procedural Content Generation For C++ Game Development - Sample Chapter
Procedural Content Generation For C++ Game Development - Sample Chapter
Procedural Content Generation For C++ Game Development - Sample Chapter
D i s t i l l e d
E x p e r i e n c e
$ 49.99 US
31.99 UK
"Community
Experience
Distilled"
C o m m u n i t y
Dale Green
Dale Green
Preface
Preface
Computer games are a vast medium with dozens of genres that have developed over
the past three to four decades. Games are bigger and more immersive than ever, and
gamers' expectations have never been higher. While linear games, ones that have a
set story and fixed progression, are still commonplace, more and more dynamic and
open-ended games are being developed.
Advances in computer hardware and video game technologies are giving a much
more literal meaning to the phrase "game world". Game maps are constantly
increasing in size and flexibility, and it's thanks to technologies such as procedural
generation that it's possible. Two gamers who buy the same game may have very
different experiences as content is generated on the fly.
In this book, we're going to introduce ourselves to procedural generation, learning
the skills needed to generate content on the fly to create dynamic and unpredictable
game systems and mechanics.
Provided with this book is a game template for a rogue-like C++ game. When we
get the project compiled and set up in Chapter 2, Project Setup and Breakdown, you'll
see that it's currently just an empty shell. However, as we work our way through the
book, you'll be introduced to the concepts behind procedurally generated content
through real-world examples. We will then implement these examples in the
empty project.
Preface
Preface
Chapter 8, Procedural Behavior and Mechanics, uses everything that we've learned so
far to create complex procedural behavior and mechanics in the form of pathfinding
and unique level goals. We'll give our enemies the intelligence to traverse levels and
chase the player. We'll also create unique level goals with unique rewards for the
player to carry out.
Chapter 9, Procedural Dungeon Generation, finishes our work on the game project.
We're going to implement what is perhaps the most icon feature of roguelike
games: procedurally generated levels. All the way through the book, we've been
working with the same fixed level. So, it's about time we started generating them
procedurally! We'll also create some variance between levels and implement the
goal generator that we created in the previous chapter.
Chapter 10, Component-Based Architecture, takes a look at component-based design,
since our work on our template project is now complete. Procedural generation is
all about flexibility. So, it makes sense that we'd want to work with the most flexible
architecture that we can. Component-based architecture can achieve this, and having
a good understanding of this design approach will help you progress and build
larger systems in the future.
Chapter 11, Epilogue, takes a retrospective look at the project and the topics that we
covered as we finish our procedural journey. For each area of procedural generation
that we've used, we'll also identify some jumping-off points should you wish to
explore the topic in depth.
An Introduction to Procedural
Generation
When you load an image on a PC, a song on an iPod, or a book on a Kindle, you load
it from storage. That image, song, and book already exists as a whole, and whenever
you want to access it, you grab the whole previously created thing. In the case of
music or a video, you can stream it in chunks, but it still already exists as a whole in
storage. Let's compare this to buying a ready-made desk from a furniture store. You
get the entire desk as one single thing and that's that; you have a desk.
Now, let's imagine that instead of buying a complete desk, you buy one that's
flat-packed. Instead of getting a pre-built desk, you get all the pieces that you need
to build one, and instructions on how to do so. When you get home, you can follow
those instructions, and you will have a desk. If you feel so inclined, you can even
deviate from the instructions and create a unique desk that is different from that of
everyone else.
Let's use this analogy in the context of game development by substituting the
purchasing of a desk with the loading of a level. In the first case, we loaded the
level as a whole, as it was pre-built. However, in the second example, we got all the
pieces that we need to build a level and put them together ourselves in whatever
order we choose.
This process of something being created via an algorithm or procedure, as
opposed to already existing, is called procedural generation. The desk was created
procedurally as you followed an algorithm to put its pieces together. The same goes
for the game level. This can be extended to almost anything. For example, music,
images, games, and text can all be procedurally generated.
[1]
Seeds
Procedural generation
Procedural generation is the process of creating content using an algorithm. This in
itself has no element of randomness. If the functions, expressions, algorithms, and
inputs that are used to generate the content remain the same, then you'll always get
the same results. This is due to the fact that computers are deterministic, which is
something that we'll cover shortly. Procedural generation is not inherently random.
Random generation
Randomness is induced when we give these algorithms different inputs or alter their
expressions. This variance is what creates the variety of the output. When someone
says something was procedurally generated, they usually mean procedurally
generated utilizing randomness.
Introducing randomness
Computers are deterministic machines. This means that if you give them the same
input, and perform the same operations, you'll get the same output every time.
With respect to the desk example, everyone gets the same pieces, follows the
same instructions, and so builds the same desk.
[2]
Chapter 1
Again, using the context of games, if everyone gets the same assets and algorithms
to put them together, we will all get the same game and experience. Sometimes,
this is the goal. However, in our case, we want to create game systems that are
unpredictable and dynamic. Therefore, we need to introduce an element of
randomness to procedural generation.
[3]
You can download the code for this program from the Packt website at http://www.
packtpub.com/support. It will be present in the Examples folder, and the project
name is random_numbers:
// Random number generation
// This program will generate a random number each time we press
enter.
#include <iostream>
using namespace std;
int main()
[4]
Chapter 1
{
while (true)
{
cout << "Press enter to generate a random number:";
cin.get();
// Generate a random integer.
int randomInteger = rand();
cout << randomInteger << endl << endl;
}
return 0;
}
This is a very simple console application that makes a call to std::rand() every
time we press the Enter key. This returns us the pseudorandom number, and we
pass it to std::cout to display it. That's how easy it is!
[5]
The modulo operator returns the remainder of the division between two numbers.
So, 9 mod 2 is 1, as 2 goes into 9 four times with 1 left over. We can use this to create
a range for the pseudorandom number generation. Let's generate a number between
0 and 249.
To do this, we need to make the following change:
// Generate a random integer.
int randomInteger = rand();
int randomInteger = rand() % 250;
Run the program a few times now, and you'll see that all the results are limited to
the range that we just defined. So now we can generate a number between 0 and n,
but what if we don't want our range to start from 0? To do this, we need to make one
more change to the line that generates a number:
// Generate a random integer.
int randomInteger = rand() % 250;
int randomInteger = rand() % 201 + 50;
Remember that the number we used in the mod calculation will generate a number
between 0 and n-1, and the number we add afterwards will increase the range by
that amount. So here, we generate a number between 0 and 200 and then increase
the range by 50 to get a number between 50 and 250.
If you're not fully comfortable with the math behind what we're
doing here, head over to Khan Academy. It's a fantastic resource
for learning and has lots of great mathematics-related material.
Run the program and note the first five numbers that are generated. In my case, they
are 91, 226, 153, 219, and 124. Now, run it again. You'll notice that something strange
happens; we received the exact same numbers.
[6]
Chapter 1
They were generated in a pseudorandom manner, right? Maybe it was just a fluke.
Let's run it again and see what we get. You will get the same result again. To
understand what's happening here, we need to take a look at seeds.
Seeds
We just created a program to generate pseudorandom numbers, but every time
we run it we get the same results. We know that these numbers are the results of
complex equations and algorithms, so why are they the same? It's because each time
we run the program, we're starting with the same seed.
Dening seeds
A seed provides a starting point for an algorithm. So, in the previous example, yes
we're using complex algorithms to generate numbers, but we're kicking off the
algorithm at the same point each time. No matter how complex the algorithm is,
if you start at the same point, and perform the same operations, you're going to get
the same results.
Imagine that we have three people, and each person is about to walk the same path
by 5 steps. If they all start from the same square, they will end at the same square:
Now, in the next diagram, we give these three people unique starting positions.
Even though they are doing the same actions as before, and are on the same path,
their results are different because they started from different locations:
In this analogy, the path is the algorithm, and the starting square is the seed.
By changing the seed we can get different results from the same actions.
[7]
You will have most likely used seeds before and not even known it. Games that
procedurally generate worlds, such as Minecraft and Lego Worlds, give you the
option to set a seed manually before generating a world. If your friend generates a
world that looks great, they can grab their seed and give it to you. When you input
that seed yourself, you kick off the algorithm at the same place that your friends did
and you end up with the same worlds.
Using seeds
Now that we know what seeds are, let's fix the previous example so that we don't
keep generating the same numbers. To do this, we will use the std::srand()
function. It's similar to std::rand(), but it takes an argument. This argument is
used to set the seed for an algorithm. We'll add the call to std::srand() before we
enter the while loop.
You only need to set the seed once per run of the application.
Once std::srand() has been called, all the subsequent calls to
std::rand() will be based upon the updated initial seed.
[8]
Chapter 1
cout << randomInteger << endl << endl;
}
return 0;
}
Now when we run this code we get different results! I got 214, 60, 239, 71, and 233.
Don't worry if your numbers don't match mine exactly; they are both CPU- and
vendor-specific. So, what will happen if we run the program again? We changed
the seed. So we should get different numbers again, right?
Not quite. We called std::srand() and set a new seed, but each time we run the
program we're setting the same seed again. We're kicking the algorithm off at the
same position each time, so we're seeing the same results. What we really want to do
is randomly generate a seed during runtime so that the algorithm always starts at a
new position.
Now, every time we run the program, we get unique numbers! You may have
noticed that if you run the program multiple times in succession, the first number
is always very similar to the last run. That's because between the runs time doesn't
change a lot. This means that the starting points are close to each other and
the results reflect this.
[9]
Saving space
Necessity, as the saying goes, is the mother of invention. As developers of today
we're spoiled with the hardware that we have at our disposal. Even the most baseline
machines that you'll get today will have a hard drive of 500 GB in size and up as
standard. This is quite a luxury considering that just a couple of decades ago that
would be MB and not GB.
Game distribution was also a very different game back then. Today, we either buy
games on a physical disk, with Blu-ray disks offering a whopping 25 GB per layer,
or download them off the Internet, where there are no size restrictions at all. Keeping
this in mind, now consider the fact that the size of most Nintendo Entertainment
System (NES) games was a mere 128 to 384 KB! These storage restrictions meant
that game developers had to fit lots of content into a small space, and procedural
generation was a great way to do this.
[ 10 ]
Chapter 1
Since building large levels and storing them wasn't possible in the past, games
were designed to build their levels and resources algorithmically. You'd put all the
resources needed on your storage media, and have the software assemble the level at
the player's end.
Hopefully now, the earlier desk analogy makes more sense. It's just like how flatpacked furniture is easier to transport, and it can then be built at home. As hardware
has developed, this has become less of a problem, but it was a great solution for early
developers who had storage concerns.
Map generation
One of the most prominent uses of procedural generation in modern video games
is the generation of game maps and terrain. The extent to which this can be used is
vast, and ranges from generating simple 2D maps to full 3D worlds and terrain.
When procedurally generating 3D terrain, noise maps, such as the ones generated by
Perlin noise, are used to represent random distribution by producing an image with
areas of both high and low concentration. This data, the variance in concentration
and intensity, can then be used in many ways. When generating a terrain, it's
commonly used to determine the height at any given position.
The procedural generation of complex 3D terrain is beyond the scope of this book.
However, we will generate 2D dungeons later in this book.
If you do want to explore 3D terrain generation, read up on terms such as
"fractal terrain generation", "height maps", and "noise generation". These
will put you on the correct path.
[ 11 ]
Texture creation
Another prominent example of procedural generation is the creation of textures.
Similar to terrain generation, the procedural generation of textures uses noise
to create variance. This can then be used to create varying textures. Different
patterns and equations are also used to create a more controlled noise that forms
recognizable patterns.
Generating textures procedurally like this means that you can potentially have an
unlimited number of possible textures without any overhead on storage. From a
limited pool of initial resources, endless combinations can be generated, an example
of which can be seen in the following image:
Perlin noise is just one example of the many algorithms that are commonly used in
procedural generation. The study of these algorithms is beyond the scope of this
book, but if you want to further explore the use of procedural generation, it would be
a good place to start.
Animation
Traditionally, game animations are created by animators, and then exported as
an animation file that is ready for use in the game. This file will store the various
movements that each part of a model will go through during animation. It then
gets applied to the game character during runtime. The player's current state will
determine which animation should be playing. For example, when you press A to
jump, the player will change to a jumping state, and the jumping animation will be
triggered. This system works great, but it is very rigid. Each step, jump, and roll
is identical.
[ 12 ]
Chapter 1
Sound
Although less common than the previous examples, procedural generation is also
used to create game sounds. This will commonly be in the form of manipulating
existing sounds. For example, sound can be spatialized, meaning it appears to be
coming from a specific position when heard by the user.
At a stretch, short, one-shot sound effects may be synthesized, but due to the little
benefit that it brings as compared to the amount of work needed to implement it,
it's seldom used. It's simply much easier to load premade sounds.
Sfxr is a small program that generates random sound effects from scratch.
Its source is available. So, if sound synthesis interests you, it will serve
as a good starting point. You can find the project at https://github.
com/grimfang4/sfxr.
An increase in replayability
Let's continue from the last point. If a game is linear, without any procedural
generation, the challenge is gone after you've played the game once. You know the
plot, you know where the enemies will be, and unless it has an amazing story or
mechanics, there's not much reason why you'd want to play the game again.
However, if your game utilizes procedural generation, then the challenge is fresh
each time the game is run. The game is always evolving; the environments are
always new. If you look at the games that have the greatest replayability, they tend
to be the ones that give the player the greatest amount of control. Most of these
games will utilize some form of procedural generation to do so.
[ 14 ]
Chapter 1
[ 15 ]
[ 16 ]
Chapter 1
Populating environments
When we load the game for the first time our objects will be in fixed locations. We're
going to start our efforts by fixing this, implementing what we've learned in this
chapter about random number generation to spawn our objects at random locations.
At the end of this chapter there are a few optional exercises that include generating
numbers within a collection of different ranges. I suggest completing them if you're
not comfortable with it already, as we'll be relying on it to achieve this.
Audio manipulation
As with graphics, SFML offers a number of functions that allow us to modify sounds.
Therefore, we'll use these to alter the pitch and volume of our sound effects to create
variance. We'll then use advanced functions to create 3D spatialized sound, bringing
depth to the scene through our audio.
Dungeon generation
Towards the end of the book, once we're comfortable using Random Number
Generator (RNG) with procedural systems, and with our game project, we are going
to implement the defining feature of roguelikes; randomly generated dungeons.
I've mentioned a few times that procedural generation can be used to create
theoretically never-ending game worlds. So, we're going to do just that. We'll
implement a system where every room that we visit is generated randomly, and
we'll give each floor a distinct feel using the graphics manipulation techniques we'll
learn in later chapters.
Component-based design
Procedural generation is all about creating dynamic systems, objects, and data.
Therefore, it makes sense that we want the most flexible game framework that we
can have so that it incorporates this well. One of the ways to achieve this is through
component-based design. Therefore, to end our work, we're going to take a quick
look at it, breaking our project down into a more component-based approach.
[ 18 ]
Chapter 1
Exercises
To enable you to test your knowledge of this chapter's content, here are a few
exercises that you should work on. They are not imperative to the rest of the book,
but working on them will help you access your strengths and weaknesses in the
material covered.
1. Using the std::rand() function with the modulo operator (%), for
updating random_numbers.cpp to generate numbers that fall within
the following ranges:
0 to 1000
150 to 600
198 to 246
2. Come up with a new way of generating a random seed during the runtime.
There are lots of ways to do this. So be creative! In my solution, the first
numbers were always similar. Find out whether you can generate a random
seed that mitigates that.
3. Have a look at your game collection and find out whether you can identify
where procedural generation has been used.
4. Which of the following are examples of procedural generation?
Loading a song
Ragdoll physics
[ 19 ]
Summary
In this chapter, we learned that procedural generation is the creation of content by
using algorithms. This concept can be applied to all digital media and is used in
games to create dynamic systems and environments. Procedural generation brings
larger games, variety, and dynamism; all at the cost of lesser control, and potentially
lesser performance as it is taxing on hardware. Some examples of the most popular
uses of procedural generation in modern gaming include terrain generation, texture
creation, and procedural animation.
In the next chapter, we will take a look at the project that has been supplied with the
book. As we learn to create procedural systems, we will be implementing them in a
real game project, with the ultimate goal of creating a roguelike game, a genre that
heavily utilizes procedural generation. We will review the game template, the SFML
modules that we will be using, and get the project setup. Then, we will compile it on
your system.
If you are familiar with C++ game development and have used SFML before, you
may already be familiar with the concepts presented in the next chapter. If that's
the case, feel free to skim through the chapter to get right into the programming in
Chapter 3, Using RNG with C++ Data Types.
[ 20 ]
Get more information Procedural Content Generation for C++ Game Development
www.PacktPub.com
Stay Connected: