quinta-feira, 19 de novembro de 2020

The UActorComponent class

The UActorComponent class is the base class used to create components that can be added to Actors.

An instance of UActorComponent does not have a Transform, cannot be attached to another component, and has no geometry to be rendered or used in a collision. There are subclasses of UActorComponent that add these features.

As an example, let's look at the class hierarchy of the UStaticMeshComponent class. This hierarchy is available on Unreal Engine API Reference.


These are the main subclasses of UActorComponent:

  • USceneComponentAn instance of this class has a Transform and can be attached to another component. 
  • UPrimitiveComponentAn instance of this class has or generates geometry that can be rendered or used in a collision. 


If you need to get the reference of the Actor that contains the component, use the GetOwner() function on the component:

AActor* ActorOwner = GetOwner();


Example usage:

We will create an ActorComponent that contains variables to store the Health, Mana, and Level of the player. The component will have an UpgradeLevel() function that will increase the player's Level and update the maximum Health and Mana values. This component can be used in C++ and Blueprints.

Create a C++ class named HealthManaComponent using the Actor Component class as the parent class as shown in the image below. 


See in the file HealthManaComponent.h that the UCLASS macro has some specifiers to allow the component to be used in Blueprints. Add the variables and functions as shown in the code below:

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "HealthManaComponent.generated.h"


UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class TUTOPROJECT_API UHealthManaComponent : public UActorComponent
{
  GENERATED_BODY()

public:	
  // Sets default values for this component's properties
  UHealthManaComponent();

  // Called every frame
  virtual void TickComponent(float DeltaTime, ELevelTick TickType,
                 FActorComponentTickFunction* ThisTickFunction) override;
	
  UFUNCTION(BlueprintPure, Category = HealthMana)
  int32 GetCurrentHealth();
	
  UFUNCTION(BlueprintCallable, Category = HealthMana)
  void SetCurrentHealth(int32 newHealth);
	
  UFUNCTION(BlueprintPure, Category = HealthMana)
  int32 GetCurrentMana();
	
  UFUNCTION(BlueprintCallable, Category = HealthMana)
  void SetCurrentMana(int32 newMana);
	
  UFUNCTION(BlueprintPure, Category = HealthMana)
  int32 GetPlayerLevel();
	
  UFUNCTION(BlueprintCallable, Category = HealthMana)
  void UpgradeLevel();
	
protected:
  // Called when the game starts
  virtual void BeginPlay() override;

  int32 CurrentHealth;
  int32 MaxHealth;
  
  int32 CurrentMana;
  int32 MaxMana;
  
  int32 PlayerLevel;
};

The FMath::Clamp() function was used in the implementation of the SetCurrentHealth() and SetCurrentMana() functions to prevent the value from being less than zero or greater than the maximum allowed.

The HealthManaComponent.cpp file should have this content:

#include "HealthManaComponent.h"

UHealthManaComponent::UHealthManaComponent()
{
  PrimaryComponentTick.bCanEverTick = true;

  CurrentHealth = 100;
  MaxHealth = 100;
  
  CurrentMana = 100;
  MaxMana = 100;
  
  PlayerLevel = 1;
}

int32 UHealthManaComponent::GetCurrentHealth()
{
  return CurrentHealth;	
}
	
void UHealthManaComponent::SetCurrentHealth(int32 NewHealth)
{
  CurrentHealth = FMath::Clamp<int32>(NewHealth, 0, MaxHealth);
}
	
int32 UHealthManaComponent::GetCurrentMana()
{
  return CurrentMana; 
}
	
void UHealthManaComponent::SetCurrentMana(int32 NewMana)
{
  CurrentMana = FMath::Clamp<int32>(NewMana, 0, MaxMana);
}

int32 UHealthManaComponent::GetPlayerLevel()
{
  return PlayerLevel;
}	
	
void UHealthManaComponent::UpgradeLevel()
{
  PlayerLevel++;

  MaxHealth = PlayerLevel * 100;
  CurrentHealth = MaxHealth;

  MaxMana = PlayerLevel * 100;
  CurrentMana = MaxMana;  
}

// Called when the game starts
void UHealthManaComponent::BeginPlay()
{
  Super::BeginPlay();
}

// Called every frame
void UHealthManaComponent::TickComponent(float DeltaTime, ELevelTick TickType,
                                FActorComponentTickFunction* ThisTickFunction)
{
  Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
}  

First let's see how to use the HealthManaComponent in a Blueprint. Compile the C++ code and create a Blueprint. On the Components tab of the Blueprint, click the Add Component button. The HealthManaComponent is in the Custom category as shown in this image:


In the Blueprint Event Graph, you can access the functions of the HealthManaComponent:


Now, let's use the HealthManaComponent in a C++ class. Create a C++ class with the name TestUActorComponent using the Actor class as the parent class. The TestUActorComponent.h file should have this content:

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "TestUActorComponent.generated.h"

UCLASS()
class TUTOPROJECT_API ATestUActorComponent : public AActor
{
  GENERATED_BODY()
	
public:	
  // Sets default values for this actor's properties
  ATestUActorComponent();

protected:
  // Called when the game starts or when spawned
  virtual void BeginPlay() override;

public:	
  // Called every frame
  virtual void Tick(float DeltaTime) override;
	
  UPROPERTY(VisibleAnywhere)
  class UHealthManaComponent* HealthManaComponent;
	
  FTimerHandle TimerHandle;

  void UpgradePlayerLevel();
};    

The TestUActorComponent class has a Timer that calls the UpgradePlayerLevel() function every 5 seconds. This function calls the HealthManaComponent's UpgradeLevel() function and then displays the component current values on the screen.

This is the contents of the file TestUActorComponent.cpp:

#include "TestUActorComponent.h"
#include "HealthManaComponent.h"

// Sets default values
ATestUActorComponent::ATestUActorComponent()
{
  // Set this actor to call Tick() every frame.
  PrimaryActorTick.bCanEverTick = true;
	
  HealthManaComponent = CreateDefaultSubobject<UHealthManaComponent>(
                                              TEXT("HealthManaComponent"));

}

// Called when the game starts or when spawned
void ATestUActorComponent::BeginPlay()
{
  Super::BeginPlay();
	
  GetWorldTimerManager().SetTimer(TimerHandle, this,
       &ATestUActorComponent::UpgradePlayerLevel, 5.f, true);	
}

void ATestUActorComponent::UpgradePlayerLevel()
{
  HealthManaComponent->UpgradeLevel();
	
  FString Message = FString::Printf(TEXT("Level: %d - Health: %d - Mana: %d"), 
                                    HealthManaComponent->GetPlayerLevel(),
                                    HealthManaComponent->GetCurrentHealth(),
                                    HealthManaComponent->GetCurrentMana());
       
  if(GEngine)
  {
    GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, Message);
  }	
}

// Called every frame
void ATestUActorComponent::Tick(float DeltaTime)
{
  Super::Tick(DeltaTime);
}    

Compile the C++ code and add an instance of TestUActorComponent at the level. Start the game and wait 5 seconds to see the message on the screen. The image below shows when the component reached level 3:



Table of Contents C++