Download

How do you place APlayerCharacter onto APlayerStart and take control?

I am trying to make a local co-op game… My goal here is to have more than 1 player using different controllers… or different keys on the keyboard.

I have the following Load Players function:


void AGeneralGameMode::LoadPlayers()
{
	// There is no point in running this loop if MaxPlayers is not set
	if (GetMaxPlayerCount())
	{
		for (int32 a = 1; a <= GetMaxPlayerCount(); a++)
		{
			UWorld* currentWorld = GetWorld();
			ABasePlayer* newPlayer = ABasePlayer::Instantiate(currentWorld);

			FString actorName = FString::Printf(TEXT("Player%dStart"), a);

			APlayerStart* playerStart = ActorHelper::FindActorsByTypeAndName<APlayerStart>(currentWorld, actorName);
			if (playerStart != NULL)
			{
				SetupPlayerModel* setupPlayerModel = new SetupPlayerModel();
				setupPlayerModel->PlayerStart = playerStart;
				setupPlayerModel->PlayerNumber = a;

				newPlayer->Setup(setupPlayerModel);
				ABasePlayerController* newController = currentWorld->SpawnActor<ABasePlayerController>(ABasePlayerController::StaticClass());
				if (newController)
				{
					currentWorld->AddController(newController);
					newController->Possess(newPlayer); 
					newController->GetPawn()->Restart();
				}

				Players.Add(newPlayer);
			}
			else
			{
				throw FString::Printf(TEXT("You are searching for a Player%dStart but it could not be found"), a);
			}
		}
	}
	else
	{
		throw "MaxPlayers was not set";
	}
}

  • I first get the world
  • Then i create a new ABasePlayer (which is APlayerCharacter)
  • Then i go find the APlayerStart actors in my map
  • Then i place the player start into a SetupPlayerModel
  • Then i create a new ABasePlayerController which is APlayerController
  • Finally I AddController to the current world, Possess the player and restart the player’s pawn.

Here is my ABasePlayer:


void ABasePlayer::Setup(SetupPlayerModel* setupPlayerModel)
{
	this->setupPlayerModel = setupPlayerModel;
	// Set up player starting position

	FVector location = setupPlayerModel->PlayerStart->GetActorLocation();
	SetActorLocation(location);

	if (GEngine)
	{
		//Debug line shows up
		GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, FString::SanitizeFloat(GetActorRotation().Yaw));
	}
}

void ABasePlayer::Init()
{
	Score = new BaseScore();
}

bool ABasePlayer::IsActive()
{
	return Active;
}

void ABasePlayer::SetAsActive()
{
	Active = true;
}

void ABasePlayer::Deactivate()
{
	Active = false;
}

BaseScore& ABasePlayer::GetScore()
{
	return *Score;
}

void ABasePlayer::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
	// set up gameplay key bindings
	InputComponent->BindAxis(*FString::Printf(TEXT("Player%dMoveRight"), setupPlayerModel->PlayerNumber), this, &ABasePlayer::MoveRight);
}

It inherits from the default APlayerCharacter you get when you first create a project… If you must see it, it looks like this: http://pastebin.com/3rWFgg5A

Finally, my input mappings look like this:


[/Script/Engine.InputSettings]
DefaultTouchInterface=/Engine/MobileResources/HUD/LeftVirtualJoystickOnly.LeftVirtualJoystickOnly

+ActionMappings=(ActionName="Jump", Key=W)
+ActionMappings=(ActionName="Jump", Key=Up)
+ActionMappings=(ActionName="Jump", Key=SpaceBar)
+ActionMappings=(ActionName="Jump", Key=Gamepad_FaceButton_Bottom)

+AxisMappings=(AxisName="MoveRight", Key=A, Scale=-1.f)
+AxisMappings=(AxisName="MoveRight", Key=D, Scale=1.f)
+AxisMappings=(AxisName="MoveRight", Key=Gamepad_LeftX, Scale=1.f)


+AxisMappings=(AxisName="Player1MoveRight", Key=A, Scale=-1.f)
+AxisMappings=(AxisName="Player1MoveRight", Key=D, Scale=1.f)
+AxisMappings=(AxisName="Player1MoveRight", Key=Gamepad_LeftX, Scale=1.f)

+AxisMappings=(AxisName="Player2MoveRight", Key=J, Scale=-1.f)
+AxisMappings=(AxisName="Player2MoveRight", Key=L, Scale=1.f)
+AxisMappings=(AxisName="Player2MoveRight", Key=Gamepad_LeftX, Scale=1.f)

I cannot understand why my players do not react to the keyboard…? Any ideas?

P.S GetMaxPlayerCount() returns 2.

Have you tried the standard method by just changing the DefaultClasses in your GameMode?


PlayerControllerClass= ABasePlayerController::StaticClass();
DefaultPawnClass = ABasePlayer::StaticClass();

The default GameMode implementation should spawn a BasePlayer for your PlayerController and possess it automatically.

If you need more control over what Pawn is spawned for what Controller, I still would go with PlayerControllerClass and then override the AGameMode::GetDefaultPawnClassForController()

Sorry DennyR, my fault, I should have explained i’m trying to make a local co-op (I’ve added this to the OP)… So i need to spawn more than 1 controller… Can you re-work your comment/solution accordingly? Thanks for your time though!

No problem. I actually thought you meant something more difficult based on your post, but I went with the simple anfirst, since I did not know what you were trying to achieve.

Anyways, with this new information, I can help a bit better :stuck_out_tongue:

So basically you should start at UGameViewportClient::CreatePlayer(). This function just adds a new local player and routes it through the normal GameMode setup stuff. Accessable via GetWorld()->GetGameViewport(). Also accessable via Blueprint, just type CreatePlayer.

The possible downside is, that it gives you splitscreen by default. If you want a single viewport and a shared camera(like Gangbeasts or Trine), you might have extra work to do.

*Edit: GetWorld()->GetGameViewport()->SetDisableSplitscreenOverride(true) disables splitscreen. That shoud hopefully solve all your problems for now :stuck_out_tongue:

There’s a lot of different ways to initialize players, it’s rather confusing. There’s Denny’s method through the viewport. As I mentioned in your other thread, you can also go through GameInstance: UGameInstance::CreateLocalPlayer.

Actually, I just looked it up and it seems like the viewport method just calls GameInstance’s CreateLocalPlayer. Just use whichever is most easily accessible. :slight_smile:

Is there a way to cast the ULocalPlayer to my own type? I have APlayerCharacter (or in this case ABasePlayerCharacter) with all the stuff required for a Player… but cannot find a way to cast the ULocalPlayer.

I have tried the following:


FString error;
ULocalPlayer* newLocalPlayer = gameInstance->CreateLocalPlayer(a, error, true);			
ABasePlayer* castPlayer = Cast<ABasePlayer>(newLocalPlayer->StaticClass());

but the cast returns an empty castPlayer


This feels like a wild goose chase… for 7 evenings now, I try to create 2 players on the screen that are controlled by specific keys and separate gamepads… I think I could have made my own game by now… I can’t help but blame the documentation :frowning: It’s really hard to find example code for things you want to do with C++… All this blue print **** is useless to me.

Really appreciate peoples help. Thanks.

Imagine there was a step by step guide to placing a player into the game and binding some keyboard keys to him… what would that step by step look like…

I have already tried:

  • Get the world

UWorld* currentWorld = GetWorld();

  • Get the game instance

UGameInstance* gameInstance = GetGameInstance();

  • Use game instance to create “local player” which i assume means, a human being who will sit infront of the computer


FString error;
ULocalPlayer* newLocalPlayer = gameInstance->CreateLocalPlayer(1, error, true);	

  • Create a controller

ABasePlayerController* newController = currentWorld->SpawnActor<ABasePlayerController>(ABasePlayerController::StaticClass());

  • Set up a APlayerCharacter (ABasePlayer in this case):

ABasePlayer* newPlayer = (ABasePlayer*)GetDefaultPawnClassForController(newController);

  • Posess the newPlayer:

newController->Possess(newPlayer);

  • … which in turn runs the ABasePlayer SetupInputcomponent function:

void ABasePlayer::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
	// set up gameplay key bindings
	const FString axisName = FString::Printf(TEXT("Player%dMoveRight"), setupPlayerModel->PlayerNumber);
	InputComponent->BindAxis(FName(*axisName), this, &ABasePlayer::MoveRight);
}

  • Create Input configurations for the new BindAxis name:

+AxisMappings=(AxisName="Player1MoveRight", Key=A, Scale=-1.f)
+AxisMappings=(AxisName="Player1MoveRight", Key=D, Scale=1.f)
+AxisMappings=(AxisName="Player1MoveRight", Key=Gamepad_LeftX, Scale=1.f)


What would the correct step by step guide look like… the above doesn’t do a gawd darn thing!

Your casts are failing because ULocalPlayer and APlayerCharacter are unrelated types. Try to think of LocalPlayer as a description of the actual, physical human player (which controller index am I using? which part of the screen am I playing in?), where PlayerCharacter is the in-game character representation of that player (what mesh am I using? where am I in the world?).

Don’t dismiss blueprint documentation out of hand – literally every single Blueprint function stems from a C++ function. If a blueprint tutorial does something you want to do, just search the codebase for the C++ version of that blueprint and use it directly.

Figuring out how to obtain a certain object from somewhere else in the code is always a challenge. But the vast majority of it goes through UWorld, which is why pretty much all UObjects have to implement a way to get their world by overriding GetWorld. In your case, given a newly created LocalPlayer, you should be able to get the player controller through world using GetPlayerControllerIterator(). The position of each controller in the iterator will match the ControllerId from your new LocalPlayer. Look for an example in UGameplayStatics::GetPlayerController (or hell, just use that outright).

I just can’t get it to work… Do you actually know how to do it? If so, could you give me some code?

I have tried now this:

pastebin: http://pastebin.com/PmqDsXqh


void AGeneralGameMode::LoadPlayers()
{
	// There is no point in running this loop if MaxPlayers is not set
	if (GetMaxPlayerCount())
	{
		UWorld* currentWorld = GetWorld();

		for (int32 a = 1; a <= GetMaxPlayerCount(); a++)
		{
			// Create a PlayerCharacter
			ABasePlayer* newPawn = ABasePlayer::Instantiate(currentWorld);

			// Find our player starting position
			FString actorName = FString::Printf(TEXT("Player%dStart"), a);
			APlayerStart* playerStart = ActorHelper::FindActorsByTypeAndName<APlayerStart>(currentWorld, actorName);

			// Go and create local player
			UGameInstance* gameInstance = GetGameInstance();
			currentWorld->GetGameViewport()->SetDisableSplitscreenOverride(true);
			FString error;
			ULocalPlayer* newLocalPlayer = gameInstance->CreateLocalPlayer(a, error, true);		

			// If we found a player starting position
			if (playerStart != NULL)
			{
				// Pass player starting position into PlayerCharacter and move to this position
				SetupPlayerModel* setupPlayerModel = new SetupPlayerModel();
				setupPlayerModel->PlayerStart = playerStart;
				setupPlayerModel->PlayerNumber = a;
				newPawn->Setup(setupPlayerModel);

				// Go and get the PlayerController that was created by CreateLocalPlayer
				APlayerController* playerController = UGameplayStatics::GetPlayerController(currentWorld, a);

				// Set the player to our Local Player
				playerController->SetPlayer(newLocalPlayer);
				// Take over the PlayerCharacter we instantiated.
				playerController->Possess(newPawn);

				// Place our PlayerCharacter into an array for future handle
				Players.Add(newPawn);
			}
			else
			{
				throw FString::Printf(TEXT("You are searching for a Player%dStart but it could not be found"), a);
			}
		}
	}
	else
	{
		throw "MaxPlayers was not set";
	}
}

Now my code spawns 2 sprites of the default man, but i have no control over them and my camera is just floating around in space:

sadasdas.png

I’ve also tried enabling input, this doesn’t work:


newPawn->EnableInput(playerController);

I’ve also tried setting the DefaultPawnClass:


DefaultPawnClass = Players.GetData()[0]->StaticClass();

I’m having a hard time following your code – it’s not following Epic’s naming convention, let alone using the engine’s functions. (Specifically: Player has a very different meaning from Pawn, so mixing the naming for the two is confusing). You also seem to be redoing a lot of initialization that the engine already does for you.

CreateLocalPlayer already creates a player controller and calls SetPlayer() for you, among other things. The default behaviour with bSpawnActor = true is also to create and possess a pawn for you, so you don’t need that ABasePlayer::Instantiate. I’m guessing that whatever actors you’ve instantiated that way aren’t actually placed in the world, and what’s happening is you’re getting and possessing a default pawn with first person camera, thanks to the built-in behaviour.

You have two different options here – set bSpawnActor to false in CreateLocalPlayer, and handle all creation and possession manually. Or set DefaultPawnClass and let baseline spawning handle that logic for you. I’d start with the latter, but you’ll need to set DefaultPawnClass in a different way because you have a chicken and egg situation going – your Players array is only initialized after the the pawn is already spawned, and therefore DefaultPawnClass can’t be used to spawn the proper pawn.

That’s pretty easy, all UObjects have a StaticClass function you can call statically to get their UClass. So in your GameMode, you would set the static class for your pawn object in the constructor:



DefaultPawnClass = ABasePlayer::StaticClass(); // Or whatever derived pawn class you might want to use instead


Then comment out this for now just to see how it goes:


				// Go and get the PlayerController that was created by CreateLocalPlayer
				APlayerController* playerController = UGameplayStatics::GetPlayerController(currentWorld, a);

				// Set the player to our Local Player
				playerController->SetPlayer(newLocalPlayer);
				// Take over the PlayerCharacter we instantiated.
				playerController->Possess(newPawn);

Thanks for your help!

Can anyone else offer me some help?

I’ve tried the above by setting DefaultPawnClass (even though each pawn may be of a different origin ABasePlayer) and then set true in the CreateLocalPlayer so that everything is spawned for me, but the ABasePlayer SetupPlayerInputComponent is never called…

I’ve now tried something like this:


void AGeneralGameMode::LoadPlayers()
{
	if (GetMaxPlayerCount())
	{
		UWorld* currentWorld = GetWorld();
		currentWorld->GetGameViewport()->SetDisableSplitscreenOverride(true);

		UGameInstance* gameInstance = currentWorld->GetGameInstance();

		for (int32 a = 1; a <= GetMaxPlayerCount(); a++)
		{
			FString error;
			ULocalPlayer* localPlayer = gameInstance->CreateLocalPlayer(a, error, true);

			Cast<ABasePlayerController>(localPlayer->PlayerController);
			Cast<ABasePlayer>(localPlayer->PlayerController->GetPawn());
		}
	}
}

and with the GameViewport too:


void AGeneralGameMode::LoadPlayers()
{
	if (GetMaxPlayerCount())
	{
		UWorld* currentWorld = GetWorld();
		currentWorld->GetGameViewport()->SetDisableSplitscreenOverride(true);

		UGameViewportClient* gameViewport = currentWorld->GetGameViewport();

		for (int32 a = 1; a <= GetMaxPlayerCount(); a++)
		{
			FString error;
			ULocalPlayer* localPlayer = gameViewport->CreatePlayer(a, error, true);

			Cast<ABasePlayerController>(localPlayer->PlayerController);
			Cast<ABasePlayer>(localPlayer->PlayerController->GetPawn());
		}
	}
}

Still absolutely no luck…
I now do not have any actors in the scene either as i guess the cast is failing to do anything useful.

Thanks to the cmartel I now know that CreateLocalPlayer creates a controller and pawn for me, but it seems i have no control over what types they come from? I have lots of code in my ABasePlayer and ABasePlayerController and want to use it… dohhhhh

I’ve re-worked this question here: http://gamedev.stackexchange.com/questions/92593/setting-up-local-players-in-unreal-engine

Is my understanding of how this is working correct?

The Input Component bit in my code is actually inside my ABasePlayer


void ABasePlayer::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
	// set up gameplay key bindings
	InputComponent->BindAxis(*FString::Printf(TEXT("Player%dMoveRight"), setupPlayerModel->PlayerNumber), this, &ABasePlayer::MoveRight);
}

d11cb16effb6397cbc3477a69dd1c9ae1bf1ee10.jpeg

GameMode has class settings for both default pawn and player controller classes. You should be able to set your player controller in the same fashion as the pawn:


PlayerControllerClass = ABasePlayerController::StaticClass();

As for input, I know the engine supports setting the input component on the pawn class and the ShooterGame sample even works that way, but it’s my opinion that this is counterintuitive at best. Input is something specific to human players, so I instead let the PlayerController handle input and I simply disabled creation of the pawn’s input component.

That being said, I’m not sure why SetupPlayerInputComponent isn’t being called. I’d venture you’re not calling a baseclass implementation (Super::Foo) somewhere in your game mode, but I might be wrong.

Also, I don’t know where you’re calling LoadPlayers(), but you should be aware that by default, the game spawns a single player pawn and that might be causing conflicts with the extra players you’re spawning.

thanks for all your help… I was wondering if you knew how to get the actor that was spawned by the CreateLocalPlayer function, can’t find documentation on it.

Feel free to rummage through the engine code when documentation fails. The login/session start case in particular is overly intricate so you have to get familiar with it. Follow function calls until you find what you’re looking for.

In CreateLocalPlayer’s case, the call chain you’re looking for is:

UGameInstance::CreateLocalPlayer
ULocalPlayer::SpawnPlayActor
UWorld::SpawnPlayActor
AGameMode::PostLogin
AGameMode::StartNewPlayer
AGameMode::RestartPlayer
AGameMode::GetDefaultPawnClassForController

And you can see that GetDefaultPawnClassForController returns AGameMode’s DefaultPawnClass. Also, along the way, you’ll see that UWorld::SpawnPlayActor also spawns the player controller, and UGameInstance::CreateLocalPlayer creates the ULocalPlayer object.

Oh man, thanks that’s really helpful dude! Any reason why when my UpdateAnimation function runs (which is from the un-changed code from the template character) the GetSprite() returns null ?

So funny and really massively annoying how i still cannot get this to work after…11 hours over 11 days. Is there really no boiler plate code that works that i can grab and get on with my game hehehe?

I now have:


AGeneralGameMode::AGeneralGameMode( const FObjectInitializer& ObjectInitializer )
	: Super( ObjectInitializer )
{
	// set default pawn class to our character
	DefaultPawnClass = ABasePlayer::StaticClass();
	PlayerControllerClass = ABasePlayerController::StaticClass();
}

void AGeneralGameMode::LoadPlayers()
{
	if( MaxPlayerCount )
	{
		UWorld* currentWorld = GetWorld();

		UGameInstance* gameInstance = currentWorld->GetGameInstance();
		UGameViewportClient* gameViewport = currentWorld->GetGameViewport();

		// Remove split screen because I want a 2D game
		gameViewport->SetDisableSplitscreenOverride( true );

		for( int32 a = 1; a <= MaxPlayerCount; a++ )
		{
			FString createLocalPlayerError;
			ULocalPlayer* localPlayer = gameInstance->CreateLocalPlayer( a, createLocalPlayerError, true );

                        // This is to allow me to run my SetUpInput() function that comes now from the ABasePlayerController
			ABasePlayer* playerActor = ( ABasePlayer* )GetDefaultPawnClassForController( localPlayer->PlayerController );
			ABasePlayerController* playerController = ( ABasePlayerController* )localPlayer->PlayerController;
                        playerController->SetUpInput( playerActor, a );
                }
       }
}

My game crashes when it gets to the UpdateAnimation function:


void ATemplatePlatformerCharacter::UpdateAnimation()
{
	const FVector PlayerVelocity = GetVelocity();
	const float PlayerSpeed = PlayerVelocity.Size();

	// Are we moving or standing still?
	UPaperFlipbook* DesiredAnimation = (PlayerSpeed > 0.0f) ? RunningAnimation : IdleAnimation;

	GetSprite()->SetFlipbook( DesiredAnimation ); // Crashes because GetSprite() returns NULL
}

ATemplatePlatformerCharacter is what my ABasePlayer currently inherits from… I’m using all the default stuff but adding bits on top of it (for now)

I’ve also tried this:


        FString createLocalPlayerError;
	ULocalPlayer* localPlayer = gameInstance->CreateLocalPlayer( a, createLocalPlayerError, true );
	
	FString url = ""; FString spawnActorError;
	localPlayer->SpawnPlayActor( url, spawnActorError, currentWorld );

	/*ENetRole role;
		currentWorld->SpawnPlayActor( localPlayer, role, url2 );*/

	PostLogin( localPlayer->PlayerController );
	StartNewPlayer( localPlayer->PlayerController );
	RestartPlayer( localPlayer->PlayerController );
	ABasePlayer* playerActor = ( ABasePlayer* )GetDefaultPawnClassForController( localPlayer->PlayerController );

	ABasePlayerController* playerController = Cast<ABasePlayerController>( localPlayer->PlayerController );
	playerController->SetUpInput( playerActor, a );

I’ve now got this:


               
                for( int32 a = 0; a < MaxPlayerCount; a++ )
		{
			ULocalPlayer* localPlayer;

			if( a == 0 )
			{
				localPlayer = gameInstance->GetFirstGamePlayer();
			}
			else
			{
				FString createLocalPlayerError;
				localPlayer = gameInstance->CreateLocalPlayer( a, createLocalPlayerError, true );
			}

			FString url;
			FString spawnPlayActorError;
			localPlayer->SpawnPlayActor( url, spawnPlayActorError, currentWorld );
			FURL urlSpawn2;
			FString spawnError;
			PostLogin( localPlayer->PlayerController );
			currentWorld->SpawnPlayActor( localPlayer, ENetRole::ROLE_None, urlSpawn2, localPlayer->GetUniqueNetIdFromCachedControllerId(), spawnError );
			StartNewPlayer( localPlayer->PlayerController );
			RestartPlayer( localPlayer->PlayerController );

			/*ABasePlayer* playerActor = ( ABasePlayer* )GetDefaultPawnClassForController( localPlayer->PlayerController );
			ABasePlayerController* playerController = ( ABasePlayerController* )localPlayer->PlayerController;
			playerController->SetUpInput( playerActor, a );*/
		}


The above spawns an actor onto the screen, wehey… My SetUpInput method if i enable it seems to crash on the GetSprite function on UpdateAnimation (Which is in no way called inside SetUpInput)



void ABasePlayerController::SetUpInput( ABasePlayer*& player, int playerNumber )
{
	// set up gameplay key bindings
	const FString axisName = FString::Printf( TEXT( "Player%dMoveRight" ), playerNumber );
	InputComponent->BindAxis( FName( *axisName ), player, &ABasePlayer::MoveRight );
}

this seems to return NULL:


ABasePlayer* playerActor = Cast<ABasePlayer>( GetDefaultPawnClassForController( localPlayer->PlayerController ) );

This is all working now, only one last problem, but too late to type up. Thanks for all your help!