Skip to content

Commit d58ffb1

Browse files
fix: in-scene parenting WorldPositionStays transform synchronization without NetworkTransform [MTT-7856] (#2796)
* Test Fix InSceneParentChildHandler was not adding the server side objects to check. * fix This fixes the issue with World Position Stays being set not working along this: - The integration scene names not being added with complex nested children - Parents and children were not being ordered as parent first then child But now that this is fixed, the world position stays set to false no longer works (whack a mole). Account for multiple children under any given parent for sorting. Minor adjustment to how scale is synchronized with in-scene placed NetworkObjects. * update Improved SceneEventData parent sorting
1 parent 21a0a1b commit d58ffb1

File tree

6 files changed

+164
-54
lines changed

6 files changed

+164
-54
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ Additional documentation and release notes are available at [Multiplayer Documen
1313
### Fixed
1414

1515
- Fixed issue where NetworkTransform could potentially attempt to "unregister" a named message prior to it being registered. (#2807)
16+
- Fixed issue where in-scene placed `NetworkObject`s with complex nested children `NetworkObject`s (more than one child in depth) would not synchronize properly if WorldPositionStays was set to true. (#2796)
1617

1718
### Changed
1819

1920
- Changed `NetworkTransform` to now use `NetworkTransformMessage` as opposed to named messages for NetworkTransformState updates. (#2810)
2021
- Changed `CustomMessageManager` so it no longer attempts to register or "unregister" a null or empty string and will log an error if this condition occurs. (#2807)
2122

22-
2323
## [1.8.0] - 2023-12-12
2424

2525
### Added

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

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -992,6 +992,11 @@ internal void SetCachedParent(Transform parentTransform)
992992
m_CachedParent = parentTransform;
993993
}
994994

995+
internal Transform GetCachedParent()
996+
{
997+
return m_CachedParent;
998+
}
999+
9951000
internal ulong? GetNetworkParenting() => m_LatestParent;
9961001

9971002
internal void SetNetworkParenting(ulong? latestParent, bool worldPositionStays)
@@ -1236,7 +1241,7 @@ private void OnTransformParentChanged()
12361241
// we call CheckOrphanChildren() method and quickly iterate over OrphanChildren set and see if we can reparent/adopt one.
12371242
internal static HashSet<NetworkObject> OrphanChildren = new HashSet<NetworkObject>();
12381243

1239-
internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpawned = false)
1244+
internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpawned = false, bool orphanedChildPass = false)
12401245
{
12411246
if (!AutoObjectParentSync)
12421247
{
@@ -1324,9 +1329,17 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa
13241329
// If we made it here, then parent this instance under the parentObject
13251330
var parentObject = NetworkManager.SpawnManager.SpawnedObjects[m_LatestParent.Value];
13261331

1332+
// If we are handling an orphaned child and its parent is orphaned too, then don't parent yet.
1333+
if (orphanedChildPass)
1334+
{
1335+
if (OrphanChildren.Contains(parentObject))
1336+
{
1337+
return false;
1338+
}
1339+
}
1340+
13271341
m_CachedParent = parentObject.transform;
13281342
transform.SetParent(parentObject.transform, m_CachedWorldPositionStays);
1329-
13301343
InvokeBehaviourOnNetworkObjectParentChanged(parentObject);
13311344
return true;
13321345
}
@@ -1336,7 +1349,7 @@ internal static void CheckOrphanChildren()
13361349
var objectsToRemove = new List<NetworkObject>();
13371350
foreach (var orphanObject in OrphanChildren)
13381351
{
1339-
if (orphanObject.ApplyNetworkParenting())
1352+
if (orphanObject.ApplyNetworkParenting(orphanedChildPass: true))
13401353
{
13411354
objectsToRemove.Add(orphanObject);
13421355
}
@@ -1797,6 +1810,12 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId)
17971810
var syncRotationPositionLocalSpaceRelative = obj.HasParent && !m_CachedWorldPositionStays;
17981811
var syncScaleLocalSpaceRelative = obj.HasParent && !m_CachedWorldPositionStays;
17991812

1813+
// Always synchronize in-scene placed object's scale using local space
1814+
if (obj.IsSceneObject)
1815+
{
1816+
syncScaleLocalSpaceRelative = obj.HasParent;
1817+
}
1818+
18001819
// If auto object synchronization is turned off
18011820
if (!AutoObjectParentSync)
18021821
{
@@ -1808,7 +1827,6 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId)
18081827
syncScaleLocalSpaceRelative = obj.HasParent;
18091828
}
18101829

1811-
18121830
obj.Transform = new SceneObject.TransformData
18131831
{
18141832
// If we are parented and we have the m_CachedWorldPositionStays disabled, then use local space

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

Lines changed: 73 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,73 @@ internal void InitializeForSynch()
245245
}
246246
}
247247

248+
/// <summary>
249+
/// Used with SortParentedNetworkObjects to sort the children of the root parent NetworkObject
250+
/// </summary>
251+
/// <param name="first">object to be sorted</param>
252+
/// <param name="second">object to be compared to for sorting the first object</param>
253+
/// <returns></returns>
254+
private int SortChildrenNetworkObjects(NetworkObject first, NetworkObject second)
255+
{
256+
var firstParent = first.GetCachedParent()?.GetComponent<NetworkObject>();
257+
// If the second is the first's parent then move the first down
258+
if (firstParent != null && firstParent == second)
259+
{
260+
return 1;
261+
}
262+
263+
var secondParent = second.GetCachedParent()?.GetComponent<NetworkObject>();
264+
// If the first is the second's parent then move the first up
265+
if (secondParent != null && secondParent == first)
266+
{
267+
return -1;
268+
}
269+
270+
// Otherwise, don't move the first at all
271+
return 0;
272+
}
273+
274+
/// <summary>
275+
/// Sorts the synchronization order of the NetworkObjects to be serialized
276+
/// by parents before children order
277+
/// </summary>
278+
private void SortParentedNetworkObjects()
279+
{
280+
var networkObjectList = m_NetworkObjectsSync.ToList();
281+
foreach (var networkObject in networkObjectList)
282+
{
283+
// Find only the root parent NetworkObjects
284+
if (networkObject.transform.childCount > 0 && networkObject.transform.parent == null)
285+
{
286+
// Get all child NetworkObjects of the root
287+
var childNetworkObjects = networkObject.GetComponentsInChildren<NetworkObject>().ToList();
288+
289+
childNetworkObjects.Sort(SortChildrenNetworkObjects);
290+
291+
// Remove the root from the children list
292+
childNetworkObjects.Remove(networkObject);
293+
294+
// Remove the root's children from the primary list
295+
foreach (var childObject in childNetworkObjects)
296+
{
297+
m_NetworkObjectsSync.Remove(childObject);
298+
}
299+
// Insert or Add the sorted children list
300+
var nextIndex = m_NetworkObjectsSync.IndexOf(networkObject) + 1;
301+
if (nextIndex == m_NetworkObjectsSync.Count)
302+
{
303+
m_NetworkObjectsSync.AddRange(childNetworkObjects);
304+
}
305+
else
306+
{
307+
m_NetworkObjectsSync.InsertRange(nextIndex, childNetworkObjects);
308+
}
309+
}
310+
}
311+
}
312+
313+
internal static bool LogSerializationOrder = false;
314+
248315
internal void AddSpawnedNetworkObjects()
249316
{
250317
m_NetworkObjectsSync.Clear();
@@ -256,22 +323,22 @@ internal void AddSpawnedNetworkObjects()
256323
}
257324
}
258325

259-
// Sort by parents before children
260-
m_NetworkObjectsSync.Sort(SortParentedNetworkObjects);
261-
262326
// Sort by INetworkPrefabInstanceHandler implementation before the
263327
// NetworkObjects spawned by the implementation
264328
m_NetworkObjectsSync.Sort(SortNetworkObjects);
265329

330+
// The last thing we sort is parents before children
331+
SortParentedNetworkObjects();
332+
266333
// This is useful to know what NetworkObjects a client is going to be synchronized with
267334
// as well as the order in which they will be deserialized
268-
if (m_NetworkManager.LogLevel == LogLevel.Developer)
335+
if (LogSerializationOrder && m_NetworkManager.LogLevel == LogLevel.Developer)
269336
{
270337
var messageBuilder = new System.Text.StringBuilder(0xFFFF);
271-
messageBuilder.Append("[Server-Side Client-Synchronization] NetworkObject serialization order:");
338+
messageBuilder.AppendLine("[Server-Side Client-Synchronization] NetworkObject serialization order:");
272339
foreach (var networkObject in m_NetworkObjectsSync)
273340
{
274-
messageBuilder.Append($"{networkObject.name}");
341+
messageBuilder.AppendLine($"{networkObject.name}");
275342
}
276343
NetworkLog.LogInfo(messageBuilder.ToString());
277344
}
@@ -362,32 +429,6 @@ private int SortNetworkObjects(NetworkObject first, NetworkObject second)
362429
return 0;
363430
}
364431

365-
/// <summary>
366-
/// Sorts the synchronization order of the NetworkObjects to be serialized
367-
/// by parents before children.
368-
/// </summary>
369-
/// <remarks>
370-
/// This only handles late joining players. Spawning and nesting several children
371-
/// dynamically is still handled by the orphaned child list when deserialized out of
372-
/// hierarchical order (i.e. Spawn parent and child dynamically, parent message is
373-
/// dropped and re-sent but child object is received and processed)
374-
/// </remarks>
375-
private int SortParentedNetworkObjects(NetworkObject first, NetworkObject second)
376-
{
377-
// If the first has a parent, move the first down
378-
if (first.transform.parent != null)
379-
{
380-
return 1;
381-
}
382-
else // If the second has a parent and the first does not, then move the first up
383-
if (second.transform.parent != null)
384-
{
385-
return -1;
386-
}
387-
return 0;
388-
}
389-
390-
391432
/// <summary>
392433
/// Client and Server Side:
393434
/// Serializes data based on the SceneEvent type (<see cref="SceneEventType"/>)

com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -165,13 +165,30 @@ private static void ProcessInSceneObjects(Scene scene, NetworkManager networkMan
165165

166166
foreach (var sobj in inSceneNetworkObjects)
167167
{
168-
if (sobj.NetworkManagerOwner != networkManager)
169-
{
170-
sobj.NetworkManagerOwner = networkManager;
171-
}
172-
if (sobj.GetComponent<ObjectNameIdentifier>() == null && sobj.GetComponentInChildren<ObjectNameIdentifier>() == null)
168+
ProcessInSceneObject(sobj, networkManager);
169+
}
170+
}
171+
172+
/// <summary>
173+
/// Assures to apply an ObjectNameIdentifier to all children
174+
/// </summary>
175+
private static void ProcessInSceneObject(NetworkObject networkObject, NetworkManager networkManager)
176+
{
177+
if (networkObject.NetworkManagerOwner != networkManager)
178+
{
179+
networkObject.NetworkManagerOwner = networkManager;
180+
}
181+
if (networkObject.GetComponent<ObjectNameIdentifier>() == null)
182+
{
183+
networkObject.gameObject.AddComponent<ObjectNameIdentifier>();
184+
var networkObjects = networkObject.gameObject.GetComponentsInChildren<NetworkObject>();
185+
foreach (var child in networkObjects)
173186
{
174-
sobj.gameObject.AddComponent<ObjectNameIdentifier>();
187+
if (child == networkObject)
188+
{
189+
continue;
190+
}
191+
ProcessInSceneObject(child, networkManager);
175192
}
176193
}
177194
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ public override void OnNetworkSpawn()
174174
{
175175
ServerRootParent = this;
176176
}
177+
ServerRelativeInstances.Add(NetworkObjectId, this);
177178
}
178179
else
179180
{

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

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ protected override IEnumerator OnTearDown()
6666

6767
private void GeneratePositionDoesNotMatch(InSceneParentChildHandler serverHandler, InSceneParentChildHandler clientHandler)
6868
{
69-
m_ErrorValidationLog.Append($"[Client-{clientHandler.NetworkManager.LocalClientId}] {nameof(NetworkObject)}-{clientHandler.NetworkObjectId}'s position {clientHandler.transform.position} does not equal the server-side position {serverHandler.transform.position}");
69+
m_ErrorValidationLog.AppendLine($"[Client-{clientHandler.NetworkManager.LocalClientId}] {nameof(NetworkObject)}-{clientHandler.NetworkObjectId}'s position {clientHandler.transform.position} does not equal the server-side position {serverHandler.transform.position}");
7070
}
7171

7272
private void GenerateRotationDoesNotMatch(InSceneParentChildHandler serverHandler, InSceneParentChildHandler clientHandler)
@@ -76,7 +76,7 @@ private void GenerateRotationDoesNotMatch(InSceneParentChildHandler serverHandle
7676

7777
private void GenerateScaleDoesNotMatch(InSceneParentChildHandler serverHandler, InSceneParentChildHandler clientHandler)
7878
{
79-
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}");
79+
m_ErrorValidationLog.AppendLine($"[Client-{clientHandler.NetworkManager.LocalClientId}] {nameof(NetworkObject)}-{clientHandler.NetworkObjectId}'s scale {clientHandler.transform.localScale} does not equal the server-side scale {serverHandler.transform.localScale}");
8080
}
8181

8282
private void GenerateParentIsNotCorrect(InSceneParentChildHandler handler, bool shouldHaveParent, bool isStillSpawnedCheck = false)
@@ -85,18 +85,41 @@ private void GenerateParentIsNotCorrect(InSceneParentChildHandler handler, bool
8585
var shouldNotBeSpawned = isStillSpawnedCheck ? " and is still spawned!" : string.Empty;
8686
if (!shouldHaveParent)
8787
{
88-
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}!");
88+
m_ErrorValidationLog.AppendLine($"[{serverOrClient}-{handler.NetworkManager.LocalClientId}] {nameof(NetworkObject)}-{handler.NetworkObjectId}'s still has the parent {handler.transform.parent.name} when it should be null{shouldNotBeSpawned}!");
8989
}
9090
else
9191
{
92-
m_ErrorValidationLog.Append($"[{serverOrClient}-{handler.NetworkManager.LocalClientId}] {nameof(NetworkObject)}-{handler.NetworkObjectId}'s does not have a parent when it should!");
92+
m_ErrorValidationLog.AppendLine($"[{serverOrClient}-{handler.NetworkManager.LocalClientId}] {nameof(NetworkObject)}-{handler.NetworkObjectId}'s does not have a parent when it should!");
93+
}
94+
}
95+
96+
private void GenerateTransformInfo(InSceneParentChildHandler serverHandler, InSceneParentChildHandler clientHandler, ulong failingClient)
97+
{
98+
if (clientHandler.NetworkManager.LocalClientId != failingClient)
99+
{
100+
return;
101+
}
102+
var serverChild = serverHandler.transform;
103+
var clientChild = clientHandler.transform;
104+
m_ErrorValidationLog.AppendLine($"[server-child][{serverChild.name}] Pos: {serverChild.position} | Rot: {serverChild.eulerAngles} | Scale: {serverChild.localScale}");
105+
m_ErrorValidationLog.AppendLine($"[client-child][{clientChild.name}] Pos: {clientChild.position} | Rot: {clientChild.eulerAngles} | Scale: {clientChild.localScale}");
106+
var clientParent = clientHandler.transform.parent;
107+
var serverParent = serverHandler.transform.parent;
108+
while (serverParent && clientParent)
109+
{
110+
m_ErrorValidationLog.AppendLine($"[server-parent][{serverParent.name}] Pos: {serverParent.position} | Rot: {serverParent.eulerAngles} | Scale: {serverParent.localScale}");
111+
m_ErrorValidationLog.AppendLine($"[client-parent][{clientParent.name}] Pos: {clientParent.position} | Rot: {clientParent.eulerAngles} | Scale: {clientParent.localScale}");
112+
clientParent = clientParent.parent;
113+
serverParent = serverParent.parent;
93114
}
94115
}
95116

96117
private bool ValidateClientAgainstServerTransformValues()
97118
{
98119
// We reset this each time because we are only interested in the last time it checked and failed
99120
m_ErrorValidationLog.Clear();
121+
var passed = true;
122+
var failingClient = (ulong)0;
100123
foreach (var instance in InSceneParentChildHandler.ServerRelativeInstances)
101124
{
102125
var serverInstanceTransform = instance.Value.transform;
@@ -108,25 +131,35 @@ private bool ValidateClientAgainstServerTransformValues()
108131
if (!Approximately(serverInstanceTransform.position, clientInstanceTransform.position))
109132
{
110133
GeneratePositionDoesNotMatch(instance.Value, clientInstance);
111-
return false;
134+
passed = false;
135+
failingClient = clientInstance.NetworkManager.LocalClientId;
112136
}
113137

114138
if (!Approximately(serverInstanceTransform.eulerAngles, clientInstanceTransform.eulerAngles))
115139
{
116-
GeneratePositionDoesNotMatch(instance.Value, clientInstance);
117-
return false;
140+
GenerateRotationDoesNotMatch(instance.Value, clientInstance);
141+
passed = false;
142+
failingClient = clientInstance.NetworkManager.LocalClientId;
118143
}
119144

120145
if (!Approximately(serverInstanceTransform.localScale, clientInstanceTransform.localScale))
121146
{
122-
GeneratePositionDoesNotMatch(instance.Value, clientInstance);
123-
return false;
147+
GenerateScaleDoesNotMatch(instance.Value, clientInstance);
148+
passed = false;
149+
failingClient = clientInstance.NetworkManager.LocalClientId;
150+
}
151+
152+
if (!passed && LogTransform)
153+
{
154+
GenerateTransformInfo(instance.Value, clientInstance, failingClient);
124155
}
125156
}
126157
}
127-
return true;
158+
return passed;
128159
}
129160

161+
internal bool LogTransform;
162+
130163
private bool ValidateAllChildrenParentingStatus(bool checkForParent)
131164
{
132165
m_ErrorValidationLog.Clear();
@@ -215,7 +248,7 @@ public IEnumerator InSceneParentingTest([Values] ParentingSpace parentingSpace)
215248
// Make sure the late joining client synchronizes properly
216249
yield return CreateAndStartNewClient();
217250
yield return WaitForConditionOrTimeOut(ValidateClientAgainstServerTransformValues);
218-
AssertOnTimeout($"Timed out waiting for the late joining client's transform values to match the server transform values!\n {m_ErrorValidationLog}");
251+
AssertOnTimeout($"[Late Join 1] Timed out waiting for the late joining client's transform values to match the server transform values!\n {m_ErrorValidationLog}");
219252

220253
// Remove the parents from all of the children
221254
InSceneParentChildHandler.ServerRootParent.DeparentAllChildren();

0 commit comments

Comments
 (0)