quarta-feira, 3 de março de 2021

Overview of sample project with C++ and Blueprint

The sample project in part III is based on the First Person template of Unreal Engine. The Blueprint template will be used and the C++ classes will be added later.

The project has four Actors: PlayerCharacter, PlayerProjectile, EnemyCannon, and EnemyProjectile. They all have a base class in C++ and one or more child Blueprints.

A simple Blueprint Function Library was also created in C++ to show how it works.

On the level, there are enemy cannons that fire homing projectiles towards the player. The player can destroy cannons and projectiles. Use the Tab key to change the type of projectile fired by the player's weapon. 


The project was made using Unreal Engine version 4.26. The project with source code is available at this link:

When opening the project for the first time, a message will appear asking if you want to rebuild the modules. Click the Yes button.


Table of Contents C++ 

Visão geral do projeto de exemplo com C++ e Blueprint

O projeto de exemplo da parte III é baseado no modelo First Person da Unreal Engine. Será usado o modelo com Blueprints e as classes C++ serão adicionadas posteriormente.

O projeto possui quatro Atores: PlayerCharacter, PlayerProjectile, EnemyCannon e EnemyProjectile. Todos possuem uma classe base em C++ e um ou mais Blueprints filhos.

Também foi criada em C++ uma Blueprint Function Library bem simples para mostrar como isto funciona.

No nível existem canhões inimigos que disparam projéteis teleguiados na direção do jogador. O jogador pode destruir os canhões e os projéteis. Use a tecla Tab para mudar o tipo de projétil disparado pela arma do jogador. 


O projeto foi feito utilizando a Unreal Engine versão 4.26. O projeto com código fonte está disponível neste link:


Ao abrir o projeto pela primeira vez, aparecerá uma mensagem perguntando se deseja fazer o rebuild dos módulos. Clique no botão Sim.


Sumário C++

segunda-feira, 1 de março de 2021

Part III - C++ and Blueprint integration

In the third part, we will analyze the integration between C++ and Blueprint. A simple project will be created using C++ and Blueprint to show how to take advantage of the two tools.

We will see how to create the base classes in C++ and extend them in Blueprint to define the Assets. It will also show you how to convert a Blueprint script to C++.

In the end, it will be shown how to create a Blueprint Function Library in C++, which can be used in any Blueprint of the project.  


Table of Contents C++ 

Parte III: Integração entre C++ e Blueprint

Na terceira parte vamos analisar a integração entre C++ e Blueprint. Será criado um projeto simples usando C++ e Blueprint para mostrar como aproveitar as vantagens das duas ferramentas.

Veremos como criar as classes base em C++ e estendê-las em Blueprint para a definição dos Assets. Também será mostrado como converter um script Blueprint para C++.

No final será mostrado como criar uma Blueprint Function Library em C++, que poderá ser usada em qualquer Blueprint do projeto.  


Sumário C++

 

sábado, 23 de janeiro de 2021

Creating Interfaces in C++

This article is not about user interface (UI). There is a concept in programming called interface, which is a type that has functions that must be implemented by the classes that inherit from the interface. It works as a standard communication protocol between different types of classes.

To better understand, let's create a simple interface that can be used in C++ code.

In the Content Browser, access the folder containing the C++ Classes. Right-click on free space and choose the New C++ Class... option as shown in the image below.


On the next screen, you have to select Unreal Interface as the parent class and click on the Next button.


In the Name field put Interactable. In the Path field, keep the default project folder. Click the Create Class button.


Let's look at the C++ code generated by Unreal Engine for the Interactable interface. I added just one line with the declaration of the Interact() function. This is the contents of the Interactable.h file:

#pragma once

#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "Interactable.generated.h"

// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class UInteractable : public UInterface
{
  GENERATED_BODY()
};


class TUTOFIRSTPERSON_API IInteractable
{
  GENERATED_BODY()

public:

  virtual void Interact(AActor* OtherActor) = 0;
};

Note that two classes have been defined, UInteractable and IInteractable. The UInteractable class inherits from UInterface and uses the UINTERFACE() macro. This class does not need to be modified and exists only to make the interface visible to the Unreal Engine's Reflection system.

The IInteractable class is the one that really represents the interface and will be inherited by other classes. It is in this class that the interface functions are declared.

The virtual keyword used before the Interact() function means that this function can be overridden. When the declaration of a virtual function ends with "= 0", it is a pure virtual function, that is, it has no implementation in the base class.


Example usage:

Let's evolve the example created in the previous article:

Using Line Traces in C++

Let's modify the WallSconce class so that it implements the Interactable interface that was created above. To do this, the WallSconce class must inherit from the IInteractable class.

Delete the PressSwitch() function and add the declaration of the Interact() function. The WallSconce.h file looks like this:

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Interactable.h"
#include "WallSconce.generated.h"

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

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(VisibleAnywhere)	
	class UPointLightComponent* PointLightComponent;
	
	virtual void Interact(AActor* OtherActor) override;
};

In the WallSconce.cpp file, delete the PressSwitch() function and add the definition of the Interact() function:

#include "WallSconce.h"
#include "Components/PointLightComponent.h"

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

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

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

  if (MeshFile.Succeeded())
  {
    StaticMeshComponent->SetStaticMesh(MeshFile.Object);
  }
  
  // PointLightComponent initialization
  PointLightComponent = CreateDefaultSubobject<UPointLightComponent>(
                                              TEXT("PointLightComponent"));
  PointLightComponent->SetIntensity(1000.f);
  PointLightComponent->SetLightColor(FLinearColor(1.f, 1.f, 1.f));
  PointLightComponent->SetupAttachment(RootScene);
  PointLightComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 30.0f));
}

void AWallSconce::Interact(AActor* OtherActor)
{
  PointLightComponent->ToggleVisibility();
  
  FString Message = FString::Printf(TEXT("Switch pressed by %s"), 
                                    *(OtherActor->GetName()));

  if(GEngine)
  {
    GEngine->AddOnScreenDebugMessage(-1, 10, FColor::Red, Message);
  }
}

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

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

We will use the same input mapping done in the previous article that uses the E key.

We will do two adjustments to the TutoFirstPersonCharacter.cpp file (this name depends on the name of your project). At the beginning of the file, replace the #include from "WallSconce.h" to "Interactable.h":

#include "Interactable.h"

In the InteractWithWorld() function, replace the code inside the if(bHitSomething) block as shown below:

void ATutoFirstPersonCharacter::InteractWithWorld()
{
  float LengthOfTrace = 300.f;
	
  FVector StartLocation;
  FVector EndLocation;
  
  StartLocation = FirstPersonCameraComponent->GetComponentLocation();
  
  EndLocation = StartLocation + 
    (FirstPersonCameraComponent->GetForwardVector() * LengthOfTrace);
	
  FHitResult OutHitResult;
  FCollisionQueryParams LineTraceParams;  
   
  bool bHitSomething = GetWorld()->LineTraceSingleByChannel(OutHitResult,
                       StartLocation, EndLocation, ECC_Visibility, LineTraceParams);

  if(bHitSomething)
  {
    
    IInteractable* InteractableObject = Cast<IInteractable>(OutHitResult.GetActor());
	
    if(InteractableObject)
    {
      InteractableObject->Interact(this);
    }
  }
}

A Cast<IInteractable> was made to check if the Actor found by the Line Trace implements the IInteractable interface. If the Cast is successful, the InteractableObject variable receives a valid reference that can be used to call the interface functions.

The TutoFirstPersonCharacter class no longer references the WallSconce class. It only references the Interactable interface. If you create another class that implements the Interactable interface, it is not necessary to modify the code of the TutoFirstPersonCharacter class to interact with the new class.

Compile the C++ code. If you did the example from the previous article, you already have a WallSconce instance on the wall. Start the game and interact with the WallSconce using the E key.


This article concludes part II of the Unreal C++ tutorials.


Table of Contents C++  

Criando Interfaces em C++

Este artigo não é sobre interface com usuário (UI). Existe um conceito de programação chamado interface, que é um tipo que possui funções que devem ser implementadas pelas classes que herdam da interface. Funciona como um protocolo padrão de comunicação entre diferentes tipos de classes. 

Para entender melhor, vamos criar uma interface simples que pode ser usada em código C++.

No Content Browser, acesse a pasta que contém as Classes C++. Clique com o botão direito em um espaço livre e escolha a opção New C++ Class... como mostra a imagem abaixo.


Na tela seguinte você tem de selecionar Unreal Interface como classe pai e clicar no botão Next.


No campo Name coloque Interactable. No campo Path, mantenha a pasta padrão do projeto. Clique no botão Create Class.


Vamos analisar o código C++ gerado pela Unreal Engine para a interface Interactable. Eu adicionei apenas uma linha com a declaração da função Interact(). Este é o conteúdo do arquivo Interactable.h:

#pragma once

#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "Interactable.generated.h"

// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class UInteractable : public UInterface
{
  GENERATED_BODY()
};


class TUTOFIRSTPERSON_API IInteractable
{
  GENERATED_BODY()

public:

  virtual void Interact(AActor* OtherActor) = 0;
};

Observe que foram definidas duas classes, a UInteractable e a IInteractable. A classe UInteractable herda de UInterface e usa a macro UINTERFACE(). Esta classe não precisa ser modificada e existe apenas para deixar a interface visível para o sistema de Reflexão da Unreal Engine.

A classe IInteractable é a que realmente representa a interface e que será herdada por outras classes. É nesta classe que são declaradas as funções da interface.

A palavra virtual usada antes da função Interact() significa que esta função pode ser sobreposta em uma subclasse. Quando a declaração de uma função virtual termina em "= 0", ela é uma função virtual pura, ou seja, ela não tem implementação na classe base. 


Exemplo de uso:

Vamos evoluir o exemplo criado no artigo anterior:

Usando Line Traces em C++

Vamos modificar a classe WallSconce para que ela implemente a interface Interactable que foi criada acima. Para fazer isso, a classe WallSconce tem de herdar da classe IInteractable.

Delete a função PressSwitch() e acrescente a declaração da função Interact(). O arquivo WallSconce.h fica assim:

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Interactable.h"
#include "WallSconce.generated.h"

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

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(VisibleAnywhere)	
	class UPointLightComponent* PointLightComponent;
	
	virtual void Interact(AActor* OtherActor) override;
};

No arquivo WallSconce.cpp, delete a função PressSwitch() e acrescente a definição da função Interact():

#include "WallSconce.h"
#include "Components/PointLightComponent.h"

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

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

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

  if (MeshFile.Succeeded())
  {
    StaticMeshComponent->SetStaticMesh(MeshFile.Object);
  }
  
  // PointLightComponent initialization
  PointLightComponent = CreateDefaultSubobject<UPointLightComponent>(
                                              TEXT("PointLightComponent"));
  PointLightComponent->SetIntensity(1000.f);
  PointLightComponent->SetLightColor(FLinearColor(1.f, 1.f, 1.f));
  PointLightComponent->SetupAttachment(RootScene);
  PointLightComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 30.0f));
}

void AWallSconce::Interact(AActor* OtherActor)
{
  PointLightComponent->ToggleVisibility();
  
  FString Message = FString::Printf(TEXT("Switch pressed by %s"), 
                                    *(OtherActor->GetName()));

  if(GEngine)
  {
    GEngine->AddOnScreenDebugMessage(-1, 10, FColor::Red, Message);
  }
}

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

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

Vamos usar o mesmo mapeamento de input feito no artigo anterior que usa a tecla E.

Faremos apenas dois ajustes no arquivo TutoFirstPersonCharacter.cpp (este nome depende do nome do seu projeto). No início do arquivo, substitua o #include de "WallSconce.h" para "Interactable.h"

#include "Interactable.h"

Na função InteractWithWorld(), substitua o código que está dentro do bloco if(bHitSomething)como mostra abaixo:

void ATutoFirstPersonCharacter::InteractWithWorld()
{
  float LengthOfTrace = 300.f;
	
  FVector StartLocation;
  FVector EndLocation;
  
  StartLocation = FirstPersonCameraComponent->GetComponentLocation();
  
  EndLocation = StartLocation + 
    (FirstPersonCameraComponent->GetForwardVector() * LengthOfTrace);
	
  FHitResult OutHitResult;
  FCollisionQueryParams LineTraceParams;  
   
  bool bHitSomething = GetWorld()->LineTraceSingleByChannel(OutHitResult,
                       StartLocation, EndLocation, ECC_Visibility, LineTraceParams);

  if(bHitSomething)
  {
    
    IInteractable* InteractableObject = Cast<IInteractable>(OutHitResult.GetActor());
	
    if(InteractableObject)
    {
      InteractableObject->Interact(this);
    }
  }
}

Foi feito um Cast<IInteractable> para verificar se o Actor encontrado pelo Line Trace implementa a interface IInteractable. Se o Cast tiver sucesso, a variável InteractableObject  recebe uma referência válida que pode ser usada para chamar as funções da interface. 

A classe TutoFirstPersonCharacter não referencia mais a classe WallSconce, mas apenas a interface Interactable. Se você criar outra classe que implementa a interface Interactable, não será necessário modificar o código da classe TutoFirstPersonCharacter para interagir com a nova classe.

Compile o código C++. Se você fez o exemplo do artigo anterior, já tem uma instância de WallSconce na parede. Inicie o jogo e interaja com o abajur usando a tecla E.


Este artigo conclui a parte II dos tutoriais de Unreal C++.


Sumário C++ 

quarta-feira, 20 de janeiro de 2021

Using Line Traces in C++

Line Traces are used to test for collision using a defined line segment. There are several types of Line Traces. A Line Trace can be based on channel (Visibility or Camera) or on the type of the object (WorldStatic, WorldDynamic, Pawn, ...). 

Line Trace functions are defined in the UWorld class. There are functions that return only the first object found and other functions that return multiple objects that have collided with the line segment.

In this article, we will focus on one of the Line Trace functions. The function is LineTraceSingleByChannel() which is channel-based and returns the first object found. The function call in the example code looks like this:

bool bHitSomething = GetWorld()->LineTraceSingleByChannel(OutHitResult,
                                                          StartLocation,
                                                          EndLocation,
                                                          ECC_Visibility,
                                                          LineTraceParams);

Let's look at each part of this function call:

  • bHitSomethingBoolean variable that stores the function's return value. Receives true if the Line Trace collided with an object.
  • GetWorld()->LineTraceSingleByChannel()The GetWorld() function returns a pointer of type UWorld that represents the current level. The Line Trace function is called using this pointer.
  • OutHitResultVariable of type FHitResult. It will be filled with various information related to the object found.
  • StartLocationFVector that defines the beginning of the line segment used in the collision test.
  • EndLocationFVector that defines the end of the line segment.
  • ECC_VisibilityThis constant indicates that the Visibility channel will be used for the collision test.
  • LineTraceParamsVariable of type FCollisionQueryParams that can be filled with parameter values that will be passed to the Line Trace function. 


Example usage:

For this example, you will need a C++ project that has the First Person template with Starter Content. I created a project with the name TutoFirstPerson.

Let's modify the Player Character to use Line Trace to interact with a wall sconce. The Player Character must be looking in the direction of the wall sconce which must be less than 3 meters away. When pressing the E key, the wall sconce light will turn on or off.

Create a C++ class with the name WallSconce using the Actor class as the parent class. This class contains a StaticMeshComponent and a PointLightComponent. It also has the PressSwitch() function that will be used by the player to turn the light on or off. The WallSconce.h file has this content:

#pragma once

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

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

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(VisibleAnywhere)	
	class UPointLightComponent* PointLightComponent;
	
	void PressSwitch();
};

The WallSconce class uses the Static Mesh SM_Lamp_Wall. In the constructor, we have the configuration of the Static Mesh and Point Light components. The PressSwitch() function calls PointLightComponent's ToggleVisibility() function. This is the contents of the WallSconce.cpp file:

#include "WallSconce.h"
#include "Components/PointLightComponent.h"

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

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

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

  if (MeshFile.Succeeded())
  {
    StaticMeshComponent->SetStaticMesh(MeshFile.Object);
  }
  
  // PointLightComponent initialization
  PointLightComponent = CreateDefaultSubobject<UPointLightComponent>(
                                              TEXT("PointLightComponent"));
  PointLightComponent->SetIntensity(1000.f);
  PointLightComponent->SetLightColor(FLinearColor(1.f, 1.f, 1.f));
  PointLightComponent->SetupAttachment(RootScene);
  PointLightComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 30.0f));
}

void AWallSconce::PressSwitch()
{
  PointLightComponent->ToggleVisibility();
}

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

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

Now we are going to create an input mapping called Interact that will be triggered when the player presses the E key.

In the level editor, access the Edit->Project Settings... menu and in the Engine category choose the Input option. Click the + symbol next to Action Mappings, enter the name Interact for the new Action Mapping, and select the E key. The Action Mappings for the project will look like this:


Open the header file of the Character class created by the First Person template. The file name in my example project is TutoFirstPersonCharacter.h. Add the declaration of the InteractWithWorld() function below the SetupPlayerInputComponent() function:

  // APawn interface
  virtual void SetupPlayerInputComponent(UInputComponent* InputComponent) 
                                                                 override;
  // End of APawn interface

  void InteractWithWorld();

In the Character cpp file, you must add the #include at the beginning of the file:

#include "WallSconce.h"

In the SetupPlayerInputComponent() function, the Interact Input will be bound with the InteractWithWorld() function. Change the name ATutoFirstPersonCharacter to the name of your project's Character class and don't forget to put the & operator before the name. It is used to return the memory address of the function. 

PlayerInputComponent->BindAction("Interact", IE_Pressed, this,
                                 &ATutoFirstPersonCharacter::InteractWithWorld);

At the end of the cpp file, create the InteractWithWorld() function with the code below. Don't forget to change the name of the class that is before the "::" operator.

void ATutoFirstPersonCharacter::InteractWithWorld()
{
  float LengthOfTrace = 300.f;
	
  FVector StartLocation;
  FVector EndLocation;
  
  StartLocation = FirstPersonCameraComponent->GetComponentLocation();
  
  EndLocation = StartLocation + 
    (FirstPersonCameraComponent->GetForwardVector() * LengthOfTrace);
	
  FHitResult OutHitResult;
  FCollisionQueryParams LineTraceParams;  
   
  bool bHitSomething = GetWorld()->LineTraceSingleByChannel(OutHitResult, 
                       StartLocation, EndLocation, ECC_Visibility, LineTraceParams);

  if(bHitSomething)
  {
    
    AWallSconce* WallSconceInstance = Cast<AWallSconce>( OutHitResult.GetActor() );
	
    if(WallSconceInstance)
    {
      WallSconceInstance->PressSwitch();
    }
  }
}

A Cast<AWallSconce> was done to check if the Actor found by the Line Trace is of the AWallSconce class. If so, then the PressSwitch() function of the AWallSconce class will be called.

Compile the C ++ code. Place an instance of WallSconce on one of the walls. Start the game, go to the WallSconce and look towards it. Press the E key to turn the light on and off.



Table of Contents C++