Skip to content

Commit c6f5c16

Browse files
fix: owner authoritative parenting backport (#3453)
This PR backports the the `NetworkObject.SyncOwnerTransformWhenParented` and `NetworkObject.AllowOwnerToParent` functionality. [MTTB-1284](https://jira.unity3d.com/browse/MTTB-1284) fix: #2842 ## Changelog - Added: `NetworkObject.SyncOwnerTransformWhenParented` which will not synchronize transform values sent by the server when parented (primarily for owner authoritative motion models). - Added: `NetworkObject.AllowOwnerToParent` which allows a client owner to parent locally and have the changes synchronize with the server and other clients (primarily for owner authoritative motion models). ## Testing and Documentation - Includes backported integration test modified to the constraints of v1.x. - Includes additions to public API documentation. <!-- Uncomment and mark items off with a * if this PR deprecates any API: ### Deprecated API - [ ] An `[Obsolete]` attribute was added along with a `(RemovedAfter yyyy-mm-dd)` entry. - [ ] An [api updater] was added. - [ ] Deprecation of the API is explained in the CHANGELOG. - [ ] The users can understand why this API was removed and what they should use instead. --> ## Backport No back port needed as this is a targeted backport of certain v2.x features that could be useful to projects still using v1.x. --------- Co-authored-by: Emma <[email protected]>
1 parent 536f075 commit c6f5c16

File tree

4 files changed

+323
-13
lines changed

4 files changed

+323
-13
lines changed

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

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,24 @@ private GlobalObjectId GetGlobalId()
417417
/// </summary>
418418
public bool AutoObjectParentSync = true;
419419

420+
/// <summary>
421+
/// Determines if the owner will apply transform values sent by the parenting message.
422+
/// </summary>
423+
/// <remarks>
424+
/// When enabled, the resultant parenting transform changes sent by the authority will be applied on all instances. <br />
425+
/// When disabled, the resultant parenting transform changes sent by the authority will not be applied on the owner's instance. <br />
426+
/// When disabled, all non-owner instances will still be synchronized by the authority's transform values when parented.
427+
/// </remarks>
428+
[Tooltip("When disabled (default enabled), the owner will not apply a server or host's transform properties when parenting changes. Primarily useful for client-server network topology configurations.")]
429+
public bool SyncOwnerTransformWhenParented = true;
430+
431+
/// <summary>
432+
/// Client-Server specific, when enabled an owner of a NetworkObject can parent locally as opposed to requiring the owner to notify the server it would like to be parented.
433+
/// This behavior is always true when using a distributed authority network topology and does not require it to be set.
434+
/// </summary>
435+
[Tooltip("When enabled (default disabled), owner's can parent a NetworkObject locally without having to send an RPC to the server or host. Only pertinent when using client-server network topology configurations.")]
436+
public bool AllowOwnerToParent;
437+
420438
internal readonly HashSet<ulong> Observers = new HashSet<ulong>();
421439

422440
#if MULTIPLAYER_TOOLS
@@ -1086,8 +1104,9 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true)
10861104
{
10871105
return false;
10881106
}
1089-
1090-
if (!NetworkManager.IsServer && !NetworkManager.ShutdownInProgress)
1107+
// If we don't have authority and we are not shutting down, then don't allow any parenting.
1108+
// If we are shutting down and don't have authority then allow it.
1109+
if (!(NetworkManager.IsServer || (AllowOwnerToParent && IsOwner)) && !NetworkManager.ShutdownInProgress)
10911110
{
10921111
return false;
10931112
}
@@ -1102,6 +1121,8 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true)
11021121
return false;
11031122
}
11041123

1124+
1125+
11051126
m_CachedWorldPositionStays = worldPositionStays;
11061127

11071128
if (parent == null)
@@ -1135,7 +1156,9 @@ private void OnTransformParentChanged()
11351156
return;
11361157
}
11371158

1138-
if (!NetworkManager.IsServer)
1159+
var hasAuthority = NetworkManager.IsServer || (AllowOwnerToParent && IsOwner);
1160+
1161+
if (!hasAuthority)
11391162
{
11401163
// Log exception if we are a client and not shutting down.
11411164
if (!NetworkManager.ShutdownInProgress)

com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -106,19 +106,42 @@ public void Handle(ref NetworkContext context)
106106
networkObject.SetNetworkParenting(LatestParent, WorldPositionStays);
107107
networkObject.ApplyNetworkParenting(RemoveParent);
108108

109-
// We set all of the transform values after parenting as they are
110-
// the values of the server-side post-parenting transform values
111-
if (!WorldPositionStays)
109+
110+
// This check is primarily for client-server network topologies when the motion model is owner authoritative:
111+
// When SyncOwnerTransformWhenParented is enabled, then always apply the transform values.
112+
// When SyncOwnerTransformWhenParented is disabled, then only synchronize the transform on non-owner instances.
113+
if (networkObject.SyncOwnerTransformWhenParented || (!networkObject.SyncOwnerTransformWhenParented && !networkObject.IsOwner))
112114
{
113-
networkObject.transform.localPosition = Position;
114-
networkObject.transform.localRotation = Rotation;
115+
// We set all of the transform values after parenting as they are
116+
// the values of the server-side post-parenting transform values
117+
if (!WorldPositionStays)
118+
{
119+
networkObject.transform.localPosition = Position;
120+
networkObject.transform.localRotation = Rotation;
121+
}
122+
else
123+
{
124+
networkObject.transform.position = Position;
125+
networkObject.transform.rotation = Rotation;
126+
}
115127
}
116-
else
128+
networkObject.transform.localScale = Scale;
129+
130+
// If client side parenting is enabled and this is the server instance, then notify the rest of the connected clients that parenting has taken place.
131+
if (networkObject.AllowOwnerToParent && context.SenderId == networkObject.OwnerClientId && networkManager.IsServer)
117132
{
118-
networkObject.transform.position = Position;
119-
networkObject.transform.rotation = Rotation;
133+
var size = 0;
134+
var message = this;
135+
foreach (var client in networkManager.ConnectedClients)
136+
{
137+
if (client.Value.ClientId == networkObject.OwnerClientId || client.Value.ClientId == networkManager.LocalClientId || !networkObject.IsNetworkVisibleTo(client.Value.ClientId))
138+
{
139+
continue;
140+
}
141+
size = networkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, client.Value.ClientId);
142+
networkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
143+
}
120144
}
121-
networkObject.transform.localScale = Scale;
122145
}
123146
}
124147
}

com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,6 @@ protected override void OnNewClientCreated(NetworkManager networkManager)
347347
base.OnNewClientCreated(networkManager);
348348
}
349349

350-
351350
/// <summary>
352351
/// Returns true when the server-host and all clients have
353352
/// instantiated the child object to be used in <see cref="NetworkTransformParentingLocalSpaceOffsetTests"/>
@@ -378,6 +377,26 @@ protected bool AllChildObjectInstancesAreSpawned()
378377
return true;
379378
}
380379

380+
/// <summary>
381+
/// Conditional check that all child object instances also have a child
382+
/// </summary>
383+
/// <returns>true if they do and false if they do not</returns>
384+
protected bool AllFirstLevelChildObjectInstancesHaveChild()
385+
{
386+
foreach (var instance in ChildObjectComponent.ClientInstances.Values)
387+
{
388+
if (instance.transform.parent == null)
389+
{
390+
return false;
391+
}
392+
}
393+
return true;
394+
}
395+
396+
/// <summary>
397+
/// Conditional check that all child instances have a child.
398+
/// </summary>
399+
/// <returns>true if they do and false if they do not</returns>
381400
protected bool AllChildObjectInstancesHaveChild()
382401
{
383402
foreach (var instance in ChildObjectComponent.ClientInstances.Values)
@@ -400,6 +419,41 @@ protected bool AllChildObjectInstancesHaveChild()
400419
return true;
401420
}
402421

422+
/// <summary>
423+
/// Conditional check that all first level child objects have no parent.
424+
/// </summary>
425+
/// <returns>true if they do and false if they do not</returns>
426+
protected bool AllFirstLevelChildObjectInstancesHaveNoParent()
427+
{
428+
foreach (var instance in ChildObjectComponent.ClientInstances.Values)
429+
{
430+
if (instance.transform.parent != null)
431+
{
432+
return false;
433+
}
434+
}
435+
return true;
436+
}
437+
438+
/// <summary>
439+
/// Conditional check that all sub-child objects have no parent.
440+
/// </summary>
441+
/// <returns>true if they do and false if they do not</returns>
442+
protected bool AllSubChildObjectInstancesHaveNoParent()
443+
{
444+
if (ChildObjectComponent.HasSubChild)
445+
{
446+
foreach (var instance in ChildObjectComponent.ClientSubChildInstances.Values)
447+
{
448+
if (instance.transform.parent != null)
449+
{
450+
return false;
451+
}
452+
}
453+
}
454+
return true;
455+
}
456+
403457
/// <summary>
404458
/// A wait condition specific method that assures the local space coordinates
405459
/// are not impacted by NetworkTransform when parented.

0 commit comments

Comments
 (0)