Programação C++ no Unreal Engine
1. O C++ e o Unreal Engine
O C++ por ter como base de desenvolvimento o C tem o benefício da rapidez e da portabilidade para diversas plataformas e ainda permite a implementação de classes, tornando a linguagem em uma boa candidata para o desenvolvimento de projetos com necessidade de velocidade acessando recursos de baixo nível e construção de classes.
O Unreal Egine utiliza a linguagem C++ aproveitando todas as funcionalidades que a linguagem fornece, como por exemplo o gerenciamento otimizado de memória, quanto a implementação a Engine fornece muitos elementos para auxiliar a codificação tornando-a mais fácil, entre eles a utilização de macros e objetos primitivos próprios da Engine.
2. Mas quando usar a linguagem C++?
Não há uma resposta definitiva dessa questão mas podemos considerar algumas diferenças entre Blueprint e o C++, segue abaixo algumas considerações.
2.1. Blueprints vs C++
-
Blueprints são mais fáceis de ser lidos e entendidos pelos membros da equipe;
-
C++ evita sobrecarga de chamadas de função economizando ciclos de CPU;
-
C++ Conseguem acesso a
Library Math
; -
É possível utilizar um sistema de versionamento como por exemplo o Git ou SVN com C++;
-
Blueprints necessitam de ferramenta específica para versionamento;
-
Para projetos em plataformas mobile o recomendado é C++.
2.2. O que é ideal?
A resposta é depende do problema mas considere o seguinte:
-
Analise a complexidade do projeto e a infraestrutura local e remota;
-
Para equipes pequenas e projetos pequenos é recomendado Blueprints;
-
Para equipes pequenas com cultura de desenvolvimento e necessidade de processamento é recomendado C++.
3. Construindo classes C++ no Unreal Engine
A seguir vamos implementar uma classe C++ no Unreal Engine para tal utilizamos o Menu Tools
> New C++ Class
.
O Unreal Engine vai criar dois arquivos, o arquivo header (.h) e o de implementação (.cpp), sugerindo separar ambos nas pastas private
e header
.
3.1. Pasta privada com os arquivos header das classes
<Projeto>/Private/ControlLight.h
// Diretiva de compilação que serve para fazer com que o arquivo seja
// incluído somente uma vez durante o processo de compilação.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
// Arquivo criado durante a compilação
#include "ControlLight.generated.h"
// Macro utilizada para indicar que uma C++ fará parte do Unreal
// Reflection system. Isto é necessário para
//que a classe seja reconhecida pelo editor do Unreal Engine.
UCLASS()
class CPP5_API AControlLight : public AActor
{
GENERATED_BODY()
public:
// Construtor da classe
AControlLight();
protected:
// Método chamado quando o jogo inicia ou quando a classe
// é adicionada na cena.
virtual void BeginPlay() override;
public:
// Método chamado a cada quadro.
virtual void Tick(float DeltaTime) override;
};
Pasta publica com a implementação das classes :
<Projeto>/Public/ControlLight.cpp
#include "ControlLight.h"
// Método construtor da classe
AControlLight::AControlLight()
{
// Configura este ator para chamar o evento Tick() a cada frame.
PrimaryActorTick.bCanEverTick = true;
}
void AControlLight::BeginPlay()
{
Super::BeginPlay();
}
void AControlLight::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
3.2. Exemplo de um arquivo header com variáveis
Abaixo vamos construir uma classe em C++ chamado plataforma para exemplificar um arquivo header
a declaração de variáveis.
#include "CoreMinimal.h"
#include "Engine/StaticMeshActor.h"
#include "Plataforma.generated.h"
UCLASS(ClassGroup=(Custom),meta=(BlueprintSpawnableComponent) )
class PROJETOPLATAFORMA_API APlataforma : public AStaticMeshActor
{
GENERATED_BODY()
public:
// Construtor da classe
APlataforma();
virtual void Tick(float DeltaTime) override;
virtual void BeginPlay() override;
UPROPERTY(EditAnywhere, Meta = (MakeEditWidget = true))
FVector TargetLocation;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Plataforma")
float SpeedPlataform = 20;
UFUNCTION(BlueprintCallable, Category = "Plataforma")
void AddActiveTrigger();
UFUNCTION(BlueprintCallable, Category = "Plataforma")
void RemoveActiveTrigger();
protected:
FVector GlobalStartLocation;
FVector GlobalTargetLocation;
UPROPERTY(EditAnywhere)
int ActiveTriggers = 1;
};
4. Sintaxe da linguagem e C++ e macros do Unreal Engine
A seguir vamos apresentar algumas características de linguagem e as macros que facilitam a implementação em C++.
4.1. O arquivo Include
É uma forma de incluir um arquivo padrão ou definido pelo usuário no programa e é principalmente escrito no início de qualquer programa C / C ++.
Esta diretiva é lida pelo pré-processador e ordena que ele insira o conteúdo de um arquivo de cabeçalho do sistema ou definido pelo usuário no programa a seguir. Esses arquivos são importados principalmente de uma fonte externa para o programa atual. O processo de importação de tais arquivos que podem ser definidos pelo sistema ou pelo usuário é conhecido como Inclusão de Arquivo. Este tipo de diretiva de pré-processador diz ao compilador para incluir um arquivo no programa de código-fonte.
4.1.1. Exemplo de Include
#include "CoreMinimal.h"
#include "Engine/StaticMeshActor.h"
#include "Plataforma.generated.h"
4.1.2. O arquivo .generated.h
O Unreal Engine faz uso extensivo de macros de pré-processador, e algumas dessas macros são definidas (#defined) no arquivo genrated.h
que acompanha cada UCLASS
. Se você criar uma UCLASS
MyClass, o arquivo MyClass.h irá incluir (#include) MyClass.generated.h. O cabeçalho gerado, MyClass.generated.h, é feito na parte inicial do processo de construção do Unreal Engine.
5. Encapsulamento
Public
– Quando precede uma lista de membros de classe, o Public palavra-chave especifica que esses membros são acessíveis a partir de qualquer função. Isso se aplica a todos os membros declarados até o próximo especificador de acesso ou o fim da classe. Ou seja visível a todos.
Private
– Quando precede uma lista de membros de classe, o Private palavra-chave especifica que esses membros são acessíveis somente dentro de funções de membro e amigos da classe. Isso se aplica a todos os membros declarados até o próximo especificador de acesso ou o fim da classe. Ou seja visível somente para membros dentro da classe.
Protected
– O Protected palavra-chave especifica o acesso a membros de classe no lista de membros até o próximo especificador de acesso (pública ou private) ou no final da definição de classe. O Protected é mistura entre Public e Private ou seja é visível somente para membros da classe e visível para subclasses.
6. UCLASS
Você também pode declarar classes C ++ personalizadas, que se comportam como classes UE4, declarando seus objetos C++ personalizados como UCLASS. UCLASS usa Smart Pointers do UE4 e rotinas de gerenciamento de memória para alocação e desalocação de acordo com as regras do Smart Pointer, podem ser carregados e lidos pelo UE4 Editor e opcionalmente acessados a partir de Blueprints.
6.1. Exemplo UCLASS
UCLASS(ClassGroup=(Custom),meta=(BlueprintSpawnableComponent) )
Os parâmetros descritos no exemplo são os especificadores da classe que irão determinar o seu comportamento.
BlueprintSpawnableComponent
- Se estiver presente, o componente Class pode ser gerado por um Blueprint.ClassGroup=GroupName
- Indica que o Navegador de ator do Unreal Editor deve incluir esta classe e qualquer subclasse dessa classe dentro do GroupName especificado quando a Visualização de grupo estiver ativada no Navegador de ator.
7. UFUNCTION
Um UFunction é uma função C ++ que é reconhecida pelo sistema de reflexão Unreal Engine 4 (UE4). Qualquer UObject ou biblioteca de função Blueprint pode declarar uma função de membro como um UFunction, colocando a macro UFUNCTION na linha acima da declaração da função no arquivo de cabeçalho. A macro oferecerá suporte a Especificadores de Função para alterar como o UE4 interpreta e usa uma função.
Ao declarar funções, os especificadores de função podem ser adicionados à declaração para controlar como a função se comporta com vários aspectos do mecanismo e do editor.
7.1. Exemplo UFUNCTION
UFUNCTION(BlueprintCallable, Category = "Plataforma")
void AddActiveTrigger();
-
BlueprintAuthorityOnly
- Esta função só será executada a partir do código Blueprint se for executada em uma máquina com autoridade de rede (um servidor, servidor dedicado ou jogo para um único jogador). -
BlueprintCallable
- A função pode ser executada em um gráfico Blueprint ou Level Blueprint.
8. UPROPERTY
As propriedades são declaradas usando a sintaxe de variável C++ padrão, precedida pela macro UPROPERTY que define metadados de propriedade e especificadores de variável.
Ao declarar propriedades, os Especificadores de Propriedade podem ser adicionados à declaração para controlar como a propriedade se comporta com vários aspectos do Motor e do Editor.
UPROPERTY(EditAnywhere, Meta = (MakeEditWidget = true))
FVector TargetLocation;
BlueprintReadOnly
- Esta propriedade pode ser lida pelo Blueprints, mas não modificada. Este especificador é incompatível com o especificador BlueprintReadWrite.BlueprintReadWrite
- Esta propriedade pode ser lida ou escrita a partir de um Blueprint. Este especificador é incompatível com o especificador BlueprintReadOnly.EditAnywhere
- Indica que esta propriedade pode ser editada por janelas de propriedades, em arquétipos e instâncias. Este especificador é incompatível com qualquer um dos especificadores “visíveis”.MakeEditWidget
- Usado para propriedades Transform ou Rotator, ou Matrizes de Transforms ou Rotators. Indica que a propriedade deve ser exposta na janela de visualização como um widget móvel.
8.1. Classe Actor em C++ com uma Static Mesh
8.1.1. Arquivo CharacterBase.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "CharacterBase.generated.h"
UCLASS(Blueprintable)
class AULACPPV1_API ACharacterBase : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ACharacterBase();
/* Configurando propriedade para adicionar uma Static Mesh */
UPROPERTY(VisibleAnywhere)
UStaticMeshComponent* MeshMain;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
};
UStaticMeshComponent
- É usando para criar uma instância de um StaticMesh
.
8.1.2. Arquivo CharacterBase.cpp
#include "CharacterBase.h"
// Sets default values
ACharacterBase::ACharacterBase()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
/*Construindo a malha na memória
- CreateDefaultSubobject - Cria um componente ou subobjeto, permitindo criar uma classe filho e retornando a classe pai.
Os objetos recém-iniciados podem ter alguns de seus valores padrão inicializados, mas o Mesh começará vazio.
Você terá que carregar o malha mais tarde.
*/
MeshMain = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh Main"));
/*
* ConstructorHelpers - No construtor, inicializamos os componentes e, em seguida, definimos seus valores usando FObjectFinder.
Também configuramos a classe para gerar usando a função StaticClass para recuperar uma instância UStatic* de um tipo de classe.
*/
static ConstructorHelpers::FObjectFinder<UStaticMesh> SphereVisualAsset(TEXT("/Game/ExampleContent/StarterContent/Shapes/Shape_Sphere.Shape_Sphere"));
if (SphereVisualAsset.Succeeded()) {
MeshMain->SetStaticMesh(SphereVisualAsset.Object);
MeshMain->SetRelativeLocation(FVector(0.0f, 0.0f, -100.0f));
MeshMain->SetWorldScale3D(FVector(0.8f));
}
MeshMain->SetupAttachment(RootComponent);
}
void ACharacterBase::BeginPlay()
{
Super::BeginPlay();
UE_LOG(LogTemp, Warning, TEXT("Teste 123..."));
}
void ACharacterBase::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
-
CreateDefaultSubobject
- Cria um componente ou subobjeto, permitindo criar uma classe filho e retornando a classe pai. Os objetos recém-iniciados podem ter alguns de seus valores padrão inicializados, mas o Mesh começará vazio. Você terá que carregar o malha mais tarde. -
ConstructorHelpers
- No construtor, inicializamos os componentes e, em seguida, definimos seus valores usando FObjectFinder. Também configuramos a classe para gerar usando a função StaticClass para recuperar uma instância UStatic* de um tipo de classe.