domingo, 1 de novembro de 2020

Function parameters and the UPARAM() macro

The C++ language allows the definition of default values for function parameters. The default value will be used if a value is not provided when the function is called.

This is an example of a function declaration with a default value:

void SpawnEnemy(int32 EnemyLevel = 1);

In the first line of the example below, the value of the EnemyLevel parameter will be 1 and in the second line, it will be 5.

SpawnEnemy();
SpawnEnemy(5);

In a UFUNCTION() we can use the AdvancedDisplay function specifier to hide the default parameters in a Blueprint node. The declaration of the ScreenMessage function below is an example.

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 indicates how many parameters should be displayed on the Blueprint node without having to expand. It is a type of specifier called Metadata.

The Blueprint node of ScreenMessage will look like this:


Click on the small arrow to show the other parameters:


Now let's talk about passing parameters by value and by reference. When passing by value, any changes made to the parameter are contained in the function itself. When passing by reference, a change made to the parameter affects the variable that was passed as a parameter. To define a parameter as a reference, use the & operator as shown in this example:

void ParamExample(int32 ByValue, int32& ByRef);

The passing of parameters by reference is widely used when the parameter is a structure or a class because it prevents a copy from being made with all the content of the parameter. Passing by reference is also used to allow a function to return more than one value.

A UFUNCTION() exposes the parameters passed by reference as output parameters. See the example below and the generated Blueprint node. 

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



We can use the UPARAM() macro with the ref specifier so that a parameter passed by reference is displayed as an input parameter:

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


The UPARAM() macro also has the DisplayName specifier that is used to change the parameter name in the Blueprint node:

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



Example usage:

Create a C++ class with the name TestUParam using the Actor class as the parent class. In the TestUParam.h file, add the declaration of two functions below the Tick() function, as shown in this code:

...

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 );
                      
};

In the TestUParam.cpp file, add the implementation of the two functions shown below:

... 

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 the C++ code. Create a Blueprint based on the C++ TestUParam class. To do this, right-click on the TestUParam class and choose the option Create Blueprint class based on TestUParam.

Open the new Blueprint and access the Event Graph. Add the functions to the BeginPlay event as shown in the image below.

Click to enlarge

Compile Blueprint and add an instance at the level. Start the game and see the messages on the screen with information about the Location vector of the instance. The red line is the length of the vector and the yellow line is the normalized vector.


Table of Contents C++