Chapter 6. Power Ups for Your Character, Power Ups for the User

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:

  • Creating a plugin for Unreal Engine using C++
  • Working with plugins from within our project codebase
  • Loading and playing assets such as sounds and particle effects from C++
  • Creating a HUD class using C++
  • Working with destructible meshes in UE and C++
  • Adjusting object hierarchies from within C++

Cleaning up shop and object hierarchies

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.

BountyDashObject

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.

Modifying existing 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!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.241.101