Skip to content

Commit c70850a

Browse files
Fixed issue with destroying player objects removing client as observer on NetworkObjects.
1 parent cb7ec98 commit c70850a

File tree

2 files changed

+139
-14
lines changed

2 files changed

+139
-14
lines changed

com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1508,20 +1508,6 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec
15081508
SpawnedObjectsList.Remove(networkObject);
15091509
}
15101510

1511-
// DANGO-TODO: When we fix the issue with observers not being applied to NetworkObjects,
1512-
// (client connect/disconnect) we can remove this hacky way of doing this.
1513-
// Basically, when a player disconnects and/or is destroyed they are removed as an observer from all other client
1514-
// NetworkOject instances.
1515-
if (networkObject.IsPlayerObject && !networkObject.IsOwner && networkObject.OwnerClientId != NetworkManager.LocalClientId)
1516-
{
1517-
foreach (var netObject in SpawnedObjects)
1518-
{
1519-
if (netObject.Value.Observers.Contains(networkObject.OwnerClientId))
1520-
{
1521-
netObject.Value.Observers.Remove(networkObject.OwnerClientId);
1522-
}
1523-
}
1524-
}
15251511
if (networkObject.IsPlayerObject)
15261512
{
15271513
RemovePlayerObject(networkObject);

com.unity.netcode.gameobjects/Tests/Runtime/PlayerObjectTests.cs

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,24 @@
66

77
namespace Unity.Netcode.RuntimeTests
88
{
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+
927
[TestFixture(HostOrServer.DAHost)]
1028
[TestFixture(HostOrServer.Host)]
1129
[TestFixture(HostOrServer.Server)]
@@ -120,4 +138,125 @@ public IEnumerator SpawnWithNoObservers()
120138
}
121139
}
122140
}
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+
}
123262
}

0 commit comments

Comments
 (0)