How to get ReplaySpectatorPlayerController in C++ Level::BeginPlay on replay?

When performing a PlayReplay, my custom MySpectatorPlayerController that is configured in my GameMode “Replay Spectator Player Controller Class” is not returned in my custom C++ AMyCommonLevelActor::BeginPlay(derived from ALevelScript). It appears to return a regular PlayerController instance that is created via the ULocalPlayer PendingLevelPlayerControllerClass. If I delay the BeginPlay in the MyCommonLevelScript::BeginPlay, the correct MySpectatorPlayerController instance is returned. It seems to me that the PlayReplay logic should somehow first create the correct “Replay Spectator Player Controller Class” instance and then invoke BeginPlay. Is this a known issue or bug? This is for a Stand Alone single player game that uses the Replay System.

// This is the BeginPlay version that returns a regular PlayerController,    
// with no HUD attach. You can examine the PlayerController type in the debugger.
void AMyCommonLevelActor::BeginPlay()
{
	UWorld* World = GetWorld();
	GameInstance = Cast<UMyGameInstance>(GetGameInstance());
	PlayerController = UGameplayStatics::GetPlayerController(World, 0);
	AHUD* HUD = PlayerController->GetHUD();
	GameHUD = Cast<AMyHUD>(HUD);
	if (GameHUD) 
	{
	   GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Cyan, FString::Printf(TEXT("Good HUD Attached")));
	}
	else
	{
	   GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Cyan, FString::Printf(TEXT("No HUD Attached")));
	}
}

// Here is the delayed creation.
// It works but is kludgy since you really don't know how long to make the timer
void AMyCommonLevelActor::BeginPlay()
{
	FTimerHandle UnusedHandle;
	GetWorldTimerManager().SetTimer(
		UnusedHandle, this, &AMyCommonLevelActor::DelayedBeginPlay, 1.0, false);
}

void AMyCommonLevelActor::DelayedBeginPlay()
{
	UWorld* World = GetWorld();
	GameInstance = Cast<UMyGameInstance>(GetGameInstance());
	PlayerController = UGameplayStatics::GetPlayerController(World, 0);
	AHUD* HUD = PlayerController->GetHUD();
	GameHUD = Cast<AMyHUD>(HUD);
	if (GameHUD) 
	{
	   GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Cyan, FString::Printf(TEXT("Good HUD Attached")));
	}
	else
	{
	   GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Cyan, FString::Printf(TEXT("No HUD Attached")));
	}
}

Okay, I figured this one out. When playing a replay, you cannot depend on the GameMode “Replay Spectator Player Controller Class” instance to be in place when the AMyCommonLevelActor::BeginPlay is called. Therefore it is a bad idea to require access to that specific MySpectatorPlayerController or its HUD inside that BeginPlay(at least for C++).

A better approach is to create the HUD and widget components inside the MySpectatorPlayerController::BeginPlay since you are assured that it is the correct player controller type and that the attached HUD is in place since the MySpectatorPlayerController::BeginPlay creates it with the ClientSetHUD function.

Hope this helps someone trying to use the replay system via C++.

1 Like