Create A Side Scroller C++ Game In Unreal Engine Part 3: Creating The LevelSpawner Class And Generating The Game Level Using Procedural Level Generation

Table of Contents

Create A Side Scroller C++ Game In Unreal Engine Part 3: Creating The LevelSpawner Class And Generating The Game Level Using Procedural Level Generation

Reading Time: 11 minutes
Level: Beginner
Version: Unreal Engine 4.27

Help Others Learn Game Development

Share on facebook
Share on twitter
Share on reddit
Share on linkedin

In part 2 of this tutorial series we created the obstacle and level classes and we created blueprints from those classes.

In this part of the tutorial we are going to spawn level parts in the game using a method called procedural level generation.

Level Spawner Class

In the C++ Classes -> SideRunner, Right Click -> New C++ Class. Make sure the class inherits the Actor class, name the class LevelSpawner and click Create Class button.

Open LevelSpawner.h file in Visual Studio, and add the following line of code above the class declaration:

				
					class ABaseLevel;
				
			

We are forward declaring the BaseLevel class because we are going to add references to level parts that we created in the previous part of this tutorial series.

At the bottom of the class add the following lines:

				
					public:

	UFUNCTION()
		void SpawnLevel(bool IsFirst);

	UFUNCTION()
		void OnOverlapBegin(UPrimitiveComponent* OverlappedComp,
			AActor* OtherActor, UPrimitiveComponent* OtherComp,
			int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
				
			

The UFUNCTION keyword above the function is a C++ function that is recognized by the Unreal Engine 4 (UE4) reflection system e.g. it’s a keyword we use to denote that this function is a C++ function that we will use in the class.

The name of the function is self explanatory, we are going to use the function to spawn level parts in the game.

OnOverlapBegin is a function that we will use to detect collision between the LevelSpawner and other actors.

Moving forward, below the lines where we declared the functions above, add the following lines:

				
					protected:

	UPROPERTY(EditAnywhere)
		TSubclassOf<ABaseLevel> Level1;

	UPROPERTY(EditAnywhere)
		TSubclassOf<ABaseLevel> Level2;

	UPROPERTY(EditAnywhere)
		TSubclassOf<ABaseLevel> Level3;

	UPROPERTY(EditAnywhere)
		TSubclassOf<ABaseLevel> Level4;

	UPROPERTY(EditAnywhere)
		TSubclassOf<ABaseLevel> Level5;

	UPROPERTY(EditAnywhere)
		TSubclassOf<ABaseLevel> Level6;

	UPROPERTY(EditAnywhere)
		TSubclassOf<ABaseLevel> Level7;

	UPROPERTY(EditAnywhere)
		TSubclassOf<ABaseLevel> Level8;

	UPROPERTY(EditAnywhere)
		TSubclassOf<ABaseLevel> Level9;

	UPROPERTY(EditAnywhere)
		TSubclassOf<ABaseLevel> Level10;

	TArray<ABaseLevel*> LevelList;
				
			
We are passing EditAnywhere as the parameter in UPROPERTY declaration and this will allow us to edit this variable on the blueprint instance of the LevelSpawner class.
 
TSubclassOf allows us to declare variables of classes e.g. a variable which is a type of class that we created. We pass the name of the class between the <> and it will denote the type of the variable it will be, in our case it is our BaseLevel class.
 
In every level variable, e.g. from Level1 to Level10, we are going to attach the appropriate blueprint with the same name. We are going to use those variables to spawn those level parts in the game.
 
The LevelList array is going to serve as a holder for every level part we create. We specified that the array will be of type BaseLevel and the * you see in the declaration is there because this variable is a pointer, and we use * to denote when we declare a pointer variable.
 
And lastly, below the lines of code we just wrote, add the following lines:
 
				
					public:

	int RandomLevel;

	FVector SpawnLocation = FVector();
	FRotator SpawnRotation = FRotator();
	FActorSpawnParameters SpawnInfo = FActorSpawnParameters();
				
			

We are going to use RandomLevel variable to randomize the level part that we will spawn in the game.

The SpawnLocation and SpawnRotation variables are going to serve as the location and rotation for the new level part we will spawn, and SpawnInfo is required when we spawn a new actor, so we need to pass it as a parameter but we will not do anything with it.

Before we proceed to code the functionality in the LevelSpawner.cpp file, I am going to leave the finished LevelSpawner.h file below as a reference:

				
					#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "LevelSpawner.generated.h"

class ABaseLevel;

UCLASS()
class SIDERUNNER_API ALevelSpawner : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	ALevelSpawner();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;


public:

	UFUNCTION()
		void SpawnLevel(bool IsFirst);

	UFUNCTION()
		void OnOverlapBegin(UPrimitiveComponent* OverlappedComp,
			AActor* OtherActor, UPrimitiveComponent* OtherComp,
			int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);

protected:

	APawn* Player;

	UPROPERTY(EditAnywhere)
		TSubclassOf<ABaseLevel> Level1;

	UPROPERTY(EditAnywhere)
		TSubclassOf<ABaseLevel> Level2;

	UPROPERTY(EditAnywhere)
		TSubclassOf<ABaseLevel> Level3;

	UPROPERTY(EditAnywhere)
		TSubclassOf<ABaseLevel> Level4;

	UPROPERTY(EditAnywhere)
		TSubclassOf<ABaseLevel> Level5;

	UPROPERTY(EditAnywhere)
		TSubclassOf<ABaseLevel> Level6;

	UPROPERTY(EditAnywhere)
		TSubclassOf<ABaseLevel> Level7;

	UPROPERTY(EditAnywhere)
		TSubclassOf<ABaseLevel> Level8;

	UPROPERTY(EditAnywhere)
		TSubclassOf<ABaseLevel> Level9;

	UPROPERTY(EditAnywhere)
		TSubclassOf<ABaseLevel> Level10;

	TArray<ABaseLevel*> LevelList;

public:

	int RandomLevel;

	FVector SpawnLocation = FVector();
	FRotator SpawnRotation = FRotator();
	FActorSpawnParameters SpawnInfo = FActorSpawnParameters();

};
				
			

The first thing we are going to add in the LevelSpawner.cpp file are the includes of all classes we will use in that file. Below the first #include in the file add the following lines:

				
					#include "BaseLevel.h"
#include "Engine.h"
#include "Components/BoxComponent.h"
				
			

All the magic in regards to spawning level parts is going to happen inside the SpawnLevel function. Here are the first lines we will add in that function:

				
					void ALevelSpawner::SpawnLevel(bool IsFirst)
{

	SpawnLocation = FVector(0.0f, 1000.0f, 0.0f);
	SpawnRotation = FRotator(0, 90, 0);

	if (!IsFirst)
	{
		ABaseLevel* LastLevel = LevelList.Last();
		SpawnLocation = LastLevel->GetSpawnLocation()->GetComponentTransform().GetTranslation();
	}

	RandomLevel = FMath::RandRange(1, 10);
	ABaseLevel* NewLevel = nullptr;

}
				
			

First we set the values for the SpawnLocation and SpawnRotation. If you are wondering why did I set 1000 as a parameter for the Y axis in the SpawnLocation vector, this is because we set the scale of the level part at 10, which means the level part will be 1000 units or cm long in the game, and in order to position every level part next to each other, we need to move it by 1000 units.

Same goes for the Y value of the SpawnRotation. How the camera is positioned for our game, we need to rotate the level part by 90 degrees on the Y axis so that we can see it properly and be able to play the game.

Next we use the IsFirst parameter and test if the level part that we are spawning is not the first level part. We use the exclamation mark(!) in front of the IsFirst variable which means if the value is false, the exclamation mark will make it the opposite and that is true, and if the value is true, the exclamation mark will make it the opposite which is false.

So essentially we are testing if the level part that is being spawned is not the first level part. The reason for this is because every level part we spawn we will put it in the LevelList array, this way we can keep track of the level parts that are currently in the game and we can always access the last level part in order to get its location.

We need the location of the last level part in order to spawn the next part after it:

Img 1

We can get the last level part from the array by using the Last function of the array. After that, we can access the location of the last part by using the GetSpawnLocation function, then calling GetComponentTransform to get the transform component of the last level part, and lastly calling GetTranslation function we get the location of the last level part.

Next we use RandRange function from FMath to generate a random number between 1 and 10. This is because we have 10 level parts and based on the number that is returned by the RandRange function we will spawn that level part in the game.

The NewLevel variable is there so that we can store a reference to the new level part that we spawn in the level. If we don’t get a reference to the new level part we will not be able to pass it to the LevelList array and save it.

We set the initial value of NewLevel to be nullptr or null pointer, and when we create a new level part we will store it in NewLevel variable.

Moving forward, after we create the NewLevel variable, add the following lines of code:

				
					if (RandomLevel == 1)
	{
		NewLevel = GetWorld()->SpawnActor<ABaseLevel>(Level1,
			SpawnLocation, SpawnRotation, SpawnInfo);
	}
	else if (RandomLevel == 2)
	{
		NewLevel = GetWorld()->SpawnActor<ABaseLevel>(Level2,
			SpawnLocation, SpawnRotation, SpawnInfo);
	}
	else if (RandomLevel == 3)
	{
		NewLevel = GetWorld()->SpawnActor<ABaseLevel>(Level3,
			SpawnLocation, SpawnRotation, SpawnInfo);
	}
	else if (RandomLevel == 4)
	{
		NewLevel = GetWorld()->SpawnActor<ABaseLevel>(Level4,
			SpawnLocation, SpawnRotation, SpawnInfo);
	}
	else if (RandomLevel == 5)
	{
		NewLevel = GetWorld()->SpawnActor<ABaseLevel>(Level5,
			SpawnLocation, SpawnRotation, SpawnInfo);
	}
	else if (RandomLevel == 6)
	{
		NewLevel = GetWorld()->SpawnActor<ABaseLevel>(Level6,
			SpawnLocation, SpawnRotation, SpawnInfo);
	}
	else if (RandomLevel == 7)
	{
		NewLevel = GetWorld()->SpawnActor<ABaseLevel>(Level7,
			SpawnLocation, SpawnRotation, SpawnInfo);
	}
	else if (RandomLevel == 8)
	{
		NewLevel = GetWorld()->SpawnActor<ABaseLevel>(Level8,
			SpawnLocation, SpawnRotation, SpawnInfo);
	}
	else if (RandomLevel == 9)
	{
		NewLevel = GetWorld()->SpawnActor<ABaseLevel>(Level9,
			SpawnLocation, SpawnRotation, SpawnInfo);
	}
	else if (RandomLevel == 10)
	{
		NewLevel = GetWorld()->SpawnActor<ABaseLevel>(Level10,
			SpawnLocation, SpawnRotation, SpawnInfo);
	}
				
			
In all of these lines we are testing for the value of RandomLevel variable. Depending on the value, we will spawn the appropriate level part.
 
You will notice that we are using NewLevel to store the new actor e.g. level part that will be created.
 
We use GetWorld and then SpawnActor function of the world to create a new level part. We need to pass the type of actor that we will create between the <>, in our case the BaseLevel, and as parameters we pass the copy of the level we want to create, spawn location, spawn rotation and spawn info.
 
The copy is one of the level parts, spawn location and rotation are self explanatory, they determine where the actor will be spawned and the rotation of the spawned actor, and lastly we have the spawn info which we declared in LevelSpawner.h file and we mentioned that we need it as a parameter but we will not use it in detail, which is what we are doing.
 
The returned value, which is the actor of the type that we specified when we used SpawnActor function, will be returned and we will store it in the NewLevel variable.
 
The next step is to connect the trigger of the NewLevel variable with our OnOverlapBegin function so that we can detect when the player collides with the level part.
 
After we finish testing the RandomLevel variable, add the following lines:
 
				
					if (NewLevel)
	{
		if (NewLevel->GetTrigger())
		{
			NewLevel->GetTrigger()->OnComponentBeginOverlap.
				AddDynamic(this, &ALevelSpawner::OnOverlapBegin);
		}
	}
				
			

First we test if we have a NewLevel variable, or to be more precise, we are testing if NewLevel is not equal to nullprt, which means we can write:

				
					if (NewLevel)
	{
	
	}
				
			

Or we can write:

				
					if (NewLevel != nullptr)
	{
	}
				
			

Both of these lines of code are testing for the same thing. After that we are testing if we have the trigger from the NewLevel variable by calling its GetTrigger function.

This is the same thing as with the NewLevel variable, because essentially we are testing if the trigger of NewLevel variable that is returned by GetTrigger function is not equal to nullptr.

If both of these if statements are true, then we get the trigger of NewLevel and we use its OnComponentBeginOverlap function to add a function listener which is our OnOverlapBegin function that will be informed when an actor collides with the trigger of NewLevel variable.

This this trigger is the one we declared in BaseLevel.h file:

				
					// VARIABLE DECLARED IN BaseLevel.h
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "My Triggers")
		UBoxComponent* Trigger;
				
			
And the one we created in our level blueprints:
 
Img 2
The last step is to add the NewLevel variable in the LevelList array. So after we add the OnComponentBeginOverlap listener to the trigger of NewLevel, add the following lines of code:
 
				
					LevelList.Add(NewLevel);
	if (LevelList.Num() > 5)
	{
		LevelList.RemoveAt(0);
	}
				
			

First we add the NewLevel to the array by using its Add function. After that we test if number of elements that are inside the LevelList array are greater than 5, we do this using the Num function.

If that is true, then we will remove the first element in that array using the RemoveAt function and passing 0 as the parameter because RemoveAt function will remove the element that is on the index we specify as the parameter, and we set that index to 0.

This is the final version of the SpawnLevel function:

				
					void ALevelSpawner::SpawnLevel(bool IsFirst)
{

	SpawnLocation = FVector(0.0f, 1000.0f, 0.0f);
	SpawnRotation = FRotator(0, 90, 0);

	if (!IsFirst)
	{
		ABaseLevel* LastLevel = LevelList.Last();
		SpawnLocation = LastLevel->GetSpawnLocation()->GetComponentTransform().GetTranslation();
	}

	RandomLevel = FMath::RandRange(1, 10);
	ABaseLevel* NewLevel = nullptr;

	if (RandomLevel == 1)
	{
		NewLevel = GetWorld()->SpawnActor<ABaseLevel>(Level1,
			SpawnLocation, SpawnRotation, SpawnInfo);
	}
	else if (RandomLevel == 2)
	{
		NewLevel = GetWorld()->SpawnActor<ABaseLevel>(Level2,
			SpawnLocation, SpawnRotation, SpawnInfo);
	}
	else if (RandomLevel == 3)
	{
		NewLevel = GetWorld()->SpawnActor<ABaseLevel>(Level3,
			SpawnLocation, SpawnRotation, SpawnInfo);
	}
	else if (RandomLevel == 4)
	{
		NewLevel = GetWorld()->SpawnActor<ABaseLevel>(Level4,
			SpawnLocation, SpawnRotation, SpawnInfo);
	}
	else if (RandomLevel == 5)
	{
		NewLevel = GetWorld()->SpawnActor<ABaseLevel>(Level5,
			SpawnLocation, SpawnRotation, SpawnInfo);
	}
	else if (RandomLevel == 6)
	{
		NewLevel = GetWorld()->SpawnActor<ABaseLevel>(Level6,
			SpawnLocation, SpawnRotation, SpawnInfo);
	}
	else if (RandomLevel == 7)
	{
		NewLevel = GetWorld()->SpawnActor<ABaseLevel>(Level7,
			SpawnLocation, SpawnRotation, SpawnInfo);
	}
	else if (RandomLevel == 8)
	{
		NewLevel = GetWorld()->SpawnActor<ABaseLevel>(Level8,
			SpawnLocation, SpawnRotation, SpawnInfo);
	}
	else if (RandomLevel == 9)
	{
		NewLevel = GetWorld()->SpawnActor<ABaseLevel>(Level9,
			SpawnLocation, SpawnRotation, SpawnInfo);
	}
	else if (RandomLevel == 10)
	{
		NewLevel = GetWorld()->SpawnActor<ABaseLevel>(Level10,
			SpawnLocation, SpawnRotation, SpawnInfo);
	}

	if (NewLevel)
	{
		if (NewLevel->GetTrigger())
		{
			NewLevel->GetTrigger()->OnComponentBeginOverlap.
				AddDynamic(this, &ALevelSpawner::OnOverlapBegin);
		}
	}

	LevelList.Add(NewLevel);
	if (LevelList.Num() > 5)
	{
		LevelList.RemoveAt(0);
	}

}
				
			
Moving forward with our implementation, inside the OnOverlapBegin that is we are using as a listener for the trigger box located in every level part, we are going to add the following line of code:
 
				
					void ALevelSpawner::OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	SpawnLevel(false);
}
				
			
Whenever we collide with the trigger box, and we will collide with the player actor, we will call the SpawnLevel function and pass false so that we create a new level part and spawn it after the location of the last level part in the level, which we get from the LevelList array and we already explained how that works.
 
The last step is to create the initial level parts when the game starts which will happen in BeginPlay function. Add the following lines inside BeginPlay:
 
				
					void ALevelSpawner::BeginPlay()
{
	Super::BeginPlay();
	
	SpawnLevel(true);
	SpawnLevel(false);
	SpawnLevel(false);
	SpawnLevel(false);
}
				
			

You will notice that I am passing true as the parameter for SpawnLevel function the first time I am calling it. This is because we need to spawn the initial level part which is the first level part in the game, after that we pass false to SpawnLevel function when we create other level parts.

Make sure that you compile the class from Visual Studio or Unreal Engine editor before we proceed.

Level Spawner Blueprint

Now that we are finished with our LevelSpawner class, we can create a blueprint out of it.

Inside the Content -> Blueprints folder, Right Click -> Blueprint Class. Make sure that you inherit LevelSpawner class:

Img 3

Name the blueprint BP_LevelSpawner and open it in the editor. In the Components tab click on the BP_LevelSpawner(self) top parent, and in the Details tab locate the empty fields where we need to attach the level parts:

Img 4

We are able to attach these level parts because when we declared them, we added the EditAnywhere paramter in the UPROPERTY for every level part that we declared.

The EditAnywhere parameter will allow us to edit the variables on blueprint instances of that class:

				
					UPROPERTY(EditAnywhere)
		TSubclassOf<ABaseLevel> Level1;
				
			
We can click on the drop down list where it says None, and locate from the search bar level blueprints:
 
Img 5
Make sure that for every level part variable you attach the correct level part blueprint:
 
Img 6

Compile and Save the changes we made to BP_LevelSpawner. Now that we attached all level parts in the appropriate fields, we can drag the BP_LevelSpawner blueprint in the level and test our game:

As soon as we run the game we saw that the level parts are created.

You probably noticed two issues, one is that when we touch the spike nothing happens, this is because we didn’t code that part of the functionality yet, so that is normal.

The second issue is that our levels stopped spawning the further we went into the game. The problem is that we jumped over the Trigger Box component that is a part of every level. The player actor needs to pass through the Trigger Box and then the code to spawn a new level part will be executed.

To fix this, we can go inside BP_Level1 blueprint, select the Trigger Box in the Components tab, and change the Z Location and Z Scale in the Transform property:

Img 7

This will make the Trigger Box component larger, and it will move its position upwards, so now the player actor will not be able to jump over it and we will not have the issue where we don’t spawn new level parts as we did before.

Where To Go From Here

In this tutorial we created the LevelSpawner class and we coded the level spawning functionality, so now when we play the game, we have an infinite level where we can move.

In the next part titled Detecting Collision Between Player And Obstacles And Wrapping Up Our Game we will detect when the player actor collides with the obstacles and restart the game when that happens, and with that we will finish the game.

Leave a Comment