You can delay pawn creation for player controllers on the GameMode until all initialization and validation is completed. I’ve done this type of thing in PreLogin and PostLogin method overloads (works for single or multiplayer). Once you’ve got the data you’ll need loaded, create the pawn in the game mode and fire an event to all player controllers to take control of their respective pawns. Then load the game UI (you can have a loading screen UI until this entire process is done).
Note: you’ll need to set the default pawn to None or Spectator in the game mode, and then create the pawn once you’ve got everything loaded. The Default pawn will be created otherwise.
Make sure your custom game mode does not have a default player pawn (or use a spectator pawn)
void YourGameMode::PostLogin(APlayerController* newPlayer)
{
checkf(newPlayer, TEXT("PostLogin() was given a null player controller ptr"));
Super::PostLogin(newPlayer);
const auto controller = CastChecked<AYourPlayerControllerClass>(newPlayer);
//custom function that automatically casts the player state to my custom player state class
const auto playerState = controller->GetPlayerStateCast();
if(!IsValid(playerState))
{
UE_LOG(LogTemp, Error, TEXT("PostLogin(): unable to get cast player state"));
return;
}
//Track players that haven't been validated
TArrayHoldingPendingValidations.Add(playerState);
//perform any validation for your custom data
Validate(playerState);
}
void YourGameMode::Validate(AYourPlayerState* state)
{
// do your validation. I use a backend service called over a TCP socket.
//When done, the socket data handler invokes HandlePlayerValidation() with a custom data payload
// you can probably combine this into one step if you do validation/setup all in unreal)
}
//call back method invoked by Validate()
void YourGameMode::HandlePlayerValidation(const FValidationResultStruct& data)
{
//Check for failure
if(!data.success)
{
UE_LOG(LogTemp, Warning, TEXT("Rejected player validation."));
//you'll have to implement kickplayer()
YourGameMode::KickPlayer(data);
return;
}
//Validation was successful
auto playerState = TArrayHoldingPendingValidations.Get(data.playerStatePtr);
if(!playerState)
{
UE_LOG(LogTemp, Warning, TEXT("Unable to get player state from tracking array"));
YourGameMode::KickPlayer(data);
return;
}
auto playerController = playerState->GetPlayerController();
FTransform pawnTransform = data.startingTransform;
//SpawnActor expects a UClass pointer and transform.
auto playerPawn = GetWorld()->SpawnActor<AYourPlayerPawn>(PropertyThatHoldsYourUClassPointer, pawnTransform);
checkf(playerPawn, TEXT("Unable to spawn player pawn during player validation."));
playerPawn->InitFromValidationData(data);
//This is where/when you spawn your player's pawn, replacing the spectator pawn if you're using one
playerController->Possess(playerPawn);
//Can send additional events etc if you need additional initialization.
OnPlayerValidation(playerController);
TArrayHoldingPendingValidations.Remove(data.playerStatePtr);
}
Might be some things to clean up, but this is the code flow how our current project does it (lots of unrelated stuff removed).