Skip to content

Commit 4ff5b8e

Browse files
fix: nested NetworkTransforms with different authority settings (#3209)
* fix First pass fixes for mixed authority mode nested networktransforms. * style adding additional comments * style Remove commented code. Add additional comments for clarity. * update adding changelog entry. * test Adding integration test to validate that mixed authority NetworkTransform hierarchies are working properly.
1 parent be45e86 commit 4ff5b8e

File tree

4 files changed

+152
-17
lines changed

4 files changed

+152
-17
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

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

1515
- Fixed exception being thrown when a `GameObject` with an associated `NetworkTransform` is disabled. (#3243)
1616
- Fixed `NetworkObject.DeferDespawn` to respect the `DestroyGameObject` parameter. (#3219)
17+
- Fixed issue where a `NetworkObject` with nested `NetworkTransform` components of varying authority modes was not being taken into consideration and would break both the initial `NetworkTransform` synchronization and fail to properly handle synchronized state updates of the nested `NetworkTransform` components. (#3209)
1718
- Fixed issue with distributing parented children that have the distributable and/or transferrable permissions set and have the same owner as the root parent, that has the distributable permission set, were not being distributed to the same client upon the owning client disconnecting when using a distributed authority network topology. (#3203)
1819
- Fixed issue where a spawned `NetworkObject` that was registered with a prefab handler and owned by a client would invoke destroy more than once on the host-server side if the client disconnected while the `NetworkObject` was still spawned. (#3200)
1920
- Fixed issue where `NetworkVariableBase` derived classes were not being re-initialized if the associated `NetworkObject` instance was not destroyed and re-spawned. (#3181)

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

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1679,8 +1679,8 @@ private void TryCommitTransform(ref Transform transformToCommit, bool synchroniz
16791679
// Synchronize any nested NetworkTransforms with the parent's
16801680
foreach (var childNetworkTransform in NetworkObject.NetworkTransforms)
16811681
{
1682-
// Don't update the same instance
1683-
if (childNetworkTransform == this)
1682+
// Don't update the same instance or any nested NetworkTransform with a different authority mode
1683+
if (childNetworkTransform == this || childNetworkTransform.AuthorityMode != AuthorityMode)
16841684
{
16851685
continue;
16861686
}
@@ -2908,8 +2908,8 @@ private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransf
29082908
// Synchronize any nested NetworkTransforms with the parent's
29092909
foreach (var childNetworkTransform in NetworkObject.NetworkTransforms)
29102910
{
2911-
// Don't update the same instance
2912-
if (childNetworkTransform == this)
2911+
// Don't update the same instance or any nested NetworkTransform with a different authority mode
2912+
if (childNetworkTransform == this || childNetworkTransform.AuthorityMode != AuthorityMode)
29132913
{
29142914
continue;
29152915
}
@@ -3032,15 +3032,29 @@ private void NonAuthorityFinalizeSynchronization()
30323032
// For all child NetworkTransforms nested under the same NetworkObject,
30333033
// we apply the initial synchronization based on their parented/ordered
30343034
// heirarchy.
3035-
if (SynchronizeState.IsSynchronizing && m_IsFirstNetworkTransform)
3035+
if (SynchronizeState.IsSynchronizing)
30363036
{
3037-
foreach (var child in NetworkObject.NetworkTransforms)
3037+
if (m_IsFirstNetworkTransform)
30383038
{
3039-
child.ApplySynchronization();
3039+
foreach (var child in NetworkObject.NetworkTransforms)
3040+
{
3041+
// Don't initialize any nested NetworkTransforms that this instance has authority over
3042+
if (child.CanCommitToTransform)
3043+
{
3044+
continue;
3045+
}
3046+
child.ApplySynchronization();
30403047

3041-
// For all nested (under the root/same NetworkObject) child NetworkTransforms, we need to run through
3042-
// initialization once more to assure any values applied or stored are relative to the Root's transform.
3043-
child.InternalInitialization();
3048+
// For all like-authority nested (under the root/same NetworkObject) child NetworkTransforms, we need to run through
3049+
// initialization once more to assure any values applied or stored are relative to the Root's transform.
3050+
child.InternalInitialization();
3051+
}
3052+
}
3053+
else // Otherwise, just run through standard synchronization of this instance
3054+
if (!CanCommitToTransform)
3055+
{
3056+
ApplySynchronization();
3057+
InternalInitialization();
30443058
}
30453059
}
30463060
}
@@ -3075,25 +3089,40 @@ protected internal override void InternalOnNetworkPostSpawn()
30753089
// This is a special case for client-server where a server is spawning an owner authoritative NetworkObject but has yet to serialize anything.
30763090
// When the server detects that:
30773091
// - We are not in a distributed authority session (DAHost check).
3078-
// - This is the first/root NetworkTransform.
30793092
// - We are in owner authoritative mode.
30803093
// - The NetworkObject is not owned by the server.
30813094
// - The SynchronizeState.IsSynchronizing is set to false.
30823095
// Then we want to:
30833096
// - Force the "IsSynchronizing" flag so the NetworkTransform has its state updated properly and runs through the initialization again.
30843097
// - Make sure the SynchronizingState is updated to the instantiated prefab's default flags/settings.
3085-
if (NetworkManager.IsServer && !NetworkManager.DistributedAuthorityMode && m_IsFirstNetworkTransform && !OnIsServerAuthoritative() && !IsOwner && !SynchronizeState.IsSynchronizing)
3098+
if (NetworkManager.IsServer && !NetworkManager.DistributedAuthorityMode && !IsOwner && !OnIsServerAuthoritative() && !SynchronizeState.IsSynchronizing)
30863099
{
3087-
// Assure the first/root NetworkTransform has the synchronizing flag set so the server runs through the final post initialization steps
3088-
SynchronizeState.IsSynchronizing = true;
3089-
// Assure the SynchronizeState matches the initial prefab's values for each associated NetworkTransfrom (this includes root + all children)
3090-
foreach (var child in NetworkObject.NetworkTransforms)
3100+
// Handle the first/root NetworkTransform slightly differently to have a sequenced synchronization of like authority nested NetworkTransform components
3101+
if (m_IsFirstNetworkTransform)
30913102
{
3092-
child.ApplyPlayerTransformState();
3103+
// Assure the NetworkTransform has the synchronizing flag set so the server runs through the final post initialization steps
3104+
SynchronizeState.IsSynchronizing = true;
3105+
3106+
// Assure the SynchronizeState matches the initial prefab's values for each associated NetworkTransfrom (this includes root + all children)
3107+
foreach (var child in NetworkObject.NetworkTransforms)
3108+
{
3109+
// Don't ApplyPlayerTransformState to any nested NetworkTransform with a different authority mode
3110+
if (child != this && child.AuthorityMode != AuthorityMode)
3111+
{
3112+
continue;
3113+
}
3114+
child.ApplyPlayerTransformState();
3115+
}
30933116
}
3117+
else
3118+
{
3119+
ApplyPlayerTransformState();
3120+
}
3121+
30943122
// Now fall through to the final synchronization portion of the spawning for NetworkTransform
30953123
}
30963124

3125+
// Standard non-authority synchronization is handled here
30973126
if (!CanCommitToTransform && NetworkManager.IsConnectedClient && SynchronizeState.IsSynchronizing)
30983127
{
30993128
NonAuthorityFinalizeSynchronization();
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
using System.Collections;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
using Unity.Netcode.Components;
5+
using Unity.Netcode.TestHelpers.Runtime;
6+
using UnityEngine;
7+
using UnityEngine.TestTools;
8+
9+
namespace Unity.Netcode.RuntimeTests
10+
{
11+
internal class NetworkTransformMixedAuthorityTests : IntegrationTestWithApproximation
12+
{
13+
private const float k_MotionMagnitude = 5.5f;
14+
private const int k_Iterations = 4;
15+
16+
protected override int NumberOfClients => 2;
17+
18+
private StringBuilder m_ErrorMsg = new StringBuilder();
19+
private List<NetworkManager> m_NetworkManagers = new List<NetworkManager>();
20+
21+
protected override void OnCreatePlayerPrefab()
22+
{
23+
m_PlayerPrefab.AddComponent<NetworkTransform>();
24+
25+
var childGameObject = new GameObject();
26+
childGameObject.transform.parent = m_PlayerPrefab.transform;
27+
var childNetworkTransform = childGameObject.AddComponent<NetworkTransform>();
28+
childNetworkTransform.AuthorityMode = NetworkTransform.AuthorityModes.Owner;
29+
childNetworkTransform.InLocalSpace = true;
30+
31+
base.OnCreatePlayerPrefab();
32+
}
33+
34+
private void MovePlayers()
35+
{
36+
foreach (var networkManager in m_NetworkManagers)
37+
{
38+
var direction = GetRandomVector3(-1.0f, 1.0f);
39+
var playerObject = networkManager.LocalClient.PlayerObject;
40+
var playerObjectId = networkManager.LocalClient.PlayerObject.NetworkObjectId;
41+
// Server authoritative
42+
var serverPlayerClone = m_ServerNetworkManager.SpawnManager.SpawnedObjects[playerObjectId];
43+
serverPlayerClone.transform.position += direction * k_MotionMagnitude;
44+
// Owner authoritative
45+
var childTransform = networkManager.LocalClient.PlayerObject.transform.GetChild(0);
46+
childTransform.localPosition += direction * k_MotionMagnitude;
47+
}
48+
}
49+
50+
private bool AllInstancePositionsMatch()
51+
{
52+
m_ErrorMsg.Clear();
53+
foreach (var networkManager in m_NetworkManagers)
54+
{
55+
var playerObject = networkManager.LocalClient.PlayerObject;
56+
var playerObjectId = networkManager.LocalClient.PlayerObject.NetworkObjectId;
57+
var serverRootPosition = m_ServerNetworkManager.SpawnManager.SpawnedObjects[playerObjectId].transform.position;
58+
var ownerChildPosition = networkManager.LocalClient.PlayerObject.transform.GetChild(0).localPosition;
59+
foreach (var client in m_NetworkManagers)
60+
{
61+
if (client == networkManager)
62+
{
63+
continue;
64+
}
65+
var playerClone = client.SpawnManager.SpawnedObjects[playerObjectId];
66+
var cloneRootPosition = playerClone.transform.position;
67+
var cloneChildPosition = playerClone.transform.GetChild(0).localPosition;
68+
69+
if (!Approximately(serverRootPosition, cloneRootPosition))
70+
{
71+
m_ErrorMsg.AppendLine($"[{playerObject.name}][{playerClone.name}] Root mismatch ({GetVector3Values(serverRootPosition)})({GetVector3Values(cloneRootPosition)})!");
72+
}
73+
74+
if (!Approximately(ownerChildPosition, cloneChildPosition))
75+
{
76+
m_ErrorMsg.AppendLine($"[{playerObject.name}][{playerClone.name}] Child mismatch ({GetVector3Values(ownerChildPosition)})({GetVector3Values(cloneChildPosition)})!");
77+
}
78+
}
79+
}
80+
return m_ErrorMsg.Length == 0;
81+
}
82+
83+
/// <summary>
84+
/// Client-Server Only
85+
/// Validates that mixed authority is working properly
86+
/// Root -- Server Authoritative
87+
/// |--Child -- Owner Authoritative
88+
/// </summary>
89+
[UnityTest]
90+
public IEnumerator MixedAuthorityTest()
91+
{
92+
m_NetworkManagers.Add(m_ServerNetworkManager);
93+
m_NetworkManagers.AddRange(m_ClientNetworkManagers);
94+
95+
for (int i = 0; i < k_Iterations; i++)
96+
{
97+
MovePlayers();
98+
yield return WaitForConditionOrTimeOut(AllInstancePositionsMatch);
99+
AssertOnTimeout($"Transforms failed to synchronize!");
100+
}
101+
}
102+
}
103+
}

com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformMixedAuthorityTests.cs.meta

Lines changed: 2 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)