Host player spawning before scene is completely load

Hi All,

I am having an issue where the Host player is spawning as soon as the scene changes but before the world is ready, using ApprovalCheck to set the player prefab and CreatePlayerObject

I have been trying a few different things to resolve this including what I expected to be the solution by using Network SceneManger events (LoadEventCompleted) to delay joining using Response Pending = true / false.

But this does not seem to work, the host player spawns before the LoadEventCompleted, the host player has the correct Skin and is created, but it is like the “response.Pending = true;” is ignored for the host?

Any ideas here or maybe a thought on another way to spawn the host player after the scene change?

public class NetworkGameManager : MonoBehaviour
    {

        NetworkManager.ConnectionApprovalResponse StoredResponse;

…

            response.Approved = true;
            response.CreatePlayerObject = true;

            // If additional approval steps are needed, set this to true until the additional steps are complete
            // once it transitions from true to false the connection approval response will be processed.
            response.Pending = true;

            StoredResponse = response;
        }

…

              case SceneEventType.LoadEventCompleted:
                    {
                        Debug.Log("load event completed");
                        StoredResponse.Pending = false;
                        break;
                    }

You didn’t post the code where you load the scene. Perhaps you only do so after the player has spawned, eg in OnNetworkSpawn rather than right after StartHost?

Thanks for the reply! The network scenemanager change scene happens as part of a player selection menu button in the main menu scene.

            StartButton?.onClick.AddListener(async () =>
            {       
            SetConnectionPayload(GameObject.Find("GameState/Canvas/panelCharacter/PreviewModels").GetComponent<ShowModelController>().modelId);      

                if (GameObject.Find("GameState").GetComponent<SteamController>().isHost)
                {
                    NetworkManager.Singleton.StartHost();
                    NetworkManager.Singleton.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent;
                    NetworkManager.Singleton.SceneManager.LoadScene("Game", LoadSceneMode.Single);
                }
                else
                {
                    NetworkManager.Singleton.StartClient();
                }

                GameObject.Find("GameState").GetComponent<GameState>().ChangeScene();
            });

Here in the logs can see the order, start host, host spawn, scene load events.

I have also found that if i commend out Response.Pending = false the ConnectionApproval is still immediately processed.

Changing other values such as response.CreatePlayerObject = true; does work in the ConnectionApproval callback.

It almost looks like the ConnectionApproval.Pending is not evaluated for a host so using this technique to delay a host spawn might not work? I reviewed the Netcode code but couldn’t quite follow the logic for pending retries on host vs client connections.

(Note: player spawn errors are because of expected in-scene placed objects)

I’ve been running through the NetworkConnection code in the debugger and I am pretty sure the Host Player is not handled the same as connecting clients in regards to ConnectionApproval.Pending retries.

Is there a best practice here I am missing when spawning in the Host Player that needs to be deferred until the scene loading is complete?

Is this a bug?

My current thought here is to “Respawn” the Host player when the Network Scene loaded event fires but that seems like a workaround to just delaying the ConnectionApproval handled spawn?

Do we have someone from Unity who can shed some light here?

Thank you!

One approach I was trying to get my head around was not creating the player object in the ConnectionApproval phase

response.CreatePlayerObject = false;

Then in the NetworkSceneEvent spawn the player with the prefab assigned in the ConnectionApproval.

                case SceneEventType.LoadEventCompleted:
                    {
                        Debug.Log("load event completed");
                        StoredResponse.Pending = false;

//INSTANTIATE PLAYER GAMEOBJECT USING CONNECTIONAPPROVAL PREFAB HASH

//SPAWN CLIENT PLAYER GAMEOBJECT

                        break;
                    }

Any thoughts on this approach?

Any examples about getting the prefab using the network object prefab hash stored in the connectionApproval data?

Thank you!

I have been able to get a bit closer by looking up the prefab from the network list using the ConnectionApproval data inside of the LoadEventCompleted Event. Not quite perfect, but promising.

                case SceneEventType.LoadEventCompleted:
                    {
                        Debug.Log("load event completed");
                        StoredResponse.Pending = false;
                        GameObject playerPrefab = NetworkManager.Singleton.NetworkConfig.Prefabs.NetworkPrefabsLists[0].PrefabList.FirstOrDefault(prefab => prefab.SourceHashToOverride == StoredResponse.PlayerPrefabHash)?.Prefab;
                        if (playerPrefab == null)
                        {
                            Debug.LogError("No matching prefab found.");
                        }
                        GameObject playerObject = Instantiate(playerPrefab);
                        playerObject.GetComponent<NetworkObject>().SpawnWithOwnership(sceneEvent.ClientId, true);
                        break;
                    }

I am still trying to figure out if this is the right way to do this so any pointers here would be greatly appreciated.

Seeing how often, and particularly in such a convoluted way you use GameObject.Find(“”) I refuse to offer more help.:stuck_out_tongue:

It seems very likely that the issue is buried somewhere in “finding” objects. You don’t do so. There’s two rules about Find which is: don’t use it and don’t you dare to even think of using it! You’ll find plenty of reasons and alternatives if you look around. :wink:

And the reasons are all the more relevant in a networked game because the same object can have different names for different clients.

I did a little digging into this and you’re correct, the host is immediately approved and the Pending flag is ignored which makes sense I guess as the host can’t be denied approval. I didn’t see a way of controlling when the host is spawned.

I did some testing looking at scene event and spawn timing and knocked up this class, it’s rather specific/inflexible but might help with what you’re after:

 public class NetworkGameManager : MonoBehaviour
    {
        public static NetworkGameManager Singleton;

        Dictionary<ulong, NetworkPrefab> clientPrefabs;

        private void Awake()
        {
            Singleton = this;
            DontDestroyOnLoad(gameObject);

            clientPrefabs = new Dictionary<ulong, NetworkPrefab>();
        }

        public void StartHost(uint hostPrefabIdHash)
        {
            Debug.Log("NetworkGameManager StartHost PrefabId: " + hostPrefabIdHash);

            NetworkManager.Singleton.ConnectionApprovalCallback += OnConnectionApproval;
            NetworkManager.Singleton.OnClientConnectedCallback += OnClientConnected;
            NetworkManager.Singleton.NetworkConfig.ConnectionData = BitConverter.GetBytes(hostPrefabIdHash);
            NetworkManager.Singleton.StartHost();           
            NetworkManager.Singleton.SceneManager.OnSceneEvent += OnSceneEvent;

            NetworkManager.Singleton.SceneManager.LoadScene("GameScene", LoadSceneMode.Single);
        }

        public void StartClient(uint clientPrefabIdHash)
        {
            Debug.Log("NetworkGameManager StartClient PrefabId: " + clientPrefabIdHash);

            NetworkManager.Singleton.NetworkConfig.ConnectionData = BitConverter.GetBytes(clientPrefabIdHash);
            NetworkManager.Singleton.StartClient();
            NetworkManager.Singleton.SceneManager.OnSceneEvent += OnSceneEvent;
        }

        private void OnSceneEvent(SceneEvent sceneEvent)
        {
            Debug.Log($"OnSceneEvent scene: {sceneEvent.SceneName} clientId: {sceneEvent.ClientId} event: {sceneEvent.SceneEventType}");

            if (NetworkManager.Singleton.IsHost)
            {
                switch (sceneEvent.SceneEventType)
                {
                    case SceneEventType.LoadEventCompleted:
                        SpawnPlayer(sceneEvent.ClientId);
                        break;
                }
            }
        }

        private void SpawnPlayer(ulong clientId)
        {
            if (clientPrefabs.TryGetValue(clientId, out var clientPrefab))
            {
                GameObject playerGO = Instantiate(clientPrefab.Prefab);
                playerGO.GetComponent<NetworkObject>().SpawnAsPlayerObject(clientId);
            }
        }

        private void OnClientConnected(ulong clientId)
        {
            Debug.Log("NetworkGameManager OnClientConnected clientId: " + clientId);

            if(clientId != NetworkManager.ServerClientId)
            {
                SpawnPlayer(clientId);
            }
        }

        private void OnConnectionApproval(NetworkManager.ConnectionApprovalRequest request, NetworkManager.ConnectionApprovalResponse response)
        {
            uint globalObjectIdHash = BitConverter.ToUInt32(request.Payload, 0);

            Debug.Log($"NetworkGameManager OnConnectionApproval clientId: {request.ClientNetworkId} globalObjectIdHash: {globalObjectIdHash}");

            foreach (var prefab in NetworkManager.Singleton.NetworkConfig.Prefabs.Prefabs)
            {
                Debug.Log("Prefab: " + prefab.SourcePrefabGlobalObjectIdHash);

                if(globalObjectIdHash == prefab.SourcePrefabGlobalObjectIdHash)
                {
                    clientPrefabs.Add(request.ClientNetworkId, prefab);
                    response.Approved = true;
                    break;
                }
            }
        }
    }

One thing I did notice, on the client the host player is spawned before the scene SynchronizeComplete event, I don’t know whether or not that matters.

Thanks for that example. It reinforces some things which I was guessing at. I bet someone will find this useful in the future!

If feels there could be some improvement in Netcode here with host player spawning. I’m not sure of a use case for when you would want the host’s prefab to spawn before the scene is ready and here we document two cases of why you would want to delay the host spawn until after the scene is ready.

1 Like