Creating Local Player for my single/localcoop game.

Hello. I’ve been having a bit of a problem with Creating Local Player (coop partner) inside my GameMode.
For now the game is going to have Local Coop so I’m using Standalone netcode.

As I understand, when using GameInstance->CreateLocalPlayer we automatically create a default pawn and additional controller for it. I kind of wanted to create a pawn which is not a default pawn but a blueprint version of a protagonist character I’ve made using a built in function but I can’t find it.

I made a hack which I think is… really dirty and problematic in a long run.

Here is the main function for spawning companion character.

void ALocalCoopGameMode::SpawnCompanionCharacter(APlayerController* MainPlayerController)
{
	UGameInstance* GameInstance = GetGameInstance();
	if (!GameInstance) return;

	FString Error;
	ULocalPlayer* NewPlayer = GameInstance->CreateLocalPlayer(1, Error, true);
	if (NewPlayer && NewPlayer->PlayerController)
	{
		APFPlayerController* P2Controller = Cast<APFPlayerController>(NewPlayer->PlayerController);
		if (!P2Controller) return;

		// Check the player type before assigning companion
		EPlayableCharacter MainCharacterType = GetInitialCharacterType(MainPlayerController);
		EPlayableCharacter CompanionType = GetOppositeCharacterType(MainCharacterType); 

		if (APFPlayerStart* CompanionStart = FindPlayerStartByCharacterType(CompanionType))
		{
			if (TSubclassOf<APFProtagonistBase> CompanionClass = GetPawnClassForCharacterType(CompanionType))
			{
				// Spawn the companion 
				APFProtagonistBase* CompanionPawn = GetWorld()->SpawnActor<APFProtagonistBase>(
					CompanionClass,
					CompanionStart->GetActorLocation(),
					CompanionStart->GetActorRotation()
				);
				if (CompanionPawn)
				{
					P2Controller->Possess(CompanionPawn);
					UE_LOG(LogTemp, Warning, TEXT("Spawned %s for Player 2 (Controller ID: %d)"), 
						*UEnum::GetValueAsString(CompanionType), 
						NewPlayer->GetControllerId());
				}
			}
		}
	}
}

Then we have first part of the hack, which checks if the ControllerId is 1 (as a second player) and if it is, then the function returns nullptr, which makes the pawn not spawn.


UClass* ALocalCoopGameMode::GetDefaultPawnClassForController_Implementation(AController* InController)
{
	APlayerController* PC = Cast<APlayerController>(InController);
	if (PC)
	{
		ULocalPlayer* LocalPlayer = Cast<ULocalPlayer>(PC->Player);
	
		if (LocalPlayer && LocalPlayer->GetControllerId() == 1)
		{
			return nullptr; 
		}
	}
	return Super::GetDefaultPawnClassForController_Implementation(InController);
}

A really ugly solution, since I’m leaving the PlayerController alone for a bit and it goes for BeginPlay without a pawn of it’s own. Also, if I ever want to implement more coop companions, then yeah, it won’t work properly.

And here we have a second hack, which I would never recommend to anyone since it opens a can of worms. I don’t remember writing anything worse in a lifetime lol.

void APFPlayerController::BeginPlay()
{
	Super::BeginPlay();
	if (!GetPawn())
	{
		GetWorld()->GetTimerManager().SetTimerForNextTick(this, &APFPlayerController::CheckPawnAndInit);
	}
	else
	{
		OnPawnReady();
	}
	
}
void APFPlayerController::CheckPawnAndInit()
{
	if (GetPawn())
	{
		OnPawnReady();
	}
	else
	{
		GetWorld()->GetTimerManager().SetTimerForNextTick(this, &APFPlayerController::CheckPawnAndInit);
	}
}

void APFPlayerController::OnPawnReady()
{
	bShowMouseCursor = true;
	SetInputMode(FInputModeGameAndUI());
	PrepareEnhancedInput();
	TArray<AActor*> FoundActors;
	UGameplayStatics::GetAllActorsOfClass(GetWorld(), APFProtagonistBase::StaticClass(), FoundActors);

	for (AActor* Actor : FoundActors)
	{
		APFProtagonistBase* Protagonist = Cast<APFProtagonistBase>(Actor);
		if (Protagonist)
		{
			// Check the EPlayableCharacter enum to determine which is which
			if (Protagonist->GetCharacterType() == EPlayableCharacter::PC)
			{
				PCCharacter = Protagonist;
			}
			else if (Protagonist->GetCharacterType() == EPlayableCharacter::Mima)
			{
				MimaCharacter = Protagonist;
			}
		}
	}
}

This basically delays BeginPlay logic and sets a TickTimer that goes until we get our pawn ready.
So it waits for SpawnCompanion to do this line of code:

P2Controller->Possess(CompanionPawn);

I would really appreciate if someone could explain to me how to do it properly. I mean, I could probably use delegate instead of a timer but it’s not really a solution, right?