Real world In-scene NetworkObject parenting of players solution
We received the following issue in Github.
Issue:
When a player Prefab has a script that dynamically adds a parent to its transform, the client can't join a game hosted by another client. You can see orignal issue here
Steps to reproduce the behavior:
- Set up basic networking game with at least one
GameObject
in a scene that isn't the player. - Add a script to the player Prefab that adds parenting to its transform via
gameObject.transform.SetParent()
in theStart()
method. - Launch one instance of the game as Host.
- Launch another instance and try to join as Client.
Solution:
If you want to do this when a player has first connected and all NetworkObjects
(in-scene placed and already dynamically spawned by the server-host) have been fully synchronized with the client then we would recommend using the NetworkManager.SceneManager.OnSceneEvent
to trap for the C2S_SyncComplete
event.
Here is an example script that we recommend using to achieve this:
using Unity.Netcode;
public class ParentPlayerToInSceneNetworkObject : NetworkBehaviour
{
public override void OnNetworkSpawn()
{
if (IsServer)
{
// Server subscribes to the NetworkSceneManager.OnSceneEvent event
NetworkManager.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent;
// Server player is parented under this NetworkObject
SetPlayerParent(NetworkManager.LocalClientId);
}
}
private void SetPlayerParent(ulong clientId)
{
if (IsSpawned && IsServer)
{
// As long as the client (player) is in the connected clients list
if (NetworkManager.ConnectedClients.ContainsKey(clientId))
{
// Set the player as a child of this in-scene placed NetworkObject
NetworkManager.ConnectedClients[clientId].PlayerObject.transform.parent = transform;
}
}
}
private void SceneManager_OnSceneEvent(SceneEvent sceneEvent)
{
// OnSceneEvent is useful for many things
switch (sceneEvent.SceneEventType)
{
// The C2S_SyncComplete event tells the server that a client-player has:
// 1.) Connected and Spawned
// 2.) Loaded all scenes that were loaded on the server at the time of connecting
// 3.) Synchronized (instantiated and spawned) all NetworkObjects in the network session
case SceneEventData.SceneEventTypes.C2S_SyncComplete:
{
// As long as we aren't the server-player
if (sceneEvent.ClientId != NetworkManager.LocalClientId)
{
// Set the newly joined and synchronized client-player as a child of this in-scene placed NetworkObject
SetPlayerParent(sceneEvent.ClientId);
}
break;
}
}
}
}
You should place this script on your in-scene placed NetworkObject
(that is, the first GameObject
) and do the parenting from it to avoid any timing issues of when it's spawned or the like. It only runs the script on the server-host side since parenting is server authoritative.
Remove any parenting code you might have had from your player Prefab before using the above script.