sexta-feira, 13 de novembro de 2020

Usando Componentes em C++

Neste artigo vamos criar uma subclasse de AActor contendo diversos componentes para mostrar na prática como eles são usados em C++.

Estes são os componentes que serão usados no exemplo:

  • Scene Component: É usado como o Root Component do Actor.
  • Static Mesh Component: Contém um Static Mesh que representa visualmente o Actor.
  • Capsule Component: Componente usado para o teste de colisão.
  • Particle System Component: Contém um emissor de partículas.
  • Point Light Component: Componente que emite luz em todas as direções a partir de um ponto.
  • Rotating Movement Component: Usado para rotacionar constantemente o Actor.
  • Audio Component: Usado para executar som no local onde está o Actor. 


Exemplo de uso:

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

Vamos criar uma cadeira elétrica. Toda a sua funcionalidade será feita através de componentes. Ela terá um emissor de partículas simulando as faíscas e um componente de áudio com o som das faíscas. Ela conterá um ponto de luz vermelho e ficará em rotação constante.

Crie uma classe C++ com o nome ElectricChair usando como classe pai a classe Actor. No arquivo ElectricChair.h faça a definição de todos os componentes e declare a função PostInitializeComponents()como mostra o código abaixo:

#pragma once

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

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

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

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	virtual void PostInitializeComponents() override;
	
	UPROPERTY(VisibleAnywhere)
	USceneComponent* RootScene;

	UPROPERTY(VisibleAnywhere)
	UStaticMeshComponent* StaticMeshComponent;
	
	UPROPERTY(VisibleAnywhere)
	class UCapsuleComponent* CapsuleComponent;
	
	UPROPERTY(VisibleAnywhere)	
	class UParticleSystemComponent* ParticleSystemComponent;
	
	UPROPERTY(VisibleAnywhere)	
	class UPointLightComponent* PointLightComponent;
	
	UPROPERTY(VisibleAnywhere)
	class URotatingMovementComponent* RotatingMovementComponent; 

	UPROPERTY(VisibleAnywhere)
	class UAudioComponent* AudioComponent;
	
	UPROPERTY(VisibleAnywhere, Category = Sound)
	class  USoundCue* SparksSoundCue;
};

No arquivo cpp temos de adicionar as linhas #include  com os arquivos header das classes dos componentes. Para saber como fazer o #include de uma classe, procure pela classe na Unreal Engine API Reference.

No construtor da classe são carregados os Assets de Static Mesh, Particle System e SoundCue. Para obter o caminho de um Asset no editor da Unreal, clique com o botão direito no Asset e escolha a opção Copy Reference.

O construtor contém as inicializações dos componentes. O arquivo ElectricChair.cpp deve ter este conteúdo:

#include "ElectricChair.h"
#include "Engine/CollisionProfile.h"
#include "Components/CapsuleComponent.h"
#include "Components/PointLightComponent.h"
#include "Components/AudioComponent.h"
#include "Particles/ParticleSystemComponent.h"
#include "GameFramework/RotatingMovementComponent.h"
#include "Sound/SoundCue.h"
#include "Engine/EngineTypes.h"


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

  // RootComponent initialization 
  RootScene = CreateDefaultSubobject<USceneComponent>(TEXT("RootScene"));
  RootComponent = RootScene;

  // CapsuleComponent initialization
  CapsuleComponent = CreateDefaultSubobject<UCapsuleComponent>(
                                           TEXT("CapsuleComponent"));
  CapsuleComponent->InitCapsuleSize(44.f, 60.f);
  CapsuleComponent->SetCollisionProfileName(
                      UCollisionProfile::BlockAllDynamic_ProfileName);
  CapsuleComponent->SetupAttachment(RootScene);
  CapsuleComponent->SetRelativeLocation(FVector(0.f, 0.f, 60.f));

  // StaticMeshComponent initialization
  StaticMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(
                                              TEXT("StaticMeshComponent"));
  StaticMeshComponent->SetCollisionProfileName(
                         UCollisionProfile::NoCollision_ProfileName);
  StaticMeshComponent->SetMobility(EComponentMobility::Movable);
  StaticMeshComponent->SetupAttachment(RootScene);
  
  ConstructorHelpers::FObjectFinder<UStaticMesh> MeshFile(
    TEXT("/Game/StarterContent/Props/SM_Chair.SM_Chair"));

  if (MeshFile.Succeeded())
  {
    StaticMeshComponent->SetStaticMesh(MeshFile.Object);
  }
  
  // AudioComponent initialization
  AudioComponent = CreateDefaultSubobject<UAudioComponent>(TEXT("AudioComponent"));
  AudioComponent->SetupAttachment(RootScene);
	
  ConstructorHelpers::FObjectFinder<USoundCue> SoundCueFile(
    TEXT("/Game/StarterContent/Audio/Fire_Sparks01_Cue.Fire_Sparks01_Cue"));
  
  if (SoundCueFile.Succeeded())
  {
    SparksSoundCue = SoundCueFile.Object;
  }
  
  // ParticleSystemComponent initialization
  ParticleSystemComponent = CreateDefaultSubobject<UParticleSystemComponent>(
                                                TEXT("ParticleSystemComponent"));
  
  ConstructorHelpers::FObjectFinder<UParticleSystem> ParticleTemplate(
    TEXT("/Game/StarterContent/Particles/P_Sparks.P_Sparks"));

  if (ParticleTemplate.Succeeded())
  {
    ParticleSystemComponent->SetTemplate(ParticleTemplate.Object);
  }
  
  ParticleSystemComponent->SetAutoActivate(true);
  ParticleSystemComponent->SetupAttachment(StaticMeshComponent);
  ParticleSystemComponent->SetRelativeLocation(FVector(-30.f, 0.f, 110.f));
  
  // PointLightComponent initialization
  PointLightComponent = CreateDefaultSubobject<UPointLightComponent>(
                                              TEXT("PointLightComponent"));
  PointLightComponent->SetIntensity(1000.f);
  PointLightComponent->SetLightColor(FLinearColor(1.f, 0.2f, 0.2f)); //RED color
  PointLightComponent->SetupAttachment(RootScene);
  PointLightComponent->SetRelativeLocation(FVector(0.f, 0.f, 100.f));
  
  // RotatingMovementComponent initialization
  RotatingMovementComponent = CreateDefaultSubobject<URotatingMovementComponent>(
                                                  TEXT("RotatingMovementComponent"));
  RotatingMovementComponent->SetUpdatedComponent(StaticMeshComponent);
  RotatingMovementComponent->RotationRate = FRotator(0.f, 90.f, 0.f);
}

void AElectricChair::PostInitializeComponents() 
{
  Super::PostInitializeComponents();
	
  AudioComponent->SetSound(SparksSoundCue);	
}

void AElectricChair::BeginPlay()
{
  Super::BeginPlay();
	
  AudioComponent->Play();
}

void AElectricChair::Tick(float DeltaTime)
{
  Super::Tick(DeltaTime);
}

A atribuição do SoundCue no Audio Component precisa ser feita na função PostInitializeComponents() Esta função é chamada depois que todos os componentes de um Actor foram inicializados.

A hierarquia entre os componentes é feita através da função SetupAttachment(). O único componente que não faz parte da hierarquia é o URotatingMovementComponent porque esta classe não herda de USceneComponent e por isso não possui um Transform.

Compile o código C++ e adicione uma instância de ElectricChair no nível. Veja na aba Details a hierarquia dos componentes:


Inicie o jogo e veja a cadeira elétrica rotacionando com efeito e som de faíscas. Eu adicionei a minha cadeira elétrica em um nível escuro. Ela ficou assim: 


Sumário C++