domingo, 28 de março de 2021

PlayerCharacter: Convertendo Blueprint script para C++

Neste artigo vamos converter uma parte do script do Blueprint FirstPersonCharacter (renomeado para BP_PlayerCharacter).

Existem dois fatores importantes que devem ser levados em consideração para decidir se um script Blueprint deve ser convertido para C++:

  • Complexidade: Algumas vezes é necessário implementar uma lógica complexa ou que envolva muitos cálculos matemáticos. Devido a natureza visual do Blueprint, estes tipos de script podem se tornar confusos ao serem representados por nodes Blueprint, sendo mais adequado e legível a implementação em C++.  
  • Velocidade: A velocidade de execução de um código C++ é mais rápida do que de um script Blueprint. Isto não significa que você deva converter todos os seus Blueprints para C++ só por este motivo. Existem inúmeros outros fatores que vão definir o desempenho de um projeto Unreal. O que você precisa verificar é se existem scripts não triviais que são executados com muita frequência, por exemplo, nodes Blueprint que estejam ligados ao evento Tick.


Iremos converter para C++ um script Blueprint simples para mostrar como isto é feito. Abra o Blueprint BP_PlayerCharacter e veja no Event Graph as ações que estão em Movement Input. Eu removi os nodes relacionados ao teste de uso de HMD (Head Mount Display) para manter o exemplo simples como mostra a imagem abaixo. 


Vamos pegar os Nodes Blueprint do evento InputAxis MoveForward e convertê-los para uma única função C++. Neste exemplo são apenas dois nodes, mas se fossem dezenas de nodes, eles também poderiam ser convertidos em uma única função C++. Faremos a mesma conversão para o evento InputAxis MoveRight.

Precisamos encontrar as funções C++ equivalentes aos nodes Blueprint. Abaixo do nome de um node Blueprint tem a linha Target is ..., que indica a classe onde foi definida a função representada pelo node. Na imagem acima temos dois nodes cujo Target é Actor e dois nodes com Target Pawn. Todas essas funções serão acessíveis na nossa classe C++ PlayerCharacter, porque a classe Character herda as variáveis e funções da classe Pawn e a classe Pawn herda as variáveis e funções da classe Actor.

O primeiro passo é pesquisar a função na Unreal Engine API Reference. Procure pelo nome do node Blueprint removendo os espaços em brancos, por exemplo, AddMovementInput. Na API Reference você pode ver a descrição dos parâmetros da função. Infelizmente, a API Reference não indica se o parâmetro é opcional. Para verificarmos isso, olhe na página da função na API Reference o caminho e nome do arquivo Header. Procure pelo arquivo na pasta Engine do seu projeto no Visual Studio. Abra o arquivo e procure pelo nome da função dentro do arquivo. A declaração da função AddMovementInput é feita desta forma:

UFUNCTION(BlueprintCallable, Category="Pawn|Input", meta=(Keywords="AddInput"))
virtual void AddMovementInput(FVector WorldDirection, float ScaleValue = 1.0f, 
                                                      bool bForce = false);

Observe que os parâmetros ScaleValue e bForce estão recebendo valores padrões. Isto significa que estes parâmetros são opcionais e a função AddMovementInput pode ser chamada passando apenas o parâmetro WorldDirection.

No artigo anterior criamos a classe C++ PlayerCharacter para ser usada como classe pai do Blueprint BP_PlayerCharacter. Abra o arquivo PlayerCharacter.h e adicione as declarações das novas funções MoveForward() e MoveRight() como mostra o código abaixo.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "PlayerCharacter.generated.h"

UCLASS()
class TUTOPART3_API APlayerCharacter : public ACharacter
{
  GENERATED_BODY()

public:
  // Sets default values for this character's properties
  APlayerCharacter();

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

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

  // Called to bind functionality to input
  virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) 
                                                                            override;

  UFUNCTION(BlueprintCallable, Category = Movement)
  void MoveForward(float Value);
	
  UFUNCTION(BlueprintCallable, Category = Movement)
  void MoveRight(float Value);
};

No arquivo PlayerCharacter.cpp temos a implementação das funções MoveForward() e MoveRight() usando a mesma lógica que estava no script Blueprint.

#include "PlayerCharacter.h"

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

}

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

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

// Called to bind functionality to input
void APlayerCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
  Super::SetupPlayerInputComponent(PlayerInputComponent);
}

void APlayerCharacter::MoveForward(float Value)
{
  AddMovementInput(GetActorForwardVector(), Value);
}

void APlayerCharacter::MoveRight(float Value)
{
  AddMovementInput(GetActorRightVector(), Value);
}

O primeiro parâmetro da função AddMovementInput() recebe o valor de retorno da função que está sendo chamada.

Só uma observação em relação ao parâmetro Value usado nas funções de movimento. Se Value for negativo, o movimento será na direção oposta.

Compile o código C++.

Abra o Blueprint BP_PlayerCharacter. Vamos susbtituir no Event Graph os nodes Blueprint que estão em Movement Input pela nossas funções C++. Mantenha os nodes de evento InputAxis e remova os outros.

Clique com o botão direito no Event Graph do Blueprint, procure as nossas funções C++ no Menu de Contexto, na categoria Movement e adicione-as ao Event Graph que deve ficar assim:


Veja que o Target dos nodes Blueprint é Player Character.

Foi feito este exemplo simples para que você possa entender este processo de conversão. 


Sumário C++