Skip to content

chore: [2.x] Add NetworkTransform parenting test #3368

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -466,14 +466,24 @@ private void AddRemoveNetworkManager(NetworkManager networkManager, bool addNetw
/// CreateAndStartNewClient Only
/// Invoked when the newly created client has been created
/// </summary>
/// <param name="networkManager">The NetworkManager instance of the client.</param>
protected virtual void OnNewClientCreated(NetworkManager networkManager)
{
// Ensure any late joining client has all NetworkPrefabs required to connect.
foreach (var networkPrefab in m_ServerNetworkManager.NetworkConfig.Prefabs.Prefabs)
{
if (!networkManager.NetworkConfig.Prefabs.Contains(networkPrefab.Prefab))
{
networkManager.NetworkConfig.Prefabs.Add(networkPrefab);
}
}
}

/// <summary>
/// CreateAndStartNewClient Only
/// Invoked when the newly created client has been created and started
/// </summary>
/// <param name="networkManager">The NetworkManager instance of the client.</param>
protected virtual void OnNewClientStarted(NetworkManager networkManager)
{
}
Expand All @@ -483,6 +493,7 @@ protected virtual void OnNewClientStarted(NetworkManager networkManager)
/// Invoked when the newly created client has been created, started, and connected
/// to the server-host.
/// </summary>
/// <param name="networkManager">The NetworkManager instance of the client.</param>
protected virtual void OnNewClientStartedAndConnected(NetworkManager networkManager)
{
}
Expand All @@ -494,6 +505,8 @@ protected virtual void OnNewClientStartedAndConnected(NetworkManager networkMana
/// <remarks>
/// Use this for testing connection and disconnection scenarios
/// </remarks>
/// <param name="networkManager">The NetworkManager instance of the client.</param>
/// <returns>True if the test should wait for the client to connect; otherwise, false.</returns>
protected virtual bool ShouldWaitForNewClientToConnect(NetworkManager networkManager)
{
return true;
Expand All @@ -503,6 +516,7 @@ protected virtual bool ShouldWaitForNewClientToConnect(NetworkManager networkMan
/// This will create, start, and connect a new client while in the middle of an
/// integration test.
/// </summary>
/// <returns>An IEnumerator to be used in a coroutine for asynchronous execution.</returns>
protected IEnumerator CreateAndStartNewClient()
{
var networkManager = NetcodeIntegrationTestHelpers.CreateNewClient(m_ClientNetworkManagers.Length, m_EnableTimeTravel);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,7 @@ protected override void OnNewClientCreated(NetworkManager networkManager)
{
AddPrefabsToClient(networkManager);
}

base.OnNewClientCreated(networkManager);
// Don't call base to avoid synchronizing the prefabs
}

private void SpawnClients(bool clearTestDeferredMessageManagerCallFlags = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,12 +235,6 @@ private bool ValidateTransformsMatch()
return true;
}

protected override void OnNewClientCreated(NetworkManager networkManager)
{
networkManager.NetworkConfig.Prefabs = m_ServerNetworkManager.NetworkConfig.Prefabs;
base.OnNewClientCreated(networkManager);
}

private bool SpawnCountsMatch()
{
var passed = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ protected override void OnNewClientCreated(NetworkManager networkManager)
{
m_LateJoinClient = networkManager;
networkManager.NetworkConfig.EnableSceneManagement = m_EnableSceneManagement;
networkManager.NetworkConfig.Prefabs = m_SpawnOwner.NetworkConfig.Prefabs;
base.OnNewClientCreated(networkManager);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ protected override void OnServerAndClientsCreated()

protected override void OnNewClientCreated(NetworkManager networkManager)
{
networkManager.NetworkConfig.Prefabs = m_ServerNetworkManager.NetworkConfig.Prefabs;
if (m_DistributedAuthority)
{
networkManager.OnFetchLocalPlayerPrefabToSpawn = FetchPlayerPrefabToSpawn;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,6 @@ protected override IEnumerator OnSetup()
return base.OnSetup();
}

protected override void OnNewClientCreated(NetworkManager networkManager)
{
networkManager.NetworkConfig.Prefabs = m_ServerNetworkManager.NetworkConfig.Prefabs;
base.OnNewClientCreated(networkManager);
}

/// <summary>
/// This validates that pre spawn can be used to instantiate and assign a NetworkVariable (or other prespawn tasks)
/// which can be useful for assigning a NetworkVariable value on the server side when the NetworkVariable has owner write permissions.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,6 @@ private bool CheckClientsSideObserverTestObj()
/// </summary>
protected override void OnNewClientCreated(NetworkManager networkManager)
{
foreach (var networkPrefab in m_ServerNetworkManager.NetworkConfig.Prefabs.Prefabs)
{
if (!networkManager.NetworkConfig.Prefabs.Contains(networkPrefab.Prefab))
{
networkManager.NetworkConfig.Prefabs.Add(networkPrefab);
}
}
networkManager.NetworkConfig.EnableSceneManagement = m_ServerNetworkManager.NetworkConfig.EnableSceneManagement;
base.OnNewClientCreated(networkManager);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,21 +81,16 @@ protected override void OnServerAndClientsCreated()

protected override void OnNewClientCreated(NetworkManager networkManager)
{
// Setup late joining client prefabs first
base.OnNewClientCreated(networkManager);

networkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab;
networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety = m_VariableLengthSafety == VariableLengthSafety.EnabledNetVarSafety;
foreach (var networkPrefab in m_ServerNetworkManager.NetworkConfig.Prefabs.Prefabs)
{
// To simulate a failure, we exclude the m_InValidNetworkPrefab from the connecting
// client's side.
if (networkPrefab.Prefab.name != m_InValidNetworkPrefab.name)
{
networkManager.NetworkConfig.Prefabs.Add(networkPrefab);
}
}
// Disable forcing the same prefabs to avoid failed connections
networkManager.NetworkConfig.ForceSamePrefabs = false;
networkManager.LogLevel = m_CurrentLogLevel;
base.OnNewClientCreated(networkManager);
// To simulate a failure, exclude the m_InValidNetworkPrefab from the connecting client's side.
networkManager.NetworkConfig.Prefabs.Remove(m_InValidNetworkPrefab);
}

[UnityTest]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,6 @@ protected override IEnumerator OnServerAndClientsConnected()
/// </summary>
protected override void OnNewClientCreated(NetworkManager networkManager)
{
networkManager.NetworkConfig.Prefabs = m_ServerNetworkManager.NetworkConfig.Prefabs;
networkManager.NetworkConfig.TickRate = GetTickRate();
if (m_EnableVerboseDebug)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,20 +91,6 @@ protected override void OnServerAndClientsCreated()
base.OnServerAndClientsCreated();
}

/// <summary>
/// Clients created during a test need to have their prefabs list updated to
/// match the server's prefab list.
/// </summary>
protected override void OnNewClientCreated(NetworkManager networkManager)
{
foreach (var networkPrefab in m_ServerNetworkManager.NetworkConfig.Prefabs.Prefabs)
{
networkManager.NetworkConfig.Prefabs.Add(networkPrefab);
}

base.OnNewClientCreated(networkManager);
}

private bool ClientIsOwner()
{
var clientId = m_ClientNetworkManagers[0].LocalClientId;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
using System.Collections;
using Unity.Netcode.Components;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
using UnityEngine.TestTools;

namespace Unity.Netcode.RuntimeTests
{
internal class NetworkTransformParentingTests : IntegrationTestWithApproximation
{
/// <summary>
/// A NetworkBehaviour that moves in space.
/// When spawned on the client, an RPC is sent to the server to spawn a player object for that client.
/// The server parents the player object to the spawner object. This gives a moving parent object and a non-moving child object.
/// The child object should always be at {0,0,0} local space, while the parent object moves around.
/// This NetworkBehaviour tests that parenting to a moving object works as expected.
/// </summary>
internal class PlayerSpawner : NetworkBehaviour
{
/// <summary>
/// Prefab for the player
/// </summary>
public NetworkObject PlayerPrefab;

/// <summary>
/// The server side NetworkObject that was spawned when the client connected.
/// </summary>
public NetworkObject SpawnedPlayer;

/// <summary>
/// Represents the different movement states of the PlayerSpawner during the test lifecycle.
/// </summary>
public enum MoveState
{
// Initial state, PlayerSpawner will move without counting frames
NotStarted,
// The player object has been spawned, start counting frames
PlayerSpawned,
// We have moved far enough to test location
ReachedPeak,
}
public MoveState State = MoveState.NotStarted;

// A count of the number of updates since the player object was spawned.
private int m_Count;

// Movement offsets and targets.
private const float k_PositionOffset = 5.0f;
private const float k_RotationOffset = 25.0f;
private readonly Vector3 m_PositionTarget = Vector3.one * k_PositionOffset * 10;
private readonly Vector3 m_RotationTarget = Vector3.one * k_RotationOffset * 10;

private void Update()
{
if (!IsServer)
{
return;
}

transform.position = Vector3.Lerp(transform.position, m_PositionTarget, Time.deltaTime * 2);
var rotation = transform.rotation;
rotation.eulerAngles = Vector3.Slerp(rotation.eulerAngles, m_RotationTarget, Time.deltaTime * 2);
transform.rotation = rotation;

if (State != MoveState.PlayerSpawned)
{
return;
}

// Move self for some time after player object is spawned
// This ensures the parent object is moving throughout the spawn process.
m_Count++;
if (m_Count > 10)
{
// Mark PlayerSpawner as having moved far enough to test.
State = MoveState.ReachedPeak;
}
}

public override void OnNetworkSpawn()
{
if (IsOwner)
{
// Owner initialises PlayerSpawner movement on spawn
transform.position = Vector3.one * k_PositionOffset;
var rotation = transform.rotation;
rotation.eulerAngles = Vector3.one * k_RotationOffset;
transform.rotation = rotation;
}
else
{
// When spawned on a client, send the RPC to spawn the player object
// Using an RPC ensures the PlayerSpawner is moving for the entire spawning of the player object.
RequestPlayerObjectSpawnServerRpc();
}
}

/// <summary>
/// A ServerRpc that requests the server to spawn a player object for the client that invoked this RPC.
/// </summary>
/// <param name="rpcParams">Parameters for the ServerRpc, including the sender's client ID.</param>
[ServerRpc(RequireOwnership = false)]
private void RequestPlayerObjectSpawnServerRpc(ServerRpcParams rpcParams = default)
{
SpawnedPlayer = Instantiate(PlayerPrefab);
SpawnedPlayer.SpawnAsPlayerObject(rpcParams.Receive.SenderClientId);
SpawnedPlayer.TrySetParent(NetworkObject, false);
State = MoveState.PlayerSpawned;
}
}

// Don't start with any clients, we will manually spawn a client inside the test
protected override int NumberOfClients => 0;

// Parent prefab with moving PlayerSpawner which will spawn the childPrefab
private GameObject m_PlayerSpawnerPrefab;

// Client and server instances
private PlayerSpawner m_ServerPlayerSpawner;
private NetworkObject m_NewClientPlayer;

protected override void OnServerAndClientsCreated()
{
m_PlayerSpawnerPrefab = CreateNetworkObjectPrefab("Parent");
var parentPlayerSpawner = m_PlayerSpawnerPrefab.AddComponent<PlayerSpawner>();
m_PlayerSpawnerPrefab.AddComponent<NetworkTransform>();

var playerPrefab = CreateNetworkObjectPrefab("Child");
var childNetworkTransform = playerPrefab.AddComponent<NetworkTransform>();
childNetworkTransform.AuthorityMode = NetworkTransform.AuthorityModes.Owner;
childNetworkTransform.InLocalSpace = true;

parentPlayerSpawner.PlayerPrefab = playerPrefab.GetComponent<NetworkObject>();

base.OnServerAndClientsCreated();
}

private bool NewPlayerObjectSpawned()
{
return m_ServerPlayerSpawner.SpawnedPlayer &&
m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects.ContainsKey(m_ServerPlayerSpawner.SpawnedPlayer.NetworkObjectId);
}

private bool HasServerInstanceReachedPeakPoint()
{
VerboseDebug($"Client Local: {m_NewClientPlayer.transform.localPosition} Server Local: {m_ServerPlayerSpawner.SpawnedPlayer.transform.localPosition}");
return m_ServerPlayerSpawner.State == PlayerSpawner.MoveState.ReachedPeak;
}

private bool ServerClientPositionMatches()
{
return Approximately(m_NewClientPlayer.transform.localPosition, m_ServerPlayerSpawner.SpawnedPlayer.transform.localPosition) &&
Approximately(m_NewClientPlayer.transform.position, m_ServerPlayerSpawner.SpawnedPlayer.transform.position);
}

[UnityTest]
public IEnumerator TestParentedPlayerUsingLocalSpace()
{
// Spawn the PlayerSpawner object and save the instantiated component
// The PlayerSpawner object will start moving.
m_ServerPlayerSpawner = SpawnObject(m_PlayerSpawnerPrefab, m_ServerNetworkManager).GetComponent<PlayerSpawner>();

// Create a new client and connect to the server
// The client will prompt the server to spawn a player object and parent it to the PlayerSpawner object.
yield return CreateAndStartNewClient();

yield return WaitForConditionOrTimeOut(NewPlayerObjectSpawned);
AssertOnTimeout($"Client did not spawn new player object!");

// Save the spawned player object
m_NewClientPlayer = m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects[m_ServerPlayerSpawner.SpawnedPlayer.NetworkObjectId];

// Let the parent PlayerSpawner move for several ticks to get an offset
yield return WaitForConditionOrTimeOut(HasServerInstanceReachedPeakPoint);
AssertOnTimeout($"Server instance never reached peak point!");

// Check that the client and server local positions match (they should both be at {0,0,0} local space)
yield return WaitForConditionOrTimeOut(ServerClientPositionMatches);
AssertOnTimeout($"Client local position {m_NewClientPlayer.transform.localPosition} does not match" +
$" server local position {m_ServerPlayerSpawner.SpawnedPlayer.transform.localPosition}");
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 0 additions & 6 deletions pvpExceptions.json
Original file line number Diff line number Diff line change
Expand Up @@ -238,12 +238,6 @@
"Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void OneTimeSetup(): undocumented",
"Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator OnSetup(): missing <returns>",
"Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator SetUp(): undocumented",
"Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void OnNewClientCreated(NetworkManager): missing <param name=\"networkManager\">",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

"Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void OnNewClientStarted(NetworkManager): missing <param name=\"networkManager\">",
"Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void OnNewClientStartedAndConnected(NetworkManager): missing <param name=\"networkManager\">",
"Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: bool ShouldWaitForNewClientToConnect(NetworkManager): missing <param name=\"networkManager\">",
"Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: bool ShouldWaitForNewClientToConnect(NetworkManager): missing <returns>",
"Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator CreateAndStartNewClient(): missing <returns>",
"Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator StopOneClient(NetworkManager, bool): missing <param name=\"networkManager\">",
"Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator StopOneClient(NetworkManager, bool): missing <param name=\"destroy\">",
"Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator StopOneClient(NetworkManager, bool): missing <returns>",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -624,18 +624,6 @@ public IEnumerator TriggerUpdateTests()
VerboseDebug($" ------------------ Trigger Test [{TriggerTest.Iteration}][{m_OwnerShipMode}] Stopping ------------------ ");
}

protected override void OnNewClientCreated(NetworkManager networkManager)
{
var networkPrefab = new NetworkPrefab() { Prefab = m_AnimationTestPrefab };
networkManager.NetworkConfig.Prefabs.Add(networkPrefab);
networkPrefab = new NetworkPrefab() { Prefab = m_AnimationOwnerTestPrefab };
networkManager.NetworkConfig.Prefabs.Add(networkPrefab);
networkPrefab = new NetworkPrefab() { Prefab = m_AnimationCheerTestPrefab };
networkManager.NetworkConfig.Prefabs.Add(networkPrefab);
networkPrefab = new NetworkPrefab() { Prefab = m_AnimationCheerOwnerTestPrefab };
networkManager.NetworkConfig.Prefabs.Add(networkPrefab);
}

/// <summary>
/// Verifies that triggers are synchronized with currently connected clients
/// </summary>
Expand Down
Loading