Download

Remove Local Player should alert the server, Network programmers how best should I do this?

Hi,

I’m creating a game that allows split screen to be played online. This is fine on servers, but causes issues on clients if split screen players leave, and another rejoins. I tracked the issue down to the following function. Note the FIXME comment left by an unreal programmer.


bool UGameInstance::RemoveLocalPlayer(ULocalPlayer* ExistingPlayer)
{
	// FIXME: Notify server we want to leave the game if this is an online game
	if (ExistingPlayer->PlayerController != NULL)
	{
		// FIXME: Do this all inside PlayerRemoved?
		ExistingPlayer->PlayerController->CleanupGameViewport();

		// Destroy the player's actors.
		if ( ExistingPlayer->PlayerController->Role == ROLE_Authority )
		{
			ExistingPlayer->PlayerController->Destroy();
		}
	}

	// Remove the player from the context list
	const int32 OldIndex = LocalPlayers.Find(ExistingPlayer);

	if (ensure(OldIndex != INDEX_NONE))
	{
		ExistingPlayer->PlayerRemoved();
		LocalPlayers.RemoveAt(OldIndex);

		// Notify the viewport so the viewport can do the fixups, resize, etc
		if (GetGameViewportClient() != NULL)
		{
			GetGameViewportClient()->NotifyPlayerRemoved(OldIndex, ExistingPlayer);
		}
	}

	// Disassociate this viewport client from the player.
	// Do this after notifications, as some of them require the ViewportClient.
	ExistingPlayer->ViewportClient = NULL;

	UE_LOG(LogPlayerManagement, Log, TEXT("UGameInstance::RemovePlayer: Removed player %s with ControllerId %i at index %i (%i remaining players)"), *ExistingPlayer->GetName(), ExistingPlayer->GetControllerId(), OldIndex, LocalPlayers.Num());

	return true;
}

I’ve spent the last few hours trying to fix this without success. I’m not knowledgeble enough about the network side of the engine to see how best to fix this. From what I can tell I need to delete and clean up the child connections associated with this local player. I have some questions about this.

  • Do these child connections exist on the server and the client?
  • Do I need to manually remove these connections on both if so?
  • How best should I tell the server to do this? Control channel message? Player controller RPC?
  • Is there anything else I need to do in order to clean up properly?
  • Am I even on the right path to fixing this?

Hi all,

I eventually fixed this by playing around in the networking code till I got what I wanted. I remember this post and decided to alleviate the pain of anyone else who tries this in the future.

Note, I edited the engine to make this work. I may clean up the code and submit a pull request if it would help people.

First I defined a function in PlayerController.h that would execute on the server and correctly disconnect the child connection of the split screen player:


/** When a client wants to be destroyed, it needs to ask the server to do kill itself. */
UFUNCTION(server, reliable, WithValidation)
void ServerDisconnect();

This is the implementation of the function, it actually disconnects the child:


bool APlayerController::ServerDisconnect_Validate()
{
	return true;
}

void APlayerController::ServerDisconnect_Implementation()
{
	UChildConnection* C = Cast<UChildConnection>(Player);
	if (C)
	{
		UNetConnection* parent = C->Parent;
		C->CleanUp();
		parent->Children.Remove(C);
	}
  }

Then lastly, where Epic left the “FIXME” note, we need to call the above function so it gets executed:

This is the RemoveLocalPlayer function in GameInstance.cpp



bool UGameInstance::RemoveLocalPlayer(ULocalPlayer* ExistingPlayer)
{
	if (ExistingPlayer->PlayerController != NULL)
	{
		// FIXME: Do this all inside PlayerRemoved?
		ExistingPlayer->PlayerController->CleanupGameViewport();

		// Destroy the player's actors.
		if ( ExistingPlayer->PlayerController->Role == ROLE_Authority )
		{
			ExistingPlayer->PlayerController->Destroy();
		}
		else
		{
			// We are a client, somehow tell the server to destroy the player controller, thus logging out
			ExistingPlayer->PlayerController->ServerDisconnect();
		}
	}

	// Remove the player from the context list
	const int32 OldIndex = LocalPlayers.Find(ExistingPlayer);

	if (ensure(OldIndex != INDEX_NONE))
	{
		ExistingPlayer->PlayerRemoved();
		LocalPlayers.RemoveAt(OldIndex);

		// Notify the viewport so the viewport can do the fixups, resize, etc
		if (GetGameViewportClient() != NULL)
		{
			GetGameViewportClient()->NotifyPlayerRemoved(OldIndex, ExistingPlayer);
		}
	}

	// Disassociate this viewport client from the player.
	// Do this after notifications, as some of them require the ViewportClient.
	ExistingPlayer->ViewportClient = NULL;

	UE_LOG(LogPlayerManagement, Log, TEXT("UGameInstance::RemovePlayer: Removed player %s with ControllerId %i at index %i (%i remaining players)"), *ExistingPlayer->GetName(), ExistingPlayer->GetControllerId(), OldIndex, LocalPlayers.Num());

	return true;
}

I’ve tested this is a myriad of situations and it successfully alleviates the issue. Split screen players on clients can connect and disconnect as many times as they like without issue.

If you found this useful do say so and I’ll make a pull request so it can be in the engine.