|
6 | 6 |
|
7 | 7 | namespace Unity.Netcode.RuntimeTests |
8 | 8 | { |
| 9 | + public class ObserversTestClass : NetworkBehaviour |
| 10 | + { |
| 11 | + public NetworkVariable<int> NetVariable = new NetworkVariable<int>(); |
| 12 | + |
| 13 | + public bool ClientRPCCalled; |
| 14 | + |
| 15 | + public void ResetRPCState() |
| 16 | + { |
| 17 | + ClientRPCCalled = false; |
| 18 | + } |
| 19 | + |
| 20 | + [ClientRpc] |
| 21 | + public void TestClientRpc() |
| 22 | + { |
| 23 | + ClientRPCCalled = true; |
| 24 | + } |
| 25 | + } |
| 26 | + |
9 | 27 | [TestFixture(HostOrServer.DAHost)] |
10 | 28 | [TestFixture(HostOrServer.Host)] |
11 | 29 | [TestFixture(HostOrServer.Server)] |
@@ -120,4 +138,125 @@ public IEnumerator SpawnWithNoObservers() |
120 | 138 | } |
121 | 139 | } |
122 | 140 | } |
| 141 | + |
| 142 | + [TestFixture(HostOrServer.DAHost)] |
| 143 | + [TestFixture(HostOrServer.Host)] |
| 144 | + [TestFixture(HostOrServer.Server)] |
| 145 | + internal class PlayerObjectSpawnTests : NetcodeIntegrationTest |
| 146 | + { |
| 147 | + protected override int NumberOfClients => 1; |
| 148 | + |
| 149 | + protected GameObject m_NewPlayerToSpawn; |
| 150 | + protected GameObject m_TestObserversObject; |
| 151 | + |
| 152 | + public PlayerObjectSpawnTests(HostOrServer hostOrServer) : base(hostOrServer) { } |
| 153 | + |
| 154 | + protected override void OnServerAndClientsCreated() |
| 155 | + { |
| 156 | + m_TestObserversObject = CreateNetworkObjectPrefab("ObserversTest"); |
| 157 | + m_TestObserversObject.AddComponent<ObserversTestClass>(); |
| 158 | + |
| 159 | + m_NewPlayerToSpawn = CreateNetworkObjectPrefab("NewPlayerInstance"); |
| 160 | + base.OnServerAndClientsCreated(); |
| 161 | + } |
| 162 | + |
| 163 | + private int GetObserverCount( System.Collections.Generic.HashSet<ulong>.Enumerator observerEnumerator ) |
| 164 | + { |
| 165 | + int observerCount = 0; |
| 166 | + while (observerEnumerator.MoveNext()) |
| 167 | + { |
| 168 | + observerCount++; |
| 169 | + } |
| 170 | + |
| 171 | + return observerCount; |
| 172 | + } |
| 173 | + private ObserversTestClass GetObserversTestClassObjectForClient(ulong clientId) |
| 174 | + { |
| 175 | +#if UNITY_2023_1_OR_NEWER |
| 176 | + var emptyComponents = Object.FindObjectsByType<ObserversTestClass>(FindObjectsSortMode.InstanceID); |
| 177 | +#else |
| 178 | + var emptyComponents = UnityEngine.Object.FindObjectsOfType<EmptyComponent>(); |
| 179 | +#endif |
| 180 | + foreach (var component in emptyComponents) |
| 181 | + { |
| 182 | + if (component.IsSpawned && component.NetworkManager.LocalClientId == clientId) |
| 183 | + { |
| 184 | + return component; |
| 185 | + } |
| 186 | + } |
| 187 | + return null; |
| 188 | + } |
| 189 | + |
| 190 | + // https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues/3059 surfaced a regression from 1.11 to 2.0 |
| 191 | + // where Destroying (not Disconnecting) a player object would remove it from all NetworkObjects observer arrays. Upon recreating |
| 192 | + // the player object they were no longer an observer for exising network objects causing them to miss NetworkVariable and RPC updates. |
| 193 | + // This test covers that case including testing RPCs and NetworkVariables still function after recreating the player object |
| 194 | + [UnityTest] |
| 195 | + public IEnumerator SpawnDestoryRespawnPlayerObjectMaintainsObservers() |
| 196 | + { |
| 197 | + yield return WaitForConditionOrTimeOut(() => m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId].ContainsKey(m_ClientNetworkManagers[0].LocalClientId)); |
| 198 | + AssertOnTimeout("Timed out waiting for client-side player object to spawn!"); |
| 199 | + |
| 200 | + // So on the server we want to spawn the observer object and then check its observers |
| 201 | + var serverObserverObject = SpawnObject(m_TestObserversObject, m_ServerNetworkManager); |
| 202 | + var serverObserverComponent = serverObserverObject.GetComponent<ObserversTestClass>(); |
| 203 | + yield return WaitForConditionOrTimeOut(() => GetObserversTestClassObjectForClient(m_ClientNetworkManagers[0].LocalClientId) != null); |
| 204 | + AssertOnTimeout("Timed out waiting for client-side observer object to spawn!"); |
| 205 | + |
| 206 | + var clientObserverComponent = GetObserversTestClassObjectForClient(m_ClientNetworkManagers[0].LocalClientId); |
| 207 | + Assert.NotNull(clientObserverComponent); |
| 208 | + Assert.AreNotEqual(serverObserverComponent, clientObserverComponent, "Client and Server Observer Test components are equal, the test is wrong."); |
| 209 | + |
| 210 | + // The server object should be the owner |
| 211 | + Assert.IsTrue(serverObserverObject.GetComponent<ObserversTestClass>().IsOwner); |
| 212 | + Assert.IsFalse(clientObserverComponent.IsOwner); |
| 213 | + |
| 214 | + // Test Networkvariables and RPCs function as expected |
| 215 | + serverObserverComponent.NetVariable.Value = 123; |
| 216 | + yield return WaitForConditionOrTimeOut(() => clientObserverComponent.NetVariable.Value == 123); |
| 217 | + AssertOnTimeout("Timed out waiting for network variable to transmit!"); |
| 218 | + |
| 219 | + serverObserverComponent.TestClientRpc(); |
| 220 | + yield return WaitForConditionOrTimeOut(() => clientObserverComponent.ClientRPCCalled); |
| 221 | + AssertOnTimeout("Timed out waiting for RPC to be called!"); |
| 222 | + |
| 223 | + serverObserverComponent.ResetRPCState(); |
| 224 | + clientObserverComponent.ResetRPCState(); |
| 225 | + |
| 226 | + // Destory the clients player object, this will remove the player object but not disconnect the client, it should leave the connection intact |
| 227 | + bool foundPlayer = false; |
| 228 | + ulong destroyedClientId = 0; |
| 229 | + foreach( var c in m_ServerNetworkManager.ConnectedClients) |
| 230 | + { |
| 231 | + if (!c.Value.PlayerObject.GetComponent<NetworkObject>().IsOwner) |
| 232 | + { |
| 233 | + destroyedClientId = c.Key; |
| 234 | + Object.Destroy(c.Value.PlayerObject); |
| 235 | + foundPlayer = true; |
| 236 | + break; |
| 237 | + } |
| 238 | + } |
| 239 | + Assert.True(foundPlayer); |
| 240 | + |
| 241 | + yield return WaitForConditionOrTimeOut(() => m_ServerNetworkManager.ConnectedClients[destroyedClientId].PlayerObject == null); |
| 242 | + AssertOnTimeout("Timed out waiting for player object to be destroyed!"); |
| 243 | + |
| 244 | + |
| 245 | + // so lets respawn the player here |
| 246 | + var newPlayer = Object.Instantiate(m_NewPlayerToSpawn); |
| 247 | + newPlayer.GetComponent<NetworkObject>().SpawnAsPlayerObject(destroyedClientId); |
| 248 | + |
| 249 | + yield return WaitForConditionOrTimeOut(() => m_ServerNetworkManager.ConnectedClients[destroyedClientId].PlayerObject != null); |
| 250 | + AssertOnTimeout("Timed out waiting for player object to respawn!"); |
| 251 | + |
| 252 | + // Test Networkvariables and RPCs function as expected after recreating the client player object |
| 253 | + serverObserverComponent.NetVariable.Value = 321; |
| 254 | + yield return WaitForConditionOrTimeOut(() => clientObserverComponent.NetVariable.Value == 321); |
| 255 | + AssertOnTimeout("Timed out waiting for network variable to transmit after respawn!"); |
| 256 | + |
| 257 | + serverObserverComponent.TestClientRpc(); |
| 258 | + yield return WaitForConditionOrTimeOut(() => clientObserverComponent.ClientRPCCalled); |
| 259 | + AssertOnTimeout("Timed out waiting for RPC to be called after respawn!"); |
| 260 | + } |
| 261 | + } |
123 | 262 | } |
0 commit comments