In this article, we are going to create some variables and write the functions of the TutoProjectCollectable class. There is a new function called InitStatue() and the functions BeginPlay() and NotifyActorBeginOverlap() of the Actor class will be overridden.
First, let's add the variables and function declarations to the TutoProjectCollectable.h header file. In the public: block add the NotifyActorBeginOverlap() function declaration below the Tick() function:
...
public:
// Sets default values for this actor's properties
ATutoProjectCollectable();
// Called every frame
virtual void Tick(float DeltaTime) override;
virtual void NotifyActorBeginOverlap(AActor* OtherActor) override;
...
In the protected: block put the variables and function declarations shown below. The variables will be used in the functions we are going to write.
...
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
class ATutoProjectGameMode* TutoProjectGameMode;
FTimerHandle TimerHandleInitStatue;
void InitStatue();
};
Now we are going to modify the TutoProjectCollectable.cpp file. It is necessary to include the TutoProjectGameMode.h and TutoProjectCharacter.h files, as we will reference these classes.
#include "TutoProjectCollectable.h"
#include "TutoProjectGameMode.h"
#include "TutoProjectCharacter.h"
In the BeginPlay() function we will get a reference to the instance of the ATutoProjectGameMode class that is being used by the game to store it in the TutoProjectGameMode variable, just as it was done in the TutoProjectHUD class. Then the InitStatue() function will be called to initialize the instance of the TutoProjectCollectable class.
// Called when the game starts or when spawned
void ATutoProjectCollectable::BeginPlay()
{
Super::BeginPlay();
TutoProjectGameMode = GetWorld()->GetAuthGameMode<ATutoProjectGameMode>();
InitStatue();
}
The InitStatue() function has two goals. The first is to define the location of the instance within the game area. The second goal is to configure a Timer that will indicate the time that an instance will remain in a location. When this time is up, the Timer will call the InitStatue() function to change the location of the instance and configure a new Timer. The number of seconds for an instance to change location is the result of the expression 6 - Player Level.
void ATutoProjectCollectable::InitStatue()
{
float NewX = FMath::RandRange(XMinimum, XMaximum);
float NewY = FMath::RandRange(YMinimum, YMaximum);
FVector NewLocation = FVector(NewX, NewY, FloorZValue);
SetActorLocation(NewLocation);
float NewTime = 6 - TutoProjectGameMode->GetPlayerLevel();
GetWorldTimerManager().SetTimer(TimerHandleInitStatue, this,
&ATutoProjectCollectable::InitStatue, NewTime, false);
}
The function FMath::RandRange(Min, Max) returns a random number between the values of Min and Max. Variables that are defined within functions, such as NewX and NewY, are discarded as soon as the function ends.
If you have doubts about the Timer, see the article where we created a Timer in the TutoProjectGameMode class:
Creating a Timer in Unreal C++
The last function is NotifyActorBeginOverlap(AActor * OtherActor), which is called when an Actor is overlapping the instance. The OtherActor parameter is a reference to the Actor who triggered the overlap. Cast is used to verify if OtherActor is of type ATutoProjectCharacter. If it is not of this type, the result of Cast will be a null pointer that is represented by nullptr.
In addition, the not operator ! is used to verify that the game is not in Game Over mode. We use the and operator && to check whether both conditions are true. In this case, we call the ItemCollected() function of the TutoProjectGameMode class and the Destroy() function to remove the instance from the game. Another instance will be created using the SpawnActor() function.
void ATutoProjectCollectable::NotifyActorBeginOverlap(AActor* OtherActor)
{
Super::NotifyActorBeginOverlap(OtherActor);
if (Cast<ATutoProjectCharacter>(OtherActor) != nullptr
&& !TutoProjectGameMode->IsGameOver())
{
TutoProjectGameMode->ItemCollected();
Destroy();
FTransform DefaultTransform;
GetWorld()->SpawnActor<ATutoProjectCollectable>(this->GetClass(), DefaultTransform);
}
}
Some notes on the code above. The Destroy() function indicates that the Actor must be destroyed, but its destruction occurs at the end of the Tick. That is why it can have statements after the Destroy() function.
The SpawnActor() function needs an FTransform to define the position where the instance will be created. The DefaultTransform variable was created using the default values of FTransform because the BeginPlay() function will be executed for the new instance and will position it within the game area.
The first parameter of the SpawnActor() function indicates the class of the instance that will be created. The statement this->GetClass() was used to get the class of the instance running this code. Thus, if an instance of a child class of TutoProjectCollectable is collected by the player, the newly created instance will use this child class.