Working with our first plugin module

Now that we have described our plugin and its module, we can begin to specify what our only code module within the plugin is going to do. The purpose of the PowerUpPlugin is to create a power-up type we can include in a game code object that will be constructed with a random power-up type. We can then identify this type from within the game code and enact functionality based on that type. As we are going to be creating a type that needs to be identified by the engine, we will need to inherit our type from UObject. This means our plugin is going to need access to the CoreUObject module provided by the engine. This also means that our plugin module will need to be publically exposed to our project codebase so we may include the plugin type. This is creating the aforementioned static link between the plugin and the project codebase.

PowerUpPlugin.Build.cs

We can create these associations by modifying the .Build.cs file. You will notice that the Build.cs file contains a class named after the module that inherits from the ModuleRules object. It is within the constructor of this C# object that you can specify the different include paths and dependencies of the module. These include paths and dependencies are found in the form of arrays that contain string literals indicating either an include path or a module dependency. The first element range modified in the generated *.Build.cs file is PublicIncludePaths. It is with this element that you may specify a range of public include paths through the AddRange() function. This function takes in a string array of include paths. By default, "PowerUpPlugin/Public" is specified as follows:

PublicIncludePaths.AddRange(
new string[] {
        "PowerUpPlugin/Public"
        // ... add public include paths required here 
    }
    );

Adding this include path means that any source located in the Public folder under PowerUpPlugin/Source/PowerUpPlugin/Public will be included publicly within our project codebase. Because of this, any plugin API code within files we include in this Public folder will be exported publically when the plugin is loaded. This will create a direct dependency between the plugin's API set and game code. As we are creating a game content plugin, this is OK.

We then specify the private include paths within the *.Build.cs file. Refer to the following code:

PrivateIncludePaths.AddRange(
    new string[]{
        "PowerUpPlugin/Private",
        //     add other private include paths required here    
    }
    );

Private dependencies within a plugin are ok as they will be hidden away from Unreal Engine or game code, and any code files stored within the Private folder will only be referenceable by code objects declared within the plugin module itself.

The next section within the module build file specifies which dependencies the plugin has with engine modules. This is where we begin to associate which engine objects our plugin will need to include. In the case of the PowerUpPlugin, we simply need public access to the CoreUObject module as we will be inheriting our power-up type from UObject. Do this now by ensuring the range added to PublicDependencyModuleNames is as follows:

PublicDependencyModuleNames.AddRange(
    new string[] {
        "Core",
        "CoreUObject",
        // ... add other public dependencies that you statically link with here ...
    }
    );

This will ensure that the PowerUpPlugin module has public access to the CoreUObject and Core modules. We can leave the private dependency range as is; we do not require any more exposed functionality. The next range DynamicallyLoadedModuleNames would be used to specify any engine modules that you would need dynamically loaded by the plugin module.

Declaring the PowerUpPlugin code module

By default, the plugin wizard will have provided you with three source files. One in the public source folder PowerUpPlugin.h, and two in the private source folder PowerUpPlugin.cpp and PowerUpPluginPrivatePCH.h. It is within these three files you will begin to construct the makeup of your plugin module.

Let's first address PowerUpPlugin.h:

#pragma once

#include "ModuleManager.h"

class FPowerUpPluginModule : public IModuleInterface
{
public:

    /** IModuleInterface implementation */
    virtual void StartupModule() override;
    virtual void ShutdownModule() override;
};

Each module has a lifetime, a lifetime that begins when StartupModule() is called, and ends when ShutdownModule() is called. The first will be called when the module is loaded into memory at the phase described in the *.uplugin file (loading phase). The second is called when the module is shut down and can be used to clean up your module upon shutdown if need be.

As you can see, the FPowerUpPluginModule inherits from the IModuleInterface. This interface is the same one that is used by all engine modules and it is through this interface that the module is loaded with regard to the description specified in the *.uplugin file. As you can see, the StartupModule() and ShutdownModule() functions have been overridden, as they are present in the interface. We do not need to add anything to this file as our PowerUpPlugin is very simple in nature. Let's now address the PowerUpPlugin.cpp:

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.

#include "PowerUpPluginPrivatePCH.h"

#define LOCTEXT_NAMESPACE "FPowerUpPluginModule"

void FPowerUpPluginModule::StartupModule()
{
}

void FPowerUpPluginModule::ShutdownModule()
{
}

#undef LOCTEXT_NAMESPACE
    
IMPLEMENT_MODULE(FPowerUpPluginModule, PowerUpPlugin)

As you can see, the PCH has been included here as it will be for all .cpp source files included in the module. We then see two empty definitions for StartupModule() and ShutdownModule(). We do not need to change these as we will not be performing any post-load or pre-shutdown operations on this code module.

There are two important things to note in this .cpp file. The first is the unique #define LOCTEXT_NAMESPACE "FPowerUpPluginModule". This acts as a guard, ensuring that this module is not implemented again somewhere else in the plugin codebase. The second is the line IMPLEMENT_MODULE(FPowerUpPluginModule, PowerUpPlugin). It is this line of code that preps the module for loading, and informs that this module must be implanted with regards to the details specified in the Modules section of the *.uplugin file. This is why the module name as declared in the *.uplugin file can be found as the second parameter of the macro.

The next thing we need to do is prepare the PCH file for use within the plugin. By default, the PCH file will only contain the following:

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.

#include "PowerUpPlugin.h"

// You should place include statements to your module's private header files here. You only need to
// add includes for headers that are used in most of your module's source files though.

All we need to do is add an include to the engine module we will be interfacing with, the CoreUObject module. Add the following line to the PCH above #include "PowerUpPlugin":

#include "CoreUObject.h"

Adding the PowerUp object

Now that we have established the basic codebase for the module, we can now add the code object we are going to be including in our game code. Start by adding a header file to PowerUpPlugin/Source/PowerUpPlugin/Public and call it PowerUpObject.h. Open this code file now.

Within this code file, we need to define an enum type that will hold all of the possible power-up types that we are going to need. We are also going to have to define the power-up class that we will be including in our game code. This object will need to have a member variable of the enum type variable we declare, as well as an accessor method for that variable.

Let's start by defining the enum type. Add the following lines of code to PowerUpObject.h:

#pragma once
#include "PowerUpObject.generated.h"

enum class EPowerUp : uint8
{
    SPEED = 1 UMETA(DisplayName="Speed"),
    SMASH = 2 UMETA(DisplayName="Smash"), 
    MAGNET = 3 UMETA(DisplayName="Magnet")
};

Here, we have specified we wish this code file to only be compiled once through the pre-processor directive #pragma once, we have also included the generated header file that will be created for this object. Next, we have defined our strongly typed enum class EPowerUp. This enum class has been strongly typed to uint8 as we will not need more the 256 unique enum values. Inside of this enum, we have declared three types: speed, smash, and magnet. These will be the power-up types that are used in our game code.

It should be noted that the functionality that we are including in this plugin is heavily linked to that of our game code. Usually, this would not be a plugin and simply additional game code files. Normally, plugin code is very generic and transportable between multiple game projects. If you wish to maintain a generic approach, you may call these power ups POWERUP_1, POWERUP_2, POWERUP_3, or something along those lines.

Next, we must define the object itself. Do this by adding the following lines to the plugin:

UCLASS()
class POWERUPPLUGIN_API UPowerUpObject : public UObject
{
    GENERATED_UCLASS_BODY()
public:

    EPowerUp GetType();

private:
    EPowerUp Type;
};

Here, we have declared a UCLASS UPowerUpObject that inherits from UObject. You will also note that this class has been declared with the POWERUPPLUGIN_API macro, meaning this object will be exposed to other modules. As we have contained this code file within the public source folder, it will be exposed publicly to engine modules and game code. Within this class, we have a very simple implementation. A getter method GetType() that will retrieve the enum type variable saved within this object, and the private EPowerUp type. Now add another source file to the plugin module, this time to the private folder, and call it PowerUpObject.cpp. Within this file, add the following code:

#include "PowerUpPluginPrivatePCH.h"
#include "PowerUpObject.h"

UPowerUpObject::UPowerUpObject(const FObjectInitializer& ObjectInitializer)
    : Super(ObjectInitializer)
{
    int iType = FMath::Rand() % 3;

    switch (iType)
    {
    case 0:
    {
        Type = EPowerUp::SPEED;
        break;
    }
    case 1:
    {
        Type = EPowerUp::SMASH;
        break;
    }
    case 2:
    {
        Type = EPowerUp::MAGNET;
        break;
    }
    default:
        break;
    }
}

EPowerUp UPowerUpObject::GetType()
{
    return Type;
}

As you can see, the object definition is quite simple. Within the constructor, we are generating a random number from 0 to 2 inclusive. We then assign the appropriate enum type based off of the result of this random number through a switch case. We have also defined the getter method for Type. We are now done writing code for our plugin! Admittedly, this is a very simple plugin; however, the knowledge covered thus far in this chapter provides you with the ability to create larger and more complex plugin implementations. If you wish to learn more about plugins in Unreal Engine 4, information can be found at https://docs.unrealengine.com/latest/CHN/Programming/Plugins/index.html.

Using our plugin in engine

There is one last thing we have to do before our plugin can be used by our codebase, and that is modifying the build.cs file for our project so that we may complete the link between the publically exposed API content of the PowerUpPlugin module and the project code. We do this by opening the BountyDash.Build.cs under BountyDashSourceBountyDash. Within this file, we need to add another value to the PublicDependencyModuleNames range so that it appears as follows:

PublicDependencyModuleNames.AddRange(new string[] 
{ "Core", "CoreUObject", "Engine", "InputCore", "PowerUpPlugin" });

This will add the PowerUpPlugin module as a public dependency of the project and we will be able to include the PowerUpObject.h we just created in our game code. Any time you wish to publically expose functionality from within a plugin module, you must add the module to this public dependency list, otherwise the exposed API will not be linked to the project.

Now that we have this in place, compile the code! If everything has gone according to plan, the code should compile and build. If not, re-address the previous code.

Creating the BountyDashPowerUp object

Now that we have publically linked our plugin module, we can include the PowerUpObject in our game code files! Let's do this now. First, create a new class called BountyDashPowerUp through the C++ class wizard within the Editor. Ensure that this class inherits from ABountyDashObject as it will be part of the generic object hierarchy.

Modify the ABountyDashPowerUp class definition found in BountyDashPowerUp.h so it appears as follows:

#include "BountyDashObject.h"
#include "BountyDashPowerUp.generated.h"

UCLASS()
class BOUNTYDASH_API ABountyDashPowerUp : public ABountyDashObject
{
    GENERATED_BODY()

public:
    // Sets default values for this actor's properties
    ABountyDashPowerUp();

    UFUNCTION()
    virtual void MyOnActorOverlap(AActor* otherActor) override;

protected:
    UPROPERTY(EditAnywhere)
    UStaticMeshComponent* Mesh;

private:
    class UPowerUpObject* PowerUp;
};

We are overriding the MyOnActorOverlap() function provided in the ABountyDashObject base class as this object will require custom overlap functionality. We have included a UStaticMeshComponent handle to be used to hold the mesh for this object. We have also included a handle to the UPowerUpObject defined in our plugin module. This power-up object will be used to determine which of the three powers the object will be.

Open BountyDashPowerUp.cpp now. First, add #include "PowerUpObject.h" to the include list for this file so we can match the forward declaration of UPowerUpObject found in the class definition. Then add the following code to the constructor of the object:

// Sets default values
ABountyDashPowerUp::ABountyDashPowerUp()
{
    // 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;

    thisPowerUp = CreateDefaultSubobject<UPowerUpObject>(TEXT( "PowerUp"));
    check(thisPowerUp);

    mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
    check(mesh);

    mesh->AttachTo(RootComponent);
    mesh->SetCollisionProfileName("OverlapAllDynamic");

Here, we are populating the handle to the UPowerUpObject type we included from the PowerUpPlugin module we made earlier. As we have publically linked the plugin module type to the project code, we have gained full use of the construction and initialization functions provided by the engine. Populating this handle is done in exactly the same way as member components. We have used the CreateDefaultSubobject template function to create a new UPowerUpObject, then we have saved a handle to this object into the PowerUp handle. The next thing we are doing is creating a static mesh component and saving that into the provided handle as well.

Now we are going to be loading a mesh based off of the EPowerUp Type member of the UPowerUpObject. There are three different enum values that the UPowerUpObject could hold. Because of this, we need to load the mesh we want to use in a slightly different way. Add the following code to the constructor:

FString AssetName;
switch (thisPowerUp->GetType())
{
case EPowerUp::SPEED:
AssetName = "/Game/StarterContent/Shapes/Shape_QuadPyramid.Shape_QuadPyramid";
    break;

case EPowerUp::SMASH:
AssetName = "/Game/StarterContent/Shapes/Shape_WideCapsule.Shape_WideCapsule";
    break;

case EPowerUp::MAGNET:
AssetName = "/Game/StarterContent/Shapes/Shape_Pipe_180.Shape_Pipe_180";
    break;

default:
    break;

}

ConstructorHelpers::FObjectFinder<UStaticMesh> myMesh(&AssetName.GetCharArray()[0]);

if (myMesh.Succeeded())
{
    mesh->SetStaticMesh(myMesh.Object);
}
} // <- closing ABountyDashPowerUp::ABountyDashPowerUp()

Here, we have declared a local FString variable AssetName; this variable will hold the asset path we are going to use to load the mesh we desire. To determine which path we populate this FString with, we have used a switch case with the EPowerUp Type member of UPowerUpObject as the target via the GetType() method. As this UPowerUpObject has already been constructed, we can guarantee that a randomly generated power-up type has already been assigned.

We then parse this populated FString variable through to the FObjectFinder constructor helper so that our mesh object may be found and loaded from the Content browser, as we have done many times before. However, the constructor of an FObjectFinder expects a const TChar* as an input parameter. So, this time, we are passing the address of the first element of the char array that makes up the FString through &AssetName.GetCharArray()[0].

The last thing we need to do is add a blank definition for the MyOnActorOverlap method we overrode in the class definition. Do this now by adding the following code to BountyDashPowerUp.cpp:

void ABountyDashPowerUp::MyOnActorOverlap(AActor* otherActor)
{
    
}

We will be populating this method with the appropriate functionality soon.

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

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