Differen GameModes / PlayerContollers?

Hey Guys,

I am pretty new to the Unreal Engine and just got started with my first project. My game is a OneVSOne Multiplayer Game.
One Player has a Diablo 3 like perspektive and can do the usual Diablo-style stuff like walking, attacking…
The other player plays the environment. He has a RTS-Top Down perspective and controls all the monsters on the map ( like a RTS game).
Its basically two different game types playing together. What would be the best way to seperate the different game logics?
From my understanding I can only have one game type if they both wanna play together. So I would need two different playerControllers ( one for the RTS Game Player and one for the RPG Game player)
Any thoughts, suggestions?

Cheers Choba

I am doing something similar for a project I am working on. I use two different player controller types for the two different player types. My game mode class is where I put the logic to decide which player controller type to spawn. I had a setup like this in the UDK and it worked out well. I’ve be porting my code over to UE4 and so far everything seems to be working out. I can provide more details/info if you need some more direction.

Hi,

thanks for the answer. I was thinking in the same direction. I know this is probably asking a lot, but is there a chance to have a look at your code?

Cheers Choba

If Epic can share their code with everyone, I can do the same as well. :slight_smile:

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.