diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 5eafa7af70..258d186344 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -21,6 +21,13 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where a client disconnected by a server-host would not receive a local notification. (#2789) +- Fixed issue where a server-host could shutdown during a relay connection but periodically the transport disconnect message sent to any connected clients could be dropped. (#2789) +- Fixed issue where a host could disconnect its local client but remain running as a server. (#2789) +- Fixed issue where `OnClientDisconnectedCallback` was not being invoked under certain conditions. (#2789) +- Fixed issue where `OnClientDisconnectedCallback` was always returning 0 as the client identifier. (#2789) +- Fixed issue where if a host or server shutdown while a client owned NetworkObjects (other than the player) it would throw an exception. (#2789) +- Fixed issue where setting values on a `NetworkVariable` or `NetworkList` within `OnNetworkDespawn` during a shutdown sequence would throw an exception. (#2789) - Fixed issue where a teleport state could potentially be overridden by a previous unreliable delta state. (#2777) - Fixed issue where `NetworkTransform` was using the `NetworkManager.ServerTime.Tick` as opposed to `NetworkManager.NetworkTickSystem.ServerTime.Tick` during the authoritative side's tick update where it performed a delta state check. (#2777) - Fixed issue where a parented in-scene placed NetworkObject would be destroyed upon a client or server exiting a network session but not unloading the original scene in which the NetworkObject was placed. (#2737) @@ -28,7 +35,8 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed issue where a `NetworkTransform` instance with interpolation enabled would result in wide visual motion gaps (stuttering) under above normal latency conditions and a 1-5% or higher packet are drop rate. (#2713) ### Changed - +- Changed the server or host shutdown so it will now perform a "soft shutdown" when `NetworkManager.Shutdown` is invoked. This will send a disconnect notification to all connected clients and the server-host will wait for all connected clients to disconnect or timeout after a 5 second period before completing the shutdown process. (#2789) +- Changed `OnClientDisconnectedCallback` will now return the assigned client identifier on the local client side if the client was approved and assigned one prior to being disconnected. (#2789) - Changed `NetworkTransform.SetState` (and related methods) now are cumulative during a fractional tick period and sent on the next pending tick. (#2777) - `NetworkManager.ConnectedClientsIds` is now accessible on the client side and will contain the list of all clients in the session, including the host client if the server is operating in host mode (#2762) - Changed `NetworkSceneManager` to return a `SceneEventProgress` status and not throw exceptions for methods invoked when scene management is disabled and when a client attempts to access a `NetworkSceneManager` method by a client. (#2735) diff --git a/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs index 538b4b9c88..79030b7be7 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs @@ -833,8 +833,7 @@ private void CheckForStateChange(int layer) stateChangeDetected = true; //Debug.Log($"[Cross-Fade] To-Hash: {nt.fullPathHash} | TI-Duration: ({tt.duration}) | TI-Norm: ({tt.normalizedTime}) | From-Hash: ({m_AnimationHash[layer]}) | SI-FPHash: ({st.fullPathHash}) | SI-Norm: ({st.normalizedTime})"); } - else - if (!tt.anyState && tt.fullPathHash != m_TransitionHash[layer]) + else if (!tt.anyState && tt.fullPathHash != m_TransitionHash[layer]) { // first time in this transition for this layer m_TransitionHash[layer] = tt.fullPathHash; @@ -1053,8 +1052,7 @@ private unsafe void WriteParameters(ref FastBufferWriter writer) BytePacker.WriteValuePacked(writer, valueBool); } } - else - if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterFloat) + else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterFloat) { var valueFloat = m_Animator.GetFloat(hash); fixed (void* value = cacheValue.Value) diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index d7c0ef4b05..d73759874a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -347,7 +347,7 @@ internal ulong TransportIdCleanUp(ulong transportId) // When this happens, the client will not have an entry within the m_TransportIdToClientIdMap or m_ClientIdToTransportIdMap lookup tables so we exit early and just return 0 to be used for the disconnect event. if (!LocalClient.IsServer && !TransportIdToClientIdMap.ContainsKey(transportId)) { - return 0; + return NetworkManager.LocalClientId; } var clientId = TransportIdToClientId(transportId); @@ -476,12 +476,18 @@ internal void DisconnectEventHandler(ulong transportClientId) s_TransportDisconnect.Begin(); #endif var clientId = TransportIdCleanUp(transportClientId); - if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) { NetworkLog.LogInfo($"Disconnect Event From {clientId}"); } + // If we are a client and we have gotten the ServerClientId back, then use our assigned local id as the client that was + // disconnected (either the user disconnected or the server disconnected, but the client that disconnected is the LocalClientId) + if (!NetworkManager.IsServer && clientId == NetworkManager.ServerClientId) + { + clientId = NetworkManager.LocalClientId; + } + // Process the incoming message queue so that we get everything from the server disconnecting us or, if we are the server, so we got everything from that client. MessageManager.ProcessIncomingMessageQueue(); @@ -496,9 +502,12 @@ internal void DisconnectEventHandler(ulong transportClientId) { OnClientDisconnectFromServer(clientId); } - else + else // As long as we are not in the middle of a shutdown + if (!NetworkManager.ShutdownInProgress) { - // We must pass true here and not process any sends messages as we are no longer connected and thus there is no one to send any messages to and this will cause an exception within UnityTransport as the client ID is no longer valid. + // We must pass true here and not process any sends messages as we are no longer connected. + // Otherwise, attempting to process messages here can cause an exception within UnityTransport + // as the client ID is no longer valid. NetworkManager.Shutdown(true); } #if DEVELOPMENT_BUILD || UNITY_EDITOR @@ -901,6 +910,7 @@ internal void OnClientDisconnectFromServer(ulong clientId) // If we are shutting down and this is the server or host disconnecting, then ignore // clean up as everything that needs to be destroyed will be during shutdown. + if (NetworkManager.ShutdownInProgress && clientId == NetworkManager.ServerClientId) { return; @@ -924,7 +934,7 @@ internal void OnClientDisconnectFromServer(ulong clientId) NetworkManager.SpawnManager.DespawnObject(playerObject, true); } } - else + else if (!NetworkManager.ShutdownInProgress) { playerObject.RemoveOwnership(); } @@ -943,7 +953,7 @@ internal void OnClientDisconnectFromServer(ulong clientId) } else { - // Handle changing ownership and prefab handlers + // Handle changing ownership and prefab handlers for (int i = clientOwnedObjects.Count - 1; i >= 0; i--) { var ownedObject = clientOwnedObjects[i]; @@ -960,7 +970,7 @@ internal void OnClientDisconnectFromServer(ulong clientId) Object.Destroy(ownedObject.gameObject); } } - else + else if (!NetworkManager.ShutdownInProgress) { ownedObject.RemoveOwnership(); } @@ -982,7 +992,7 @@ internal void OnClientDisconnectFromServer(ulong clientId) ConnectedClientIds.Remove(clientId); var message = new ClientDisconnectedMessage { ClientId = clientId }; - NetworkManager.MessageManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ConnectedClientIds); + MessageManager?.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ConnectedClientIds); } // If the client ID transport map exists @@ -1033,6 +1043,12 @@ internal void DisconnectClient(ulong clientId, string reason = null) throw new NotServerException($"Only server can disconnect remote clients. Please use `{nameof(Shutdown)}()` instead."); } + if (clientId == NetworkManager.ServerClientId) + { + Debug.LogWarning($"Disconnecting the local server-host client is not allowed. Use NetworkManager.Shutdown instead."); + return; + } + if (!string.IsNullOrEmpty(reason)) { var disconnectReason = new DisconnectReasonMessage @@ -1077,16 +1093,8 @@ internal void Initialize(NetworkManager networkManager) /// internal void Shutdown() { - LocalClient.IsApproved = false; - LocalClient.IsConnected = false; - ConnectedClients.Clear(); - ConnectedClientIds.Clear(); - ConnectedClientsList.Clear(); if (LocalClient.IsServer) { - // make sure all messages are flushed before transport disconnect clients - MessageManager?.ProcessSendQueues(); - // Build a list of all client ids to be disconnected var disconnectedIds = new HashSet(); @@ -1122,9 +1130,15 @@ internal void Shutdown() { DisconnectRemoteClient(clientId); } + + // make sure all messages are flushed before transport disconnects clients + MessageManager?.ProcessSendQueues(); } else if (NetworkManager != null && NetworkManager.IsListening && LocalClient.IsClient) { + // make sure all messages are flushed before disconnecting + MessageManager?.ProcessSendQueues(); + // Client only, send disconnect and if transport throws and exception, log the exception and continue the shutdown sequence (or forever be shutting down) try { @@ -1136,6 +1150,12 @@ internal void Shutdown() } } + LocalClient.IsApproved = false; + LocalClient.IsConnected = false; + ConnectedClients.Clear(); + ConnectedClientIds.Clear(); + ConnectedClientsList.Clear(); + if (NetworkManager != null && NetworkManager.NetworkConfig?.NetworkTransport != null) { NetworkManager.NetworkConfig.NetworkTransport.OnTransportEvent -= HandleNetworkEvent; diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index c994e4d8f3..4fd66e3bfb 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -73,13 +73,89 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) if (m_ShuttingDown) { - ShutdownInternal(); + // Host-server will disconnect any connected clients prior to finalizing its shutdown + if (IsServer) + { + ProcessServerShutdown(); + } + else + { + // Clients just disconnect immediately + ShutdownInternal(); + } } } break; } } + /// + /// Used to provide a graceful shutdown sequence + /// + internal enum ServerShutdownStates + { + None, + WaitForClientDisconnects, + InternalShutdown, + ShuttingDown, + }; + + internal ServerShutdownStates ServerShutdownState; + private float m_ShutdownTimeout; + + /// + /// This is a "soft shutdown" where the host or server will disconnect + /// all clients, with a provided reasons, prior to invoking its final + /// internal shutdown. + /// + internal void ProcessServerShutdown() + { + var minClientCount = IsHost ? 2 : 1; + switch (ServerShutdownState) + { + case ServerShutdownStates.None: + { + if (ConnectedClients.Count >= minClientCount) + { + var hostServer = IsHost ? "host" : "server"; + var disconnectReason = $"Disconnected due to {hostServer} shutting down."; + for (int i = ConnectedClientsIds.Count - 1; i >= 0; i--) + { + var clientId = ConnectedClientsIds[i]; + if (clientId == ServerClientId) + { + continue; + } + ConnectionManager.DisconnectClient(clientId, disconnectReason); + } + ServerShutdownState = ServerShutdownStates.WaitForClientDisconnects; + m_ShutdownTimeout = Time.realtimeSinceStartup + 5.0f; + } + else + { + ServerShutdownState = ServerShutdownStates.InternalShutdown; + ProcessServerShutdown(); + } + break; + } + case ServerShutdownStates.WaitForClientDisconnects: + { + if (ConnectedClients.Count < minClientCount || m_ShutdownTimeout < Time.realtimeSinceStartup) + { + ServerShutdownState = ServerShutdownStates.InternalShutdown; + ProcessServerShutdown(); + } + break; + } + case ServerShutdownStates.InternalShutdown: + { + ServerShutdownState = ServerShutdownStates.ShuttingDown; + ShutdownInternal(); + break; + } + } + } + /// /// The client id used to represent the server /// @@ -704,6 +780,12 @@ public int MaximumFragmentedMessageSize internal void Initialize(bool server) { + // Make sure the ServerShutdownState is reset when initializing + if (server) + { + ServerShutdownState = ServerShutdownStates.None; + } + // Don't allow the user to start a network session if the NetworkManager is // still parented under another GameObject if (NetworkManagerCheckForParent(true)) @@ -970,7 +1052,6 @@ public bool StartHost() private void HostServerInitialize() { LocalClientId = ServerClientId; - //ConnectionManager.ConnectedClientIds.Add(ServerClientId); NetworkMetrics.SetConnectionId(LocalClientId); MessageManager.SetLocalClientId(LocalClientId); @@ -1051,11 +1132,6 @@ public void Shutdown(bool discardMessageQueue = false) MessageManager.StopProcessing = discardMessageQueue; } } - - if (NetworkConfig != null && NetworkConfig.NetworkTransport != null) - { - NetworkConfig.NetworkTransport.OnTransportEvent -= ConnectionManager.HandleNetworkEvent; - } } // Ensures that the NetworkManager is cleaned up before OnDestroy is run on NetworkObjects and NetworkBehaviours when unloading a scene with a NetworkManager diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs index c72813ee7e..927687c692 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs @@ -100,7 +100,15 @@ protected void MarkNetworkBehaviourDirty() "Are you modifying a NetworkVariable before the NetworkObject is spawned?"); return; } - + if (m_NetworkBehaviour.NetworkManager.ShutdownInProgress) + { + if (m_NetworkBehaviour.NetworkManager.LogLevel <= LogLevel.Developer) + { + Debug.LogWarning($"NetworkVariable is written to during the NetworkManager shutdown! " + + "Are you modifying a NetworkVariable within a NetworkBehaviour.OnDestroy or NetworkBehaviour.OnDespawn method?"); + } + return; + } m_NetworkBehaviour.NetworkManager.BehaviourUpdater.AddForUpdate(m_NetworkBehaviour.NetworkObject); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs index 02afdc8dec..8c17d1bb3e 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs @@ -2769,4 +2769,71 @@ public IEnumerator TestInheritedFields() Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut, nameof(CheckTestObjectComponentValuesOnAll)); } } + + public class NetvarDespawnShutdown : NetworkBehaviour + { + private NetworkVariable m_IntNetworkVariable = new NetworkVariable(); + private NetworkList m_IntList; + + private void Awake() + { + m_IntList = new NetworkList(); + } + + public override void OnNetworkDespawn() + { + if (IsServer) + { + m_IntNetworkVariable.Value = 5; + for (int i = 0; i < 10; i++) + { + m_IntList.Add(i); + } + } + base.OnNetworkDespawn(); + } + } + + /// + /// Validates that setting values for NetworkVariable or NetworkList during the + /// OnNetworkDespawn method will not cause an exception to occur. + /// + public class NetworkVariableModifyOnNetworkDespawn : NetcodeIntegrationTest + { + protected override int NumberOfClients => 1; + + private GameObject m_TestPrefab; + + protected override void OnServerAndClientsCreated() + { + m_TestPrefab = CreateNetworkObjectPrefab("NetVarDespawn"); + m_TestPrefab.AddComponent(); + base.OnServerAndClientsCreated(); + } + + private bool OnClientSpawnedTestPrefab(ulong networkObjectId) + { + var clientId = m_ClientNetworkManagers[0].LocalClientId; + if (!s_GlobalNetworkObjects.ContainsKey(clientId)) + { + return false; + } + + if (!s_GlobalNetworkObjects[clientId].ContainsKey(networkObjectId)) + { + return false; + } + + return true; + } + + [UnityTest] + public IEnumerator ModifyNetworkVariableOrListOnNetworkDespawn() + { + var instance = SpawnObject(m_TestPrefab, m_ServerNetworkManager); + yield return WaitForConditionOrTimeOut(() => OnClientSpawnedTestPrefab(instance.GetComponent().NetworkObjectId)); + m_ServerNetworkManager.Shutdown(); + // As long as no excetptions occur, the test passes. + } + } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs index aea5149053..227b72ef79 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs @@ -176,9 +176,8 @@ public IEnumerator TestPeerDisconnectCallback([Values] ClientDisconnectType clie } } - // If disconnected client-side, only server will receive it. - // If disconnected server-side, one for server, one for self - Assert.AreEqual(clientDisconnectType == ClientDisconnectType.ClientDisconnectsFromServer ? 1 : 2, m_ClientDisconnectCount); + // If disconnected, the server and the client that disconnected will be notified + Assert.AreEqual(2, m_ClientDisconnectCount); // Host receives peer disconnect, dedicated server does not Assert.AreEqual(m_UseHost ? 3 : 2, m_PeerDisconnectCount); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/StopStartRuntimeTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/StopStartRuntimeTests.cs index 16fa90e7a7..8d52f1fc63 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/StopStartRuntimeTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/StopStartRuntimeTests.cs @@ -1,7 +1,6 @@ using System.Collections; using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; -using UnityEngine; using UnityEngine.TestTools; namespace Unity.Netcode.RuntimeTests @@ -16,15 +15,19 @@ protected override void OnOneTimeSetup() base.OnOneTimeSetup(); } + + private bool m_ServerStopped; [UnityTest] public IEnumerator WhenShuttingDownAndRestarting_SDKRestartsSuccessfullyAndStaysRunning() { // shutdown the server + m_ServerNetworkManager.OnServerStopped += OnServerStopped; m_ServerNetworkManager.Shutdown(); - // wait 1 frame because shutdowns are delayed - var nextFrameNumber = Time.frameCount + 1; - yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber); + // wait until the OnServerStopped is invoked + m_ServerStopped = false; + yield return WaitForConditionOrTimeOut(() => m_ServerStopped); + AssertOnTimeout("Timed out waiting for the server to stop!"); // Verify the shutdown occurred Assert.IsFalse(m_ServerNetworkManager.IsServer); @@ -45,15 +48,23 @@ public IEnumerator WhenShuttingDownAndRestarting_SDKRestartsSuccessfullyAndStays Assert.IsTrue(m_ServerNetworkManager.IsListening); } + private void OnServerStopped(bool obj) + { + m_ServerNetworkManager.OnServerStopped -= OnServerStopped; + m_ServerStopped = true; + } + [UnityTest] public IEnumerator WhenShuttingDownTwiceAndRestarting_SDKRestartsSuccessfullyAndStaysRunning() { // shutdown the server + m_ServerNetworkManager.OnServerStopped += OnServerStopped; m_ServerNetworkManager.Shutdown(); - // wait 1 frame because shutdowns are delayed - var nextFrameNumber = Time.frameCount + 1; - yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber); + // wait until the OnServerStopped is invoked + m_ServerStopped = false; + yield return WaitForConditionOrTimeOut(() => m_ServerStopped); + AssertOnTimeout("Timed out waiting for the server to stop!"); // Verify the shutdown occurred Assert.IsFalse(m_ServerNetworkManager.IsServer); diff --git a/testproject/Assets/Scripts/ConnectionModeScript.cs b/testproject/Assets/Scripts/ConnectionModeScript.cs index f69e37aefb..70bc46a1d9 100644 --- a/testproject/Assets/Scripts/ConnectionModeScript.cs +++ b/testproject/Assets/Scripts/ConnectionModeScript.cs @@ -256,18 +256,21 @@ private void StartClient() { NetworkManager.Singleton.StartClient(); OnNotifyConnectionEventClient?.Invoke(); - if (m_ConnectionModeButtons != null) + if (m_ConnectionModeButtons) { m_ConnectionModeButtons.SetActive(false); NetworkManager.Singleton.OnClientStopped += OnClientStopped; } - m_DisconnectClientButton?.SetActive(true); + if (m_DisconnectClientButton) + { + m_DisconnectClientButton.SetActive(true); + } } private void OnClientStopped(bool obj) { - if (m_ConnectionModeButtons != null) + if (m_ConnectionModeButtons) { NetworkManager.Singleton.OnClientStopped -= OnClientStopped; m_ConnectionModeButtons.SetActive(true); diff --git a/testproject/Assets/Scripts/ExitButtonScript.cs b/testproject/Assets/Scripts/ExitButtonScript.cs index 4e49c135b4..bd5dd5593a 100644 --- a/testproject/Assets/Scripts/ExitButtonScript.cs +++ b/testproject/Assets/Scripts/ExitButtonScript.cs @@ -7,14 +7,68 @@ public class ExitButtonScript : MonoBehaviour [SerializeField] private MenuReference m_SceneMenuToLoad; + [SerializeField] + private bool m_DestroyNetworkManagerOnExit = true; + + private void Start() + { + if (!NetworkManager.Singleton) + { + return; + } + + NetworkManager.Singleton.OnServerStopped += OnNetworkManagerStopped; + NetworkManager.Singleton.OnClientStopped += OnNetworkManagerStopped; + + NetworkManager.Singleton.OnServerStarted += OnNetworkManagerStarted; + NetworkManager.Singleton.OnClientStarted += OnNetworkManagerStarted; + } + + private void OnNetworkManagerStarted() + { + NetworkManager.Singleton.OnClientDisconnectCallback -= OnClientDisconnectCallback; + NetworkManager.Singleton.OnClientDisconnectCallback += OnClientDisconnectCallback; + } + + private void OnClientDisconnectCallback(ulong clientId) + { + var disconnectedReason = "Disconnected from session."; + if (NetworkManager.Singleton && !string.IsNullOrEmpty(NetworkManager.Singleton.DisconnectReason)) + { + disconnectedReason = $"{NetworkManager.Singleton.DisconnectReason}"; + } + Debug.Log($"[Client-{clientId}] {disconnectedReason}"); + } + /// /// A very basic way to exit back to the first scene in the build settings /// public void OnExitScene() { - if (NetworkManager.Singleton) + if (!NetworkManager.Singleton) + { + LoadExitScene(); + return; + } + + if (NetworkManager.Singleton.IsListening) + { + if (!NetworkManager.Singleton.ShutdownInProgress) + { + + NetworkManager.Singleton.Shutdown(); + } + } + else + { + LoadExitScene(); + } + } + + private void LoadExitScene() + { + if (m_DestroyNetworkManagerOnExit) { - NetworkManager.Singleton.Shutdown(); Destroy(NetworkManager.Singleton.gameObject); } @@ -26,6 +80,12 @@ public void OnExitScene() { SceneManager.LoadSceneAsync(0, LoadSceneMode.Single); } + } + private void OnNetworkManagerStopped(bool obj) + { + NetworkManager.Singleton.OnServerStopped -= OnNetworkManagerStopped; + NetworkManager.Singleton.OnClientStopped -= OnNetworkManagerStopped; + LoadExitScene(); } } diff --git a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ChildObjectScript.cs b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ChildObjectScript.cs index 0eb38e85fb..e3413a4ec1 100644 --- a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ChildObjectScript.cs +++ b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ChildObjectScript.cs @@ -71,8 +71,7 @@ public override void OnNetworkObjectParentChanged(NetworkObject parentNetworkObj transform.localRotation = m_OriginalLocalRotation; transform.localScale = m_OriginalLocalScale; } - else - if (parentNetworkObject == null && m_LastParent) + else if (parentNetworkObject == null && m_LastParent) { // This example will drop the object at the current world position transform.position = m_LastParent.transform.position; diff --git a/testproject/Assets/Tests/Manual/Scripts/StatsDisplay.cs b/testproject/Assets/Tests/Manual/Scripts/StatsDisplay.cs index 460066554a..4d45807013 100644 --- a/testproject/Assets/Tests/Manual/Scripts/StatsDisplay.cs +++ b/testproject/Assets/Tests/Manual/Scripts/StatsDisplay.cs @@ -54,7 +54,10 @@ public override void OnNetworkSpawn() } else { - m_ClientServerToggle?.SetActive(true); + if (m_ClientServerToggle) + { + m_ClientServerToggle.SetActive(false); + } UpdateButton(); }