Let's create a nonrepetitive, endless level that the player can enjoy.
In this chapter, we will cover the following topics:
The next big chunk of development in our game is the level. A level is simply the environment that a player is placed in virtually. You are probably a gamer yourself, so I don't really need to explain this much. However, I want to talk about one thing—we need to make a decision on how we want the level to look and behave to keep the player engaged all the time.
A level can be either randomly generated during the game (for example, in Run), or have static, designed by level designer layout (for example, Super Mario Bros.).
There are pros and cons to both level types. A designed level can be customized very easily and is easier to develop in general. However, the player might not like the repetitiveness of the level at all.
If we choose the generating-during-gameplay approach, we have slightly more work to do. However, the level can be endless and random every time the player sees it. The player will always be challenged by different level layouts. Let's choose this approach. If you are feeling a bit confused now, I will break it down into a few features of a level, which are as follows:
Player
game object and destroyed behind the Player
game object.Individual level chunks in the preceding illustration are represented by rectangles with black edges. The section marked in the center of the image is the viewport visible to the player through Unity's camera.
So, in simple words, the player is traveling from left to right. At a later stage, we will make the camera follow the player. When the player game object enters the trigger (the green rectangle), the oldest chunk on the left-hand side will be destroyed; at the same time, we will instantiate a new chunk in front of the player.
Great! So, this approach gives us an endless level that generates itself, and technically the player can play forever.
The level chunk is the most important part of the level. It's like a Lego piece. A single piece can't bring about much fun for the player. However, when you take a lot of Lego pieces and fix them together, you can build a structure that's really entertaining. Our level will work exactly the same way. Level chunks right next to each other will create a nice gaming experience (and fun) for the player.
Before we talk about coding the level chunks, we need to make sure that we understand the fundamental parts of each chunk:
This is a level chunk. It can contain whatever you wish to add to it. It's up to you to design the chunks. You need to remember, however, that every chunk must have the following features to fit your game:
So, this is it! A very simple part of a level that we call a level chunk. Let's do something exciting now and write a -procedural level generation.
Remember that we are not writing any code without quick planning. Let's quickly think about the LevelGenerator
script and the functionality.
We will definitely need to write methods for:
AddPiece
, which is the level chunk right behind the last level chunk that is already generatedRemoveOldestPiece
, to keep Unity clean of already used level chunksRemoveAllPieces
, to cleanse the level of all chunksGenerateInitialPieces
, to generate a few pieces straightaway when the game startsDon't panic! I promise to go through this gradually. In fact, most of the statements that we will use in these four basic methods are already well known to you. I have used the term piece instead of chunk, but these are the same things. Let's stick to piece instead of chunk in our code.
Before we get to coding, let's prepare some assets.
Download and import LevelPieceBasic.unitypackage
into your project. You will notice that the PlayerPieceBasic
prefab has been imported into the prefabs
folder. Drag this prefab into the Hierarchy window. Make sure that the entire PlayerPieceBasic
game object in the Scene is placed on GroundLayer
. Otherwise, the Player
script won't detect that the player is on the ground. If we get this wrong, the player won't be able to jump.
At this stage, we will have quite an unorganized project hierarchy. It might be worth giving it a little cleanup. We won't use the FloorShort
and KillTrigger
game objects for a while, so let's delete them. Our Hierarchy and Scene windows should look more or less like this now:
Let's take a look at the prefab we just imported. As you can see, there is nothing fancy here. It's just a straight floor piece without any obstacles or holes. It's perfect for a start.
If you look closely at what's inside LevelPieceBasic
, you will notice a very simple structure, as we have mentioned before. There are lots of individual floor pieces, ExitPoint
, and LeaveTrigger
. At the moment, we don't have any scripts attached to them.
The key element for generating the level is knowing the position where the elements will appear. We will use the ExitPoint
world space position for this. I understand that this might sound a bit confusing again. Let's write a very simple piece of code that we will use to manage the LevelPiece
and hold the reference to the ExitPoint
game object.
Create a new C# script, call it LevelPiece
, and add the exitPoint
public member, as shown here:
That's it! No complicated code here! We are using the LevelPiece
component just to hold the ExitPoint
transform reference.
Add the LevelPiece
component to the LevelPieceBasic
game object, and assign ExitPoint
by dragging and dropping the ExitPoint
game object on top of the slot, as shown in this screenshot:
You are probably asking yourself, "Why do we need this stuff right now?" Good question! We are just about to build the LevelGenerator
class that will spawn level pieces in the level. The LevelPiece
class will help us manage the pieces that are already in the game and will massively speed up their positioning correctly through the ExitPoint
reference. Please be patient; everything will become clear to you soon.
We are ready to start writing the level generator. Woohoo! But before we proceed, let's have a recap of what functionality we have planned to include.
We will definitely need to write methods for:
AddPiece
, the level chunk right behind the last level chunk that is already generatedRemoveOldestPiece
, to keep Unity free of already used level chunksRemoveAllPieces
, to clean up the level of all chunksGenerateInitialPieces
, to generate few pieces on the game start straightawayLet's create a new script and call it LevelGenerator
. Let's also change the way we talk about new code here. As LevelGenerator
is quite an important class, I want you to understand it fully. That's why we will talk about variables as of now. Later, we will move on to methods.
Now add the following code to LevelGenerator
:
I believe you are quite confident with reading this code and have probably spotted that... this code doesn't do anything? Yes, that's correct. The first part of the LevelGenerator
class will store some crucial variables.
As you can see in line 7, we are declaring a static variable. You already know that we need this static variable for easy access using the singleton approach.
Lines 8, 9, and 10 declare some useful variables:
levelPrefabs
: This is the list of all already prepared level pieces. We will store all different level pieces that we want the generator to copy from.levelStartPoint
: This is a transform. We will plug in a game object in the scene that we will use to describe where the level is starting. In simple words, this is the position of the very first level piece in the level.pieces
: This is another list of level pieces. We will use this variable to store all level pieces that are in the game at the time.What is the difference between the levelPrefabs
and pieces
variables? Basically the levelPrefabs
elements are our blueprints. Every time we ask the generator to add a new piece to the level, the generator will randomly pick one prefab (blueprint
), make a copy of it, place it in the scene, and add this copied level piece at the end of the pieces
list. So, remember that the levelPrefabs
list won't change during the gameplay. The pieces
list, however, will constantly change as the player progress through the level.
We just mentioned some very useful information about variables. Right now, it's really easy to come back to the code, look at the lines, and know what line is doing what. In the near future, however, you will notice how easy it is to get lost in the code. We are all human, and we do forget stuff very often. That's why it is a very good practice to comment on your code.
Comments are fragments of code that the compiler skips. The computer isn't really interested in anything written there; the comments are for the developer. In other words, comments are for you and other developers reading your code.
To add the simplest comment into your code in C#, you simply have to add two forward-facing slashes, followed by the comment text.
Let's add some comments into LevelGenerator
now to make our life easier in the future, as follows:
This looks much better. It's much easier to look at this code now and know exactly what we are planning to do.