diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md
index 7ad0374887..b440b033c6 100644
--- a/com.unity.netcode.gameobjects/CHANGELOG.md
+++ b/com.unity.netcode.gameobjects/CHANGELOG.md
@@ -15,6 +15,8 @@ Additional documentation and release notes are available at [Multiplayer Documen
### Fixed
+- Fixed issue where `NetworkClient` could persist some settings if re-using the same `NetworkManager` instance. (#3494)
+- Fixed issue where a pooled `NetworkObject` was not resetting the internal latest parent property when despawned. (#3494)
- Fixed issue where the initial client synchronization pre-serialization process was not excluding spawned `NetworkObjects` that already had pending visibility for the client being synchronized. (#3493)
- Fixed issue where invoking `NetworkObject.NetworkShow` and `NetworkObject.ChangeOwnership` consecutively within the same call stack location could result in an unnecessary change in ownership error message generated on the target client side. (#3493)
- Fixed issue where `NetworkVariable`s on a `NetworkBehaviour` could fail to synchronize changes if one has `NetworkVariableUpdateTraits` set and is dirty but is not ready to send. (#3465)
diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs
index 4db8e5c350..b539491533 100644
--- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs
@@ -1265,8 +1265,8 @@ internal void ShutdownInternal()
// In the event shutdown is invoked within OnClientStopped or OnServerStopped, set it to false again
m_ShuttingDown = false;
- // Reset the client's roles
- ConnectionManager.LocalClient.SetRole(false, false);
+ // Completely reset the NetworkClient
+ ConnectionManager.LocalClient = new NetworkClient();
// This cleans up the internal prefabs list
NetworkConfig?.Prefabs?.Shutdown();
diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
index d20088feb1..47eca79f97 100644
--- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
@@ -913,6 +913,14 @@ public void Despawn(bool destroy = true)
NetworkManager.SpawnManager.DespawnObject(this, destroy);
}
+ internal void ResetOnDespawn()
+ {
+ // Always clear out the observers list when despawned
+ Observers.Clear();
+ IsSpawned = false;
+ m_LatestParent = null;
+ }
+
///
/// Removes all ownership of an object from any client. Can only be called from server
///
diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs
index e503346722..d0a1c99778 100644
--- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs
@@ -1191,15 +1191,13 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec
}
}
- networkObject.IsSpawned = false;
-
if (SpawnedObjects.Remove(networkObject.NetworkObjectId))
{
SpawnedObjectsList.Remove(networkObject);
}
- // Always clear out the observers list when despawned
- networkObject.Observers.Clear();
+ // Reset the NetworkObject when despawned.
+ networkObject.ResetOnDespawn();
var gobj = networkObject.gameObject;
if (destroyGameObject && gobj != null)
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs
index bef91313a8..1cc9bc40c9 100644
--- a/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs
@@ -150,8 +150,8 @@ public IEnumerator ClientPlayerDisconnected([Values] ClientDisconnectType client
if (clientDisconnectType == ClientDisconnectType.ServerDisconnectsClient)
{
- m_ClientNetworkManagers[0].OnClientDisconnectCallback += OnClientDisconnectCallback;
- m_ClientNetworkManagers[0].OnConnectionEvent += OnConnectionEvent;
+ clientManager.OnClientDisconnectCallback += OnClientDisconnectCallback;
+ clientManager.OnConnectionEvent += OnConnectionEvent;
m_ServerNetworkManager.OnConnectionEvent += OnConnectionEvent;
m_ServerNetworkManager.DisconnectClient(m_ClientId);
}
@@ -159,9 +159,18 @@ public IEnumerator ClientPlayerDisconnected([Values] ClientDisconnectType client
{
m_ServerNetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback;
m_ServerNetworkManager.OnConnectionEvent += OnConnectionEvent;
- m_ClientNetworkManagers[0].OnConnectionEvent += OnConnectionEvent;
+ clientManager.OnConnectionEvent += OnConnectionEvent;
- yield return StopOneClient(m_ClientNetworkManagers[0]);
+ yield return StopOneClient(clientManager);
+
+ if (clientManager.ConnectionManager != null)
+ {
+ Assert.False(clientManager.ConnectionManager.LocalClient.IsClient, $"{clientManager.name} still has IsClient setting!");
+ Assert.False(clientManager.ConnectionManager.LocalClient.IsConnected, $"{clientManager.name} still has IsConnected setting!");
+ Assert.False(clientManager.ConnectionManager.LocalClient.ClientId != 0, $"{clientManager.name} still has ClientId ({clientManager.ConnectionManager.LocalClient.ClientId}) setting!");
+ Assert.False(clientManager.ConnectionManager.LocalClient.IsApproved, $"{clientManager.name} still has IsApproved setting!");
+ Assert.IsNull(clientManager.ConnectionManager.LocalClient.PlayerObject, $"{clientManager.name} still has Player assigned!");
+ }
}
yield return WaitForConditionOrTimeOut(() => m_ClientDisconnected);
@@ -216,6 +225,15 @@ public IEnumerator ClientPlayerDisconnected([Values] ClientDisconnectType client
Assert.IsTrue(m_DisconnectedEvent.ContainsKey(m_ServerNetworkManager), $"Could not find the server {nameof(NetworkManager)} disconnect event entry!");
Assert.IsTrue(m_DisconnectedEvent[m_ServerNetworkManager].ClientId == NetworkManager.ServerClientId, $"Expected ClientID {m_ClientId} but found ClientID {m_DisconnectedEvent[m_ServerNetworkManager].ClientId} for the server {nameof(NetworkManager)} disconnect event entry!");
+ yield return s_DefaultWaitForTick;
+ if (m_ServerNetworkManager.ConnectionManager != null)
+ {
+ Assert.False(m_ServerNetworkManager.ConnectionManager.LocalClient.IsClient, $"{m_ServerNetworkManager.name} still has IsClient setting!");
+ Assert.False(m_ServerNetworkManager.ConnectionManager.LocalClient.IsConnected, $"{m_ServerNetworkManager.name} still has IsConnected setting!");
+ Assert.False(m_ServerNetworkManager.ConnectionManager.LocalClient.ClientId != 0, $"{m_ServerNetworkManager.name} still has ClientId ({clientManager.ConnectionManager.LocalClient.ClientId}) setting!");
+ Assert.False(m_ServerNetworkManager.ConnectionManager.LocalClient.IsApproved, $"{m_ServerNetworkManager.name} still has IsApproved setting!");
+ Assert.IsNull(m_ServerNetworkManager.ConnectionManager.LocalClient.PlayerObject, $"{m_ServerNetworkManager.name} still has Player assigned!");
+ }
}
}
}
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs
index d7465cf37e..ff0106355f 100644
--- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs
@@ -352,5 +352,53 @@ public override void OnNetworkDespawn()
OnNetworkDespawnCalledCount++;
}
}
+
+ private bool AllClientsSpawnedObject()
+ {
+ foreach (var networkManager in m_ClientNetworkManagers)
+ {
+ if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_SpawnedInstanceId))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private bool AllClientsDespawnedObject()
+ {
+ foreach (var networkManager in m_ClientNetworkManagers)
+ {
+ if (networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_SpawnedInstanceId))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private ulong m_SpawnedInstanceId;
+ ///
+ /// Validates that NetworkObject is reset properly when despawned but not destroyed.
+ ///
+ /// IEnumerator
+ [UnityTest]
+ public IEnumerator NetworkObjectResetOnDespawn()
+ {
+ var authorityNetworkManager = m_ServerNetworkManager;
+ var instance = SpawnObject(m_ObserverPrefab, authorityNetworkManager).GetComponent();
+ m_SpawnedInstanceId = instance.NetworkObjectId;
+ yield return WaitForConditionOrTimeOut(AllClientsSpawnedObject);
+ AssertOnTimeout($"Not all clients spawned an instance of {instance.name}!");
+
+ instance.Despawn(false);
+
+ yield return WaitForConditionOrTimeOut(AllClientsDespawnedObject);
+ AssertOnTimeout($"Not all clients de-spawned an instance of {instance.name}!");
+
+ Assert.IsNull(instance.GetNetworkParenting(), "Last parent was not reset!");
+
+ Object.Destroy(instance.gameObject);
+ }
}
}