In the previous chapter, you created the player projectile and used Anim Notifies to spawn the player projectile during the Throw animation. The player projectile will serve as the player’s main offensive gameplay mechanic to use against the enemies throughout the level. Due to the combination of default Anim Notifies provided by UE5 and your own custom Anim_ProjectileNotify class, the player projectile mechanic looks and feels great.
In this chapter, we will cover the following topics:
By the end of this chapter, you will have the finished SuperSideScroller game project, complete with coin collectibles and a corresponding UI to track the number of coins collected, a new potion power-up that increases the player’s movement speed and jump height, as well as a base class in which to derive potentially new power-ups and collectibles for the game.
For this chapter, you will need the following technical requirements:
Let’s begin this chapter by learning more about URotatingMovementComponent, which we will use for our collectibles.
The project for this chapter can be found in the Chapter15 folder of the code bundle for this book, which can be downloaded here: https://github.com/PacktPublishing/Elevating-Game-Experiences-with-Unreal-Engine-5-Second-Edition.
URotatingMovementComponent is one of a few movement components that exists within UE5. You are already familiar with CharacterMovementComponent and ProjectileMovementComponent from the SuperSideScroller game project alone, and RotatingMovementComponent is just that – another movement component. As a refresher, movement components allow different types of movements to occur on actors, or characters, that they belong to.
Note
CharacterMovementComponent, which allows you to control the movement parameters of your character, such as their movement speed and jump height, was covered in Chapter 10, Creating the SuperSideScroller Game, when you created the SuperSideScroller player character. ProjectileMovementComponent, which allows you to add projectile-based movement functionality to actors, such as speed and gravity, was covered in Chapter 14, Spawning the Player Projectile, when you developed the player projectile.
RotatingMovementComponent is a very simple movement component compared to CharacterMovementComponent and that’s because it only involves rotating the actor that RotatingMovementComponent is a part of; nothing more. RotatingMovementComponent continuously rotates a component based on the defined Rotation Rate, pivot translation, and the option to use rotation in local space or world space. Additionally, RotatingMovementComponent is much more efficient compared to other methods of rotating an actor, such as through Event Tick or Timelines within Blueprints.
Note
More information about movement components can be found here: https://docs.unrealengine.com/en-US/Engine/Components/Movement/index.html#rotatingmovementcomponent.
We will be using RotatingMovementComponent to allow the coin collectible and potion power-up to rotate in place along the Z-axis. This rotation will draw the player’s attention to the collectible and give them a visual cue that the collectible is important.
Now that you have a better understanding of RotatingMovementComponent, let’s move on and create the PickableActor_Base class, which is what the coin collectible and the potion power-up will derive from.
In this exercise, you will be creating the PickableActor_Base actor class, which will be used as the base class that both the collectible coin and potion power-up will derive from. You will also create a Blueprint class from this C++ base class to preview how URotatingMovementComponent works. Follow these steps to complete this exercise:
Note
You have performed many of the following steps numerous times throughout the SuperSideScroller game project, so there will be limited screenshots to help guide you. Only when introducing a new concept will there be an accompanying screenshot.
void PickableActor_Base::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
Note
In many cases, the use of the Tick() function for movement updates can lead to performance issues as the Tick() function is called every single frame. Instead, try using Gameplay Timer functions to perform certain updates at specified intervals, rather than on each frame. You can learn more about Gameplay Timers here: https://docs.unrealengine.com/4.27/en-US/ProgrammingAndScripting/ProgrammingWithCPP/UnrealArchitecture/Timers/.
UPROPERTY(VisibleDefaultsOnly, Category = PickableItem)
class USphereComponent* CollisionComp;
The declaration of USphereComponent should be very familiar to you by now; we’ve done this in previous chapters, such as Chapter 14, Spawning the Player Projectile, when we created the PlayerProjectile class.
UPROPERTY(VisibleDefaultsOnly, Category = PickableItem)
class UStaticMeshComponent* MeshComp;
UPROPERTY(VisibleDefaultsOnly, Category = PickableItem)
class URotatingMovementComponent* RotationComp;
#include "Components/SphereComponent.h"
#include "Components/StaticMeshComponent.h"
#include "GameFramework/RotatingMovementComponent.h"
APickableActor_Base::APickableActor_Base()
{
}
CollisionComp = CreateDefaultSubobject
<USphereComponent>(TEXT("SphereComp"));
CollisionComp->InitSphereRadius(30.0f);
CollisionComp->BodyInstance.SetCollisionProfileName("OverlapAllDynamic");
RootComponent = CollisionComp;
MeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MeshComp"));
MeshComp->AttachToComponent(RootComponent,
FAttachmentTransformRules::KeepWorldTransform);
MeshComp->SetCollisionEnabled(ECollisionEnabled::NoCollision);
The first line initializes MeshComp UStaticMeshComponent using the CreateDefaultSubobject() template function. Next, you are attaching MeshComp to the root component, which you made for CollisionComp, using the AttachTo() function. Lastly, MeshComp UStaticMeshComponent should not have any collision by default, so you are using the SetCollisionEnabled() function and passing in the ECollisionEnable::NoCollision enumerator value.
RotationComp =
CreateDefaultSubobject<URotatingMovementComponent>(
TEXT("RotationComp"));
Figure 15.1 – The Shape_Cone mesh assigned to MeshComp StaticMeshComponent
(X=100.000000,Y=100.000000,Z=100.000000)
These values determine how fast the actor will rotate along each axis per second. This means that the cone-shaped actor will rotate along each axis at 100 degrees per second on each axis.
Figure 15.2 – The BP_PickableActor_Base rotating in place
Note
You can find the assets and code for this exercise here: https://github.com/PacktPublishing/Game-Development-Projects-with-Unreal-Engine/tree/master/Chapter15/Exercise15.01.
With this exercise complete, you’ve created the base components required for the PickableActor_Base class and learned how to implement and use URotatingMovementComponent. With the PickableActor_Base class ready, and with URotatingMovementComponent implemented on the Blueprint actor, we can complete the class by adding overlap detection functionality, destroying the collectible actor, and spawning audio effects when the actor is picked up by the player. In the following activity, you will add the remaining functionality required for the PickableActor_Base class.
Now that the PickableActor_Base class has all the required components and has its constructor initializing the components, it is time to add the remaining aspects of its functionality. These will be inherited by the coin collectible and potion power-up later in this chapter. This additional functionality includes player overlap detection, destroying the collectible actor, and spawning an audio effect to give feedback to the player that it has been successfully picked up. Perform the following steps to add functionality that allows a USoundBase class object to be played when the collectible overlaps with the player:
Expected output:
Figure 15.3 – The BP_PickableActor_Base object can be picked up by the player
Note
The solution for this activity can be found on https://github.com/PacktPublishing/Elevating-Game-Experiences-with-Unreal-Engine-5-Second-Edition/tree/main/Activity%20solutions.
With this activity complete, you have proven your knowledge regarding how to add the OnBeginOverlap() functionality to your actor classes and how to use this function to perform logic for your actor. In the case of PickableActor_Base, we added logic that will spawn a custom sound and destroy the actor.
Now that the PickableActor_Base class is set and ready, it is time to develop the collectible coin and power-up potion classes that will derive from it. The coin collectible class will inherit from the PickableActor_Base class you have just created. It will override key functionality, such as the PlayerPickedUp() function, so that we can implement unique logic for the collectible when it’s picked up by the player. In addition to overriding functionality from the inherited parent PickableActor_Base class, the coin collectible class will have its own unique set of properties, such as its current coin value and unique pickup sound. We’ll create the coin collectible class together in the next exercise.
Exercise 15.02 – creating the PickableActor_Collectable class
In this exercise, you will be creating the PickableActor_Collectable class, which will be derived from the PickableActor_Base class you created in Exercise 15.01 – creating the PickableActor_Base class and adding URotatingMovement, and finished in Activity 15.01 – player overlap detection and spawning effects in PickableActor_Base. This class will be used as the main collectible coin that the player can collect within the level. Follow these steps to complete this exercise:
protected:
virtual void BeginPlay() override;
The reason we are overriding the BeginPlay() function is that URotatingMovementComponent requires the actor to initialize and use BeginPlay() to correctly rotate the actor. Therefore, we need to create the override declaration of this function and create a basic definition inside the source file. First, however, we need to override another important function from the PickableActor_Base parent class.
virtual void PlayerPickedUp(class ASuperSideScroller_Player* Player)override;
With this, we are saying that we are going to use, and override, the functionality of the PlayerPickedUp() function.
public:
UPROPERTY(EditAnywhere, Category = Collectable)
int32 CollectableValue = 1;
Here, we are creating the integer variable that will be accessible in Blueprints and has a default value of 1. If you so choose, with the EditAnywhere UPROPERTY() keyword, you can change how much a coin collectible is worth.
void APickableActor_Collectable::PlayerPickedUp(class
ASuperSideScroller_Player* Player)
{
}
Super::PlayerPickedUp(Player);
The call to the parent function, which uses Super::PlayerPickedUp(Player), will ensure that the functionality you created in the PickableActor_Base class is called. As you may recall, the PlayerPickedUp() function in the parent class makes a call to spawn the PickupSound sound object and destroys the actor.
void APickableActor_Collectable::BeginPlay()
{
}
Super::BeginPlay();
Note
You can find the assets and code for this exercise at the following link: https://packt.live/35fRN3E.
Now that you’ve successfully compiled the PickableActor_Collectable class, you have created the framework needed for the coin collectible. In the following activity, you will create a Blueprint from this class and finalize the coin collectible actor.
Now that the PickableActor_Collectable class has all of the necessary inherited functionality and unique properties it needs, it is time to create the Blueprint from this class and add a Static Mesh, update its URotatingMovementComponent, and apply a sound to the PickUpSound property. Perform the following steps to finalize the PickableActor_Collectable actor:
Expected output:
Figure 15.4 – The coin collectible rotates and can be overlapped by the player
Note
The solution for this activity can be found on https://github.com/PacktPublishing/Elevating-Game-Experiences-with-Unreal-Engine-5-Second-Edition/tree/main/Activity%20solutions.
With this activity complete, you have proven that you know how to migrate assets into your UE5 project and how to use and update URotatingMovementComponent to fit the needs of the coin collectible. Now that the coin collectible actor is complete, it is time to add functionality to the player so that the player can keep track of how many coins they have collected.
First, we will create the logic that will count the coins using UE_LOG. Later, we will implement the coin counter using the Unreal Motion Graphics (UMG) UI Designer system on the game’s UI.
In Chapter 11, Working with Blend Space 1D, Key Bindings, and State Machines, we used and learned about the UE_LOG function to log when the player should throw the projectile. Then, we used the UE_LOG function in Chapter 13, Creating and Adding the Enemy Artificial Intelligence, to log when the player projectile hit an object. UE_LOG is a robust logging tool we can use to output important information from our C++ functions into the Output Log window inside the editor when playing our game. So far, we have only logged FStrings to display general text in the Output Log window to know that our functions were being called. Now, it is time to learn how to log variables to debug how many coins the player has collected.
Note
There is another useful debug function available in C++ with UE5 known as AddOnScreenDebugMessage. You can learn more about this function here: https://docs.unrealengine.com/en-US/API/Runtime/Engine/Engine/UEngine/AddOnScreenDebugMessage/1/index.html.
When creating the FString syntax used by the TEXT() macro, we can add format specifiers to log different types of variables. We will only be discussing how to add the format specifier for integer variables.
Note
You can find more information on how to specify other variable types by reading the following documentation: https://www.ue4community.wiki/Logging#Logging_an_FString.
This is what UE_LOG() looks like when passing in FString “Example Text”:
UE_LOG(LogTemp, Warning, TEXT("Example Text"));
Here, you have Log Category, Log Verbose Level, and the actual FString, “Example Text”, to display in the log. To log an integer variable, you need to add %d to your FString within the TEXT() macro, followed by the integer variable name outside the TEXT() macro, separated by a comma. Here is an example:
UE_LOG(LogTemp, Warning, TEXT("My integer variable %d), MyInteger);
The format specifier is identified by the % symbol, and each variable type has a designated letter that corresponds with it. In the case of integers, the letter d is used, representing a digit. You will be using this method of logging integer variables to log the number of coin collectibles the player has in the next exercise.
In this exercise, you will be creating the necessary properties and functions that will allow you to track how many coins the player collects throughout the level. You will use this tracking to show the player using UMG later in this chapter. Follow these steps to complete this exercise:
int32 NumberofCollectables;
This will be a private property that will keep track of the current number of coins the player has collected. You will be creating a public function that will return this integer value. We do this for safety reasons to ensure that no other classes can modify this value.
UFUNCTION(BlueprintPure)
int32 GetCurrentNumberofCollectables() { return NumberofCollectables; };
Here, we are using UFUNCTION() and the BlueprintPure keyword to expose this function to Blueprints so that we can use it later in UMG.
void IncrementNumberofCollectables(int32 Value);
This is the main function you will use to keep track of how many coins the player has collected. We will also add some safety measures to ensure this value is never negative.
void ASuperSideScroller_Player::IncrementNumberofCollectables(int32 Value)
{
}
if(Value == 0)
{
return;
}
This if() statement says that if the value input parameter is less than or equal to 0, the function will end. With the IncrementNumberofCollectables() function returning void, it is perfectly okay to use the return keyword in this way.
We’re adding this check of ensuring the value parameter that’s passed into the IncrementNumberofCollectables() function is neither 0 nor negative because it is important to establish good coding practices; this guarantees that all possible outcomes are handled. In an actual development environment, there could be designers or other programmers who attempt to use the IncrementNumberofCollectables() function and try to pass in a negative value, or a value that equals 0. If the function does not take these possibilities into account, there is potential for bugs later on in development.
else
{
NumberofCollectables += Value;
}
UE_LOG(LogTemp, Warning, TEXT("Number of Coins: %d"), NumberofCollectables);
With this UE_LOG(), we are making a more robust log to track the number of coins. This lays out the groundwork of how the UI will work. This is because we will be logging the same information to the player using UMG later in this chapter.
With UE_LOG() added, all we need to do is call the IncrementNumberofCollectables() function inside the PickableActor_Collectable class.
#include "SuperSideScroller_Player.h"
Player->IncrementNumberofCollectables(CollectableValue);
Note
You can find the assets and code for this exercise here: https://github.com/PacktPublishing/Game-Development-Projects-with-Unreal-Engine/tree/master/Chapter15/Exercise15.03.
With this exercise completed, you have now completed half of the work needed to develop the UI element of tracking the number of coins collected by the player. The next half will involve using the functionality developed in this activity inside UMG to show this information to the player on-screen. To do this, we need to learn more about UMG inside UE5.
UMG UI Designer is UE5’s main tool for creating UI menus, in-game HUD elements such as health bars, and other user interfaces you may want to present to the player.
In the SuperSideScroller game, we will only be using the Text widget to construct our Coin Collection UI in Exercise 15.04 – creating the Coin Counter UI HUD element. We’ll learn more about the Text widget in the next section.
The Text widget is one of the simpler widgets that exists. This is because it only allows you to display text information to the user and customize the visuals of this text. Almost every single game uses text in one way or another to display information to its players. Overwatch, for example, uses a text-based UI to display crucial match data to its players. Without the use of text, it would be very difficult – maybe even impossible – to convey key pieces of statistical data to the player, such as total damage dealt, total time playing the game, and much more.
The Text widget appears in the Palette tab within UMG. When you add a Text widget to the Canvas panel, it will display the text Text Block by default. You can customize this text by adding your text to the Text parameter of the widget. Alternatively, you can use Function Binding to display more robust text that can reference internal or external variables. Function Binding should be used whenever you need to display information that can change; this could be text that represents a player’s score, how much money the player has, or in our case, the number of coins the player has collected:
You will be using the Function Binding functionality of the Text widget to display the number of coins collected by the player using the GetCurrentNumberofCollectables() function you created in Exercise 15.03 – tracking the number of coins for the player.
Now that we have the Text widget in the Canvas panel, it is time to position this widget where we need it to be. For this, we will take advantage of anchors.
Anchors are used to define where a widget’s desired location should be on the Canvas panel. Once defined, this Anchor point will ensure that the widget will maintain this position with varying screen sizes through different platform devices such as phones, tablets, and computers. Without an anchor, a widget’s position can become inconsistent between different screen resolutions, which is never desired.
Note
For more information about anchors, please refer to the following documentation: https://docs.unrealengine.com/en-US/Engine/UMG/UserGuide/Anchors/index.html.
For our Coin Collection UI and the Text widget you will use, the Anchor point will be at the top-left corner of the screen. You will also add a position offset from this Anchor point so that the text is more visible and readable to the player. Before moving on to creating our Coin Collection UI, let’s learn about Text Formatting, which you will use to display the current number of collected coins to the player.
Much like the UE_LOG() macro available to us in C++, Blueprints offers a similar solution to display text and format it to allow custom variables to be added to it. The Format Text function takes in a single text input labeled Format and returns the Result text. This can be used to display information:
Figure 15.5 – The Format Text function
Instead of using the % symbol like UE_LOG() does, the Format Text function uses the {} symbols to denote arguments that can be passed into the string. In-between the {} symbols, you need to add an argument name; this can be anything you want, but it should be representative of what the argument is. Refer to the example shown in the following screenshot:
Figure 15.6 – An example integer in the Format Text function
The Format Text function only supports Byte, Integer, Float, Text, or EText Gender variable types, so if you are attempting to pass any other type of variable into the function as an argument, you must convert it into one of the supported types.
Note
The Format Text function is also used for Text Localization, where you can support multiple languages for your game. More information about how this can be done in both C++ and Blueprints can be found here: https://docs.unrealengine.com/en-US/Gameplay/Localization/Formatting/index.html.
You will be using the Format Text function in conjunction with the Text widget in UMG in the next exercise, where we will be creating the Coin Counter UI widget to display the number of coins that have been collected by the player. You will also be using Anchor points to position the Text widget at the top-left corner of the screen.
In this exercise, you will be creating the UMG UI asset, which will display and update the number of coins collected by the player. You will use the GetCurrentNumberofCollectables() inline function you created in Exercise 15.02 – creating the PickableActor_Collectable class, to display this value on the screen using a simple Text widget. Follow these steps to accomplish this:
Figure 15.7 – The Widget panel’s empty hierarchy
Note
For a more detailed reference regarding all the available widgets inside UMG, please read the following documentation from Epic Games: https://docs.unrealengine.com/en-US/Engine/UMG/UserGuide/WidgetTypeReference/index.html.
Before changing the text of this widget, we need to update its anchor, position, and font size so that it fits the needs we have for displaying the necessary information to the player.
Figure 15.8 – By default, there are options to anchor a widget at different locations on the screen
Anchoring allows the widget to maintain its desired location within the Canvas panel, regardless of varying screen sizes.
Now that the Text widget is anchored to the top-left corner, we need to set its relative position to this anchor so that there is an offset for better positioning and readability of the text.
Figure 15.9 – The Size To Content parameter will ensure that the Text widget won’t be cut off
Figure 15.10 – The Text widget is now anchored to the top left of the Canvas panel
Now that we have the Text widget positioned and sized the way we need it to be, let’s add a new binding to the text so that it will automatically update and match the value of the number of collectibles the player has.
Figure 15.11 – The new bound function of the text
Figure 15.12 – The Player variable with the Instance Editable and Expose on Spawn parameters enabled
By making the Player variable Public and exposed on spawn, you will be able to assign this variable when creating the widget and adding it to the screen. We will do this in Exercise 15.05 – adding the coin counter UI to the player screen.
Now that we have a reference variable to SuperSideScroller_Player, let’s continue with the Get Number of Collectables bind function.
Figure 15.13 – The Get Current Number of Collectables function you created in Exercise 15.03
Figure 15.14 – Now, we can create customized and formatted text
Coins: {coins}
Please refer to the following screenshot:
Figure 15.15 – Now, there is a new input argument for the formatted text
Remember that using the {} symbols denotes a text argument that allows you to pass variables into the text.
Figure 15.16 – The Text widget will update automatically based on the Get Current Number of Collectables function
Note
You can find the assets and code for this exercise here: https://packt.live/3eQJjTU.
With this exercise completed, you have created the UI UMG widget needed to display the current number of coins collected by the player. By using the GetCurrentNumberofCollectables() C++ function and the binding functionality of the Text widget, the UI will always update its value based on the number of coins collected. In the next exercise, we will add this UI to the player’s screen, but first, we’ll briefly learn about how to add and remove UMG from the player screen.
Now that we have created Coin Collection UI in UMG, it is time to learn how to add and remove the UI to and from the player screen, respectively. By adding Coin Collection UI to the player screen, the UI becomes visible to the player and can be updated as the player collects coins.
In Blueprints, there is a function called Create Widget, as shown in the following screenshot. Without a class assigned, it will be labeled Construct None, but do not let this confuse you:
Figure 15.17 – The Create Widget function as-is by default, without a class applied
This function requires the class of the User widget to be created and requires a Player Controller that will be referenced as the owning player of this UI. This function then returns the spawned user widget as its Return Value, where you can then add it to the player’s viewport using the Add to Viewport function. The Create Widget function only instantiates the widget object; it does not add this widget to the player’s screen. It is the Add to Viewport function that makes this widget visible on the player’s screen:
Figure 15.18 – The Add to Viewport function with ZOrder
The viewport is the game screen that overlays your view of the game world, and it uses what is called ZOrder to determine the overlay depth in cases where multiple UI elements need to overlap above or below one another. By default, the Add to Viewport function will add the User widget to the screen and make it fill the entire screen – that is, unless the Set Desired Size In Viewport function is called to set the size that it should fill manually:
Figure 15.19 – The Size parameter determines the desired size of the passed in the User widget
In C++, you also have a function called CreateWidget():
template<typename WidgetT, typename OwnerT> WidgetT * CreateWidget ( OwnerT * OwningObject, TSubclassOf < UUserWidget > UserWidgetClass, FName WidgetName )
The CreateWidget() function is available through the UserWidget class, which can be found in /Engine/Source/Runtime/UMG/Public/Blueprint/UserWidget.h.
An example of this can be found in Chapter 8, Creating User Interfaces with UMG, where you used the CreateWidget() function to create BP_HUDWidget:
HUDWidget = CreateWidget<UHUDWidget>(this, BP_HUDWidget);
Refer back to Chapter 8, Creating User Interfaces with UMG, and Exercise 8.06 – creating the health bar C++ logic, for more information regarding the CreateWidget() function in C++.
This function works almost identically to its Blueprint counterpart because it takes in the Owning Object parameter, much like the Owning Player parameter of the Blueprint function, and it requires the User Widget class to be created. The C++ CreateWidget() function also takes in an FName parameter to represent the widget’s name.
Now that we have learned about the methods to use to add a UI to the player screen, let’s put this knowledge to the test. In the following exercise, you will be implementing the Create Widget and Add to Viewport Blueprint functions so that we can add the coin collection UI that we created in Exercise 15.04 – creating the Coin Counter UI HUD element, to the player screen.
In this exercise, you will be creating a new Player Controller class so that you can use the player controller to add the BP_UI_CoinCollection widget Blueprint to the player’s screen. From there, you will also create a new Game Mode class and apply this game mode to the SuperSideScroller project. Perform the following steps to complete this exercise:
Figure 15.20 – Finding the new SuperSideScroller_Controller class to create a new Blueprint from
To add the BP_UI_CoinCollection widget to the screen, we need to use the Add to Viewport function and the Create Widget function. We want the UI to be added to the player’s screen after the player character has been Possessed by the player controller.
Figure 15.21 – Event On Possess
The Event On Possess event node returns Possessed Pawn. We will use this Possessed Pawn to pass into our BP_UI_CoinCollection UI Widget, but first, we need to Cast To the SuperSideScroller_Player class.
Figure 15.22 – We need to Cast To SuperSideScroller_Player
Figure 15.23 – The Create Widget function
After updating the Class parameter to the BP_UI_CoinCollection class, you will notice that the Create Widget function will update to show the Player variable you created, set to Exposed on Spawn.
Figure 15.24 – The Owning Player input parameter is of the Player Controller type
The Owning Player parameter refers to the Player Controller type that will show and own this UI object. Since we are adding this UI to the SuperSideScroller_Controller Blueprint, we can just use the Self reference variable to pass into the function.
Figure 15.25 – Creating the BP_UI_CoinCollection widget
Note
You can find the preceding screenshot in full resolution for better viewing at the following link: https://github.com/PacktPublishing/Game-Development-Projects-with-Unreal-Engine/blob/master/Chapter15/Images/New_25.png.
Figure 15.26 – After creating the BP_UI_CoinCollection widget, we can add it to the player viewport
Note
You can find the preceding screenshot in full resolution for better viewing at the following link: https://packt.live/2UwufBd.
Now that the player controller adds the BP_UI_CoinCollection widget to the player’s viewport, we need to create a GameMode Blueprint and apply both the BP_SuperSideScroller_MainCharacter and BP_SuperSideScroller_PC classes to this game mode.
The GameMode Blueprint contains a list of classes that you can customize with your unique classes. For now, we will only worry about Player Controller Class and Default Pawn Class.
Now that we have a custom GameMode that utilizes our custom Player Controller and Player Character classes, let’s add this game mode to the Project Settings window so that the game mode is used by default when using PIE and when cooking builds of the project.
Note
Changes made to the Maps & Modes section are automatically saved and written to the DefaultEngine.ini file, which can be found in your project’s Config folder. Default GameMode can be overwritten per level by updating the GameMode Override parameter, which can be found in the World Settings window of your level.
Figure 15.27 – Now, every coin you collect will appear on the player UI
Note
You can find the assets and code for this exercise here: https://github.com/PacktPublishing/Game-Development-Projects-with-Unreal-Engine/tree/master/Chapter15/Exercise15.05.
With this exercise complete, you have created the UI UMG widget needed to display the current number of coins collected by the player. By using the GetCurrentNumberofCollectables() C++ function and the binding functionality of the Text widget, the UI will always update its value based on the number of coins collected.
So far, we have focused on the collectible coin and allowing players to collect these coins and add the total coins collected to the player’s UI. Now, we will focus on the potion power-up and granting movement speed and jump height increases to the player for a short period. To implement this functionality, we first need to study timers.
Timers in UE5 allow you to perform actions after a delay or every X number of seconds. In the case of the SuperSideScroller potion power-up, a timer will be used to restore the player’s movement and jump to their defaults after 8 seconds.
Note
In Blueprints, you can use a Delay node in addition to timer handles to achieve the same results. However, in C++, timers are the best means to achieve delays and reoccurring logic.
Timers are managed by Timer Manager, or FTimerManager, which exists in the UWorld object. There are two main functions that you will be using from the FTimerManager class, called SetTimer() and ClearTimer():
void SetTimer ( FTimerHandle & InOutHandle, TFunction < void )> && Callback, float InRate, bool InbLoop, float InFirstDelay ) void ClearTimer(FTimerHandle& InHandle)
You may have noticed that, in both functions, there is a required FTimerHandle. This handle is used to control the timer you have set. Using this handle, you can pause, resume, clear, and even extend the timer.
The SetTimer() function also has other parameters to help you customize this timer when initially setting it. The callback function will be called after the timer has been completed, and if the InbLoop parameter is True, it will continue to call the callback function indefinitely, until the timer has been stopped. The InRate parameter is the duration of the timer itself, while InFirstDelay is an initial delay that’s applied to the timer before it begins its timer for InRate.
The header file for the FTimerManager class can be found here: /Engine/Source/Runtime/Engine/Public/TimerManager.h.
Note
You can learn more about timers and FTimerHandle by reading the documentation here: https://docs.unrealengine.com/4.27/en-US/ProgrammingAndScripting/ProgrammingWithCPP/UnrealArchitecture/Timers/.
In the following exercise, you will create your own FTimerHandle in the SuperSideScroller_Player class and use it to control how long the effects of the potion power-up last on the player.
In this exercise, you will be creating the logic behind the potion power-up and how it will affect the player’s character. You will take advantage of timers and timer handles to ensure that the power-up effects only last for a short duration. Follow these steps to accomplish this:
FTimerHandle PowerupHandle;
This timer handle will be responsible for keeping track of how much time has elapsed since it was initiated. This will allow us to control how long the potion power-up’s effects will last.
bool bHasPowerupActive;
We will use this Boolean variable when updating the Sprint() and StopSprinting() functions to ensure we update the player’s sprint movement speed appropriately based on whether the power-up is active.
void IncreaseMovementPowerup();
This is the function that will be called from the potion power-up class to enable the effects of the power-up for the player.
void EndPowerup();
With all the necessary variables and functions declared, it’s time to start defining these new functions and handling the power-up effects on the player.
void ASuperSideScroller_Player::IncreaseMovementPowerup()
{
}
bHasPowerupActive = true;
GetCharacterMovement()->MaxWalkSpeed = 500.0f;
GetCharacterMovement()->JumpZVelocity = 1500.0f;
Here, we are changing MaxWalkSpeed from the default value of 300.0f to 500.0f. As you may recall, the default sprinting speed is also 500.0f. We will address this later in this activity to increase the sprinting speed when the power-up is active.
UWorld* World = GetWorld();
if (World)
{
}
As we’ve done many times before in this project, we’re using the GetWorld() function to get a reference to the UWorld object and saving this reference in its variable.
World->GetTimerManager().SetTimer(PowerupHandle, this,
&ASuperSideScroller_Player::EndPowerup, 8.0f, false);
Here, you are using the TimerManager class to set a timer. The SetTimer() function takes in the FTimerHandle component to use; in this case, the PowerupHandle variable you created. Next, we need to pass in a reference to the player class by using the this keyword. Then, we need to provide the callback function to call after the timer has ended, which in this case is the &ASuperSideScroller_Player::EndPowerup function. 8.0f represents the duration of the timer; feel free to adjust this as you see fit, but for now, 8 seconds is fine. Lastly, there is the final boolean parameter of the SetTimer() function that determines whether this timer should loop; in this case, it should not.
void ASuperSideScroller_Player::EndPowerup()
{
}
bHasPowerupActive = false;
GetCharacterMovement()->MaxWalkSpeed = 300.0f;
GetCharacterMovement()->JumpZVelocity = 1000.0f;
Here, we are changing both the MaxWalkSpeed and JumpZVelocity parameters of the character movement component to their default values.
UWorld* World = GetWorld();
if (World)
{
}
World->GetTimerManager().ClearTimer(PowerupHandle);
By using the ClearTimer() function and passing in PowerupHandle, we are ensuring that this timer is no longer valid and will no longer affect the player.
Now that we have created the functions that handle the power-up effects and the timer associated with the effects, we need to update both the Sprint() and StopSprinting() functions so that they also take into account the speed of the player when the power-up is active.
void ASuperSideScroller_Player::Sprint()
{
if (!bIsSprinting)
{
bIsSprinting = true;
if (bHasPowerupActive)
{
GetCharacterMovement()->MaxWalkSpeed = 900.0f;
}
else
{
GetCharacterMovement()->MaxWalkSpeed = 500.0f;
}
}
}
Here, we are updating the Sprint() function to take into account whether bHasPowerupActive is true. If this variable is true, then we increase MaxWalkSpeed while sprinting from 500.0f to 900.0f, as shown here:
if (bHasPowerupActive)
{
GetCharacterMovement()->MaxWalkSpeed = 900.0f;
}
If bHasPowerupActive is false, then we increase MaxWalkSpeed to 500.0f, as we did by default.
void ASuperSideScroller_Player::StopSprinting()
{
if (bIsSprinting)
{
bIsSprinting = false;
if (bHasPowerupActive)
{
GetCharacterMovement()->MaxWalkSpeed = 500.0f;
}
else
{
GetCharacterMovement()->MaxWalkSpeed = 300.0f;
}
}
}
Here, we are updating the StopSprinting() function to take into account whether bHasPowerupActive is true. If this variable is true, then we set the MaxWalkSpeed value to 500.0f instead of 300.0f, as shown here:
if (bHasPowerupActive)
{
GetCharacterMovement()->MaxWalkSpeed = 500.0f;
}
If bHasPowerupActive is false, then we set MaxWalkSpeed to 300.0f, as we did by default.
Note
You can find the assets and code for this exercise here: https://github.com/PacktPublishing/Game-Development-Projects-with-Unreal-Engine/tree/master/Chapter15/Exercise15.06.
With this exercise complete, you have created the potion power-up effects within the player character. The power-up increases both the default movement speed of the player and increases their jump height. Moreover, the effects of the power-up increase the sprinting speed. By using timer handles, you were able to control how long the power-up effect would last.
Now, it is time to create the potion power-up actor so that we can have a representation of this power-up in the game.
Now that the SuperSideScroller_Player class handles the effects of the potion power-up, it’s time to create the potion power-up class and Blueprint. This activity aims to create the potion power-up class, inherit from the PickableActor_Base class, implement the overlapping functionality to grant the movement effects that you implemented in Exercise 15.06 – adding the potion power-up behavior to the player, and create the Blueprint actor for the potion power-up. Follow these steps to create the potion power-up class and create the potion Blueprint actor:
Expected output:
Figure 15.28 – The potion power-up
Note
The solution for this activity can be found on https://github.com/PacktPublishing/Elevating-Game-Experiences-with-Unreal-Engine-5-Second-Edition/tree/main/Activity%20solutions.
With this activity complete, you were able to put your knowledge to the test in terms of creating a new C++ class that inherits from the PickableActor_Base class and overrides the PlayerPickedUp() function to add custom logic. By adding the call to the IncreaseMovementPowerup() function from the player class, you were able to add the movement power-up effects to the player when overlapping with the actor. Then, by using a custom mesh, material, and audio assets, you were able to bring the Blueprint actor to life from the PickableActor_Powerup class.
Now that we have created the coin collectible and the potion power-up, we need to implement a new gameplay feature into the project: the Brick class. In games such as Super Mario, bricks contain hidden coins and power-ups for the players to find. These bricks also serve as a means of reaching elevated platforms and areas within the level. In our SuperSideScroller project, the Brick class will serve the purpose of containing hidden coin collectibles for the player, and as a means of allowing the player to reach areas of the level by using the bricks as paths to access hard-to-reach locations. So, in the next section, we will create the Brick class, which needs to be broken to find the hidden coins.
Now that we have created the coin collectible and the potion power-up, it is time to create the Brick class, which will contain hidden coins for the player to collect. The brick is the final gameplay element of the SuperSideScroller project. In this exercise, you will be creating the Brick class, which will be used as part of the platforming mechanic of the SuperSideScroller game project, but also as a means to hold collectibles for players to find. Follow these steps to create this Brick class and its Blueprint:
By default, the SuperSideScroller_Brick class comes with the Tick() function, but we will not need this function for the Brick class. Remove the function declaration for Tick() from the SuperSideScroller_Brick.h header file and remove the function definition from the SuperSideScroller_Brick.cpp source file before continuing.
UPROPERTY(VisibleDefaultsOnly, Category = Brick)
class UStaticMeshComponent* BrickMesh;
UPROPERTY(VisibleDefaultsOnly, Category = Brick)
class UBoxComponent* BrickCollision;
UFUNCTION()
void OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor,
UPrimitiveComponent* OtherComp, FVector
NormalImpulse,
const FHitResult& Hit);
Note
Recall that you used the OnHit() function when developing the PlayerProjectile class in Chapter 13, Creating and Adding the Enemy Artificial Intelligence, for this project. Please review that chapter for more information about the OnHit() function.
UPROPERTY(EditAnywhere)
bool bHasCollectable;
This Boolean will determine whether the brick contains a coin collectible for the player.
UPROPERTY(EditAnywhere)
int32 CollectableValue = 1;
The brick will need to contain a unique sound and particle system so that it has a nice layer of polish for when the brick is destroyed by the player. We’ll add these properties next.
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Brick)
class USoundBase* HitSound;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Brick)
class UParticleSystem* Explosion;
Now that we have all the necessary properties for the Brick class, let’s move on to the SuperSideScroller_Brick.cpp source file, where we will initialize the components.
#include "Components/StaticMeshComponent.h"
#include "Components/BoxComponent.h"
BrickMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("BrickMesh"));
BrickMesh->SetCollisionProfileName("BlockAll");
RootComponent = BrickMesh;
BrickCollision = CreateDefaultSubobject<UBoxComponent>
(TEXT("BrickCollision"));
BrickCollision->SetCollisionProfileName("BlockAll");
BrickCollision->AttachToComponent(RootComponent,
FAttachmentTransformRules::KeepWorldTransform);
void ASuperSideScroller_Brick::OnHit(UPrimitiveComponent* HitComp, AActor*
OtherActor, UPrimitiveComponent* OtherComp, FVector
NormalImpulse, const
FHitResult& Hit)
{
}
BrickCollision->OnComponentHit.AddDynamic(this,
&ASuperSideScroller_Brick::OnHit);
The last thing we need to do with the BrickMesh component is adjust its scale so that it fits the needs of the player character, as well as the platforming mechanics of the SuperSideScroller game project.
(X=0.750000,Y=0.750000,Z=0.750000)
Now that the BrickMesh component is 75% of its normal size, the Brick actor will become more manageable for us as designers when we place the actor into the game world, as well as when we’re developing interesting platforming sections within the level.
The final step here is to update the location of the BrickCollision component so that it only has some of its collision sticking out from the bottom of the BrickMesh component.
(X=0.000000,Y=0.000000,Z=30.000000)
The BrickCollision component should now be positioned as follows:
Figure 15.29 – Now, the BrickCollision component is just barely outside the BrickMesh component
We are making this adjustment to the position of the BrickCollision component so that the player can only hit UBoxComponent when jumping underneath the brick. By making it slightly outside of the BrickMesh component, we can control it better and ensure that this component cannot be hit by the player in any other way.
Note
You can find the assets and code for this exercise here: https://github.com/PacktPublishing/Game-Development-Projects-with-Unreal-Engine/tree/master/Chapter15/Exercise15.07.
With this exercise complete, you were able to create the base framework for the SuperSideScroller_Brick class and put together the Blueprint actor to represent the brick in the game world. By adding a cube mesh and brick material, you added a nice visual polish to the brick. In the following exercise, you will add the remaining C++ logic to the brick. This will allow the player to destroy the brick and obtain a collectible.
In the previous exercise, you created the base framework for the SuperSideScroller_Brick class by adding the necessary components and creating the BP_Brick Blueprint actor. In this exercise, you will add on top of the C++ code of Exercise 15.07 – creating the Brick class, to grant logic to the Brick class. This will allow the brick to give players coin collectibles. Perform the following steps to accomplish this:
void AddCollectable(class ASuperSideScroller_Player* Player);
We want to pass in a reference to the SuperSideScroller_Player class so that we can call the IncrementNumberofCollectables() function from that class.
void PlayHitSound();
The PlayHitSound() function will be responsible for spawning the HitSound property you created in Exercise 15.07 – creating the Brick class.
void PlayHitExplosion();
The PlayHitExplosion() function will be responsible for spawning the Explosion property you created in Exercise 15.07 – creating the Brick class.
With the remaining functions needed for the SuperSideScroller_Brick class declared in the header file, let’s move on and define these functions inside the source file.
#include "Engine/World.h"
#include "Kismet/GameplayStatics.h"
#include "SuperSideScroller_Player.h"
The includes for the World and GameplayStatics classes are necessary to spawn both the HitSound and the Explosion effects for the brick. Including the SuperSideScroller_Player class is required to make the call to the IncrementNumberofCollectables() class function.
void ASuperSideScroller_Brick::AddCollectable(class
ASuperSideScroller_Player* Player)
{
}
Player->IncrementNumberofCollectables(CollectableValue);
void ASuperSideScroller_Brick::PlayHitSound()
{
UWorld* World = GetWorld();
if (World && HitSound)
{
UGameplayStatics::SpawnSoundAtLocation(World,
HitSound,
GetActorLocation());
}
}
void ASuperSideScroller_Brick::PlayHitExplosion()
{
UWorld* World = GetWorld();
if (World && Explosion)
{
UGameplayStatics::SpawnEmitterAtLocation(World,
Explosion,
GetActorTransform());
}
}
With these functions defined, let’s update the OnHit() function so that if the player does hit the BrickCollision component, we can spawn HitSound and Explosion, and also add a coin collectible to the player’s collection.
ASuperSideScroller_Player* Player =
Cast<ASuperSideScroller_Player>(OtherActor);
if (Player && bHasCollectable)
{
}
AddCollectable(Player);
PlayHitSound();
PlayHitExplosion();
Destroy();
With these assets migrated to your project, return to the Unreal Engine 5 editor of your SuperSideScroller project.
Figure 15.30 – This Brick actor is set to have a collectible spawn
Figure 15.31 – Now, the player can hit the brick and it will be destroyed
When bHasCollectable is True, SuperSideScroller_Brick will play our HitSound, spawn the Explosion particle system, add a coin collectible to the player, and be destroyed.
Note
You can find the assets and code for this exercise here: https://github.com/PacktPublishing/Game-Development-Projects-with-Unreal-Engine/tree/master/Chapter15/Exercise15.08.
With this exercise complete, you have now finished developing the gameplay mechanics for the SuperSideScroller game project. Now, the SuperSideScroller_Brick class can be used for both the platforming gameplay and the coin-collecting mechanic that we want for the game.
Now that the brick can be destroyed and hidden coins can be collected, all the gameplay elements that we set out to create for the SuperSideScroller game project are complete.
In this chapter, you put your knowledge to the test to create the remaining gameplay mechanics for the SuperSideScroller game project. Using a combination of C++ and Blueprints, you developed the potion power-up and coins for the player to collect in the level. Also, by using your knowledge from Chapter 14, Spawning the Player Projectile, you added unique audio and visual assets to these collectible items to add a nice layer of polish to the game.
You learned and took advantage of the UMG UI system within UE5 to create a simple, yet effective, UI feedback system to display the number of coins that the player has collected. By using the binding feature of the Text widget, you were able to keep the UI updated with the number of coins the player has currently collected. Lastly, you created a Brick class using the knowledge you learned from the SuperSideScroller project to hide coins for the player so that they can collect and find them.
The SuperSideScroller project has been an extensive project that expanded upon many of the tools and practices available within UE5. In Chapter 10, Creating the SuperSideScroller Game, we imported custom skeleton and animation assets to use in developing the Animation Blueprint of the player character. In Chapter 11, Working with Blend Space 1D, Key Bindings, and State Machines, we used Blend Spaces to allow the player character to blend between idle, walking, and sprinting animations, while also using an Animation State Machine to handle the jumping and movement states of the player character. We then learned how to control the player’s movement and jump height using the character movement component.
In Chapter 12, Animation Blending and Montages, we learned more about animation blending inside Animation Blueprints by using the Layered Blend per Bone function and Saved Cached Poses. By adding a new AnimSlot for the upper body animation of the player character’s throw animation, we were able to have both the player movement animations and the throw animation blend together smoothly. In Chapter 13, Creating and Adding the Enemy Artificial Intelligence, we used the robust systems of behavior trees and Blackboards to develop AI behavior for the enemy. We created a Task that will allow the enemy AI to move in-between points from a custom Blueprint that we also developed to determine patrol points for the AI.
In Chapter 14, Spawning the Player Projectile, we learned how to create an Anim Notify and how to implement this notify in our Animation Montage for the player character’s throw to spawn the player projectile. Then, we learned about how to create projectiles and how to use Projectile Movement Component to have the player projectile move in the game world.
Finally, in this chapter, we learned how to create UI using the UMG toolset for the coin collectible, as well as how to manipulate our Character Movement Component to create the potion power-up for the player. Lastly, you created a Brick class that can be used to hide coins for the player to find and collect.
In the next chapter, you will learn about the basics of multiplayer, server-client architectures, and the gameplay framework classes used for multiplayer inside UE5. You will use this knowledge to expand upon the multiplayer FPS project in UE5.
This summarization only really scratches the surface of what we learned and accomplished in the SuperSideScroller project. Before you move on, here are some challenges for you to test your knowledge and expand upon the project:
Test your knowledge from this section by adding the following functionality to the SuperSideScroller project.