domingo, 28 de março de 2021

PlayerCharacter: Converting Blueprint script to C++

In this article, we will convert a part of the script of the Blueprint FirstPersonCharacter (renamed BP_PlayerCharacter).

There are two important factors that must be taken into account when deciding whether to convert a Blueprint script to C++:

  • Complexity: Sometimes it is necessary to implement a logic that is complex or involves many mathematical calculations. Due to the visual nature of Blueprint, these types of scripts can become confusing when represented by Blueprint nodes, making implementation in C++ more suitable and readable.  
  • SpeedThe execution speed of C++ code is faster than that of a Blueprint script. This does not mean that you should convert all of your Blueprints to C++ for this reason alone. There are countless other factors that will define the performance of an Unreal project. What you need to check is whether there are non-trivial scripts that are executed very often, for example, Blueprint nodes that are linked to the Tick event.


We will convert a simple Blueprint script to C++ to show how this is done. Open the Blueprint BP_PlayerCharacter and see in the Event Graph the actions that are in Movement Input. I removed the nodes related to the usage test of the HMD (Head Mount Display) to keep the example simple as shown in the image below. 


Let's take the Blueprint Nodes from the InputAxis MoveForward event and convert them to a single C++ function. In this example, there are only two nodes, but if there were dozens of nodes, they could also be converted into a single C++ function. We will do the same conversion for the InputAxis MoveRight event.

We need to find the C++ functions equivalent to the Blueprint nodes. Below the name of a Blueprint node is the Target is... line, which indicates the class where the function represented by the node was defined. In the image above we have two nodes whose Target is Actor and two nodes with Target Pawn. All of these functions will be accessible in our C++ PlayerCharacter class because the Character class inherits the variables and functions of the Pawn class and the Pawn class inherits the variables and functions of the Actor class.

The first step is to search for the function in the Unreal Engine API Reference. Look for the Blueprint node name by removing the blanks, for example, AddMovementInput. In the API Reference, you can see the description of the function parameters. Unfortunately, the API Reference does not indicate whether the parameter is optional. To verify this, look at the function page in the API Reference for the path and name of the Header file. Look for the file in your project's Engine folder in Visual Studio. Open the file and look for the function name inside the file. The declaration of the AddMovementInput function is done as follows:

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

Note that the ScaleValue and bForce parameters are taking default values. This means that these parameters are optional and the AddMovementInput function can be called by passing only the WorldDirection parameter.

In the previous article, we created the C++ PlayerCharacter class to be used as the parent class of the Blueprint BP_PlayerCharacter. Open the PlayerCharacter.h file and add the declarations for the new MoveForward() and MoveRight() functions as shown in the code below.

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

In the PlayerCharacter.cpp file we have the implementation of the MoveForward() and MoveRight() functions using the same logic that was in the Blueprint script.

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

The first parameter of the AddMovementInput() function receives the return value from the function being called.

Just a note regarding the Value parameter used in the motion functions. If Value is negative, the movement will be in the opposite direction.

Compile the C++ code.

Open the Blueprint BP_PlayerCharacter. In the Event Graph, we will replace the Blueprint nodes that are in Movement Input by our C++ functions. Keep the InputAxis event nodes and remove the others.

Right-click on the Blueprint Event Graph, look for our C++ functions in the Context Menu in the Movement category and add them to the Event Graph which should look like this:


Note that the Target of the Blueprint nodes is Player Character.

We made this simple example so that you can understand this conversion process.


Table of Contents C++