diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 5374068ba0..f8ca65e0ab 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -23,6 +23,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed - Issue where a NullReferenceException was thrown when a non-authority failed to spawn a NetworkObject. (#4067) +- Issue where the active scene was not being serialized as the 1st scene which could result in various errors including a soft synchronization error if, on the client-side, other synchronized scenes had already been loaded prior to the active scene, which is always loaded as `LoadSceneMode.SingleMode` when client synchronization is set to `LoadSceneMode.SingleMode`, resulting in the previously loaded scene(s) to be unloaded. (#4065) - Issue when FastBufferReader is attempting to read a string and all or a portion of the character count has already been read by user script, it could read a character length that results in a negative byte length which could result in an editor crash. (#4052) - Issue where NetworkRigidbodyBase was not applying rotation correctly when using Rigidbody2D. (#4012) - Issue where NetworkRigidbodyBase was always checking the 3D rigid body's interpolation mode when determining if it is kinematic and needs to put the rigid body to sleep and then switch to interpolation. (#4012) diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index a28baf1f94..f499f8262c 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -1951,6 +1951,21 @@ private void OnClientLoadedScene(uint sceneEventId, Scene scene) /// internal List ClientConnectionQueue = new List(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void AddSceneToClientSynchronization(ref SceneEventData sceneEventData, ref Scene scene) + { + // If we are just a normal client and in distributed authority mode, then always use the known server scene handle + if (NetworkManager.DistributedAuthorityMode && NetworkManager.CMBServiceConnection) + { + sceneEventData.AddSceneToSynchronize(SceneHashFromNameOrPath(scene.path), ClientSceneHandleToServerSceneHandle[scene.handle]); + } + else + { + sceneEventData.AddSceneToSynchronize(SceneHashFromNameOrPath(scene.path), scene.handle); + } + } + /// /// Server Side: /// This is used for players that have just had their connection approved and will assure they are synchronized @@ -2001,61 +2016,53 @@ internal void SynchronizeNetworkObjects(ulong clientId, bool synchronizingServic // Organize how (and when) we serialize our NetworkObjects var hasSynchronizedActive = false; - for (int i = 0; i < SceneManager.sceneCount; i++) - { - var scene = SceneManager.GetSceneAt(i); - // NetworkSceneManager does not synchronize scenes that are not loaded by NetworkSceneManager - // unless the scene in question is the currently active scene. - if (ExcludeSceneFromSychronization != null && !ExcludeSceneFromSychronization(scene)) + // It is possible a user might not want to synchronize the active scene, so we will check to see if it is valid before adding it to the synchronization list. + // !! Important !! + // The active scene MUST always be the first scene in the synchronization list. + if (ValidateSceneBeforeLoading(activeScene.buildIndex, activeScene.name, sceneEventData.LoadSceneMode)) + { + sceneEventData.SceneHash = SceneHashFromNameOrPath(activeScene.path); + if (sceneEventData.SceneHash == sceneEventData.ActiveSceneHash) { - continue; + hasSynchronizedActive = true; } - if (scene == DontDestroyOnLoadScene) + // If we are just a normal client, then always use the server scene handle + if (NetworkManager.DistributedAuthorityMode) { - continue; + sceneEventData.SenderClientId = NetworkManager.LocalClientId; + sceneEventData.SceneHandle = ClientSceneHandleToServerSceneHandle[activeScene.handle]; } - - // This would depend upon whether we are additive or not - // If we are the base scene, then we set the root scene index; - if (activeScene == scene) + else { - if (!ValidateSceneBeforeLoading(scene.buildIndex, scene.name, sceneEventData.LoadSceneMode)) - { - continue; - } - sceneEventData.SceneHash = SceneHashFromNameOrPath(scene.path); - if (sceneEventData.SceneHash == sceneEventData.ActiveSceneHash) - { - hasSynchronizedActive = true; - } - - // If we are just a normal client, then always use the server scene handle - if (NetworkManager.DistributedAuthorityMode) - { - sceneEventData.SenderClientId = NetworkManager.LocalClientId; - sceneEventData.SceneHandle = ClientSceneHandleToServerSceneHandle[scene.handle]; - } - else - { - sceneEventData.SceneHandle = scene.handle; - } + sceneEventData.SceneHandle = activeScene.handle; } - else if (!ValidateSceneBeforeLoading(scene.buildIndex, scene.name, LoadSceneMode.Additive)) + AddSceneToClientSynchronization(ref sceneEventData, ref activeScene); + } + + for (int i = 0; i < SceneManager.sceneCount; i++) + { + var scene = SceneManager.GetSceneAt(i); + // Skip adding the active scene at this point as we are just adding all other additively loaded scenes to the synchronization list. + // Skip adding the dont destroy on load scene as that is never synchronized. + if ((scene.handle == activeScene.handle) || (scene == DontDestroyOnLoadScene)) { continue; } - // If we are just a normal client and in distributed authority mode, then always use the known server scene handle - if (NetworkManager.DistributedAuthorityMode && NetworkManager.CMBServiceConnection) + // NetworkSceneManager does not synchronize scenes that are not loaded by NetworkSceneManager + // unless the scene in question is the currently active scene. + if (ExcludeSceneFromSychronization != null && !ExcludeSceneFromSychronization(scene)) { - sceneEventData.AddSceneToSynchronize(SceneHashFromNameOrPath(scene.path), ClientSceneHandleToServerSceneHandle[scene.handle]); + continue; } - else + + if (!ValidateSceneBeforeLoading(scene.buildIndex, scene.name, LoadSceneMode.Additive)) { - sceneEventData.AddSceneToSynchronize(SceneHashFromNameOrPath(scene.path), scene.handle); + continue; } + AddSceneToClientSynchronization(ref sceneEventData, ref scene); } if (!hasSynchronizedActive && NetworkManager.CMBServiceConnection && synchronizingService) @@ -2108,9 +2115,12 @@ private void OnClientBeginSync(uint sceneEventId) var sceneHash = sceneEventData.GetNextSceneSynchronizationHash(); var sceneHandle = sceneEventData.GetNextSceneSynchronizationHandle(); var sceneName = SceneNameFromHash(sceneHash); + var activeSceneName = SceneNameFromHash(sceneEventData.ActiveSceneHash); var activeScene = SceneManager.GetActiveScene(); - var loadSceneMode = sceneHash == sceneEventData.SceneHash ? sceneEventData.LoadSceneMode : LoadSceneMode.Additive; + var activeSceneLoaded = activeSceneName == activeScene.name; + + var loadSceneMode = sceneHash == sceneEventData.SceneHash && !activeSceneLoaded ? sceneEventData.LoadSceneMode : LoadSceneMode.Additive; // Store the sceneHandle and hash sceneEventData.NetworkSceneHandle = sceneHandle;