Skip to content

Commit 3e764e0

Browse files
fix
This resolves issues with in-scene placed NetworkObject parenting where the original parent has changed. This resolves the issue where loading a scene would not order the serialization of loaded in-scene placed NetworkObjects based on their parent-child hierarchy.
1 parent 53b94ca commit 3e764e0

File tree

4 files changed

+87
-25
lines changed

4 files changed

+87
-25
lines changed

com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3543,7 +3543,10 @@ private void InternalInitialization(bool isOwnershipChange = false)
35433543
{
35443544
if (CanCommitToTransform)
35453545
{
3546-
InLocalSpace = transform.parent != null;
3546+
if (NetworkObject.HasParentNetworkObject(transform))
3547+
{
3548+
InLocalSpace = true;
3549+
}
35473550
}
35483551
// Always apply this if SwitchTransformSpaceWhenParented is set.
35493552
TickSyncChildren = true;

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

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -308,15 +308,18 @@ internal void OnValidate()
308308
/// </remarks>
309309
private void CheckForInScenePlaced()
310310
{
311-
if (PrefabUtility.IsPartOfAnyPrefab(this) && gameObject.scene.IsValid() && gameObject.scene.isLoaded && gameObject.scene.buildIndex >= 0)
311+
if (gameObject.scene.IsValid() && gameObject.scene.isLoaded && gameObject.scene.buildIndex >= 0)
312312
{
313-
var prefab = PrefabUtility.GetCorrespondingObjectFromSource(gameObject);
314-
var assetPath = AssetDatabase.GetAssetPath(prefab);
315-
var sourceAsset = AssetDatabase.LoadAssetAtPath<NetworkObject>(assetPath);
316-
if (sourceAsset != null && sourceAsset.GlobalObjectIdHash != 0 && InScenePlacedSourceGlobalObjectIdHash != sourceAsset.GlobalObjectIdHash)
313+
if (PrefabUtility.IsPartOfAnyPrefab(this))
317314
{
318-
InScenePlacedSourceGlobalObjectIdHash = sourceAsset.GlobalObjectIdHash;
319-
EditorUtility.SetDirty(this);
315+
var prefab = PrefabUtility.GetCorrespondingObjectFromSource(gameObject);
316+
var assetPath = AssetDatabase.GetAssetPath(prefab);
317+
var sourceAsset = AssetDatabase.LoadAssetAtPath<NetworkObject>(assetPath);
318+
if (sourceAsset != null && sourceAsset.GlobalObjectIdHash != 0 && InScenePlacedSourceGlobalObjectIdHash != sourceAsset.GlobalObjectIdHash)
319+
{
320+
InScenePlacedSourceGlobalObjectIdHash = sourceAsset.GlobalObjectIdHash;
321+
EditorUtility.SetDirty(this);
322+
}
320323
}
321324
IsSceneObject = true;
322325

@@ -335,6 +338,24 @@ private void CheckForInScenePlaced()
335338
}
336339
#endif // UNITY_EDITOR
337340

341+
internal bool HasParentNetworkObject(Transform transform)
342+
{
343+
if (transform.parent != null)
344+
{
345+
var networkObject = transform.parent.GetComponent<NetworkObject>();
346+
if (networkObject != null && networkObject != this)
347+
{
348+
return true;
349+
}
350+
351+
if (transform.parent.parent != null)
352+
{
353+
return HasParentNetworkObject(transform.parent);
354+
}
355+
}
356+
return false;
357+
}
358+
338359
/// <summary>
339360
/// Gets the NetworkManager that owns this NetworkObject instance
340361
/// </summary>
@@ -2295,7 +2316,7 @@ private void OnTransformParentChanged()
22952316
// we call CheckOrphanChildren() method and quickly iterate over OrphanChildren set and see if we can reparent/adopt one.
22962317
internal static HashSet<NetworkObject> OrphanChildren = new HashSet<NetworkObject>();
22972318

2298-
internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpawned = false, bool orphanedChildPass = false)
2319+
internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpawned = false, bool orphanedChildPass = false, bool silentParenting = false)
22992320
{
23002321
if (!AutoObjectParentSync)
23012322
{
@@ -2368,7 +2389,10 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa
23682389
// to WorldPositionStays which can cause scaling issues if the parent's
23692390
// scale is not the default (Vetctor3.one) value.
23702391
transform.SetParent(null, m_CachedWorldPositionStays);
2371-
InvokeBehaviourOnNetworkObjectParentChanged(null);
2392+
if (!silentParenting)
2393+
{
2394+
InvokeBehaviourOnNetworkObjectParentChanged(null);
2395+
}
23722396
return true;
23732397
}
23742398

@@ -2393,7 +2417,10 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa
23932417
}
23942418
SetCachedParent(parentObject.transform);
23952419
transform.SetParent(parentObject.transform, m_CachedWorldPositionStays);
2396-
InvokeBehaviourOnNetworkObjectParentChanged(parentObject);
2420+
if (!silentParenting)
2421+
{
2422+
InvokeBehaviourOnNetworkObjectParentChanged(parentObject);
2423+
}
23972424
return true;
23982425
}
23992426

@@ -3083,12 +3110,6 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId = NetworkManager
30833110
var syncRotationPositionLocalSpaceRelative = obj.HasParent && !m_CachedWorldPositionStays;
30843111
var syncScaleLocalSpaceRelative = obj.HasParent && !m_CachedWorldPositionStays;
30853112

3086-
// Always synchronize in-scene placed object's scale using local space
3087-
if (obj.IsSceneObject)
3088-
{
3089-
syncScaleLocalSpaceRelative = obj.HasParent;
3090-
}
3091-
30923113
// If auto object synchronization is turned off
30933114
if (!AutoObjectParentSync)
30943115
{
@@ -3165,6 +3186,16 @@ internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBuf
31653186
// Synchronize NetworkBehaviours
31663187
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(reader));
31673188
networkObject.SynchronizeNetworkBehaviours(ref bufferSerializer, networkManager.LocalClientId);
3189+
Debug.Log($"Spawning {networkObject.name}");
3190+
3191+
// If we are an in-scene placed NetworkObject and we originally had a parent but when synchronized we are
3192+
// being told we do not have a parent, then we want to clear the latest parent so it is not automatically
3193+
// "re-parented" to the original parent. This can happen if not unloading the scene and the parenting of
3194+
// the in-scene placed Networkobject changes several times over different sessions.
3195+
if (sceneObject.IsSceneObject && !sceneObject.HasParent && networkObject.m_LatestParent.HasValue)
3196+
{
3197+
networkObject.m_LatestParent = null;
3198+
}
31683199

31693200
// Spawn the NetworkObject
31703201
networkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, sceneObject, sceneObject.DestroyWithScene);

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

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,14 @@ internal void AddSpawnedNetworkObjects()
329329
m_NetworkObjectsSync.Add(sobj);
330330
}
331331
}
332+
SortObjectsToSync();
333+
}
332334

335+
/// <summary>
336+
/// Used to order the object serialization for both synchronization and scene loading
337+
/// </summary>
338+
private void SortObjectsToSync()
339+
{
333340
// Sort by INetworkPrefabInstanceHandler implementation before the
334341
// NetworkObjects spawned by the implementation
335342
m_NetworkObjectsSync.Sort(SortNetworkObjects);
@@ -671,20 +678,31 @@ internal void SerializeScenePlacedObjects(FastBufferWriter writer)
671678
// If distributed authority mode and sending to the service, then ignore observers
672679
var distributedAuthoritySendingToService = distributedAuthority && TargetClientId == NetworkManager.ServerClientId;
673680

681+
// Clear our objects to sync and build a list of the in-scene placed NetworkObjects instantiated and spawned locally
682+
m_NetworkObjectsSync.Clear();
674683
foreach (var keyValuePairByGlobalObjectIdHash in m_NetworkManager.SceneManager.ScenePlacedObjects)
675684
{
676685
foreach (var keyValuePairBySceneHandle in keyValuePairByGlobalObjectIdHash.Value)
677686
{
678687
if (keyValuePairBySceneHandle.Value.Observers.Contains(TargetClientId) || distributedAuthoritySendingToService)
679688
{
680-
// Serialize the NetworkObject
681-
var sceneObject = keyValuePairBySceneHandle.Value.GetMessageSceneObject(TargetClientId, distributedAuthority);
682-
sceneObject.Serialize(writer);
683-
numberOfObjects++;
689+
m_NetworkObjectsSync.Add(keyValuePairBySceneHandle.Value);
684690
}
685691
}
686692
}
687693

694+
// Sort the objects to sync based on parenting hierarchy
695+
SortObjectsToSync();
696+
697+
// Serialize the sorted objects to sync.
698+
foreach(var objectToSycn in m_NetworkObjectsSync)
699+
{
700+
// Serialize the NetworkObject
701+
var sceneObject = objectToSycn.GetMessageSceneObject(TargetClientId, distributedAuthority);
702+
sceneObject.Serialize(writer);
703+
numberOfObjects++;
704+
}
705+
688706
// Write the number of despawned in-scene placed NetworkObjects
689707
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync.Count);
690708
// Write the scene handle and GlobalObjectIdHash value

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -922,19 +922,29 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO
922922
// SPECIAL CASE FOR IN-SCENE PLACED: (only when the parent has a NetworkObject)
923923
// This is a special case scenario where a late joining client has joined and loaded one or
924924
// more scenes that contain nested in-scene placed NetworkObject children yet the server's
925-
// synchronization information does not indicate the NetworkObject in question has a parent.
926-
// Under this scenario, we want to remove the parent before spawning and setting the transform values.
925+
// synchronization information does not indicate the NetworkObject in question has a parent =or=
926+
// the parent has changed.
927+
// For this we will want to remove the parent before spawning and setting the transform values based
928+
// on several possible scenarios.
927929
if (sceneObject.IsSceneObject && networkObject.transform.parent != null)
928930
{
929931
var parentNetworkObject = networkObject.transform.parent.GetComponent<NetworkObject>();
930932
// if the in-scene placed NetworkObject has a parent NetworkObject but the synchronization information does not
931-
// include parenting, then we need to force the removal of that parent
933+
// include parenting, then we need to force the removal of that parent (i.e. it is at the root)
932934
if (!sceneObject.HasParent && parentNetworkObject)
933935
{
934936
// remove the parent
935937
networkObject.ApplyNetworkParenting(true, true);
936938
}
937-
else if (sceneObject.HasParent && !parentNetworkObject)
939+
else // If we are parented and our latest parent known is not the parent =or= we are keeping world space values when parenting.
940+
if (parentNetworkObject && sceneObject.HasParent && sceneObject.LatestParent.HasValue
941+
&& (sceneObject.LatestParent.Value != parentNetworkObject.NetworkObjectId || sceneObject.WorldPositionStays))
942+
{
943+
// remove the parent silently so we can re-parent an in-scene placed NetworkObject
944+
networkObject.ApplyNetworkParenting(true, true, silentParenting: true);
945+
}
946+
else // special case to handle being parented under a GameObject with no NetworkObject
947+
if (sceneObject.HasParent && !parentNetworkObject)
938948
{
939949
nonNetworkObjectParent = true;
940950
}

0 commit comments

Comments
 (0)