Before we can keep track of party members, we'll need a way to track a character's current state, such as how much HP the character has or what it has equipped.
To do this, we'll create a new class named GameCharacter
. As usual, create a new class and pick Object
as the parent class.
The header for this class looks like the following code snippet:
#pragma once #include "Data/FCharacterInfo.h" #include "Data/FCharacterClassInfo.h" #include "GameCharacter.generated.h" UCLASS( BlueprintType ) class RPG_API UGameCharacter : public UObject { GENERATED_BODY() public: FCharacterClassInfo* ClassInfo; UPROPERTY( EditAnywhere, BlueprintReadWrite, Category = CharacterInfo ) FString CharacterName; UPROPERTY( EditAnywhere, BlueprintReadWrite, Category = CharacterInfo ) int32 MHP; UPROPERTY( EditAnywhere, BlueprintReadWrite, Category = CharacterInfo ) int32 MMP; UPROPERTY( EditAnywhere, BlueprintReadWrite, Category = CharacterInfo ) int32 HP; UPROPERTY( EditAnywhere, BlueprintReadWrite, Category = CharacterInfo ) int32 MP; UPROPERTY( EditAnywhere, BlueprintReadWrite, Category = CharacterInfo ) int32 ATK; UPROPERTY( EditAnywhere, BlueprintReadWrite, Category = CharacterInfo ) int32 DEF; UPROPERTY( EditAnywhere, BlueprintReadWrite, Category = CharacterInfo ) int32 LUCK; public: static UGameCharacter* CreateGameCharacter( FCharacterInfo* characterInfo, UObject* outer ); public: void BeginDestroy() override; };
For now, we're keeping track of the character's name, character's source class information, and character's current stats. Later, we will use the UCLASS
and UPROPERTY
macros to expose information to the Blueprint. We'll add other information later as we work on the combat system.
As for the .cpp
file, it will look like this:
#include "RPG.h" #include "GameCharacter.h" UGameCharacter* UGameCharacter::CreateGameCharacter( FCharacterInfo* characterInfo, UObject* outer ) { UGameCharacter* character = NewObject<UGameCharacter>( outer ); // locate character classes asset UDataTable* characterClasses = Cast<UDataTable>( StaticLoadObject( UDataTable::StaticClass(), NULL, TEXT( "DataTable'/Game/Data/CharacterClasses.CharacterClasses'" ) ) ); if( characterClasses == NULL ) { UE_LOG( LogTemp, Error, TEXT( "Character classes datatable not found!" ) ); } else { character->CharacterName = characterInfo->Character_Name; FCharacterClassInfo* row = characterClasses->FindRow<FCharacterClassInfo>( *( characterInfo->Class_ID ), TEXT( "LookupCharacterClass" ) ); character->ClassInfo = row; character->MHP = character->ClassInfo->StartMHP; character->MMP = character->ClassInfo->StartMMP; character->HP = character->MHP; character->MP = character->MMP; character->ATK = character->ClassInfo->StartATK; character->DEF = character->ClassInfo->StartDEF; character->LUCK = character->ClassInfo->StartLuck; } return character; } void UGameCharacter::BeginDestroy() { Super::BeginDestroy(); }
The CreateGameCharacter
factory for our UGameCharacter
class takes a pointer to an FCharacterInfo
struct, which is returned from a Data Table, and also an Outer
object, which is passed to the NewObject
function. It then attempts to find the character class Data Table from a path, and if the result is not null, it locates the proper row in the Data Table, stores the result, and also initializes the stats and the CharacterName
field. In the preceding code, you can see the path where the character class Data Table is located. You can get this path by right-clicking on your Data Table from the Content Browser, selecting Copy Reference, and then pasting the result into your code.
While this is currently a very basic bare-bones representation of a character, it will work for now. Next, we're going to store a list of these characters as the current party.
We have already created a GameMode
class, and this might seem like the perfect place to keep track of information such as party members and inventory, right?
However, GameMode
does not persist between level loads! This means that unless you save some information to disk, you lose all of that data whenever you load a new area.
The GameInstance
class was introduced to deal with just this sort of problem. A GameInstance
class persists through the whole game, regardless of level loads, unlike GameMode
. We're going to create a new GameInstance
class to keep track of our persistent data, such as party members and inventory.
Create a new class, and this time, select GameInstance
as the parent class (you'll have to search for it). Name it RPGGameInstance
.
In the header file, we're going to add a TArray
of the UGameCharacter
pointers, a flag to know whether the game has been initialized, and an Init
function. Your RPGGameInstance.h
file should look like this:
#pragma once #include "Engine/GameInstance.h" #include "GameCharacter.h" #include "RPGGameInstance.generated.h" UCLASS() class RPG_API URPGGameInstance : public UGameInstance { GENERATED_BODY() URPGGameInstance( const class FObjectInitializer& ObjectInitializer ); public: TArray<UGameCharacter*> PartyMembers; protected: bool isInitialized; public: void Init(); };
In the Init
function for the game instance, we'll add a single default party member and then set the isInitialized
flag to true
. Your RPGGameInstance.cpp
should look like this:
#include "RPG.h" #include "RPGGameInstance.h" URPGGameInstance::URPGGameInstance(const class FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { isInitialized = false; } void URPGGameInstance::Init() { if( this->isInitialized ) return; this->isInitialized = true; // locate characters asset UDataTable* characters = Cast<UDataTable>( StaticLoadObject( UDataTable::StaticClass(), NULL, TEXT( "DataTable'/Game/Data/Characters.Characters'" ) ) ); if( characters == NULL ) { UE_LOG( LogTemp, Error, TEXT( "Characters data table not found!" ) ); return; } // locate character FCharacterInfo* row = characters->FindRow<FCharacterInfo>( TEXT( "S1" ), TEXT( "LookupCharacterClass" ) ); if( row == NULL ) { UE_LOG( LogTemp, Error, TEXT( "Character ID 'S1' not found!" ) ); return; } // add character to party this->PartyMembers.Add( UGameCharacter::CreateGameCharacter( row, this ) ); }
You may run into a linker error at this point if you try to compile. It is recommended that before you move on, save and close everything. Then restart your project. After you do that, compile the project.
To set this class as your GameInstance
class, in Unreal, open Edit | Project Settings, go to Maps & Modes, scroll down to the Game Instance box, and pick RPGGameInstance from the drop-down list. Finally, from the game mode, we override BeginPlay
to call this Init
function.
Open RPGGameMode.h
and add virtual void BeginPlay() override;
at the end of your class so that your header will now look like this:
#pragma once #include "GameFramework/GameMode.h" #include "RPGGameMode.generated.h" UCLASS() class RPG_API ARPGGameMode : public AGameMode { GENERATED_BODY() ARPGGameMode(const class FObjectInitializer& ObjectInitializer); virtual void BeginPlay() override; };
And in RPGGameMode.cpp
, cast RPGGameInstance
at BeginPlay
so that RPGGameMode.cpp
now looks like this:
#include "RPG.h" #include "RPGGameMode.h" #include "RPGCharacter.h" #include "RPGGameInstance.h" ARPGGameMode::ARPGGameMode(const class FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { DefaultPawnClass = ARPGCharacter::StaticClass(); } void ARPGGameMode::BeginPlay() { Cast<URPGGameInstance>(GetGameInstance())->Init(); }
Once you compile the code, you now have a list of active party members. It's time to start prototyping the combat engine.
18.188.119.81