quinta-feira, 19 de novembro de 2020

A classe UActorComponent

A classe UActorComponent é a classe base usada na criação de componentes que podem ser adicionados em Atores.

Uma instância de UActorComponent não possui um Transform, não pode ser anexado à outro componente e não possui geometria para ser renderizada ou usada em colisão. Existem subclasses de UActorComponent que adicionam estas funcionalidades.

Como exemplo, vamos ver a hierarquia de classes da classe UStaticMeshComponent. Esta hierarquia está disponível na Unreal Engine API Reference.


Estas são as principais subclasses de UActorComponent:

  • USceneComponent: Uma instância desta classe possui um Transform e pode ser anexada à outro componente. 
  • UPrimitiveComponent: Uma instância desta classe possui ou gera geometria que pode ser renderizada ou usada em colisão. 


Se você precisar pegar a referência do Actor que contém o componente, use a função GetOwner() no componente:

AActor* ActorOwner = GetOwner();


Exemplo de uso:

Vamos criar um ActorComponent que contém variáveis para guardar o Health, Mana e Level do jogador. O componente terá uma função UpgradeLevel() que aumentará o nível do jogador e atualizará os valores máximos de Health e Mana. Este componente poderá ser usado em C++ e em Blueprints.

Crie uma classe C++ com o nome HealthManaComponent usando como classe pai a classe Actor Component como mostra a imagem abaixo. 


Veja no arquivo HealthManaComponent.h que a macro UCLASS possui alguns especificadores para permitir que o componente possa ser usado em Blueprints. Adicione as variáveis e funções como mostra o código abaixo:

#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;
};

Na implementação das funções SetCurrentHealth() e SetCurrentMana() foi usado a função FMath::Clamp() para evitar que o valor seja menor que zero ou maior que o máximo permitido.

O arquivo HealthManaComponent.cpp deve ter este conteúdo:

#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);
}  

Primeiro vamos ver como usar o HealthManaComponent em um Blueprint. Compile o código C++ e crie um Blueprint. Na aba Components do Blueprint, clique no botão Add Component. O HealthManaComponent está na categoria Custom como mostra esta imagem:


No Event Graph do Blueprint, você consegue acessar as funções do HealthManaComponent:


Agora, vamos usar o HealthManaComponent em uma classe C++. Crie uma classe C++ com o nome TestUActorComponent usando como classe pai a classe Actor. O arquivo TestUActorComponent.h deve ter este conteúdo:

#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();
};    

A classe TestUActorComponent possui um Timer que chama a função UpgradePlayerLevel() a cada 5 segundos. Esta função chama a função UpgradeLevel() do HealthManaComponent e depois exibe na tela os valores atuais do componente. 

Este é o conteúdo do arquivo 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 o código C++ e adicione uma instância de TestUActorComponent no nível. Inicie o jogo e aguarde 5 segundos para ver a mensagem na tela. A imagem abaixo mostra quando o componente alcançou o nível 3:



Sumário C++