Introduction

In this article we implement our base character class. Firstly we implement BaseStats class in order to store our character’s base stats and pull them from this class where it is necessary. Secondly, we implement an AnimInstance class in order to create the character’s animation blueprint. Final class to be implemented is the character class.

Base Stats

Let’s start with holding our base stats in a class. We will be gathering all the base values of variables from this class. For this article we don’t implement archer and warrior classes, but let’s describe them as well.

#pragma once

namespace BaseStats
{
	const float HEALTH = 100.f;
	const float ARMOR = 0.f;
	const float MAGIG_RESISTANCE = 0.f;
	const float PHYSICAL_DAMAGE = 0.f;
	const float MAGIC_DAMAGE = 0.f;
	const float ATTACK_SPEED = 0.f;

	namespace BaseArcherStats
	{
		const float HEALTH = 60.f;
		const float ARMOR = 10.f;
		const float MAGIG_RESISTANCE = 5.f;
		const float PHYSICAL_DAMAGE = 10.f;
		const float MAGIC_DAMAGE = 0.f;
		const float ATTACK_SPEED = 10.f;
	};

	namespace BaseWarriorStats
	{
		const float HEALTH = 80.f;
		const float ARMOR = 20.f;
		const float MAGIG_RESISTANCE = 10.f;
		const float PHYSICAL_DAMAGE = 8.f;
		const float MAGIC_DAMAGE = 0.f;
		const float ATTACK_SPEED = 7.5f;
	};
};

Making Animation Blueprint Base Class with C++

Before starting animation implementations I’d like to mention the skeletal mesh and animations. Firstly I modeled our mesh in blender and create 14 different animations for this mesh. Then I import all of the assets to the project.

Blend Space for Idle / Walk / Run Cycles

Blend space is a useful tool for interpolating between different animations. Here, I just use it for idle, walk, and run animations which are based on the character’s movement speed.

 

Create a class derived from UAnimInstance:

We are going to hold two variables: one for character speed which will be used in Idle/Walk/Run BlendSpace1D and the other one is to check if character is attacking or not. In the future time we are going to implement more variables in this class to control our animations.

#pragma once

#include "CoreMinimal.h"
#include "Animation/AnimInstance.h"
#include "RTSBR_Character_AnimInstance.generated.h"

/**
 * 
 */
UCLASS()
class RTSBATTLEROYALE_API URTSBR_Character_AnimInstance : public UAnimInstance
{
	GENERATED_BODY()
	
protected:
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	float characterMovementSpeed_;

	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	bool isCharacterAttacking_;

	UFUNCTION(BlueprintCallable, Category = "UpdateAnimationProperties")
	void UpdateAnimationProperties();
};
#include "RTSBR_Character_AnimInstance.h"

#include "RTSBR_Character.h"

void URTSBR_Character_AnimInstance::UpdateAnimationProperties()
{
	const APawn *character = TryGetPawnOwner();
	const ARTSBR_Character *rtsbrCharacter;

	if(character && (rtsbrCharacter = Cast<ARTSBR_Character>(character)) != nullptr)
	{
		isCharacterAttacking_ = rtsbrCharacter->GetIsAttacking();
		characterMovementSpeed_ = rtsbrCharacter->GetVelocity().Size();
	}
}

 

Animation Blueprint and State Machine

Let’s create our animation blueprint derived from the base class we just created.


Call UpdateAnimationProperties() function we implemented in our URTSBR_Character_AnimInstance class in the animation blueprint.

We are getting the character movement speed from RTSBR_Character_AnimInstance class.

Character

After making everything ready, we can implement our character class with ease. This class will be the base class of every character type of the game and will hold the basic information. For now we are needed to implement its:

Variables:

  • Health
  • Armor
  • Magic Resistance
  • Attack Damage
  • Magic Damage
  • Attack Speed

Functions:

  • ApplyDamage which will be called from the attacker actor for making the character damaged
  • Die function which will have lots of functionality such as making animation instance play the dying animation, deleting the character from character pool of the player etc.

Implementing these variables and functions is enough for now, but we are going to implement more as we need.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "RTSBR_Character.generated.h"

class Damage
{
public:
	Damage() : physicalDamage(0.f), magicDamage(0.f)
	{}

	Damage(const float physicalDmg, const float magicDmg) : physicalDamage(physicalDmg), magicDamage(magicDmg)
	{}

	float physicalDamage;
	float magicDamage;
};

UCLASS()
class RTSBATTLEROYALE_API ARTSBR_Character : public ACharacter
{
	GENERATED_BODY()

public:
	ARTSBR_Character();

	// Will be called from the attacker actor in order to damage the character
	void ApplyDamage(const Damage &damage);

	// For animations
	bool GetIsAttacking() const
	{
		return isAttacking_;
	}

protected:
	virtual void Attack();
	virtual float CalculateDamage();
	virtual void Die();

	Damage damage_;
	float health_;
	float armor_;
	float magicResistance_;
	float attackSpeed_;

	bool isAttacking_;

private:	
	virtual void Tick(float deltaTime) override;
	virtual void BeginPlay() override;

};
#include "RTSBR_Character.h"
#include "ConstructorHelpers.h"
#include "RTSBR_Character_AnimInstance.h"
#include "BaseStats.h"

ARTSBR_Character::ARTSBR_Character() : 
	damage_(BaseStats::PHYSICAL_DAMAGE, BaseStats::MAGIC_DAMAGE), 
	health_(BaseStats::HEALTH), armor_(BaseStats::ARMOR), magicResistance_(BaseStats::MAGIC_RESISTANCE), 
	attackSpeed_(BaseStats::ATTACK_SPEED), isAttacking_(false)
{
	// Set the character tickable for now in order to test the animation
	PrimaryActorTick.bCanEverTick = true;

	// Find character skeletal mesh and set it
	static ConstructorHelpers::FObjectFinder<USkeletalMesh> characterSkeletalMeshAsset(TEXT("/Game/Assets/Characters/RTSBR_Character/RTSBR_Character_SkeletalMesh"));
	if (characterSkeletalMeshAsset.Succeeded())
	{
		GetMesh()->SetSkeletalMesh(characterSkeletalMeshAsset.Object);
		GetMesh()->SetRelativeLocation(FVector(0.f, 0.f, -90.f));

		// Find character animation instance and set it
		static ConstructorHelpers::FClassFinder<URTSBR_Character_AnimInstance> animationBlueprintClass(TEXT("/Game/Blueprints/Characters/Animations/BP_RTSBR_CharacterAnimation"));
		if (animationBlueprintClass.Class)
		{
			GetMesh()->SetAnimInstanceClass(animationBlueprintClass.Class);
		}
	}
}

void ARTSBR_Character::BeginPlay()
{
	Super::BeginPlay();
}

void ARTSBR_Character::Tick(float deltaTime)
{
	Super::Tick(deltaTime);

	// Move the character forward in order to test the animation of it
	const FVector direction = FVector(1.f, 0.f, 0.f);
	AddMovementInput(direction, 1.f);
}

void ARTSBR_Character::ApplyDamage(const Damage& damage)
{
	// Calculate damage value with respect to the character's resistance amounts
	health_ -= (damage.physicalDamage * armor_ + damage.magicDamage * magicResistance_) * 0.01f;

	if(health_ <= 0.f)
	{
		Die();
	}
}

void ARTSBR_Character::Attack()
{

}

float ARTSBR_Character::CalculateDamage()
{
	return 0.f;
}

void ARTSBR_Character::Die()
{
	// Let's destroy the character for now
	Destroy();
}

Result

 

Next time I am going to discourse on character selection and commanding. Until then, take care 🙂