Splitscreen in online steam game

Hello all,

I’m working on an online multiplayer game and I want players to be able to play splitscreen locally, but in a session with other people online. I already have steamworks integrated and everything is working fine aside from the split screen.

When split screen players are in and people join or leave the online session, crashes occur because the other split screen players (not player 1) do not have uniqueNetId’s, since in the steam subsystem this Id is their steam profile Id.

If I set split screen players to all have the Id of player 1, functions such as GetPlayerControllerFromNetId() will not work, since there will be multiple answers.

Is this possible? How should I go about achieving this?

Thanks

I found some code in UNetConnection relating to child connections for secondary viewports. How do I use this correctly?


UCLASS(customConstructor, Abstract, MinimalAPI, transient, config=Engine)
class UNetConnection : public UPlayer
{
	GENERATED_UCLASS_BODY()

	/** child connections for secondary viewports */
	UPROPERTY(transient)
	TArray<class UChildConnection*> Children;

}

I’ve discovered this code, which joins a split screen player correctly, assigning a netId and all. However, this isn’t called when I call CreatePlayer, and so none of it happens and the NetId is NULL.

Note this code is found in this function:


void UWorld::NotifyControlMessage(UNetConnection* Connection, uint8 MessageType, class FInBunch& Bunch)

How do I get this to execute when I want to join a split screen player in an online game?


case NMT_JoinSplit:
			{
				// Handle server-side request for spawning a new controller using a child connection.
				FString SplitRequestURL;
				FUniqueNetIdRepl UniqueIdRepl;
				FNetControlMessage<NMT_JoinSplit>::Receive(Bunch, SplitRequestURL, UniqueIdRepl);

				// Compromise for passing splitscreen playercount through to gameplay login code,
				// without adding a lot of extra unnecessary complexity throughout the login code.
				// NOTE: This code differs from NMT_Login, by counting + 2 for SplitscreenCount
				//			(once for pending child connection, once for primary non-child connection)
				FURL InURL( NULL, *SplitRequestURL, TRAVEL_Absolute );

				if ( !InURL.Valid )
				{
					UE_LOG( LogNet, Error, TEXT( "NMT_JoinSplit: Invalid URL %s" ), *SplitRequestURL );
					Bunch.SetError();
					break;
				}

				uint8 SplitscreenCount = FMath::Min(Connection->Children.Num() + 2, 255);

				// Don't allow clients to specify this value
				InURL.RemoveOption(TEXT("SplitscreenCount"));
				InURL.AddOption(*FString::Printf(TEXT("SplitscreenCount=%i"), SplitscreenCount));

				SplitRequestURL = InURL.ToString();


				// skip to the first option in the URL
				const TCHAR* Tmp = *SplitRequestURL;
				for (; *Tmp && *Tmp != '?'; Tmp++);


				// keep track of net id for player associated with remote connection
				Connection->PlayerId = UniqueIdRepl.GetUniqueNetId();

				// go through the same full login process for the split player even though it's all in the same frame
				FString ErrorMsg;
				AuthorityGameMode->PreLogin(Tmp, Connection->LowLevelGetRemoteAddress(), Connection->PlayerId, ErrorMsg);
				if (!ErrorMsg.IsEmpty())
				{
					// if any splitscreen viewport fails to join, all viewports on that client also fail
					UE_LOG(LogNet, Log, TEXT("PreLogin failure: %s"), *ErrorMsg);
					NETWORK_PROFILER(GNetworkProfiler.TrackEvent(TEXT("PRELOGIN FAILURE"), *ErrorMsg));
					FNetControlMessage<NMT_Failure>::Send(Connection, ErrorMsg);
					Connection->FlushNet(true);
					//@todo sz - can't close the connection here since it will leave the failure message 
					// in the send buffer and just close the socket. 
					//Connection->Close();
				}
				else
				{
					// create a child network connection using the existing connection for its parent
					check(Connection->GetUChildConnection() == NULL);
					check(CurrentLevel);

					UChildConnection* ChildConn = NetDriver->CreateChild(Connection);
					ChildConn->PlayerId = Connection->PlayerId;
					ChildConn->RequestURL = SplitRequestURL;
					ChildConn->ClientWorldPackageName = CurrentLevel->GetOutermost()->GetFName();

					// create URL from string
					FURL JoinSplitURL(NULL, *SplitRequestURL, TRAVEL_Absolute);

					UE_LOG(LogNet, Log, TEXT("JOINSPLIT: Join request: URL=%s"), *JoinSplitURL.ToString());
					APlayerController* PC = SpawnPlayActor(ChildConn, ROLE_AutonomousProxy, JoinSplitURL, ChildConn->PlayerId, ErrorMsg, uint8(Connection->Children.Num()));
					if (PC == NULL)
					{
						// Failed to connect.
						UE_LOG(LogNet, Log, TEXT("JOINSPLIT: Join failure: %s"), *ErrorMsg);
						NETWORK_PROFILER(GNetworkProfiler.TrackEvent(TEXT("JOINSPLIT FAILURE"), *ErrorMsg));
						// remove the child connection
						Connection->Children.Remove(ChildConn);
						// if any splitscreen viewport fails to join, all viewports on that client also fail
						FNetControlMessage<NMT_Failure>::Send(Connection, ErrorMsg);
						Connection->FlushNet(true);
						//@todo sz - can't close the connection here since it will leave the failure message 
						// in the send buffer and just close the socket. 
						//Connection->Close();
					}
					else
					{
						// Successfully spawned in game.
						UE_LOG(LogNet, Log, TEXT("JOINSPLIT: Succeeded: %s PlayerId: %s"), 
							*ChildConn->PlayerController->PlayerState->PlayerName,
							ChildConn->PlayerController->PlayerState->UniqueId.IsValid() ? *ChildConn->PlayerController->PlayerState->UniqueId->ToDebugString() : TEXT("INVALID"));
					}
				}

I am also having trouble combining splitscreen with networked multiplayer. My problem is that the Client doesn’t successfully possess the new pawn, but gets their playercontroller ID changed to -1 (on the client side. I know it’s supposed to be -1 on the server side) when it used to be 0.