quinta-feira, 29 de outubro de 2020

A macro UFUNCTION()

A macro UFUNCTION() é usada para expor funções C++ para o editor de Blueprints. É possível criar funções C++ que serão chamadas no editor de Blueprints. Também é possível chamar em C++, funções que foram implementadas em Blueprints.

Um Blueprint pode representar uma função C++ de três formas diferentes. Eventos são implementados como funções C++ que não retornam valor, ou seja, seu tipo de retorno é void:


Uma função Blueprint com pino de execução é implementada como uma função C++ que não esteja marcada como BlueprintPure


Se a função C++ estiver marcada com BlueprintPure então ela é representada como uma função pura. Uma função pura não possui pino de execução, não pode modificar os dados da instância e pode ser usada em expressões. É muito utilizada em funções do tipo Get.


Agora vamos falar sobre os especificadores de funções. Em primeiro lugar, toda UFUNCTION() deve ter o especificador Category para que a função seja exibida corretamente no menu de contexto de um Blueprint.

Para que uma UFUNCTION() possa ser usada no Event Graph de um Blueprint, ela precisa ter um desses dois especificadores:

  • BlueprintCallable: Aparecerá como uma função Blueprint com pino de execução.
  • BlueprintPure: Aparecerá como uma função pura.


É possível definir uma função C++ que será implementada em Blueprint. Para fazer isso, use um desses dois especificadores na UFUNCTION():

  • BlueprintImplementableEvent: A implementação da função é feita apenas no Blueprint. Se for encontrada uma implementação em C++, irá gerar um erro de compilação.
  • BlueprintNativeEvent: Permite a implementação de uma versão em C++ da função que será usada se não for implementada a versão em Blueprint. A versão em C++ deve ter o nome da função mais o sufixo _Implementation.

Se o tipo de retorno da função C++ for void, ela será implementada como um Evento em Blueprints. Se a função C++ retornar valor, então ela será implementada como uma função Blueprint. 

Vamos ver todos esses especificadores em ação no exemplo de uso.


Exemplo de uso:

Crie uma classe C++ com o nome TestUFunction usando como classe pai a classe Actor. No arquivo TestUFunction.h, adicione a declaração de quatro 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 = Tutorial)
  int32 WelcomeMsg(FString PlayerName);
	
  UFUNCTION(BlueprintPure, Category = Tutorial)
  float CalculateDamage(float PowerAttack, float DefenseValue);
	
  UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category = Tutorial)
  void TreasureFound();
	
  UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = Tutorial)
  FString NativeExample();
};

No arquivo TestUFunction.cpp, adicione a implementação das três funções mostradas abaixo: 

...

int32 ATestUFunction::WelcomeMsg(FString PlayerName)
{
  FString Message = FString::Printf(TEXT("Welcome %s."), *PlayerName);
  
  if(GEngine)
  {
    GEngine->AddOnScreenDebugMessage(-1, 10, FColor::Red, Message);
  }
  
  //Returns the number of characters in PlayerName
  return PlayerName.Len();
}

float ATestUFunction::CalculateDamage(float PowerAttack, float DefenseValue)
{
  return (PowerAttack * 10) / DefenseValue;
}

FString ATestUFunction::NativeExample_Implementation()
{
  return TEXT("NativeExample: C++ version");
}

Observe que a função TreasureFound() não foi implementada. É porque ela será implementada somente em Blueprint devido o especificador  BlueprintImplementableEvent. A função NativeExample() tem o especificador BlueprintNativeEvent que permite que seja implementada em C++ e em Blueprint. A versão C++ dela foi implementada com o nome NativeExample_Implementation().

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

Clique com o botão direito no Event Graph do Blueprint e veja que as quatro funções declaradas em C++ estão sendo listadas na categoria Tutorial como mostra a imagem abaixo. Elas aparecem nesta listagem porque possuem o especificador BlueprintCallable ou BlueprintPure.


Vamos chamar as quatro funções no Evento BeginPlay do Blueprint. Adicione as funções no Event Graph como mostra a imagem abaixo.

Clique para aumentar

Antes de testarmos, precisamos implementar a função TreasureFound() no Blueprint. Na aba My Blueprint, clique no botão Override que está ao lado de Functions. Veja na listagem que aparecem as funções Treasure Found e Native Example. Clique em Treasure Found.


O tipo de retorno da função TreasureFound() é void, por isso ela é implementada como um Evento em Blueprint. Adicione uma função Print String com uma mensagem:


Compile o Blueprint e adicione uma instância no nível. Inicie o jogo e veja as mensagens na tela. Cada linha representa a execução de uma das funções.


Como não foi implementada uma versão em Blueprint da função NativeExample(), a versão em C++ foi usada. Para concluir este exemplo, vamos implementar a versão Blueprint de NativeExample().

Na aba My Blueprint, clique no botão Override ao lado de Functions e selecione Native Example. Ela é implementada como uma função porque possui valor de retorno. Veja na imagem o node Parent: Native Example. Este node executará a versão C++ de NativeExample(). Desta forma você pode acrescentar funcionalidade no Blueprint para esta função. Mas se você quiser que a versão em Blueprint substitua a versão C++, basta remover este node. 


Para nosso exemplo, remova o node Parent: Native Example e escreva uma mensagem no parâmetro Return Value.


Compile o Blueprint e execute o jogo. Veja que apenas a versão em Blueprint de NativeExample() foi chamada. 


Sumário C++