Mostrando postagens com marcador Tutorials. Mostrar todas as postagens
Mostrando postagens com marcador Tutorials. Mostrar todas as postagens

segunda-feira, 26 de abril de 2021

Conclusion of Unreal C++ tutorials

This article concludes part III of the Unreal C++ tutorials. The main purpose of this part is to show how C++ and Blueprint can be used together to get the best of both worlds.

The part III project was made using Unreal Engine version 4.26 and is available at this link:


A simple project was created so that you can understand all the concepts of C++ and Blueprint used in this project. When you assimilate the basic blocks of programming, it becomes easier to be able to program your own games and to understand other more complex programming tutorials.

You don't learn to program just by repeating step-by-step instructions. Programming is similar to a puzzle, you need to understand how each piece works to be able to put them together in the correct way to solve a specific problem.

I want to thank the readers, programmers, and instructors who use and recommend my materials. I am very grateful for all the feedback I receive and feel immense satisfaction in knowing that the material I produce has been very useful for the Unreal Engine community.


Table of Contents C++

 

domingo, 25 de abril de 2021

Creating a Blueprint Function Library in C++

During the development of a project, you will need to create some functions that are needed in different parts of the project, both in C ++ and in Blueprints. For these cases, you can create a Blueprint Function Library in C++ to gather these functions in one place.

Let's create a simple Blueprint Function Library in our project to show how it works in practice.

In the Content Browser, access the TutoPart3 folder that is inside the C++ Classes folder. Right-click on free space and choose the New C++ Class... option: 


On the next screen, choose the Blueprint Function Libray class as the parent class and click the Next button.


In the Name field, write TutoBPFunctionLibrary. In the Path field, keep the default project folder. Click the Create Class button. 

Our example will contain only one function, but you can create several functions in a Blueprint Function Library, as long as they are all static.

The name of the example function is GetNumberOfInstances. It returns the number of instances that exist at the current level of the class passed as a parameter.

Add the function declaration to the TutoBPFunctionLibrary.h file as shown in the code below. 

#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "TutoBPFunctionLibrary.generated.h"


UCLASS()
class TUTOPART3_API UTutoBPFunctionLibrary : public UBlueprintFunctionLibrary
{
  GENERATED_BODY()

  UFUNCTION(BlueprintCallable, Category = TutoBPLibrary, 
            meta = (WorldContext = "WorldContextObject") )
  static int32 GetNumberOfInstances(UObject* WorldContextObject, 
                                    TSubclassOf < AActor > ActorClass );	
};

The first parameter of the function is WorldContextObject which is a reference to the current level. The WorldContext specifier was used to automatically fill it in the Blueprint node, as explained in the previous article.

In the second parameter, a TSubclassOf was used so that only Actor subclasses are accepted.

Add the implementation of the GetNumberOfInstances() function to the TutoBPFunctionLibrary.cpp file. 

#include "TutoBPFunctionLibrary.h"
#include "Kismet/GameplayStatics.h"

int32 UTutoBPFunctionLibrary::GetNumberOfInstances(UObject* WorldContextObject, 
                                                   TSubclassOf < AActor > ActorClass)
{
 TArray< AActor* > InstancesFound;

 UGameplayStatics::GetAllActorsOfClass(WorldContextObject,ActorClass, InstancesFound);
 
 return InstancesFound.Num(); 
}

The GetAllActorsOfClass() function of the UGameplayStatics class was used. This function fills a TArray with the references of the existing instances in the current level of the class passed as a parameter. Our GetNumberOfInstances() function returns the number of elements in the TArray.

Compile the C++ code.

We will use the GetNumberOfInstances() function in Blueprint to see if all the enemy cannons were destroyed. When this occurs, a message will appear on the screen.

Open the Blueprint FirstPersonGameMode in the FirstPersonBP/Blueprints folder. Right-click on the Event Graph, scroll down the list until you find the category Tuto BPLibrary and select the function Get Number Of Instances. 


The image below is the Blueprint node that represents our function. In the Actor Class parameter, select the EnemyCannon class.


In the Event BeginPlay, we will create a Timer that will execute a custom event called CheckCannons once per second. The CheckCannons event calls the Get Number Of Instance function. If the number of instances of EnemyCannon is equal to zero, then the Print String node displays a message on the screen. The Blueprint script must look like this: 


To add the custom event, right-click on the Event Graph and choose the Add Custom Event option.

Compile the Blueprint and start the game. Destroy all the cannons on the level to see if the message is displayed.


Table of Contents C++

 

sábado, 24 de abril de 2021

Understanding the WorldContextObject

In an Actor class, you can get a World (current level) reference using the GetWorld() function.

However, static functions must receive an UObject as a parameter in order to call its GetWorld() function and obtain the World reference. This parameter is known as WorldContextObject.

There is a specifier of type Metadata called WorldContext that is used to automatically fill in the parameter WorldContextObject when it is called in Blueprint. That way, the WorldContextObject parameter is not even displayed in the Blueprint node. 

The code below shows an example of using the WorldContext specifier in the declaration of the GetPlayerPawn() function of the UGameplayStatics class: 

UFUNCTION(BlueprintPure, Category="Game", 
          meta=(WorldContext="WorldContextObject", 
                UnsafeDuringActorConstruction="true"))
static class APawn* GetPlayerPawn(const UObject* WorldContextObject, 
                                  int32 PlayerIndex);


Note that the Blueprint node of GetPlayerPawn() does not display the WorldContextObject parameter:

 

You must provide a WorldContextObject if you call GetPlayerPawn() in C++. If it is being called from an Actor class, you can pass the this pointer or to make it clearer you can pass the return of the GetWorld() function as shown in this example:

APawn* PlayerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0);


Now that we know what static functions are and understand the use of WorldContextObject, we are ready to create our own Blueprint Function Library in the next article.


Table of Contents C++ 

 

sexta-feira, 23 de abril de 2021

Static functions and the UGameplayStatics class

The static keyword in C++ can be used with variables, functions, and objects. In this article, we will focus on the use of static when declaring functions in a class.

When a function is declared as static in a C++ class, it has a class scope. This means that it is independent of the instances of the class and can be called using only the name of the class and the :: operator.

Consequently, a static function does not have access to the instance variables. The only variables it can access are the variables that have also been declared as static.

The code below shows a simple example of a static function that squares a number.

static float Square(float value)
{
  return value * value;
}

You do not need to create an instance of the class in order to be able to call the static function. Assuming that the function was created in a class named ATestStatic, the call to the Square() function can be done as follows:

float result = ATestStatic::Square(4.5f);

The keyword static is widely used in the creation of gameplay utility functions. An example of this is the UGameplayStatics class which contains several static functions that can be called in C++ and Blueprint.

UGameplayStatics is a Blueprint Function Library. In another article, we will see how to create a Blueprint Function Library.

The code below shows the declarations of five functions of the UGameplayStatics class that are frequently used.

UFUNCTION(BlueprintPure, Category="Game", 
          meta=(WorldContext="WorldContextObject", 
                UnsafeDuringActorConstruction="true"))
static class APawn* GetPlayerPawn(const UObject* WorldContextObject, 
                                  int32 PlayerIndex);

UFUNCTION(BlueprintPure, Category="Game", 
          meta=(WorldContext="WorldContextObject", 
                UnsafeDuringActorConstruction="true"))
static class APlayerController* GetPlayerController(
                                   const UObject* WorldContextObject, 
                                   int32 PlayerIndex);

UFUNCTION(BlueprintCallable, 
          meta=(WorldContext="WorldContextObject", 
                AdvancedDisplay = "2", 
                DisplayName = "Open Level (by Name)"), 
          Category="Game")
static void OpenLevel(const UObject* WorldContextObject, FName LevelName, 
               bool bAbsolute = true, FString Options = FString(TEXT("")));

UFUNCTION(BlueprintCallable, Category="Audio", 
          meta=(WorldContext="WorldContextObject", 
                AdvancedDisplay = "3", 
                UnsafeDuringActorConstruction = "true", 
                Keywords = "play"))
static void PlaySoundAtLocation(const UObject* WorldContextObject, 
               USoundBase* Sound, FVector Location, FRotator Rotation, 
               float VolumeMultiplier = 1.f, float PitchMultiplier = 1.f, 
               float StartTime = 0.f, 
               class USoundAttenuation* AttenuationSettings = nullptr, 
               USoundConcurrency* ConcurrencySettings = nullptr, 
               AActor* OwningActor = nullptr);

UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category="Game|Damage")
static float ApplyDamage(AActor* DamagedActor, float BaseDamage, 
               AController* EventInstigator, AActor* DamageCauser, 
               TSubclassOf<class UDamageType> DamageTypeClass);

The images below show the Blueprint nodes equivalent to the above functions.



Note that some functions have a UObject* WorldContextObject as their first parameter and that this parameter does not appear in Blueprint nodes. The WorldContextObject will be the topic of the next article.


Table of Contents C++

 

quarta-feira, 21 de abril de 2021

EnemyCannon: Implementing BlueprintNativeEvent

In this article, we will implement the Blueprint version of BlueprintNativeEvent DestroyCannon().

Open the BP_EnemyCannon Blueprint that we created in the previous article.

On the My Blueprint tab, click the Override button next to Functions and select Destroy Cannon. The DestroyCannon() function will be represented as an Event in Blueprint because it does not return a value.


Right-click on the Event Graph next to the Destroy Cannon Event and add the Spawn Actor from Class action. In the Class parameter, select BlueprintEffectExplosion. Right-click on the Spawn Transform parameter and select the Split Struct Pin option so that we can enter Location and Scale separately.


Set the value 5 to X, Y, and Z in SpawnActor's Spawn Transform Scale parameter. Add the GetActorLocation and DestroyActor Actions to the Event Graph and connect the nodes as shown in the image below.


These actions will create an explosion at the cannon location and remove the cannon from the game.

When the DestroyCannon() function is called in C++, it will be this Blueprint version that will be executed.

Compile and save the Blueprint.


Table of Contents C++  


segunda-feira, 19 de abril de 2021

EnemyCannon: Extending into Blueprint

In this article, we are going to create the child Blueprint of EnemyCannon.

In the Content Browser, access the TutoPart3 folder that is inside the C++ Classes folder. Right-click on the EnemyCannon class and choose the option Create Blueprint class based on EnemyCannon:


On the next screen, write BP_EnemyCannon in the Name field. In the Path field, choose the Blueprints folder that is inside the FirstPersonBP folder and click the Create Blueprint Class button.


Open the Blueprint BP_EnemyCannon and see in the Components tab the components that we defined in the C++ class.


Select the StaticMesh component. On the Details tab, select SM_MatPreviewMesh_02 for StaticMesh and M_Metal_Burnished_Steel for Material Element 0.


Select the ProjectileSpawnLocation component. In the Details tab, in Location set 150 to X and 200 to Z.

We need to define the Projectile Class that will be used by EnemyCannon. To do this, click on the Class Defaults button. In the Details tab, Cannon category, select the BP_EnemyProjectile in the Projectile Class variable. The values of the FireRate and MaxHits variables can also be modified in Class Defaults.


The variables shown in the image above can also be modified in the instances of EnemyCannon that are in the level, thus allowing to have Enemy Cannons in the level with different types of projectiles, firing times, and maximum number of hits.

See in the Viewport tab the appearance of BP_EnemyCannon. The red arrow is invisible in the game. 


Compile and save the Blueprint.

In the next article, we will implement the BlueprintNativeEvent DestroyCannon() in Blueprint.


Table of Contents C++

 

quinta-feira, 15 de abril de 2021

EnemyCannon: Counting the Hits

In this article, we are going to add a hit count to the EnemyCannon class. When the number of Hits reaches a specified maximum value, the EnemyCannon instance will be destroyed.

We will use two integer variables. NumHits will store the number of times an EnemyCannon has been hit and MaxHits will store the number of hits needed to destroy an EnemyCannon.

We will use the NotifyHit() event/function to increment NumHits. This event is called when an EnemyCannon is hit.

The Cannon destruction is done by the DestroyCannon() function. It is a UFUNCTION() of type BlueprintNativeEvent that allows a default C++ implementation that will be used if the Blueprint version of the function is not implemented. For more information about UFUNCTION(), visit this link.

The 1st version of the EnemyCannon.h file was created at this link.

Add the declaration of the new variables and functions in the EnemyCannon.h file below the ShootCannon() function:

...
  FTimerHandle ShotTimerHandle;
	
  void ShootCannon();	

  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Cannon)
  int32 MaxHits;
	
  UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Cannon)
  int32 NumHits;

  UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = Cannon)
  void DestroyCannon();
	
  virtual void NotifyHit(class UPrimitiveComponent* MyComp, class AActor* Other, 
          class UPrimitiveComponent* OtherComp, bool bSelfMoved, FVector HitLocation, 
          FVector HitNormal, FVector NormalImpulse, const FHitResult& Hit) override;

};

The next step is to modify the EnemyCannon.cpp file that was created at this link.

In the constructor, initialize the MaxHits variable with a value of 5:

FireRate = 5.f;	
MaxHits = 5;

Add the implementation of the NotifyHit() and DestroyCannon_Implementation() functions at the end of the EnemyCannon.cpp file:

void AEnemyCannon::NotifyHit(class UPrimitiveComponent* MyComp, class AActor* Other,
           class UPrimitiveComponent* OtherComp, bool bSelfMoved, FVector HitLocation,
           FVector HitNormal, FVector NormalImpulse, const FHitResult& Hit)
{
  Super::NotifyHit(MyComp, Other, OtherComp, bSelfMoved, HitLocation, HitNormal, 
                   NormalImpulse, Hit);
	
  if (Cast<APlayerProjectile>(Other) != nullptr)
  {
    NumHits++;
		
    if( NumHits == MaxHits )
    {
      DestroyCannon();
    }
  }
}

void AEnemyCannon::DestroyCannon_Implementation()
{
  Destroy();
}

In the NotifyHit() function, a Cast<APlayerProjectile>(Other) is used because we will only count a Hit if the EnemyCannon is hit by a projectile of the type APlayerProjectile (or child classes).

When the value of the NumHits variable is equal to MaxHits, the DestroyCannon() function will be called.

Note that the implementation of the DestroyCannon() function has the suffix _Implementation. This suffix is necessary because it is a BlueprintNativeEvent.


Table of Contents C++

 

segunda-feira, 12 de abril de 2021

EnemyCannon: C++ implementation file

In the previous article, we analyzed the contents of the 1st version of the EnemyCannon.h file. In this article, the contents of the equivalent EnemyCannon.cpp file will be explained.

At the beginning of the file, there is the inclusion of the necessary header files using the #include lines. In the constructor, we have the initialization of the components and of the FireRate variable.

#include "EnemyCannon.h"
#include "EnemyProjectile.h"
#include "Components/ArrowComponent.h"
#include "Kismet/GameplayStatics.h"
#include "PlayerProjectile.h"

// Sets default values
AEnemyCannon::AEnemyCannon()
{
  PrimaryActorTick.bCanEverTick = true;
	
  RootScene = CreateDefaultSubobject<USceneComponent>(TEXT("RootScene"));
  RootComponent = RootScene;

  StaticMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh"));
  StaticMesh->SetCollisionProfileName(
                       UCollisionProfile::BlockAllDynamic_ProfileName);
  StaticMesh->SetupAttachment(RootScene);
	
  ProjectileSpawnLocation = CreateDefaultSubobject<UArrowComponent>(
                                                  TEXT("ProjectileSpawnLocation"));
  ProjectileSpawnLocation->SetupAttachment(StaticMesh);

  FireRate = 5.f;	
}

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

  FVector Direction = PlayerPawn->GetActorLocation() 
                      - StaticMesh->GetComponentLocation();
  
  FRotator Rotator = FVector(Direction.X, Direction.Y, 0).Rotation();
  
  StaticMesh->SetWorldRotation(Rotator);
}

// Called when the game starts or when spawned
void AEnemyCannon::BeginPlay()
{
  Super::BeginPlay();
	
  PlayerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0);
	
  GetWorldTimerManager().SetTimer(ShotTimerHandle, this, 
                                  &AEnemyCannon::ShootCannon, FireRate, true);	
}


void AEnemyCannon::ShootCannon()
{	
  GetWorld()->SpawnActor<AEnemyProjectile>(ProjectileClass, 
                         ProjectileSpawnLocation->GetComponentTransform() );
}

In the Tick() function there is the logic for the cannon to always aim in the direction of the player. First, we calculate the Direction vector by taking the player's Location vector and subtracting the cannon's Location vector.

The FVector structure has a function called Rotation() that returns a FRotator in the direction indicated by the vector. An FVector was created based on the Direction vector but disregarding the Z coordinate so that the cannon does not rotate up and down. The FRotator resulting from this new FVector was used to define the current cannon rotation.

In the BeginPlay() function, the reference to the instance representing the player is obtained and stored in the PlayerPawn variable. Then we have the creation of the Timer, whose reference will be stored in ShotTimerHandle, and which will call the ShootCannon function periodically using the value in the FireRate variable as the time interval.

In the ShootCannon() function we have the creation of the projectile based on the class that is stored in ProjectileClass using the Transform of the ProjectileSpawnLocation component to define the position and rotation of the projectile.

In the next article, we'll look at the variables and functions needed to count the Hits and destroy the cannon.


Table of Contents C++


sexta-feira, 9 de abril de 2021

EnemyCannon: C++ header file

In this article, we will start to implement the C++ base class that will be used to represent the enemy cannon.

In the Content Browser, access the TutoPart3 folder that is inside the C++ Classes folder. Right-click on free space and choose the New C++ Class... option.

On the next screen, choose the Actor class as the parent class and click the Next button.

In the Name field, write EnemyCannon. In the Path field, keep the default project folder. Click the Create Class button.

The code below shows the first version of the EnemyCannon.h file. In another article, we will add more variables and functions to count the Hits and destroy the cannon.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "EnemyCannon.generated.h"

class AEnemyProjectile;
class UArrowComponent;

UCLASS()
class TUTOPART3_API AEnemyCannon : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AEnemyCannon();

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

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;
	
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
	USceneComponent* RootScene;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
	UStaticMeshComponent* StaticMesh;
	
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
	UArrowComponent* ProjectileSpawnLocation;
	
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Cannon)
	TSubclassOf<AEnemyProjectile> ProjectileClass;
	
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Cannon)
	float FireRate;
	
	APawn* PlayerPawn;
	
	FTimerHandle ShotTimerHandle;
	
	void ShootCannon();

};

The enemy cannon does not leave its position but rotates to always be aiming towards the player's current position. To do this, a reference to the player instance is stored in the PlayerPawn pointer.

It fires projectiles of type AEnemyProjectile or of child classes. The projectile class used by a cannon instance is stored in the ProjectileClass variable.

The ProjectileSpawnLocation variable is a component of type UArrowComponent used to specify the location where the projectile will be created.

The time between cannon shots is stored in seconds in the FireRate variable. This variable will be used in the creation of a Timer (referenced by ShotTimerHandle) that will be calling the ShootCannon() function when the time that is in FireRate passes.

In the next article, we'll look at the contents of the EnemyCannon.cpp implementation file.


Table of Contents C++


quarta-feira, 7 de abril de 2021

EnemyProjectile: Extending into Blueprint

We are going to create a child Blueprint based on the C++ class EnemyProjectile to define the Assets that will be used.

In the Content Browser, access the TutoPart3 folder that is inside the C++ Classes folder. Right-click on the EnemyProjectile class and choose the option Create Blueprint class based on EnemyProjectile:


On the next screen, write BP_EnemyProjectile in the Name field. In the Path field, choose the Blueprints folder that is inside the FirstPersonBP folder and click the Create Blueprint Class button.


Double-click on the new Blueprint to open the Blueprint editor. The components that we defined in the C++ class can be seen in the Components tab.


Select the StaticMesh component. On the Details tab, select Shape_Cone for StaticMesh and M_Tech_Hex_Tile_Pulse for Material. In Rotation set -90.0 to Y and change the Scale to 0.5 in X, Y and Z.



Select the Particles component. On the Details tab, select P_Fire as the Template.


Select the ProjectileMovement component. The values that we define in C++ can be modified in the Details tab.


In the Blueprint Event Graph, add the Event Hit and the DestroyActor action:


Click on the Viewport tab to see how the BP_EnemyProjectile looks.


Compile and save the Blueprint.


Table of Contents C++


segunda-feira, 5 de abril de 2021

EnemyProjectile: Creating a Homing Projectile

In this article, we will create a C++ base class to represent the Homing projectiles that are fired by enemy cannons.

In the Content Browser, access the TutoPart3 folder that is inside the C++ Classes folder. Right-click on free space and choose the New C++ Class... option:


On the next screen, choose the Actor class as the parent class and click the Next button.



In the Name field, write EnemyProjectile. In the Path field, keep the default project folder. Click the Create Class button.

The EnemyProjectile class will use four types of components:

  • USphereComponent: It is used as the Root component and for collision testing.
  • UStaticMeshComponent: Contains the Static Mesh that will visually represent the projectile.
  • UParticleSystemComponent: Component with a particle emitter that will be used to simulate a fire effect on the projectile.
  • UProjectileMovementComponent: Component used to move the projectile. It will be configured to follow the player.

 

Add the declaration of the components in the EnemyProjectile.h file that will look like this:

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "EnemyProjectile.generated.h"

UCLASS()
class TUTOPART3_API AEnemyProjectile : public AActor
{
  GENERATED_BODY()
	
public:	
  // Sets default values for this actor's properties
  AEnemyProjectile();

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

public:	
  // Called every frame
  virtual void Tick(float DeltaTime) override;
	
  UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
  UStaticMeshComponent* StaticMesh;
	
  UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
  class USphereComponent* CollisionComponent;
	
  UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
  class UParticleSystemComponent* Particles;

  UPROPERTY(VisibleAnywhere, BlueprintReadOnly)	
  class UProjectileMovementComponent* ProjectileMovement;
	
};

In the EnemyProjectile.cpp file, we have the initialization of the components done in the constructor. Don't forget to add the #include lines for the components.

#include "EnemyProjectile.h"
#include "Engine/CollisionProfile.h"
#include "Components/SphereComponent.h"
#include "Particles/ParticleSystemComponent.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "Kismet/GameplayStatics.h"


// Sets default values
AEnemyProjectile::AEnemyProjectile()
{
  PrimaryActorTick.bCanEverTick = true;

  InitialLifeSpan = 10.f;
	
  CollisionComponent = CreateDefaultSubobject<USphereComponent>(
                                             TEXT("CollisionComponent"));
  RootComponent = CollisionComponent;
  CollisionComponent->InitSphereRadius(25.f);
  CollisionComponent->SetCollisionProfileName(
                              UCollisionProfile::BlockAllDynamic_ProfileName);

  StaticMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh"));
  StaticMesh->SetupAttachment(CollisionComponent);
	
  Particles = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("Particles"));
  Particles->SetupAttachment(StaticMesh);
	
  ProjectileMovement = CreateDefaultSubobject<UProjectileMovementComponent>(
                                             TEXT("ProjectileMovement"));
  ProjectileMovement->InitialSpeed = 300.f;
  ProjectileMovement->MaxSpeed = 300.f;
  ProjectileMovement->bRotationFollowsVelocity = true;
  ProjectileMovement->bIsHomingProjectile = true;
  ProjectileMovement->HomingAccelerationMagnitude = 300.f;
  ProjectileMovement->ProjectileGravityScale = 0.f;
}

// Called when the game starts or when spawned
void AEnemyProjectile::BeginPlay()
{
  Super::BeginPlay();
	
  APawn* PlayerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0);
	
  if(PlayerPawn)
  {
    ProjectileMovement->HomingTargetComponent = PlayerPawn->GetRootComponent();
  }
}

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

I did the following steps to configure the UProjectileMovementComponent as a Homing  projectile:

  • I assigned true to the variable bRotationFollowsVelocity so that the projectile rotates when it changes direction.
  • I assigned true to the variable bIsHomingProjectile to indicate that this is a Homing Projectile.
  • I assigned a value for the HomingAccelerationMagnitude variable that indicates the acceleration that the projectile will move towards the target. I used the same value as the velocity of the projectile.
  • I removed the force of gravity from the projectile by assigning a value of 0 to the ProjectileGravityScale variable.
  • The last step is to assign a USceneComponent to the variable HomingTargetComponent that will be used as a target. This is done in the BeginPlay() function. The target is the player's RootComponent.   


Compile the C++ code. The Static Mesh and Particle System Assets will be selected in the child Blueprints. Besides that, the initial values assigned to the components can be modified in child Blueprints. 


Table of Contents C++


sexta-feira, 2 de abril de 2021

PlayerCharacter: Blueprint adjustments

In this article we will make the necessary adjustments to the Blueprint BP_PlayerCharacter to implement the projectile change by the player.

First, let's add an Input mapping for the player to change the projectile using the Tab key.

In the Level Editor, access the Edit->Project Settings... menu and in the Engine category, choose the Input option. Click the + symbol next to Action Mappings, write the name Change Projectile for the new Action Mapping and select the Tab key:


Open the Blueprint BP_PlayerCharacter. In the Event Graph, add the InputAction Change Projectile event and the Change Projectile function that we created in C++.


In the My Blueprint tab, click on the eye icon and check the Show Inherited Variables option so that the variables we defined in the C ++ class are displayed. The variables are in the Projectile category.


Select the Array ProjectileClasses. In the Details tab, add 3 elements to the Array and select the Blueprint classes BP_PlayerProjectile1, BP_PlayerProjectile2 and BP_PlayerProjectile3. Each of these Blueprints represents a type of projectile.



In the Event Graph, InputAction Fire event, look for the SpawnActor node. Click and drag the CurrentProjectileClass variable from the MyBlueprint tab and drop it on the Class parameter of the SpawnActor node.


When the player shoots, the SpawnActor node will create an instance of the class that is stored in the CurrentProjectileClass variable.



quarta-feira, 31 de março de 2021

PlayerCharacter: Array of PlayerProjectile

The PlayerCharacter can change the type of PlayerProjectile that is being used during the game. The classes that represent the different types of PlayerProjectile will be added to a TArray in the PlayerCharacter C++ class.

Each element of TArray is of type TSubclassOf<APlayerProjectile>. The use of TSubclassOf allows the storage of references to the APlayerProjectile class or to any of its child classes (subclasses).

The code below is the new version of the PlayerCharacter.h file containing the variables and function needed for the change of projectiles. The new content is in bold. Note that before the UCLASS() macro, a forward declaration of the APlayerProjectile class was made. That way we just need to add the #include "PlayerProjectile.h" to the cpp file.

#pragma once

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

class APlayerProjectile;

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);
	
  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Projectile)
  TSubclassOf<APlayerProjectile> CurrentProjectileClass;
	
  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Projectile)
  TArray< TSubclassOf<APlayerProjectile> > ProjectileClasses;
	
  UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Projectile)
  int32 IndexProjectileClass;
	
  UFUNCTION(BlueprintCallable, Category = Projectile)
  void ChangeProjectile();
};

Let's look at each of the new declarations:

  • TSubclassOf<APlayerProjectile> CurrentProjectileClass: This variable stores the reference of the class of type APlayerProjectile that represents the projectile selected by the player.
  • TArray< TSubclassOf<APlayerProjectile> > ProjectileClassesArray that stores all class references of type APlayerProjectile that can be selected by the player. 
  • int32 IndexProjectileClassStores the index with the position in the Array of the selected APlayerProjectile
  • void ChangeProjectile(): Function that changes to the next projectile and updates the variables. When it reaches the end of the Array, it returns to the beginning.

In the PlayerCharacter.cpp file we have the ChangeProjectile() function definition. In addition, in the BeginPlay() function, the CurrentProjectileClass variable is initialized with the first element of the Array, which is the element with index 0. The new version of the file looks like this:  

#include "PlayerCharacter.h"
#include "PlayerProjectile.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();

  if(CurrentProjectileClass == nullptr && ProjectileClasses.Num() > 0)
  {
    CurrentProjectileClass = ProjectileClasses[0];
  }	
}

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

void APlayerCharacter::ChangeProjectile()
{
  IndexProjectileClass++;
	
  if(IndexProjectileClass == ProjectileClasses.Num()) 
  {
    IndexProjectileClass = 0;
  }
	
  CurrentProjectileClass = ProjectileClasses[IndexProjectileClass];
}


In the next article we will make the necessary adjustments to the Blueprint BP_PlayerCharacter and add the elements of the PlayerProjectile Array.


Table of Contents C++

  

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++