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?