Skip to content

Commit 464e7cb

Browse files
chore: [2.x] Add NetworkTransform parenting test (#3368)
1. Adds a test replicating the bug fixed in #3361 2. Move the code to sync prefabs for late joining clients into the base `NetcodeIntegrationTest` 3. Fix some pvp exceptions ## Changelog - Added: Test covering the bug fixed in #3361 ## Testing and Documentation - Adds Unit test --------- Co-authored-by: Noel Stephens <[email protected]>
1 parent 7d4801f commit 464e7cb

20 files changed

+212
-113
lines changed

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,14 +466,24 @@ private void AddRemoveNetworkManager(NetworkManager networkManager, bool addNetw
466466
/// CreateAndStartNewClient Only
467467
/// Invoked when the newly created client has been created
468468
/// </summary>
469+
/// <param name="networkManager">The NetworkManager instance of the client.</param>
469470
protected virtual void OnNewClientCreated(NetworkManager networkManager)
470471
{
472+
// Ensure any late joining client has all NetworkPrefabs required to connect.
473+
foreach (var networkPrefab in m_ServerNetworkManager.NetworkConfig.Prefabs.Prefabs)
474+
{
475+
if (!networkManager.NetworkConfig.Prefabs.Contains(networkPrefab.Prefab))
476+
{
477+
networkManager.NetworkConfig.Prefabs.Add(networkPrefab);
478+
}
479+
}
471480
}
472481

473482
/// <summary>
474483
/// CreateAndStartNewClient Only
475484
/// Invoked when the newly created client has been created and started
476485
/// </summary>
486+
/// <param name="networkManager">The NetworkManager instance of the client.</param>
477487
protected virtual void OnNewClientStarted(NetworkManager networkManager)
478488
{
479489
}
@@ -483,6 +493,7 @@ protected virtual void OnNewClientStarted(NetworkManager networkManager)
483493
/// Invoked when the newly created client has been created, started, and connected
484494
/// to the server-host.
485495
/// </summary>
496+
/// <param name="networkManager">The NetworkManager instance of the client.</param>
486497
protected virtual void OnNewClientStartedAndConnected(NetworkManager networkManager)
487498
{
488499
}
@@ -494,6 +505,8 @@ protected virtual void OnNewClientStartedAndConnected(NetworkManager networkMana
494505
/// <remarks>
495506
/// Use this for testing connection and disconnection scenarios
496507
/// </remarks>
508+
/// <param name="networkManager">The NetworkManager instance of the client.</param>
509+
/// <returns>True if the test should wait for the client to connect; otherwise, false.</returns>
497510
protected virtual bool ShouldWaitForNewClientToConnect(NetworkManager networkManager)
498511
{
499512
return true;
@@ -503,6 +516,7 @@ protected virtual bool ShouldWaitForNewClientToConnect(NetworkManager networkMan
503516
/// This will create, start, and connect a new client while in the middle of an
504517
/// integration test.
505518
/// </summary>
519+
/// <returns>An IEnumerator to be used in a coroutine for asynchronous execution.</returns>
506520
protected IEnumerator CreateAndStartNewClient()
507521
{
508522
var networkManager = NetcodeIntegrationTestHelpers.CreateNewClient(m_ClientNetworkManagers.Length, m_EnableTimeTravel);

com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,7 @@ protected override void OnNewClientCreated(NetworkManager networkManager)
259259
{
260260
AddPrefabsToClient(networkManager);
261261
}
262-
263-
base.OnNewClientCreated(networkManager);
262+
// Don't call base to avoid synchronizing the prefabs
264263
}
265264

266265
private void SpawnClients(bool clearTestDeferredMessageManagerCallFlags = true)

com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -235,12 +235,6 @@ private bool ValidateTransformsMatch()
235235
return true;
236236
}
237237

238-
protected override void OnNewClientCreated(NetworkManager networkManager)
239-
{
240-
networkManager.NetworkConfig.Prefabs = m_ServerNetworkManager.NetworkConfig.Prefabs;
241-
base.OnNewClientCreated(networkManager);
242-
}
243-
244238
private bool SpawnCountsMatch()
245239
{
246240
var passed = true;

com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/ExtendedNetworkShowAndHideTests.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ protected override void OnNewClientCreated(NetworkManager networkManager)
9797
{
9898
m_LateJoinClient = networkManager;
9999
networkManager.NetworkConfig.EnableSceneManagement = m_EnableSceneManagement;
100-
networkManager.NetworkConfig.Prefabs = m_SpawnOwner.NetworkConfig.Prefabs;
101100
base.OnNewClientCreated(networkManager);
102101
}
103102

com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ protected override void OnServerAndClientsCreated()
4545

4646
protected override void OnNewClientCreated(NetworkManager networkManager)
4747
{
48-
networkManager.NetworkConfig.Prefabs = m_ServerNetworkManager.NetworkConfig.Prefabs;
4948
if (m_DistributedAuthority)
5049
{
5150
networkManager.OnFetchLocalPlayerPrefabToSpawn = FetchPlayerPrefabToSpawn;

com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourPrePostSpawnTests.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,6 @@ protected override IEnumerator OnSetup()
9393
return base.OnSetup();
9494
}
9595

96-
protected override void OnNewClientCreated(NetworkManager networkManager)
97-
{
98-
networkManager.NetworkConfig.Prefabs = m_ServerNetworkManager.NetworkConfig.Prefabs;
99-
base.OnNewClientCreated(networkManager);
100-
}
101-
10296
/// <summary>
10397
/// This validates that pre spawn can be used to instantiate and assign a NetworkVariable (or other prespawn tasks)
10498
/// which can be useful for assigning a NetworkVariable value on the server side when the NetworkVariable has owner write permissions.

com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,6 @@ private bool CheckClientsSideObserverTestObj()
8686
/// </summary>
8787
protected override void OnNewClientCreated(NetworkManager networkManager)
8888
{
89-
foreach (var networkPrefab in m_ServerNetworkManager.NetworkConfig.Prefabs.Prefabs)
90-
{
91-
if (!networkManager.NetworkConfig.Prefabs.Contains(networkPrefab.Prefab))
92-
{
93-
networkManager.NetworkConfig.Prefabs.Add(networkPrefab);
94-
}
95-
}
9689
networkManager.NetworkConfig.EnableSceneManagement = m_ServerNetworkManager.NetworkConfig.EnableSceneManagement;
9790
base.OnNewClientCreated(networkManager);
9891
}

com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -81,21 +81,16 @@ protected override void OnServerAndClientsCreated()
8181

8282
protected override void OnNewClientCreated(NetworkManager networkManager)
8383
{
84+
// Setup late joining client prefabs first
85+
base.OnNewClientCreated(networkManager);
86+
8487
networkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab;
8588
networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety = m_VariableLengthSafety == VariableLengthSafety.EnabledNetVarSafety;
86-
foreach (var networkPrefab in m_ServerNetworkManager.NetworkConfig.Prefabs.Prefabs)
87-
{
88-
// To simulate a failure, we exclude the m_InValidNetworkPrefab from the connecting
89-
// client's side.
90-
if (networkPrefab.Prefab.name != m_InValidNetworkPrefab.name)
91-
{
92-
networkManager.NetworkConfig.Prefabs.Add(networkPrefab);
93-
}
94-
}
9589
// Disable forcing the same prefabs to avoid failed connections
9690
networkManager.NetworkConfig.ForceSamePrefabs = false;
9791
networkManager.LogLevel = m_CurrentLogLevel;
98-
base.OnNewClientCreated(networkManager);
92+
// To simulate a failure, exclude the m_InValidNetworkPrefab from the connecting client's side.
93+
networkManager.NetworkConfig.Prefabs.Remove(m_InValidNetworkPrefab);
9994
}
10095

10196
[UnityTest]

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,6 @@ protected override IEnumerator OnServerAndClientsConnected()
338338
/// </summary>
339339
protected override void OnNewClientCreated(NetworkManager networkManager)
340340
{
341-
networkManager.NetworkConfig.Prefabs = m_ServerNetworkManager.NetworkConfig.Prefabs;
342341
networkManager.NetworkConfig.TickRate = GetTickRate();
343342
if (m_EnableVerboseDebug)
344343
{

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

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -91,20 +91,6 @@ protected override void OnServerAndClientsCreated()
9191
base.OnServerAndClientsCreated();
9292
}
9393

94-
/// <summary>
95-
/// Clients created during a test need to have their prefabs list updated to
96-
/// match the server's prefab list.
97-
/// </summary>
98-
protected override void OnNewClientCreated(NetworkManager networkManager)
99-
{
100-
foreach (var networkPrefab in m_ServerNetworkManager.NetworkConfig.Prefabs.Prefabs)
101-
{
102-
networkManager.NetworkConfig.Prefabs.Add(networkPrefab);
103-
}
104-
105-
base.OnNewClientCreated(networkManager);
106-
}
107-
10894
private bool ClientIsOwner()
10995
{
11096
var clientId = m_ClientNetworkManagers[0].LocalClientId;
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
using System.Collections;
2+
using Unity.Netcode.Components;
3+
using Unity.Netcode.TestHelpers.Runtime;
4+
using UnityEngine;
5+
using UnityEngine.TestTools;
6+
7+
namespace Unity.Netcode.RuntimeTests
8+
{
9+
internal class NetworkTransformParentingTests : IntegrationTestWithApproximation
10+
{
11+
/// <summary>
12+
/// A NetworkBehaviour that moves in space.
13+
/// When spawned on the client, an RPC is sent to the server to spawn a player object for that client.
14+
/// The server parents the player object to the spawner object. This gives a moving parent object and a non-moving child object.
15+
/// The child object should always be at {0,0,0} local space, while the parent object moves around.
16+
/// This NetworkBehaviour tests that parenting to a moving object works as expected.
17+
/// </summary>
18+
internal class PlayerSpawner : NetworkBehaviour
19+
{
20+
/// <summary>
21+
/// Prefab for the player
22+
/// </summary>
23+
public NetworkObject PlayerPrefab;
24+
25+
/// <summary>
26+
/// The server side NetworkObject that was spawned when the client connected.
27+
/// </summary>
28+
public NetworkObject SpawnedPlayer;
29+
30+
/// <summary>
31+
/// Represents the different movement states of the PlayerSpawner during the test lifecycle.
32+
/// </summary>
33+
public enum MoveState
34+
{
35+
// Initial state, PlayerSpawner will move without counting frames
36+
NotStarted,
37+
// The player object has been spawned, start counting frames
38+
PlayerSpawned,
39+
// We have moved far enough to test location
40+
ReachedPeak,
41+
}
42+
public MoveState State = MoveState.NotStarted;
43+
44+
// A count of the number of updates since the player object was spawned.
45+
private int m_Count;
46+
47+
// Movement offsets and targets.
48+
private const float k_PositionOffset = 5.0f;
49+
private const float k_RotationOffset = 25.0f;
50+
private readonly Vector3 m_PositionTarget = Vector3.one * k_PositionOffset * 10;
51+
private readonly Vector3 m_RotationTarget = Vector3.one * k_RotationOffset * 10;
52+
53+
private void Update()
54+
{
55+
if (!IsServer)
56+
{
57+
return;
58+
}
59+
60+
transform.position = Vector3.Lerp(transform.position, m_PositionTarget, Time.deltaTime * 2);
61+
var rotation = transform.rotation;
62+
rotation.eulerAngles = Vector3.Slerp(rotation.eulerAngles, m_RotationTarget, Time.deltaTime * 2);
63+
transform.rotation = rotation;
64+
65+
if (State != MoveState.PlayerSpawned)
66+
{
67+
return;
68+
}
69+
70+
// Move self for some time after player object is spawned
71+
// This ensures the parent object is moving throughout the spawn process.
72+
m_Count++;
73+
if (m_Count > 10)
74+
{
75+
// Mark PlayerSpawner as having moved far enough to test.
76+
State = MoveState.ReachedPeak;
77+
}
78+
}
79+
80+
public override void OnNetworkSpawn()
81+
{
82+
if (IsOwner)
83+
{
84+
// Owner initialises PlayerSpawner movement on spawn
85+
transform.position = Vector3.one * k_PositionOffset;
86+
var rotation = transform.rotation;
87+
rotation.eulerAngles = Vector3.one * k_RotationOffset;
88+
transform.rotation = rotation;
89+
}
90+
else
91+
{
92+
// When spawned on a client, send the RPC to spawn the player object
93+
// Using an RPC ensures the PlayerSpawner is moving for the entire spawning of the player object.
94+
RequestPlayerObjectSpawnServerRpc();
95+
}
96+
}
97+
98+
/// <summary>
99+
/// A ServerRpc that requests the server to spawn a player object for the client that invoked this RPC.
100+
/// </summary>
101+
/// <param name="rpcParams">Parameters for the ServerRpc, including the sender's client ID.</param>
102+
[ServerRpc(RequireOwnership = false)]
103+
private void RequestPlayerObjectSpawnServerRpc(ServerRpcParams rpcParams = default)
104+
{
105+
SpawnedPlayer = Instantiate(PlayerPrefab);
106+
SpawnedPlayer.SpawnAsPlayerObject(rpcParams.Receive.SenderClientId);
107+
SpawnedPlayer.TrySetParent(NetworkObject, false);
108+
State = MoveState.PlayerSpawned;
109+
}
110+
}
111+
112+
// Don't start with any clients, we will manually spawn a client inside the test
113+
protected override int NumberOfClients => 0;
114+
115+
// Parent prefab with moving PlayerSpawner which will spawn the childPrefab
116+
private GameObject m_PlayerSpawnerPrefab;
117+
118+
// Client and server instances
119+
private PlayerSpawner m_ServerPlayerSpawner;
120+
private NetworkObject m_NewClientPlayer;
121+
122+
protected override void OnServerAndClientsCreated()
123+
{
124+
m_PlayerSpawnerPrefab = CreateNetworkObjectPrefab("Parent");
125+
var parentPlayerSpawner = m_PlayerSpawnerPrefab.AddComponent<PlayerSpawner>();
126+
m_PlayerSpawnerPrefab.AddComponent<NetworkTransform>();
127+
128+
var playerPrefab = CreateNetworkObjectPrefab("Child");
129+
var childNetworkTransform = playerPrefab.AddComponent<NetworkTransform>();
130+
childNetworkTransform.AuthorityMode = NetworkTransform.AuthorityModes.Owner;
131+
childNetworkTransform.InLocalSpace = true;
132+
133+
parentPlayerSpawner.PlayerPrefab = playerPrefab.GetComponent<NetworkObject>();
134+
135+
base.OnServerAndClientsCreated();
136+
}
137+
138+
private bool NewPlayerObjectSpawned()
139+
{
140+
return m_ServerPlayerSpawner.SpawnedPlayer &&
141+
m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects.ContainsKey(m_ServerPlayerSpawner.SpawnedPlayer.NetworkObjectId);
142+
}
143+
144+
private bool HasServerInstanceReachedPeakPoint()
145+
{
146+
VerboseDebug($"Client Local: {m_NewClientPlayer.transform.localPosition} Server Local: {m_ServerPlayerSpawner.SpawnedPlayer.transform.localPosition}");
147+
return m_ServerPlayerSpawner.State == PlayerSpawner.MoveState.ReachedPeak;
148+
}
149+
150+
private bool ServerClientPositionMatches()
151+
{
152+
return Approximately(m_NewClientPlayer.transform.localPosition, m_ServerPlayerSpawner.SpawnedPlayer.transform.localPosition) &&
153+
Approximately(m_NewClientPlayer.transform.position, m_ServerPlayerSpawner.SpawnedPlayer.transform.position);
154+
}
155+
156+
[UnityTest]
157+
public IEnumerator TestParentedPlayerUsingLocalSpace()
158+
{
159+
// Spawn the PlayerSpawner object and save the instantiated component
160+
// The PlayerSpawner object will start moving.
161+
m_ServerPlayerSpawner = SpawnObject(m_PlayerSpawnerPrefab, m_ServerNetworkManager).GetComponent<PlayerSpawner>();
162+
163+
// Create a new client and connect to the server
164+
// The client will prompt the server to spawn a player object and parent it to the PlayerSpawner object.
165+
yield return CreateAndStartNewClient();
166+
167+
yield return WaitForConditionOrTimeOut(NewPlayerObjectSpawned);
168+
AssertOnTimeout($"Client did not spawn new player object!");
169+
170+
// Save the spawned player object
171+
m_NewClientPlayer = m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects[m_ServerPlayerSpawner.SpawnedPlayer.NetworkObjectId];
172+
173+
// Let the parent PlayerSpawner move for several ticks to get an offset
174+
yield return WaitForConditionOrTimeOut(HasServerInstanceReachedPeakPoint);
175+
AssertOnTimeout($"Server instance never reached peak point!");
176+
177+
// Check that the client and server local positions match (they should both be at {0,0,0} local space)
178+
yield return WaitForConditionOrTimeOut(ServerClientPositionMatches);
179+
AssertOnTimeout($"Client local position {m_NewClientPlayer.transform.localPosition} does not match" +
180+
$" server local position {m_ServerPlayerSpawner.SpawnedPlayer.transform.localPosition}");
181+
}
182+
}
183+
}

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

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

pvpExceptions.json

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -238,12 +238,6 @@
238238
"Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void OneTimeSetup(): undocumented",
239239
"Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator OnSetup(): missing <returns>",
240240
"Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator SetUp(): undocumented",
241-
"Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void OnNewClientCreated(NetworkManager): missing <param name=\"networkManager\">",
242-
"Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void OnNewClientStarted(NetworkManager): missing <param name=\"networkManager\">",
243-
"Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void OnNewClientStartedAndConnected(NetworkManager): missing <param name=\"networkManager\">",
244-
"Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: bool ShouldWaitForNewClientToConnect(NetworkManager): missing <param name=\"networkManager\">",
245-
"Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: bool ShouldWaitForNewClientToConnect(NetworkManager): missing <returns>",
246-
"Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator CreateAndStartNewClient(): missing <returns>",
247241
"Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator StopOneClient(NetworkManager, bool): missing <param name=\"networkManager\">",
248242
"Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator StopOneClient(NetworkManager, bool): missing <param name=\"destroy\">",
249243
"Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator StopOneClient(NetworkManager, bool): missing <returns>",

testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -624,18 +624,6 @@ public IEnumerator TriggerUpdateTests()
624624
VerboseDebug($" ------------------ Trigger Test [{TriggerTest.Iteration}][{m_OwnerShipMode}] Stopping ------------------ ");
625625
}
626626

627-
protected override void OnNewClientCreated(NetworkManager networkManager)
628-
{
629-
var networkPrefab = new NetworkPrefab() { Prefab = m_AnimationTestPrefab };
630-
networkManager.NetworkConfig.Prefabs.Add(networkPrefab);
631-
networkPrefab = new NetworkPrefab() { Prefab = m_AnimationOwnerTestPrefab };
632-
networkManager.NetworkConfig.Prefabs.Add(networkPrefab);
633-
networkPrefab = new NetworkPrefab() { Prefab = m_AnimationCheerTestPrefab };
634-
networkManager.NetworkConfig.Prefabs.Add(networkPrefab);
635-
networkPrefab = new NetworkPrefab() { Prefab = m_AnimationCheerOwnerTestPrefab };
636-
networkManager.NetworkConfig.Prefabs.Add(networkPrefab);
637-
}
638-
639627
/// <summary>
640628
/// Verifies that triggers are synchronized with currently connected clients
641629
/// </summary>

0 commit comments

Comments
 (0)