Skip to content

Commit f43e700

Browse files
fix: NetworkObjects do not honor when Auto Object Parent Sync is turned off (#2281)
* fix This resolves the issue where NetworkObjects were not taking Auto Object Parent Sync into consideration. * test The test to validate that nested in-scene placed NetworkObjects that both have Auto Object Parent Synchronization enabled and disabled synchronize their transforms properly on the client side. The same test covers NetworkObjects nested under GameObjects with no NetworkObject components. All nested instances have varying position, rotation, and scale values.
1 parent b5f82ee commit f43e700

File tree

7 files changed

+832
-9
lines changed

7 files changed

+832
-9
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
1111

1212
### Fixed
1313

14+
- Fixed issue where in-scene placed `NetworkObjects` were not honoring the `AutoObjectParentSync` property. (#2281)
1415
- Fixed the issue where `NetworkManager.OnClientConnectedCallback` was being invoked before in-scene placed `NetworkObject`s had been spawned when starting `NetworkManager` as a host. (#2277)
1516
- Creating a `FastBufferReader` with `Allocator.None` will not result in extra memory being allocated for the buffer (since it's owned externally in that scenario). (#2265)
1617

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

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -830,6 +830,9 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa
830830
// parent and then re-parents the child under a GameObject with a NetworkObject component attached.
831831
if (parentNetworkObject == null)
832832
{
833+
// If we are parented under a GameObject, go ahead and mark the world position stays as false
834+
// so clients synchronize their transform in local space. (only for in-scene placed NetworkObjects)
835+
m_CachedWorldPositionStays = false;
833836
return true;
834837
}
835838
else // If the parent still isn't spawned add this to the orphaned children and return false
@@ -841,8 +844,11 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa
841844
else
842845
{
843846
// If we made it this far, go ahead and set the network parenting values
844-
// with the default WorldPoisitonSays value
845-
SetNetworkParenting(parentNetworkObject.NetworkObjectId, true);
847+
// with the WorldPoisitonSays value set to false
848+
// Note: Since in-scene placed NetworkObjects are parented in the scene
849+
// the default "assumption" is that children are parenting local space
850+
// relative.
851+
SetNetworkParenting(parentNetworkObject.NetworkObjectId, false);
846852

847853
// Set the cached parent
848854
m_CachedParent = parentNetworkObject.transform;
@@ -1235,6 +1241,13 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId)
12351241
if (!AlwaysReplicateAsRoot && transform.parent != null)
12361242
{
12371243
parentNetworkObject = transform.parent.GetComponent<NetworkObject>();
1244+
// In-scene placed NetworkObjects parented under GameObjects with no NetworkObject
1245+
// should set the has parent flag and preserve the world position stays value
1246+
if (parentNetworkObject == null && obj.Header.IsSceneObject)
1247+
{
1248+
obj.Header.HasParent = true;
1249+
obj.WorldPositionStays = m_CachedWorldPositionStays;
1250+
}
12381251
}
12391252

12401253
if (parentNetworkObject != null)
@@ -1254,19 +1267,37 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId)
12541267
if (IncludeTransformWhenSpawning == null || IncludeTransformWhenSpawning(OwnerClientId))
12551268
{
12561269
obj.Header.HasTransform = true;
1270+
1271+
// We start with the default AutoObjectParentSync values to determine which transform space we will
1272+
// be synchronizing clients with.
1273+
var syncRotationPositionLocalSpaceRelative = obj.Header.HasParent && !m_CachedWorldPositionStays;
1274+
var syncScaleLocalSpaceRelative = obj.Header.HasParent && !m_CachedWorldPositionStays;
1275+
1276+
// If auto object synchronization is turned off
1277+
if (!AutoObjectParentSync)
1278+
{
1279+
// We always synchronize position and rotation world space relative
1280+
syncRotationPositionLocalSpaceRelative = false;
1281+
// Scale is special, it synchronizes local space relative if it has a
1282+
// parent since applying the world space scale under a parent with scale
1283+
// will result in the improper scale for the child
1284+
syncScaleLocalSpaceRelative = obj.Header.HasParent;
1285+
}
1286+
1287+
12571288
obj.Transform = new SceneObject.TransformData
12581289
{
12591290
// If we are parented and we have the m_CachedWorldPositionStays disabled, then use local space
12601291
// values as opposed world space values.
1261-
Position = parentNetworkObject && !m_CachedWorldPositionStays ? transform.localPosition : transform.position,
1262-
Rotation = parentNetworkObject && !m_CachedWorldPositionStays ? transform.localRotation : transform.rotation,
1292+
Position = syncRotationPositionLocalSpaceRelative ? transform.localPosition : transform.position,
1293+
Rotation = syncRotationPositionLocalSpaceRelative ? transform.localRotation : transform.rotation,
12631294

12641295
// We only use the lossyScale if the NetworkObject has a parent. Multi-generation nested children scales can
12651296
// impact the final scale of the child NetworkObject in question. The solution is to use the lossy scale
12661297
// which can be thought of as "world space scale".
12671298
// More information:
12681299
// https://docs.unity3d.com/ScriptReference/Transform-lossyScale.html
1269-
Scale = parentNetworkObject && !m_CachedWorldPositionStays ? transform.localScale : transform.lossyScale,
1300+
Scale = syncScaleLocalSpaceRelative ? transform.localScale : transform.lossyScale,
12701301
};
12711302
}
12721303

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO
402402

403403
if (networkObject != null)
404404
{
405-
// SPECIAL CASE:
405+
// SPECIAL CASE FOR IN-SCENE PLACED: (only when the parent has a NetworkObject)
406406
// This is a special case scenario where a late joining client has joined and loaded one or
407407
// more scenes that contain nested in-scene placed NetworkObject children yet the server's
408408
// synchronization information does not indicate the NetworkObject in question has a parent.
@@ -423,7 +423,9 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO
423423
// but it is up to the user to set those values
424424
if (sceneObject.Header.HasTransform && !isSpawnedByPrefabHandler)
425425
{
426-
if (worldPositionStays)
426+
// If world position stays is true or we have auto object parent synchronization disabled
427+
// then we want to apply the position and rotation values world space relative
428+
if (worldPositionStays || !networkObject.AutoObjectParentSync)
427429
{
428430
networkObject.transform.position = position;
429431
networkObject.transform.rotation = rotation;
@@ -443,6 +445,9 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO
443445
// that is the default value of Vector3.
444446
if (!sceneObject.Header.IsPlayerObject)
445447
{
448+
// Since scale is always applied to local space scale, we do the transform
449+
// space logic during serialization such that it works out whether AutoObjectParentSync
450+
// is enabled or not (see NetworkObject.SceneObject)
446451
networkObject.transform.localScale = scale;
447452
}
448453
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System.Collections.Generic;
2+
using UnityEngine;
3+
using Unity.Netcode;
4+
5+
6+
namespace TestProject.RuntimeTests
7+
{
8+
/// <summary>
9+
/// Helper class that builds 4 transform lists based on parent-child hierarchy
10+
/// for the <see cref="ParentingInSceneObjectsTests.InSceneNestedAutoSyncObjectTest"/>
11+
/// </summary>
12+
public class ParentingAutoSyncManager : NetworkBehaviour
13+
{
14+
public static ParentingAutoSyncManager ServerInstance;
15+
public static Dictionary<ulong, ParentingAutoSyncManager> ClientInstances = new Dictionary<ulong, ParentingAutoSyncManager>();
16+
17+
public GameObject WithNetworkObjectAutoSyncOn;
18+
public GameObject WithNetworkObjectAutoSyncOff;
19+
public GameObject GameObjectAutoSyncOn;
20+
public GameObject GameObjectAutoSyncOff;
21+
22+
public List<Transform> NetworkObjectAutoSyncOnTransforms = new List<Transform>();
23+
public List<Transform> NetworkObjectAutoSyncOffTransforms = new List<Transform>();
24+
public List<Transform> GameObjectAutoSyncOnTransforms = new List<Transform>();
25+
public List<Transform> GameObjectAutoSyncOffTransforms = new List<Transform>();
26+
27+
public static void Reset()
28+
{
29+
ServerInstance = null;
30+
ClientInstances.Clear();
31+
}
32+
33+
public override void OnNetworkSpawn()
34+
{
35+
if (IsServer)
36+
{
37+
ServerInstance = this;
38+
}
39+
else
40+
{
41+
ClientInstances.Add(NetworkManager.LocalClientId, this);
42+
}
43+
var currentRoot = WithNetworkObjectAutoSyncOn.transform;
44+
NetworkObjectAutoSyncOnTransforms.Add(currentRoot);
45+
NetworkObjectAutoSyncOnTransforms.Add(currentRoot.GetChild(0));
46+
NetworkObjectAutoSyncOnTransforms.Add(currentRoot.GetChild(0).GetChild(0));
47+
48+
currentRoot = WithNetworkObjectAutoSyncOff.transform;
49+
NetworkObjectAutoSyncOffTransforms.Add(currentRoot);
50+
NetworkObjectAutoSyncOffTransforms.Add(currentRoot.GetChild(0));
51+
NetworkObjectAutoSyncOffTransforms.Add(currentRoot.GetChild(0).GetChild(0));
52+
53+
currentRoot = GameObjectAutoSyncOn.transform;
54+
GameObjectAutoSyncOnTransforms.Add(currentRoot);
55+
GameObjectAutoSyncOnTransforms.Add(currentRoot.GetChild(0));
56+
GameObjectAutoSyncOnTransforms.Add(currentRoot.GetChild(0).GetChild(0));
57+
58+
currentRoot = GameObjectAutoSyncOff.transform;
59+
GameObjectAutoSyncOffTransforms.Add(currentRoot);
60+
GameObjectAutoSyncOffTransforms.Add(currentRoot.GetChild(0));
61+
GameObjectAutoSyncOffTransforms.Add(currentRoot.GetChild(0).GetChild(0));
62+
}
63+
}
64+
}

testproject/Assets/Tests/Runtime/ObjectParenting/ParentingAutoSyncManager.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)