Skip to content

Commit 6daf541

Browse files
fix: NetworkSpawnManager.OnDespawnObject removing parent on client-side (#2252)
* fix Fixes issue where despawning a parent NetworkObject would try to remove itself from the child on the client-side. * test Adding a test to verify this fix
1 parent 1049865 commit 6daf541

File tree

4 files changed

+103
-4
lines changed

4 files changed

+103
-4
lines changed

com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,14 @@ public bool TrySetParent(GameObject parent, bool worldPositionStays = true)
624624
return networkObject == null ? false : TrySetParent(networkObject, worldPositionStays);
625625
}
626626

627+
/// <summary>
628+
/// Used when despawning the parent, we want to preserve the cached WorldPositionStays value
629+
/// </summary>
630+
internal bool TryRemoveParentCachedWorldPositionStays()
631+
{
632+
return TrySetParent((NetworkObject)null, m_CachedWorldPositionStays);
633+
}
634+
627635
/// <summary>
628636
/// Removes the parent of the NetworkObject's transform
629637
/// </summary>

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -778,15 +778,26 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec
778778
}
779779

780780
// If we are shutting down the NetworkManager, then ignore resetting the parent
781-
if (!NetworkManager.ShutdownInProgress)
781+
// and only attempt to remove the child's parent on the server-side
782+
if (!NetworkManager.ShutdownInProgress && NetworkManager.IsServer)
782783
{
783784
// Move child NetworkObjects to the root when parent NetworkObject is destroyed
784785
foreach (var spawnedNetObj in SpawnedObjectsList)
785786
{
786787
var latestParent = spawnedNetObj.GetNetworkParenting();
787788
if (latestParent.HasValue && latestParent.Value == networkObject.NetworkObjectId)
788789
{
789-
spawnedNetObj.gameObject.transform.parent = null;
790+
// Try to remove the parent using the cached WorldPositioNStays value
791+
// Note: WorldPositionStays will still default to true if this was an
792+
// in-scene placed NetworkObject and parenting was predefined in the
793+
// scene via the editor.
794+
if (!spawnedNetObj.TryRemoveParentCachedWorldPositionStays())
795+
{
796+
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
797+
{
798+
NetworkLog.LogError($"{nameof(NetworkObject)} #{spawnedNetObj.NetworkObjectId} could not be moved to the root when its parent {nameof(NetworkObject)} #{networkObject.NetworkObjectId} was being destroyed");
799+
}
800+
}
790801

791802
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
792803
{

testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentChildHandler.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ public static void ResetInstancesTracking(bool enableVerboseDebug)
4848
ClientRelativeInstances.Clear();
4949
}
5050

51+
public InSceneParentChildHandler GetChild()
52+
{
53+
return m_Child;
54+
}
55+
5156
private Vector3 GenerateVector3(Vector3 min, Vector3 max)
5257
{
5358
var result = Vector3.zero;

testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,13 @@ private void GenerateScaleDoesNotMatch(InSceneParentChildHandler serverHandler,
7878
m_ErrorValidationLog.Append($"[Client-{clientHandler.NetworkManager.LocalClientId}] {nameof(NetworkObject)}-{clientHandler.NetworkObjectId}'s scale {clientHandler.transform.localScale} does not equal the server-side scale {serverHandler.transform.localScale}");
7979
}
8080

81-
private void GenerateParentIsNotCorrect(InSceneParentChildHandler handler, bool shouldHaveParent)
81+
private void GenerateParentIsNotCorrect(InSceneParentChildHandler handler, bool shouldHaveParent, bool isStillSpawnedCheck = false)
8282
{
8383
var serverOrClient = handler.NetworkManager.IsServer ? "Server" : "Client";
84+
var shouldNotBeSpawned = isStillSpawnedCheck ? " and is still spawned!" : string.Empty;
8485
if (!shouldHaveParent)
8586
{
86-
m_ErrorValidationLog.Append($"[{serverOrClient}-{handler.NetworkManager.LocalClientId}] {nameof(NetworkObject)}-{handler.NetworkObjectId}'s still has the parent {handler.transform.parent.name} when it should be null!");
87+
m_ErrorValidationLog.Append($"[{serverOrClient}-{handler.NetworkManager.LocalClientId}] {nameof(NetworkObject)}-{handler.NetworkObjectId}'s still has the parent {handler.transform.parent.name} when it should be null{shouldNotBeSpawned}!");
8788
}
8889
else
8990
{
@@ -127,6 +128,7 @@ private bool ValidateClientAgainstServerTransformValues()
127128

128129
private bool ValidateAllChildrenParentingStatus(bool checkForParent)
129130
{
131+
m_ErrorValidationLog.Clear();
130132
foreach (var instance in InSceneParentChildHandler.ServerRelativeInstances)
131133
{
132134
if (!instance.Value.IsRootParent)
@@ -273,6 +275,79 @@ public IEnumerator InSceneParentingTest([Values] ParentingSpace parentingSpace)
273275
AssertOnTimeout($"[Final Pass - Last Test] Timed out waiting for all children to be removed from their parent!\n {m_ErrorValidationLog}");
274276
}
275277

278+
/// <summary>
279+
/// Validates the root parent is despawned and its child is moved to the root (null)
280+
/// </summary>
281+
private bool ValidateRootParentDespawnedAndChildAtRoot()
282+
{
283+
m_ErrorValidationLog.Clear();
284+
285+
var childOfRoot_ServerSide = InSceneParentChildHandler.ServerRootParent.GetChild();
286+
if (InSceneParentChildHandler.ServerRootParent.IsSpawned)
287+
{
288+
m_ErrorValidationLog.Append("Server-Side root parent is still spawned!");
289+
GenerateParentIsNotCorrect(childOfRoot_ServerSide, false, InSceneParentChildHandler.ServerRootParent.IsSpawned);
290+
return false;
291+
}
292+
293+
if (childOfRoot_ServerSide.transform.parent != null)
294+
{
295+
m_ErrorValidationLog.Append("Server-Side root parent is not null!");
296+
return false;
297+
}
298+
299+
foreach (var clientInstances in InSceneParentChildHandler.ClientRelativeInstances)
300+
{
301+
foreach (var instance in clientInstances.Value)
302+
{
303+
if (instance.Value.IsRootParent)
304+
{
305+
var childHandler = instance.Value.GetChild();
306+
307+
if (instance.Value.IsSpawned)
308+
{
309+
m_ErrorValidationLog.Append("Client-Side is still spawned!");
310+
return false;
311+
}
312+
if (childHandler != null && childHandler.transform.parent != null)
313+
{
314+
m_ErrorValidationLog.Append("Client-Side still has parent!");
315+
return false;
316+
}
317+
}
318+
}
319+
}
320+
return true;
321+
}
322+
323+
[UnityTest]
324+
public IEnumerator DespawnParentTest([Values] ParentingSpace parentingSpace)
325+
{
326+
InSceneParentChildHandler.WorldPositionStays = parentingSpace == ParentingSpace.WorldPositionStays;
327+
SceneManager.sceneLoaded += SceneManager_sceneLoaded;
328+
SceneManager.LoadScene(k_BaseSceneToLoad, LoadSceneMode.Additive);
329+
m_InitialClientsLoadedScene = false;
330+
m_ServerNetworkManager.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent;
331+
332+
var sceneEventStartedStatus = m_ServerNetworkManager.SceneManager.LoadScene(k_TestSceneToLoad, LoadSceneMode.Additive);
333+
Assert.True(sceneEventStartedStatus == SceneEventProgressStatus.Started, $"Failed to load scene {k_TestSceneToLoad} with a return status of {sceneEventStartedStatus}.");
334+
yield return WaitForConditionOrTimeOut(() => m_InitialClientsLoadedScene);
335+
AssertOnTimeout($"Timed out waiting for all clients to load scene {k_TestSceneToLoad}!");
336+
337+
// [Currently Connected Clients]
338+
// remove the parents, change all transform values, and re-parent
339+
InSceneParentChildHandler.ServerRootParent.DeparentSetValuesAndReparent();
340+
yield return WaitForConditionOrTimeOut(ValidateClientAgainstServerTransformValues);
341+
AssertOnTimeout($"Timed out waiting for all clients transform values to match the server transform values!\n {m_ErrorValidationLog}");
342+
343+
// Now despawn the root parent
344+
InSceneParentChildHandler.ServerRootParent.NetworkObject.Despawn(false);
345+
346+
// Verify all clients despawned the parent object and the child of the parent has root as its parent
347+
yield return WaitForConditionOrTimeOut(ValidateRootParentDespawnedAndChildAtRoot);
348+
AssertOnTimeout($"{m_ErrorValidationLog}");
349+
}
350+
276351
private void SceneManager_OnSceneEvent(SceneEvent sceneEvent)
277352
{
278353
if (sceneEvent.SceneName != k_TestSceneToLoad)

0 commit comments

Comments
 (0)