Ok, this resetting behavior is built in at a few places in C++:
APawn::Reset()
APlayerController::Reset()
APlayerController::ClientReset_Implementation()
The cleanest solution I find is to make subclasses and override the default behaviors. I made 3:
ResettablePawn.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "ResettablePawn.generated.h"
UCLASS()
class BUMPERCROP_API AResettablePawn : public APawn
{
GENERATED_BODY()
public:
AResettablePawn(); //constructor
virtual void Reset() override;
};
ResettablePawn.cpp
#include "ResettablePawn.h"
#include "Engine/GameEngine.h"
// constructor
AResettablePawn::AResettablePawn()
{
PrimaryActorTick.bCanEverTick = true;
}
void AResettablePawn::Reset()
{
K2_OnReset();
}
(K2_OnReset() is the function that triggers the Blueprint OnReset event)
ResettablePlayerController.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "ResettablePlayerController.generated.h"
UCLASS()
class BUMPERCROP_API AResettablePlayerController : public APlayerController
{
GENERATED_BODY()
public:
AResettablePlayerController(); //constructor
virtual void Reset() override;
};
ResettablePlayerController.cpp
#include "ResettablePlayerController.h"
AResettablePlayerController::AResettablePlayerController()
{
PrimaryActorTick.bCanEverTick = true;
}
void AResettablePlayerController::Reset()
{
K2_OnReset();
}
That takes care of the first two functions, but the third one isn’t virtual. For that one, we can override the GameModeBase::ResetLevel() function, keep most of the code the same, but remove the client reset call.
The client reset would need to be replaced with a different function if the game were networked, but that’s out of scope for now.
ResettableGameMode.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameMode.h"
#include "ResettableGameMode.generated.h"
/**
*
*/
UCLASS()
class BUMPERCROP_API AResettableGameMode : public AGameMode
{
GENERATED_BODY()
public:
virtual void ResetLevel() override;
};
ResettableGameMode.cpp
#include "ResettableGameMode.h"
#include "EngineUtils.h"
#include "Engine/LevelScriptActor.h"
void AResettableGameMode::ResetLevel()
{
UE_LOG(LogGameMode, Verbose, TEXT("Reset %s"), *GetName());
// Reset ALL controllers first
for (FConstControllerIterator Iterator = GetWorld()->GetControllerIterator(); Iterator; ++Iterator)
{
AController* Controller = Iterator->Get();
Controller->Reset();
}
// Reset all actors (except controllers, the GameMode, and any other actors specified by ShouldReset())
for (FActorIterator It(GetWorld()); It; ++It)
{
AActor* A = *It;
if (IsValid(A) && A != this && !A->IsA<AController>() && ShouldReset(A))
{
A->Reset();
}
}
// Reset the GameMode
Reset();
// Notify the level script that the level has been reset
ALevelScriptActor* LevelScript = GetWorld()->GetLevelScriptActor();
if (LevelScript)
{
LevelScript->LevelReset();
}
}
Then I just switched the parent classes of my custom Blueprint pawn, player controller, and game mode to these new versions.
This is perhaps a bit overkill - another option is just creating a new type of Reset that didn’t have these built-in behaviors might be easier, but at least it was good getting practice digging into the engine.