sexta-feira, 13 de novembro de 2020

Using Components in C++

In this article, we will create a subclass of AActor containing several components to show in practice how they are used in C++.

These are the components that will be used in the example:

  • Scene Component: It is used as the Actor's Root Component.
  • Static Mesh Component: It contains a Static Mesh that visually represents the Actor.
  • Capsule Component: Component used for the collision test.
  • Particle System Component: Contains a particle emitter.
  • Point Light Component: Component that emits light in all directions from a point.
  • Rotating Movement Component: Used to constantly rotate the Actor.
  • Audio Component: Used to play sound at the Actor's location. 


Example usage:

For this example, you need a project with the Starter Content.

Let's create an electric chair. All of its functionality will be done through components. It will have a particle emitter simulating the sparks and an audio component with the sound of the sparks. It will contain a red point of light and will be in constant rotation.

Create a C++ class with the name ElectricChair using the Actor class as the parent class. In the file ElectricChair.h define all the components and declare the function PostInitializeComponents(), as shown in the code below:

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

In the cpp file we have to add the #include lines with the header files of the component classes. To learn how to #include a class, look for the class on the Unreal Engine API Reference.

In the class constructor, the Assets of Static Mesh, Particle System, and SoundCue are loaded. To obtain the path of an Asset in the Unreal editor, right-click the Asset and select the Copy Reference option.

The constructor contains the component initializations. The ElectricChair.cpp file should have this content:

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

The assignment of SoundCue to the Audio Component needs to be done in the PostInitializeComponents() function. This function is called after all components of an Actor have been initialized.

The hierarchy between components is done through the SetupAttachment() function. The only component that is not part of the hierarchy is the URotatingMovementComponent because this class does not inherit from USceneComponent and therefore does not have a Transform.

Compile the C++ code and add an instance of ElectricChair at the level. See the hierarchy of components in the Details tab:


Start the game and see the electric chair rotating with sparks effect and sound. I added my electric chair on a dark level. It looked like this: 


Table of Contents C++