Skip to content

Commit 74b3051

Browse files
fix
This is the back port of PR-3387 that resolves some issues with in-scene placed NetworkObjects in parent-child hierarchy and the synchronization of late joining clients when the hierarchy has changed from that of its default hierarchy defined within the scene.
1 parent 9738a6e commit 74b3051

File tree

3 files changed

+82
-60
lines changed

3 files changed

+82
-60
lines changed

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

Lines changed: 37 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -185,15 +185,18 @@ private bool IsEditingPrefab()
185185
/// </remarks>
186186
private void CheckForInScenePlaced()
187187
{
188-
if (PrefabUtility.IsPartOfAnyPrefab(this) && !IsEditingPrefab() && gameObject.scene.IsValid() && gameObject.scene.isLoaded && gameObject.scene.buildIndex >= 0)
188+
if (gameObject.scene.IsValid() && gameObject.scene.isLoaded && gameObject.scene.buildIndex >= 0)
189189
{
190-
var prefab = PrefabUtility.GetCorrespondingObjectFromSource(gameObject);
191-
var assetPath = AssetDatabase.GetAssetPath(prefab);
192-
var sourceAsset = AssetDatabase.LoadAssetAtPath<NetworkObject>(assetPath);
193-
if (sourceAsset != null && sourceAsset.GlobalObjectIdHash != 0 && InScenePlacedSourceGlobalObjectIdHash != sourceAsset.GlobalObjectIdHash)
190+
if (PrefabUtility.IsPartOfAnyPrefab(this))
194191
{
195-
InScenePlacedSourceGlobalObjectIdHash = sourceAsset.GlobalObjectIdHash;
196-
EditorUtility.SetDirty(this);
192+
var prefab = PrefabUtility.GetCorrespondingObjectFromSource(gameObject);
193+
var assetPath = AssetDatabase.GetAssetPath(prefab);
194+
var sourceAsset = AssetDatabase.LoadAssetAtPath<NetworkObject>(assetPath);
195+
if (sourceAsset != null && sourceAsset.GlobalObjectIdHash != 0 && InScenePlacedSourceGlobalObjectIdHash != sourceAsset.GlobalObjectIdHash)
196+
{
197+
InScenePlacedSourceGlobalObjectIdHash = sourceAsset.GlobalObjectIdHash;
198+
EditorUtility.SetDirty(this);
199+
}
197200
}
198201
IsSceneObject = true;
199202
}
@@ -1241,7 +1244,7 @@ private void OnTransformParentChanged()
12411244
// we call CheckOrphanChildren() method and quickly iterate over OrphanChildren set and see if we can reparent/adopt one.
12421245
internal static HashSet<NetworkObject> OrphanChildren = new HashSet<NetworkObject>();
12431246

1244-
internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpawned = false, bool orphanedChildPass = false)
1247+
internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpawned = false, bool orphanedChildPass = false, bool enableNotification = true)
12451248
{
12461249
if (!AutoObjectParentSync)
12471250
{
@@ -1314,7 +1317,10 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa
13141317
// to WorldPositionStays which can cause scaling issues if the parent's
13151318
// scale is not the default (Vetctor3.one) value.
13161319
transform.SetParent(null, m_CachedWorldPositionStays);
1317-
InvokeBehaviourOnNetworkObjectParentChanged(null);
1320+
if (enableNotification)
1321+
{
1322+
InvokeBehaviourOnNetworkObjectParentChanged(null);
1323+
}
13181324
return true;
13191325
}
13201326

@@ -1340,7 +1346,10 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa
13401346

13411347
m_CachedParent = parentObject.transform;
13421348
transform.SetParent(parentObject.transform, m_CachedWorldPositionStays);
1343-
InvokeBehaviourOnNetworkObjectParentChanged(parentObject);
1349+
if (enableNotification)
1350+
{
1351+
InvokeBehaviourOnNetworkObjectParentChanged(parentObject);
1352+
}
13441353
return true;
13451354
}
13461355

@@ -1819,6 +1828,8 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId)
18191828
{
18201829
var obj = new SceneObject
18211830
{
1831+
HasParent = transform.parent != null,
1832+
WorldPositionStays = m_CachedWorldPositionStays,
18221833
NetworkObjectId = NetworkObjectId,
18231834
OwnerClientId = OwnerClientId,
18241835
IsPlayerObject = IsPlayerObject,
@@ -1829,31 +1840,16 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId)
18291840
TargetClientId = targetClientId
18301841
};
18311842

1832-
NetworkObject parentNetworkObject = null;
1833-
1834-
if (!AlwaysReplicateAsRoot && transform.parent != null)
1843+
// Handle Parenting
1844+
if (!AlwaysReplicateAsRoot && obj.HasParent)
18351845
{
1836-
parentNetworkObject = transform.parent.GetComponent<NetworkObject>();
1837-
// In-scene placed NetworkObjects parented under GameObjects with no NetworkObject
1838-
// should set the has parent flag and preserve the world position stays value
1839-
if (parentNetworkObject == null && obj.IsSceneObject)
1840-
{
1841-
obj.HasParent = true;
1842-
obj.WorldPositionStays = m_CachedWorldPositionStays;
1843-
}
1844-
}
1846+
var parentNetworkObject = transform.parent.GetComponent<NetworkObject>();
18451847

1846-
if (parentNetworkObject != null)
1847-
{
1848-
obj.HasParent = true;
1849-
obj.ParentObjectId = parentNetworkObject.NetworkObjectId;
1850-
obj.WorldPositionStays = m_CachedWorldPositionStays;
1851-
var latestParent = GetNetworkParenting();
1852-
var isLatestParentSet = latestParent != null && latestParent.HasValue;
1853-
obj.IsLatestParentSet = isLatestParentSet;
1854-
if (isLatestParentSet)
1848+
if (parentNetworkObject)
18551849
{
1856-
obj.LatestParent = latestParent.Value;
1850+
obj.ParentObjectId = parentNetworkObject.NetworkObjectId;
1851+
obj.LatestParent = GetNetworkParenting();
1852+
obj.IsLatestParentSet = obj.LatestParent != null && obj.LatestParent.HasValue;
18571853
}
18581854
}
18591855

@@ -1866,12 +1862,6 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId)
18661862
var syncRotationPositionLocalSpaceRelative = obj.HasParent && !m_CachedWorldPositionStays;
18671863
var syncScaleLocalSpaceRelative = obj.HasParent && !m_CachedWorldPositionStays;
18681864

1869-
// Always synchronize in-scene placed object's scale using local space
1870-
if (obj.IsSceneObject)
1871-
{
1872-
syncScaleLocalSpaceRelative = obj.HasParent;
1873-
}
1874-
18751865
// If auto object synchronization is turned off
18761866
if (!AutoObjectParentSync)
18771867
{
@@ -1949,6 +1939,15 @@ internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBuf
19491939
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(reader));
19501940
networkObject.SynchronizeNetworkBehaviours(ref bufferSerializer, networkManager.LocalClientId);
19511941

1942+
// If we are an in-scene placed NetworkObject and we originally had a parent but when synchronized we are
1943+
// being told we do not have a parent, then we want to clear the latest parent so it is not automatically
1944+
// "re-parented" to the original parent. This can happen if not unloading the scene and the parenting of
1945+
// the in-scene placed Networkobject changes several times over different sessions.
1946+
if (sceneObject.IsSceneObject && !sceneObject.HasParent && networkObject.m_LatestParent.HasValue)
1947+
{
1948+
networkObject.m_LatestParent = null;
1949+
}
1950+
19521951
// Spawn the NetworkObject
19531952
networkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, sceneObject, sceneObject.DestroyWithScene);
19541953

com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,14 @@ internal void AddSpawnedNetworkObjects()
322322
m_NetworkObjectsSync.Add(sobj);
323323
}
324324
}
325+
SortObjectsToSync();
326+
}
325327

328+
/// <summary>
329+
/// Used to order the object serialization for both synchronization and scene loading
330+
/// </summary>
331+
private void SortObjectsToSync()
332+
{
326333
// Sort by INetworkPrefabInstanceHandler implementation before the
327334
// NetworkObjects spawned by the implementation
328335
m_NetworkObjectsSync.Sort(SortNetworkObjects);
@@ -568,20 +575,31 @@ internal void SerializeScenePlacedObjects(FastBufferWriter writer)
568575
// Write our count place holder (must not be packed!)
569576
writer.WriteValueSafe((ushort)0);
570577

578+
// Clear our objects to sync and build a list of the in-scene placed NetworkObjects instantiated and spawned locally
579+
m_NetworkObjectsSync.Clear();
571580
foreach (var keyValuePairByGlobalObjectIdHash in m_NetworkManager.SceneManager.ScenePlacedObjects)
572581
{
573582
foreach (var keyValuePairBySceneHandle in keyValuePairByGlobalObjectIdHash.Value)
574583
{
575584
if (keyValuePairBySceneHandle.Value.Observers.Contains(TargetClientId))
576585
{
577-
// Serialize the NetworkObject
578-
var sceneObject = keyValuePairBySceneHandle.Value.GetMessageSceneObject(TargetClientId);
579-
sceneObject.Serialize(writer);
580-
numberOfObjects++;
586+
m_NetworkObjectsSync.Add(keyValuePairBySceneHandle.Value);
581587
}
582588
}
583589
}
584590

591+
// Sort the objects to sync based on parenting hierarchy
592+
SortObjectsToSync();
593+
594+
// Serialize the sorted objects to sync.
595+
foreach (var objectToSycn in m_NetworkObjectsSync)
596+
{
597+
// Serialize the NetworkObject
598+
var sceneObject = objectToSycn.GetMessageSceneObject(TargetClientId);
599+
sceneObject.Serialize(writer);
600+
numberOfObjects++;
601+
}
602+
585603
// Write the number of despawned in-scene placed NetworkObjects
586604
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync.Count);
587605
// Write the scene handle and GlobalObjectIdHash value

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

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,6 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO
573573
var scale = sceneObject.HasTransform ? sceneObject.Transform.Scale : default;
574574
var parentNetworkId = sceneObject.HasParent ? sceneObject.ParentObjectId : default;
575575
var worldPositionStays = (!sceneObject.HasParent) || sceneObject.WorldPositionStays;
576-
var isSpawnedByPrefabHandler = false;
577576

578577
// If scene management is disabled or the NetworkObject was dynamically spawned
579578
if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.IsSceneObject)
@@ -605,33 +604,40 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO
605604
networkObject.DestroyWithScene = sceneObject.DestroyWithScene;
606605
networkObject.NetworkSceneHandle = sceneObject.NetworkSceneHandle;
607606

608-
609607
var nonNetworkObjectParent = false;
610608
// SPECIAL CASE FOR IN-SCENE PLACED: (only when the parent has a NetworkObject)
611609
// This is a special case scenario where a late joining client has joined and loaded one or
612610
// more scenes that contain nested in-scene placed NetworkObject children yet the server's
613-
// synchronization information does not indicate the NetworkObject in question has a parent.
614-
// Under this scenario, we want to remove the parent before spawning and setting the transform values.
611+
// synchronization information does not indicate the NetworkObject in question has a parent = or =
612+
// the parent has changed.
613+
// For this we will want to remove the parent before spawning and setting the transform values based
614+
// on several possible scenarios.
615615
if (sceneObject.IsSceneObject && networkObject.transform.parent != null)
616616
{
617617
var parentNetworkObject = networkObject.transform.parent.GetComponent<NetworkObject>();
618-
// if the in-scene placed NetworkObject has a parent NetworkObject but the synchronization information does not
619-
// include parenting, then we need to force the removal of that parent
620-
if (!sceneObject.HasParent && parentNetworkObject)
621-
{
622-
// remove the parent
623-
networkObject.ApplyNetworkParenting(true, true);
624-
}
625-
else if (sceneObject.HasParent && !parentNetworkObject)
618+
// special case to handle being parented under a GameObject with no NetworkObject
619+
nonNetworkObjectParent = !parentNetworkObject && sceneObject.HasParent;
620+
621+
// If the in-scene placed NetworkObject has a parent NetworkObject...
622+
if (parentNetworkObject)
626623
{
627-
nonNetworkObjectParent = true;
624+
// Then remove the parent only if:
625+
// - The authority says we don't have a parent (but locally we do).
626+
// - The auhtority says we have a parent but either of the two are true:
627+
// -- It isn't the same parent.
628+
// -- It was parented using world position stays.
629+
if (!sceneObject.HasParent || (sceneObject.IsLatestParentSet
630+
&& (sceneObject.LatestParent.Value != parentNetworkObject.NetworkObjectId || sceneObject.WorldPositionStays)))
631+
{
632+
// If parenting without notifications then we are temporarily removing the parent to set the transform
633+
// values before reparenting under the current parent.
634+
networkObject.ApplyNetworkParenting(true, true, enableNotification: !sceneObject.HasParent);
635+
}
628636
}
629637
}
630638

631-
// Set the transform unless we were spawned by a prefab handler
632-
// Note: prefab handlers are provided the position and rotation
633-
// but it is up to the user to set those values
634-
if (sceneObject.HasTransform && !isSpawnedByPrefabHandler)
639+
// Set the transform only if the sceneObject includes transform information.
640+
if (sceneObject.HasTransform)
635641
{
636642
// If world position stays is true or we have auto object parent synchronization disabled
637643
// then we want to apply the position and rotation values world space relative
@@ -674,7 +680,6 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO
674680
networkObject.SetNetworkParenting(parentId, worldPositionStays);
675681
}
676682

677-
678683
// Dynamically spawned NetworkObjects that occur during a LoadSceneMode.Single load scene event are migrated into the DDOL
679684
// until the scene is loaded. They are then migrated back into the newly loaded and currently active scene.
680685
if (!sceneObject.IsSceneObject && NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad)

0 commit comments

Comments
 (0)