Welcome to Chapter 6! During this chapter, we are going to finish and polish the Bounty Dash project. We are going to be looking into the Unreal Engine plugin system and how to create reuseable plugins we can share between projects! This chapter, much like the last, will be heavily based on C++. The purpose of this chapter is to complete your foundation knowledge of how to use C++ with Unreal Engine.
We are going to be writing our first C++ plugin as well as our first custom C++ HUD
object. We will use the first to create a power-up system so we may collect power ups and have them result in a gameplay change for the character. The HUD
class will be used so we may show gameplay information as screen text. During this chapter, we will also be polishing the Bounty Dash
project. We are going to learn how to load and run sound cues from C++, how to load and play particle effects from C++, and how we can utilize the destructible Mesh
functionality of Unreal Engine to create a power up that will let the player smash through any obstacles and finish the encroaching wall of death!
We will be covering the following topics:
HUD
class using C++We are going to start this chapter off by preparing our codebase. During the course of this chapter we are going to be adding new objects to the game scene, such as power ups, particle effects, and destructible objects that we need to behave much in the same way as obstacles and coins currently do. As our current class hierarchy has our ACoin
object inheriting from AObstacle
, we have begun an inheritance chain that will lead to many development headaches. Now is a good time for us to right this with a quick fix, and construct a much smarter and cleaner object hierarchy.
When creating object hierarchies, we should ask ourselves, what functionality will be shared among all objects in the hierarchy? Any functionality you can think of that would adhere to this question should be included in the base object. Currently, most of our base functionality is present in the AObstacle
object. This process will be removing a large amount of functionality from this object and placing it in a generic base class object.
Let's begin by creating a new base object called BountyDashObject
(through the C++ class wizard) that inherits from AActor
. This object is going to act as our base class for our object hierarchy. We will be migrating most of the functionality out of AObstacle
into this object. Once the object has been generated, address the BountyDashObject.h
file, and amend this file so that it appears as follows:
UCLASS() class BOUNTYDASH_API ABountyDashObject : public AActor { GENERATED_BODY() float KillPoint; public: // Sets default values for this actor's properties ABountyDashObject(); // Called when the game starts or when spawned virtual void BeginPlay() override; // Called every frame virtual void Tick(float DeltaSeconds) override; void SetKillPoint(float point); float GetKillPoint(); protected: UFUNCTION() virtual void MyOnActorOverlap(AActor* otherActor); UFUNCTION() virtual void MyOnActorEndOverlap(AActor* otherActor); UPROPERTY(EditAnywhere) USphereComponent* Collider; };
As you can see, this currently looks very similar to that of the AObstacle
object definition; don't worry about that for now, we will tidy that up soon. First off, let's quickly break down this object.
It contains two members; one is a private float value called KillPoint
that will be used for despawn
logic. The second is a protected USphereComponent
handle that will be used to store the sphere collider, which will be included in all objects that inherit from this class. This sphere collider will act as the root component. This object also contains the standard BeginPlay()
and Tick()
functions that we expect in a class that inherits from AActor
. We have also included the two collision functions, MyOnActorOverlap()
and MyOnActorEndOverlap()
, as most of the objects that will inherit from this class will require collision in some way. Getter
and Setter
methods have also been provided for the killPoint
float variable.
Based on this class definition, we can assume that every object that inherits from this class will need to tick on a per-frame basis, collide with things through the USphereComponent
, and despawn
through the killPoint
variable. That seems like a good start for our base functionality. Let's now work with the BountyDashObject.cpp
file to define the base functionality. Open the generated .cpp
file and add the following code to the constructor:
ABountyDashObject::ABountyDashObject() { // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; Collider = CreateDefaultSubobject<USphereComponent>(TEXT("Collider")); check(Collider) RootComponent = Collider; Collider->SetCollisionProfileName("OverlapAllDynamic"); OnActorBeginOverlap.AddDynamic(this, &ABountyDashObject::MyOnActorOverlap); OnActorBeginOverlap.AddDynamic(this, &ABountyDashObject::MyOnActorEndOverlap); }
This will ensure that any object that inherits from this class will have a properly initialized USphereComponent
as its root. This constructor also ensures that every inherited object has the two collision functions bound to the correct overlap delegates. As we saw in Chapter 5, Upgrade Activated – Making Bounty Dash with C++, objects that inherit from this class may override these functions virtually without having to rebind the function to the delegate.
Next we can define the BeginPlay()
and Tick()
functions. These have already been described many times in Chapter 5, Upgrade Activated – Making Bounty Dash with C++ so we do not need an explanation:
// Called when the game starts or when spawned void ABountyDashObject::BeginPlay() { Super::BeginPlay(); } // Called every frame void ABountyDashObject::Tick(float DeltaTime) { Super::Tick(DeltaTime); float gameSpeed = GetCustomGameMode<ABountyDashGameMode>(GetWorld())-> GetInvGameSpeed(); AddActorLocalOffset(FVector(gameSpeed, 0.0f, 0.0f)); if (GetActorLocation().X < KillPoint) { Destroy(); } }
It is important to note that the Tick()
function definition will cause any object that inherits from ABountyDashObject
to translate down the game field until the specified KillPoint
, much like coins and obstacles do now. Also we just referenced our Bounty Dash game mode, ensure to add #include BountyDashGameMode.h
to the include list for this .cpp
.
Lastly, we must include empty bodies for our bound collision functions. These have been left empty, we do not wish to specify any generic functionality as almost every inherited object will require unique functionality for collision. We are also going to define the getter and setter methods for the KillPoint
member variable:
void ABountyDashObject::MyOnActorOverlap(AActor* otherActor) { } void ABountyDashObject::MyOnActorEndOverlap(AActor* otherActor) { } void ABountyDashObject::SetKillPoint(float point) { KillPoint = point; } float ABountyDashObject::GetKillPoint() { return KillPoint; }
Now that we have defined our base functionality, we can re-write the AObstalce
and ACoin
objects.
We need to remove the duplicate functionality from AObstacle
then re-parent it to ABountyDashObject
. The changes to ACoin
will be much less drastic as this object was already created with most of the inheritance in place already in mind. Let's start with Obstacle.h
. Change the code within so it matches the following:
#include "BountyDashObject.h" #include "Obstacle.generated.h" UCLASS(BlueprintType) class BOUNTYDASH_API AObstacle : public ABountyDashObject { GENERATED_BODY() public: // Sets default values for this actor's properties AObstacle(); public: UPROPERTY(EditAnywhere, BlueprintReadWrite) UStaticMeshComponent* Mesh; };
What we have done here is remove all of the functionality that was defined in ABountyDashObject
, then re-parented AObstalce
to ABountyDashObject
so that the functionality will be inherited instead. Now change Obstacle.cpp
to match the following:
// Sets default values AObstacle::AObstacle() { // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh")); check(Mesh); Mesh->AttachTo(Collider); Mesh->SetCollisionResponseToAllChannels(ECR_Ignore); }
As the rest of the functionality is included in ABountyDashObject
, we can remove the BeginPlay()
, Tick()
, KillPoint
, getter
and setter
methods, and collision
functions from this .cpp
.
Now we can work with the ACoin
object. When working with Coin.h
, all we have to do is re-parent the class to ABountyDashObject
and add a UStaticMeshComponent
handle. The code should appear as follows:
#include "BountyDashObject.h" #include "Coin.generated.h" UCLASS() class BOUNTYDASH_API ACoin : public ABountyDashObject { GENERATED_BODY() ACoin(); // Called every frame virtual void Tick(float DeltaSeconds) override; UFUNCTION() virtual void MyOnActorOverlap(AActor* otherActor) override; protected: UPROPERTY(EditAnywhere, BlueprintReadWrite) UStaticMeshComponent* Mesh; };
As we have changed the parent class, we must also change #include "Obstacle.h"
to #include "BountyDashObject.h"
. Then, in coin.cpp
, ensure the constructor appears as follows:
ACoin::ACoin() { Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh")); check(Mesh); Mesh->AttachTo(RootComponent); Mesh->SetCollisionProfileName("OverlapAllDynamic"); }
Also ensure you add #include "Obstacle.h"
to the include list as we have removed it from the header, and reference AObstacle
in the ACoin::MyOnActorOverlap
override.
Great work! We are now done modifying the existing codebase to support our new generic hierarchy. Ensure the code compiles and the project still functions as it did at the end of Chapter 5, Upgrade Activated – Making Bounty Dash with C++.
The next thing we are going to do is create our very own C++ plugin!
3.143.9.223