[4.7.6] Play in Editor ignore bStartPlayersAsSpectators

Hello,

While trying to setup a simple “select your team” UMG, I was having a hard time to do it. Then, after 2~3 hours of debugging, exploring how things works, I found out that Play in editor (called by the function StartPIEGameInstance()) ignores the flag bStartPlayersAsSpectators set in the GameMode constructor on the listen server (but allows it on connected clients).

There are more issues I had in the past with listen servers / play in editor, but I didn’t really investigate them further.

Is there a way to make play in editor (and I’m assuming listen servers too) to acknowledge and follow the bStartPlayersAsSpectators flag, or is that really a bug?

Thanks in advance!

This is probably related. The function:

void AGameMode::RestartPlayer(AController* NewPlayer)

Never checks for bIsSpectator prior attempting to spawn the player, forcing spectators to spawn.

Hey ,

It sounds as though the clients start as spectators as expected but the host does not, is that correct? Do you have the same issue in a new project? If so how is the GameMode constructor setup and passing the “Spectators” variable to StartPIEGameInstance()? What behavior do you see if you play using a standalone game rather than PIE?

Cheers

Hello ,

It sounds as though the clients start as spectators as expected but the host does not, is that correct?

Yes, that’s correct.

Do you have the same issue in a new project?

Yes

If so how is the GameMode constructor setup and passing the “Spectators” variable to StartPIEGameInstance()?

The constructor is the default one with a single line added:

AUE4JumpGameMode::AUE4JumpGameMode(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	// set default pawn class to our Blueprinted character
	static ConstructorHelpers::FClassFinder<APawn> PlayerPawnClassFinder(TEXT("/Game/FirstPerson/Blueprints/FirstPersonCharacter"));
	DefaultPawnClass = PlayerPawnClassFinder.Class;

	// use our custom HUD class
	HUDClass = AUE4JumpHUD::StaticClass();

	bStartPlayersAsSpectators = true;
}

(This constructor has the name AUE4JumpGameMode, but it’s a new FPS C++ template project with animations added from 3rd person C++ template. There are no other changes other than this - but this happens on brand new projects too).

There is no overload on the StartPIEGameInstance(), so it’s using default values. I have checked the code in the same engine file (4.7\Engine\Editor\UnrealEd\Private\Playlevel.cpp) and found this:

	FModifierKeysState KeysState = FSlateApplication::Get().GetModifierKeys();
	if (bInSimulateInEditor || KeysState.IsControlDown())
	{
		// if control is pressed, start in spectator mode
		bStartInSpectatorMode = true;
	}

What behavior do you see if you play using a standalone game rather than PIE?

By running:

UE4Jump.exe /Game/FirstPerson/Maps/FirstPersonExampleMap?listen 

I get an instant spawn of the player on the host.

Then, running

UE4Jump.exe 127.0.0.1

I get a second game window, connected to the first in spectate mode. The connection works (I can see the listen server moving around while in spectator mode). This is the reason why I used a slighly modified project to test this issue (added 3rd person models and animations to it).

I hope this helps! Please, contact me if you need any more info :slight_smile:

Update:

After re-reading my post (it’s annoying to type in the comments section after the text filled up the screen - the browsers gets all jumpy! :frowning: ), regarding this piece of code:

     FModifierKeysState KeysState = FSlateApplication::Get().GetModifierKeys();
     if (bInSimulateInEditor || KeysState.IsControlDown())
     {
         // if control is pressed, start in spectator mode
         bStartInSpectatorMode = true;
     }

Apparently the editor starts in spectator mode, but strangely enough, his character is spawned (feels just like it just needs to be possessed, but there’s no button for it), and the client spawns too (and it’s NOT on spectate mode!)
It seems I found a second bug while getting details for this ><

Thank you for your time and support! :slight_smile:

Hey -

I was able to reproduce the bug by creating up a new game mode and setting the “Start Players as Spectators” checkbox and has been reported as UE-15891.

Cheers

I know this post is a bit old, but I just want to mention my testing of this bug in 4.8.2 that the client isn’t in spectator mode, but merely doesn’t possess anything. (Not sure if that was the same case in previous versions)

I think this issue isn’t fixed yet. But I’d appreciate some help to try and fix this issue :slight_smile:

EDIT: I’m testing this solution in UE4 4.12.5, and I’m pretty sure the previous solution I wrote a year ago doesn’t work now. So I implemented a new solution. Pull request has been requested at https://github.com/EpicGames/UnrealEngine/pull/2679

in AGameMode::Login

-	if (bSpectator || MustSpectate(NewPlayerController))
+	if (bSpectator || MustSpectate(NewPlayerController) || bStartPlayersAsSpectators)

and in AGameMode::HandleSeamlessTravelPlayer

-		if (PC->PlayerState->bOnlySpectator)
+		if (PC->PlayerState->bOnlySpectator || bStartPlayersAsSpectators)

I’ve tested this using the blueprint 3rd person template, made sure it originally didn’t work, then applied the fix and made sure the fix worked. If anyone finds any weird side effects from this, would love to know.

------ OLD POST from a year ago kept for reference purposes -----

So I spent quite some time diving into the engine’s source code to see how the related parts work. I have a patch/fix that solves the problem as far as I can see at the moment. But I’m sure it’s not the proper fix due to the complexity of the code base.

With the bStartPlayersAsSpectator flag being on, it does set the PlayerController to spectate state, however whenever GameMode starts to handle match being started, it goes through AGameMode::HandleMatchHasStarted which performs this code

for (FConstPlayerControllerIterator Iterator = GetWorld()->GetPlayerControllerIterator(); Iterator; ++Iterator)
{
	APlayerController* PlayerController = *Iterator;
	if ((PlayerController->GetPawn() == NULL) && PlayerCanRestart(PlayerController))
	{
		RestartPlayer(PlayerController);
	}
}

The issue lies in the fact that because it is in the SpectateState, GetPawn() will always == to NULL. Thus it restarts the player. AND In RestartPlayer(), there is no checking of the SpectateState, this is probably because RestartPlayer() takes in an AController. And GetSpectatorPawn is a APlayerController thing. So it requires some casting to check if it’s an APlayerController and further more work.

A simple fix is to change the code to the following

for (FConstPlayerControllerIterator Iterator = GetWorld()->GetPlayerControllerIterator(); Iterator; ++Iterator)
{
	APlayerController* PlayerController = *Iterator;
	if ((PlayerController->GetPawnOrSpectator() == NULL) && PlayerCanRestart(PlayerController))
	{
		RestartPlayer(PlayerController);
	}
}

Because we know it’s only the APlayerController that needs to be restarted, we can use the GetPawnOrSpectator function.

However it is a simple fix because I haven’t checked where else uses RestartPlayer(), as that would still kick an APlayerController out of spectate state. There also isn’t a way to change a PlayerController to spectate mode via blueprints, so any solution seems to rely on C++. An extra point to mention is that AGameMode::StartNewPlayer does have checks for bStartPlayersAsSpectators. So a proper fix would probably involve modifying RestartPlayer(). I will probably look into it to see if I can implement a more complete reasonable fix.

@
Has issue UE-15891 been solved?

Thank you, sorry to revive such old thread.

Hey ,

This bug report is currently under assessment and unfortunately we do not have a time frame of when this will be resolved.

Man you’re genius! It seems like this bug was never fixed( still exists in 4.12 ).
Following your idea, I found a way to fix it without modifying the engine source:
1.) If you haven’t done it already, derive from the APlayerController, and use it as default
2.) override the CanRestartPlayer function
3.) return false if ( GetStateName() == NAME_Spectating ), otherwise, run the default function
And voila, the host starts in spectator mode!

EDIT: Not sure if that fix, actually puts the playerstate into bOnlySpectator, as I realised at least in 4.12.5 RestartPlayer doesn’t seem to be called regardless. It’s possible the resulting affect of honya15’s fix results in just no possessed pawn. Haven’t tried this fix myself personally.

Huh, nice interesting way of fixing it. Thanks for the share!

In case anyone was wondering(which I was) APlayerController::PostInitializeComponents sets StateName = NAME_Spectating;
StateName is then changed in AController::ChangeState (If it ever is prompted to be changed to playing state)

Thus the fix works by having bStartPlayersAsSpectators not triggering RestartPlayer in StartNewPlayer, and using the default StateName of AController to make sure the PlayerController isn’t restarted.

I kinda avoid all this, by just not using bStartPlayersAsSpectators and ended up setting my PlayerControllers to StartSpectatingOnly() before allowing the match to start if necessary. So they never get restarted because of the proper checks for PlayerState->bOnlySpectator

Hey there, in 4.14 GameMode has changed significantly, and the result of what happened here depends on if you inhert from the legacy GameMode or the new GameModeBase. For the legacy GameMode I have decided to keep it the same behavior as before, but in GameModeBase this will not occur.

The function that causes this to happen is AGameMode::HandleMatchHasStarted(), so if you want to override what this does you can override that function, or override PlayerCanRestart. If you switch to inheriting from GameModeBase you will have full control over things like match start/stop so can do whatever you want.