So, to better learn about C++ development in Unreal Engine, I decided to take the eBook tutorials by Packt (“Building an RPG”). I had many irritating hours of error fixing, old code replacement with new, writing and trying to understand the code better, and all of those situations I managed to compile projects (even though VS IDE kept spitting out errors that did not even exist there – somehow Visual Studio has really odd flaws and syntax gliches whenever you include new headers or edit code).
Anyway, I finally stumbled upon this:
error c2039: 'combatInstance': is not a member of 'UGameCharacter'
This error does seem to make sense, because I can’t seem to find any declaration of this member/variable in any of the header files in the whole project, even after specifically searching in the solution explorer. It’s not mentioned anywhere in the book till page 63, in which they just show you a fragment of the code and tells you that CombatInstance pointer of all characters should point to itself, which will be done in the constructor. This one’s not that bad of a bad situations, because sometimes they don’t even document specific things they do and instead tells you to replace the whole code with “this-and-that” … and later on you realise that there are about 5 missing includes and a whole code block with 60% typos… this isn’t the case, but it still irritates a bit.
#GameCharacter.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
//#include "CoreMinimal.h"
//#include "UObject/NoExportTypes.h"
#include "FCharacterInfo.h"
#include "FCharacterClassInfo.h"
#include "FEnemyInfo.h"
#include "GameCharacter.generated.h"
class CombatEngine;
UCLASS(BlueprintType)
class UNREALRPG_API UGameCharacter : public UObject
{
GENERATED_BODY()
UGameCharacter(const class FObjectInitializer& objectInitializer);
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;
static UGameCharacter* CreateGameCharacter(FCharacterInfo* characterInfo, UObject* outer);
static UGameCharacter* CreateGameCharacter(FEnemyInfo* enemyInfo, UObject* outer);
void BeginDestroy() override;
// page 60
protected:
float testDelayTimer;
public:
void BeginMakeDecision();
bool MakeDecision(float DeltaSeconds);
void BeginExecuteAction();
bool ExecuteAction(float DeltaSeconds);
};
#CombatEngine.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "CombatEngine.h"
CombatEngine::CombatEngine(TArray<UGameCharacter*> playerParty, TArray<UGameCharacter*> enemyParty)
{
this->playerParty = playerParty;
this->enemyParty = enemyParty;
// first add all players to combat order
for (int i = 0; i < playerParty.Num(); i++)
{
this->combatantOrder.Add(playerParty[i]);
}
// next add all enemies to combat order
for (int i = 0; i < enemyParty.Num(); i++)
{
this->combatantOrder.Add(enemyParty[i]);
}
// add to combat instance
for (int i = 0; i < this->combatantOrder.Num(); i++)
{
this->combatantOrder[i]->combatInstance = this; // 'not a member' error
}
this->tickTargetIndex = 0;
this->SetPhase(CombatPhase::CPHASE_Decision);
}
CombatEngine::~CombatEngine()
{
// free enemies
for (int i = 0; i < this->enemyParty.Num(); i++)
{
this->enemyParty[i] = nullptr;
}
for (int i = 0; i < this->combatantOrder.Num(); i++)
{
this->combatantOrder[i]->combatInstance = nullptr; // 'not a member' error
}
}
bool CombatEngine::Tick(float DeltaSeconds)
{
switch (phase)
{
case CombatPhase::CPHASE_Decision:
{
if (!this->waitingForCharacter)
{
this->currentTickTarget->BeginMakeDecision();
this->waitingForCharacter = true;
}
bool decisionMade = this->currentTickTarget->MakeDecision(DeltaSeconds);
if (decisionMade)
{
SelectNextCharacter();
// no text character, switch to action phase
if (this->tickTargetIndex == -1)
{
this->SetPhase(CombatPhase::CPHASE_Action);
}
}
}
break;
case CombatPhase::CPHASE_Action:
{
if (!this->waitingForCharacter)
{
this->currentTickTarget->BeginExecuteAction();
this->waitingForCharacter = true;
}
bool actionFinished = this->currentTickTarget->ExecuteAction(DeltaSeconds);
if (actionFinished)
{
SelectNextCharacter();
// no text character, switch to action phase
if (this->tickTargetIndex == -1)
{
this->SetPhase(CombatPhase::CPHASE_Decision);
}
}
}
break;
// in case of victory or combat, return true (combat is finished)
case CombatPhase::CPHASE_GameOver:
case CombatPhase::CPHASE_Victory:
return true;
break;
}
// check for game over
int deadCount = 0;
for (int i = 0; i < this->playerParty.Num(); i++)
{
if (this->playerParty[i]->HP <= 0) deadCount++;
}
// all players have died, switch to game over phase
if (deadCount == this->playerParty.Num())
{
this->SetPhase(CombatPhase::CPHASE_GameOver);
return false;
}
// check for victory
deadCount = 0;
for (int i = 0; i < this->enemyParty.Num(); i++)
{
if (this->enemyParty[i]->HP <= 0) deadCount++;
}
// all enemies have died, switch to victory phase
if (deadCount == this->enemyParty.Num())
{
this->SetPhase(CombatPhase::CPHASE_Victory);
return false;
}
// if execution reaches here, combat has not finished
return false;
}
void CombatEngine::SetPhase(CombatPhase phase)
{
this->phase = phase;
switch (phase)
{
case CombatPhase::CPHASE_Action:
case CombatPhase::CPHASE_Decision:
// set the active target to the first character in the combat order
this->tickTargetIndex = 0;
this->SelectNextCharacter();
break;
case CombatPhase::CPHASE_Victory:
// todo: handle victory
break;
case CombatPhase::CPHASE_GameOver:
// todo: handle game over
break;
}
}
void CombatEngine::SelectNextCharacter()
{
this->waitingForCharacter = false;
for (int i = this->tickTargetIndex; i < this->combatantOrder.Num(); i++)
{
UGameCharacter* character = this->combatantOrder[i];
if (character->HP > 0)
{
this->tickTargetIndex = i + 1;
this->currentTickTarget = character;
return;
}
this->tickTargetIndex = -1;
this->currentTickTarget = nullptr;
}
}