domingo, 1 de novembro de 2020

Parâmetros de função e a macro UPARAM()

A linguagem C++ permite a definição de valores padrões para parâmetros de funções. O valor padrão será utilizado se não for fornecido um valor quando a função for chamada.

Este é um exemplo de declaração de função com um valor padrão:

void SpawnEnemy(int32 EnemyLevel = 1);

Na primeira linha do exemplo abaixo o valor do parâmetro EnemyLevel será 1 e na segunda linha será 5.

SpawnEnemy();
SpawnEnemy(5);

Em uma UFUNCTION() nós podemos usar o especificador de função AdvancedDisplay para esconder os parâmetros padrão em um Blueprint node. A declaração da função ScreenMessage abaixo é um exemplo.

UFUNCTION(BlueprintCallable, Category = TestUParam, meta=(AdvancedDisplay = 1))
void ScreenMessage(FString Message, 
                   FLinearColor TextColor = FLinearColor(1.f, 0.f, 0.f), 
                   float TimeToDisplay = 5.f);

AdvancedDisplay indica quantos parâmetros devem ser exibidos no node Blueprint sem precisar expandir. Ele é um tipo de especificador chamado de Metadata.

O Blueprint node do ScreenMessage será exibido assim:


Clique na pequena seta para mostrar os demais parâmetros:


Agora vamos falar sobre passagem de parâmetros por valor e por referência. Na passagem por valor, qualquer modificação feita no parâmetro fica contido na própria função. Na passagem por referência, uma modificação feito no parâmetro afeta a variável que foi passada como parâmetro. Para definir um parâmetro como uma referência utilize o operador & como mostra este exemplo:

void ParamExample(int32 ByValue, int32& ByRef);

A passagem de parâmetros por referência é muito usada quando o parâmetro é uma estrutura ou uma classe porque evita que seja feita uma cópia com todo o contéudo do parâmetro. A passagem por referência também é usada para permitir que uma função retorne mais de um valor.

Uma UFUNCTION() expõem os parâmetros passados por referência como parâmetros de saída. Veja o exemplo abaixo e o Blueprint node gerado. 

UFUNCTION(BlueprintCallable, Category = TestUParam)
void InputOutputParam(FVector& InVector, FVector& OutVector); 



Podemos usar a macro UPARAM() com o especificador ref para que um parâmetro passado por referência seja exibido como um parâmetro de entrada:

UFUNCTION(BlueprintCallable, Category = TestUParam)
void InputOutputParam2( UPARAM(ref) FVector& InVector, FVector& OutVector); 


A macro UPARAM() também tem o especificador DisplayName que é usado para mudar o nome do parâmetro no Blueprint node:

UFUNCTION(BlueprintCallable, Category = TestUParam)
void InputOutputParam3( UPARAM(ref, DisplayName="Input Vector") FVector& InVector,
                        UPARAM(DisplayName="Output Vector") FVector& OutVector); 



Exemplo de uso:

Crie uma classe C++ com o nome TestUParam usando como classe pai a classe Actor. No arquivo TestUParam.h, adicione a declaração de duas funções abaixo da função Tick(), como mostra este código:

...

public:	
  // Called every frame
  virtual void Tick(float DeltaTime) override;
	
  UFUNCTION(BlueprintCallable, Category = TestUParam, meta=(AdvancedDisplay = 1))
  void ScreenMessage(FString Message, 
                     FLinearColor TextColor = FLinearColor(1.f, 0.f, 0.f),
                     float TimeToDisplay = 5.f);
	
  UFUNCTION(BlueprintCallable, Category = TestUParam)
  bool AnalyzeVector( UPARAM(ref, DisplayName="Input Vector") FVector& InVector, 
                      float& VectorLength, 
                      FVector& NormalVector );
                      
};

No arquivo TestUParam.cpp, adicione a implementação das duas funções mostradas abaixo:

... 

void ATestUParam::ScreenMessage(FString Message, FLinearColor TextColor, 
                                float TimeToDisplay)
{
  if(GEngine)
  {
    GEngine->AddOnScreenDebugMessage(-1, TimeToDisplay, TextColor.ToFColor(true), 
                                     Message);
  }	
}

bool ATestUParam::AnalyzeVector( FVector& InVector, float& VectorLength, 
                                 FVector& NormalVector )
{
  bool IsNormalized = InVector.IsNormalized();
	
  NormalVector.X = InVector.X;
  NormalVector.Y = InVector.Y;
  NormalVector.Z = InVector.Z;
	
  if (IsNormalized)
  {
    VectorLength = 1.f;
  }
  else
  {
    VectorLength = InVector.Size();
		
    NormalVector.Normalize();
  }
	
  return IsNormalized;
}

Compile o código C++. Crie um Blueprint baseado na classe C++ TestUParam. Para isso, clique com o botão direito na classe TestUParam e escolha a opção Create Blueprint class based on TestUParam.

Abra o novo Blueprint e acesse o Event Graph. Adicione as funções no evento BeginPlay como mostra a imagem abaixo. 

Clique para aumentar

Compile o Blueprint e adicione uma instância no nível. Inicie o jogo e veja as mensagens na tela com informações sobre o vetor Location da instância. A linha vermelha é o comprimento do vetor e a linha amarela é o vetor normalizado.


Sumário C++