segunda-feira, 11 de janeiro de 2021

Referência de classe e TSubclassOf

Às vezes precisamos criar uma variável que armazena referência para uma classe e não para uma instância da classe. Por exemplo, em uma classe que representa uma arma, pode ter uma variável que será usada para definir a classe do projétil que será disparado pela arma.

A Unreal Engine possui um template de classe chamado TSubclassOf que pode ser usado na declaração de variáveis que referenciam classes. Este template fornece segurança de tipo, só permitindo que seja atribuído referências à classe que foi especificada ou suas subclasses.

O código abaixo define uma variável usando o TSubclassOf que pode ser modificada no editor:

UPROPERTY(EditAnywhere, Category="TSubclassOf Example")
TSubclassOf<ATutoProjectCollectable> CollectableClass;

A imagem abaixo mostra como esta variável é representada no editor. A seleção de classes fica restrita às classes permitidas.


Exemplo de uso:

Para este exemplo será necessário um projeto com o Starter Content

Vamos criar dois Atores, o PickupSpawner e o PickupActor. O PickupSpawner cria uma instância de PickupActor ao iniciar o jogo. No editor de nível, podemos selecionar a subclasse de PickupActor que será criada pela instância do PickupSpawner. 

Crie uma classe C++ com o nome PickupActor usando como classe pai a classe Actor. Vamos manter esta classe simples apenas com um Static Mesh para representação visual. O arquivo PickupActor.h deve ficar com este conteúdo:

#pragma once

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

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

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)
	USceneComponent* RootScene;

	UPROPERTY(VisibleAnywhere)
	UStaticMeshComponent* StaticMeshComponent;
};

A classe PickupActor usa o Static Mesh Shape_Cone. O arquivo PickupActor.cpp fica assim: 

#include "PickupActor.h"

// Sets default values
APickupActor::APickupActor()
{
  // Set this actor to call Tick() every frame.
  PrimaryActorTick.bCanEverTick = true;

  RootScene = CreateDefaultSubobject<USceneComponent>("RootScene");
  RootComponent = RootScene;

  StaticMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>("StaticMesh");
  StaticMeshComponent->SetupAttachment(RootScene);
  
  ConstructorHelpers::FObjectFinder<UStaticMesh> MeshFile(
    TEXT("/Game/StarterContent/Shapes/Shape_Cone.Shape_Cone"));

  if (MeshFile.Succeeded())
  {
    StaticMeshComponent->SetStaticMesh(MeshFile.Object);
  }
}

// Called when the game starts or when spawned
void APickupActor::BeginPlay()
{
  Super::BeginPlay();	
}

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

Crie uma classe C++ com o nome PickupSpawner usando como classe pai a classe Actor. Esta classe contém uma variável definida como TSubclassOf<APickupActor> que permitirá a seleção de outra subclasses para ser armazenada na variável. Este é o conteúdo do arquivo PickupSpawner.h:

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "PickupActor.h"
#include "PickupSpawner.generated.h"

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

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)
	USceneComponent* RootScene;

	UPROPERTY(VisibleAnywhere)
	UStaticMeshComponent* StaticMeshComponent;

	UPROPERTY(EditAnywhere, Category=Configuration)
	TSubclassOf<APickupActor> PickupClass;
};

A geração da instância de APickupActor é feita na função BeginPlay() usando a classe que estiver armazenada na variável PickupClass. Este é o conteúdo do arquivo PickupSpawner.cpp:

#include "PickupSpawner.h"

// Sets default values
APickupSpawner::APickupSpawner()
{
  // Set this actor to call Tick() every frame.
  PrimaryActorTick.bCanEverTick = true;
	
  RootScene = CreateDefaultSubobject<USceneComponent>("RootScene");
  RootComponent = RootScene;

  StaticMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>("StaticMesh");
  StaticMeshComponent->SetupAttachment(RootScene);
  
  ConstructorHelpers::FObjectFinder<UStaticMesh> MeshFile(
    TEXT("/Game/StarterContent/Architecture/SM_AssetPlatform.SM_AssetPlatform"));

  if (MeshFile.Succeeded())
  {
    StaticMeshComponent->SetStaticMesh(MeshFile.Object);
  }
}

// Called when the game starts or when spawned
void APickupSpawner::BeginPlay()
{
  Super::BeginPlay();
  
  if(PickupClass)
  {
    FVector SpawnLocation = GetActorLocation() + FVector(0.0f, 0.0f, 50.0f);
	
    FRotator SpawnRotation = FRotator(0.0f, 0.0f, 0.0f);
	
    GetWorld()->SpawnActor<APickupActor>(PickupClass, SpawnLocation, SpawnRotation );
  }
}

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

Compile o código C++ e adicione três instâncias de PickupSpawner no nível.

Vamos criar duas subclasses de PickupActor usando Blueprints apenas para mudar o StaticMesh.

Clique com o botão direito na classe PickupActor e escolha a opção Create Blueprint class based on PickupActor, como mostra a imagem abaixo.

Coloque o nome BP_PickupCube no novo Blueprint. Na aba Components, selecione o StaticMeshComponent.

Na aba Details, escolha o Static Mesh Shape_Cube e compile o Blueprint.


Use estes mesmos passos para criar mais um Blueprint com o nome BP_PickupCapsule e Static Mesh Shape_NarrowCapsule.

Selecione uma das instâncias de PickupSpawner que está no nível. Na aba Details, categoria Configuration, mude o valor do PickupClass para BP_PickupCapsule


Em outra instância de PickupSpawner, selecione o BP_PickupCube. Inicie o jogo e veja as instâncias de PickupSpawner criarem diferentes tipos de PickupActor 


Sumário C++