If Epic can share their code with everyone, I can do the same as well.
I handle it by overriding three different methods in my Game Mode class.
Here’s the header for for my Game Mode class:
EliminationGameMode.h
#pragma once
#include "GameFramework/GameMode.h"
#include "EliminationGameMode.generated.h"
/**
*
*/
UCLASS()
class AEliminationGameMode : public AGameMode
{
GENERATED_UCLASS_BODY()
virtual void BeginPlay() OVERRIDE;
virtual APlayerController* Login(const FString& portal, const FString& options, const TSharedPtr<class FUniqueNetId>& uniqueId, FString& errorMessage) OVERRIDE;
virtual void PostLogin(APlayerController* newPlayer) OVERRIDE;
virtual APlayerController* SpawnPlayerController(FVector const& SpawnLocation, FRotator const& SpawnRotation) OVERRIDE;
virtual void GenericPlayerInitialization(AController* C) OVERRIDE;
// Selects the best spawn point for the player.
virtual AActor* ChoosePlayerStart(AController* player) OVERRIDE;
virtual void RestartPlayer(AController* newPlayer) OVERRIDE;
// Initialize the GameState actor.
virtual void InitGameState() OVERRIDE;
virtual UClass* GetDefaultPawnClassForController(AController* inController) OVERRIDE;
protected:
// Check if player can use spawnpoint.
virtual bool IsSpawnpointAllowed(APlayerStart* SpawnPoint, AController* Player) const;
// Check if player should use spawnpoint.
virtual bool IsSpawnpointPreferred(APlayerStart* SpawnPoint, AController* Player) const;
APlayerController* SpawnPlayerController(FVector const& SpawnLocation, FRotator const& SpawnRotation, TSubclassOf<APlayerController> pcClass);
TSubclassOf<class APawn> MarinePawnClass;
TSubclassOf<class APawn> BaseSpectatorPawnClass;
};
In my constructor, I grab the blueprints for the two different pawns that will be spawned.
Constructor:
AEliminationGameMode::AEliminationGameMode(const class FPostConstructInitializeProperties& PCIP)
: Super(PCIP)
{
//Set the default pawn to a blueprinted pawn.
static ConstructorHelpers::FObjectFinder<UBlueprint> playerPawnObject(TEXT("Blueprint'/Game/Blueprints/Pawns/BP_MarineCharacter.BP_MarineCharacter'"));
if (playerPawnObject.Object != NULL)
{
MarinePawnClass = (UClass*)playerPawnObject.Object->GeneratedClass;
}
//Get the blueprint base spectator pawn.
static ConstructorHelpers::FObjectFinder<UBlueprint> spectatorPawn(TEXT("Blueprint'/Game/Blueprints/BugPlayer/BP_BaseSpectatorPawn.BP_BaseSpectatorPawn'"));
if (spectatorPawn.Object != NULL)
{
BaseSpectatorPawnClass = (UClass*)spectatorPawn.Object->GeneratedClass;
}
GameStateClass = ABaseGameState::StaticClass();
SpectatorClass = ABaseSpectatorPawn::StaticClass();
}
I override the Login method so I can call my custom SpawnPlayerController. My implementation is the same as the base class except for one line (See red text below).
Login:
APlayerController* AEliminationGameMode::Login(const FString& portal, const FString& options, const TSharedPtr<class FUniqueNetId>& uniqueId, FString& errorMessage)
{
ABaseGameState* const gameState = GetGameState<ABaseGameState>();
TSubclassOf<APlayerController> newPlayerControllerClass;
errorMessage = GameSession->ApproveLogin(options);
if (!errorMessage.IsEmpty())
{
return NULL;
}
if (gameState == NULL)
{
return NULL;
}
// Spawn the Bug Player first.
// TODO: Find out why the Marine Player can't spawn first. Bug player doesn't spawn correctly when the Marine Player spawns first.
if (gameState->BugPlayer == NULL)
{
newPlayerControllerClass = ABugPlayerController::StaticClass();
}
else
{
newPlayerControllerClass = AMarinePlayerController::StaticClass();
}
APlayerController* newPlayer = SpawnPlayerController(FVector::ZeroVector, FRotator::ZeroRotator, newPlayerControllerClass);
// Handle spawn failure.
if (newPlayer == NULL)
{
//UE_LOG(LogGameMode, Log, TEXT("Couldn't spawn player controller of class %s"), PlayerControllerClass ? *PlayerControllerClass->GetName() : TEXT("NULL"));
errorMessage = FString::Printf(TEXT("Failed to spawn player controller"));
return NULL;
}
// Customize incoming player based on URL options
InitNewPlayer(newPlayer, uniqueId, options);
// Find a start spot.
AActor* const StartSpot = FindPlayerStart(newPlayer, portal);
if (StartSpot == NULL)
{
errorMessage = FString::Printf(TEXT("Failed to find PlayerStart"));
return NULL;
}
FRotator InitialControllerRot = StartSpot->GetActorRotation();
InitialControllerRot.Roll = 0.f;
newPlayer->SetInitialLocationAndRotation(StartSpot->GetActorLocation(), InitialControllerRot);
newPlayer->StartSpot = StartSpot;
// Register the player with the session
GameSession->RegisterPlayer(newPlayer, uniqueId, HasOption(options, TEXT("bIsFromInvite")));
// Init player's name
FString InName = ParseOption(options, TEXT("Name")).Left(20);
if (InName.IsEmpty())
{
InName = FString::Printf(TEXT("%s%i"), *DefaultPlayerName, newPlayer->PlayerState->PlayerId);
}
ChangeName(newPlayer, InName, false);
// Set up spectating
bool bSpectator = FCString::Stricmp(*ParseOption(options, TEXT("SpectatorOnly")), TEXT("1")) == 0;
if (bSpectator || MustSpectate(newPlayer))
{
newPlayer->StartSpectatingOnly();
return newPlayer;
}
if (bDelayedStart)
{
newPlayer->bPlayerIsWaiting = true;
newPlayer->ChangeState(NAME_Spectating);
return newPlayer;
}
return newPlayer;
}
Here is my custom SpawnPlayerController.
SpawnPlayerController:
APlayerController* AEliminationGameMode::SpawnPlayerController(FVector const& spawnLocation, FRotator const& spawnRotation, TSubclassOf<APlayerController> pcClass)
{
APlayerController* pc;
FActorSpawnParameters spawnInfo;
ABaseGameState* const gameState = GetGameState<ABaseGameState>();
spawnInfo.Instigator = Instigator;
pc = GetWorld()->SpawnActor<APlayerController>(pcClass, spawnLocation, spawnRotation, spawnInfo);
ABasePlayerController* castedPC = Cast<ABasePlayerController>(pc);
if (castedPC->Team == Constants::MARINES)
{
gameState->AddMarinePlayer(castedPC);
}
else
{
gameState->BugPlayer = castedPC;
}
return pc;
}
Lastly, I override GetDefaultPawnClassForController so I can spawn the correct pawn for each controller.
GetDefaultPawnClassForController:
UClass* AEliminationGameMode::GetDefaultPawnClassForController(AController* inController)
{
UClass* result;
// See what type of controller was passed in.
ABasePlayerController* baseController = Cast<ABasePlayerController>(inController);
if (baseController == Constants::MARINES)
{
result = MarinePawnClass;
}
else
{
result = BaseSpectatorPawnClass;
}
return result;
}
I know I have some cleanup/optimization that can be done with this code. I’m sure it will change as I add more stuff to my game. I am always looking for ways to become a better programmer so if anyone has any ideas/suggestions on how I could do this better, please let me know. Hopefully, this can help you get started with what you want to do. Let me know if you have any other questions.