From 3c73eaac72d19d785f15a46fdded5829c67c567e Mon Sep 17 00:00:00 2001 From: Kitty Draper Date: Thu, 21 May 2026 13:54:19 -0500 Subject: [PATCH 1/7] Support InLocalSpace in AnticipatedNetworkTransform --- .../Components/AnticipatedNetworkTransform.cs | 171 +++++++++++-- .../NetworkTransformAnticipationTests.cs | 238 +++++++++++++++++- .../TestHelpers/NetcodeIntegrationTest.cs | 92 +++++++ 3 files changed, 475 insertions(+), 26 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs index ce277533c7..00bd431cbb 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs @@ -161,8 +161,18 @@ public void AnticipateMove(Vector3 newPosition) { return; } - transform.position = newPosition; + + if (InLocalSpace) + { + transform.localPosition = newPosition; + } + else + { + transform.position = newPosition; + } + m_AnticipatedTransform.Position = newPosition; + if (CanCommitToTransform) { m_AuthoritativeTransform.Position = newPosition; @@ -187,7 +197,15 @@ public void AnticipateRotate(Quaternion newRotation) { return; } - transform.rotation = newRotation; + + if (InLocalSpace) + { + transform.localRotation = newRotation; + } + else + { + transform.rotation = newRotation; + } m_AnticipatedTransform.Rotation = newRotation; if (CanCommitToTransform) { @@ -240,7 +258,14 @@ public void AnticipateState(TransformState newState) return; } var transform_ = transform; - transform_.SetPositionAndRotation(newState.Position, newState.Rotation); + if (InLocalSpace) + { + transform_.SetLocalPositionAndRotation(newState.Position, newState.Rotation); + } + else + { + transform_.SetPositionAndRotation(newState.Position, newState.Rotation); + } transform_.localScale = newState.Scale; m_AnticipatedTransform = newState; if (CanCommitToTransform) @@ -277,7 +302,17 @@ private void ProcessSmoothing() m_PreviousAnticipatedTransform = m_AnticipatedTransform; if (!CanCommitToTransform) { - transform_.SetPositionAndRotation(m_AnticipatedTransform.Position, m_AnticipatedTransform.Rotation); + if (InLocalSpace) + { + transform_.SetLocalPositionAndRotation(m_AnticipatedTransform.Position, + m_AnticipatedTransform.Rotation); + } + else + { + transform_.SetPositionAndRotation(m_AnticipatedTransform.Position, + m_AnticipatedTransform.Rotation); + } + transform_.localScale = m_AnticipatedTransform.Scale; } } @@ -320,12 +355,25 @@ public void SetupForRender() if (Transform.CanCommitToTransform) { var transform_ = Transform.transform; - Transform.m_AuthoritativeTransform = new TransformState + if (Transform.InLocalSpace) { - Position = transform_.position, - Rotation = transform_.rotation, - Scale = transform_.localScale - }; + Transform.m_AuthoritativeTransform = new TransformState + { + Position = transform_.localPosition, + Rotation = transform_.localRotation, + Scale = transform_.localScale + }; + } + else + { + Transform.m_AuthoritativeTransform = new TransformState + { + Position = transform_.position, + Rotation = transform_.rotation, + Scale = transform_.localScale + }; + } + if (Transform.m_CurrentSmoothTime >= Transform.m_SmoothDuration) { // If we've had a call to Smooth() we'll continue interpolating. @@ -334,7 +382,17 @@ public void SetupForRender() Transform.m_AnticipatedTransform = Transform.m_AuthoritativeTransform; } - transform_.SetPositionAndRotation(Transform.m_AnticipatedTransform.Position, Transform.m_AnticipatedTransform.Rotation); + if (Transform.InLocalSpace) + { + transform_.SetLocalPositionAndRotation(Transform.m_AnticipatedTransform.Position, + Transform.m_AnticipatedTransform.Rotation); + } + else + { + transform_.SetPositionAndRotation(Transform.m_AnticipatedTransform.Position, + Transform.m_AnticipatedTransform.Rotation); + } + transform_.localScale = Transform.m_AnticipatedTransform.Scale; } } @@ -344,7 +402,17 @@ public void SetupForUpdate() if (Transform.CanCommitToTransform) { var transform_ = Transform.transform; - transform_.SetPositionAndRotation(Transform.m_AuthoritativeTransform.Position, Transform.m_AuthoritativeTransform.Rotation); + if (Transform.InLocalSpace) + { + transform_.SetLocalPositionAndRotation(Transform.m_AuthoritativeTransform.Position, + Transform.m_AuthoritativeTransform.Rotation); + } + else + { + transform_.SetPositionAndRotation(Transform.m_AuthoritativeTransform.Position, + Transform.m_AuthoritativeTransform.Rotation); + } + transform_.localScale = Transform.m_AuthoritativeTransform.Scale; } } @@ -367,12 +435,25 @@ public void ResetAnticipation() private void ResetAnticipatedState() { var transform_ = transform; - m_AuthoritativeTransform = new TransformState + if (InLocalSpace) + { + m_AuthoritativeTransform = new TransformState + { + Position = transform_.localPosition, + Rotation = transform_.localRotation, + Scale = transform_.localScale + }; + } + else { - Position = transform_.position, - Rotation = transform_.rotation, - Scale = transform_.localScale - }; + m_AuthoritativeTransform = new TransformState + { + Position = transform_.position, + Rotation = transform_.rotation, + Scale = transform_.localScale + }; + } + m_AnticipatedTransform = m_AuthoritativeTransform; m_PreviousAnticipatedTransform = m_AnticipatedTransform; @@ -439,7 +520,7 @@ public override void OnNetworkSpawn() return; } m_OutstandingAuthorityChange = true; - ApplyAuthoritativeState(); + //ApplyAuthoritativeState(); ResetAnticipatedState(); m_AnticipatedObject = new AnticipatedObject { Transform = this }; @@ -491,7 +572,15 @@ public void Smooth(TransformState from, TransformState to, float durationSeconds { m_AnticipatedTransform = to; m_PreviousAnticipatedTransform = m_AnticipatedTransform; - transform_.SetPositionAndRotation(to.Position, to.Rotation); + if (InLocalSpace) + { + transform_.SetLocalPositionAndRotation(to.Position, to.Rotation); + } + else + { + transform_.SetPositionAndRotation(to.Position, to.Rotation); + } + transform_.localScale = to.Scale; m_SmoothDuration = 0; m_CurrentSmoothTime = 0; @@ -502,7 +591,15 @@ public void Smooth(TransformState from, TransformState to, float durationSeconds if (!CanCommitToTransform) { - transform_.SetPositionAndRotation(from.Position, from.Rotation); + if (InLocalSpace) + { + transform_.SetLocalPositionAndRotation(from.Position, from.Rotation); + } + else + { + transform_.SetPositionAndRotation(from.Position, from.Rotation); + } + transform_.localScale = from.Scale; } @@ -543,14 +640,30 @@ protected override void OnTransformUpdated() var previousAnticipatedTransform = m_AnticipatedTransform; // Update authority state to catch any possible interpolation data - m_AuthoritativeTransform.Position = transform_.position; - m_AuthoritativeTransform.Rotation = transform_.rotation; - m_AuthoritativeTransform.Scale = transform_.localScale; + if (InLocalSpace) + { + m_AuthoritativeTransform.Position = transform_.localPosition; + m_AuthoritativeTransform.Rotation = transform_.localRotation; + m_AuthoritativeTransform.Scale = transform_.localScale; + } + else + { + m_AuthoritativeTransform.Position = transform_.position; + m_AuthoritativeTransform.Rotation = transform_.rotation; + m_AuthoritativeTransform.Scale = transform_.localScale; + } if (!m_OutstandingAuthorityChange) { // Keep the anticipated value unchanged, we have no updates from the server at all. - transform_.SetPositionAndRotation(previousAnticipatedTransform.Position, previousAnticipatedTransform.Rotation); + if (InLocalSpace) + { + transform_.SetLocalPositionAndRotation(previousAnticipatedTransform.Position, previousAnticipatedTransform.Rotation); + } + else + { + transform_.SetPositionAndRotation(previousAnticipatedTransform.Position, previousAnticipatedTransform.Rotation); + } transform_.localScale = previousAnticipatedTransform.Scale; return; } @@ -558,7 +671,17 @@ protected override void OnTransformUpdated() if (StaleDataHandling == StaleDataHandling.Ignore && m_LastAnticipaionCounter > m_LastAuthorityUpdateCounter) { // Keep the anticipated value unchanged because it is more recent than the authoritative one. - transform_.SetPositionAndRotation(previousAnticipatedTransform.Position, previousAnticipatedTransform.Rotation); + if (InLocalSpace) + { + transform_.SetLocalPositionAndRotation(previousAnticipatedTransform.Position, + previousAnticipatedTransform.Rotation); + } + else + { + transform_.SetPositionAndRotation(previousAnticipatedTransform.Position, + previousAnticipatedTransform.Rotation); + } + transform_.localScale = previousAnticipatedTransform.Scale; return; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs index 61024d46da..117e8e3b15 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs @@ -1,11 +1,15 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Numerics; using NUnit.Framework; using Unity.Netcode.Components; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.TestTools.Utils; +using Quaternion = UnityEngine.Quaternion; +using Vector3 = UnityEngine.Vector3; namespace Unity.Netcode.RuntimeTests { @@ -16,6 +20,12 @@ public void MoveRpc(Vector3 newPosition) { transform.position = newPosition; } + + [Rpc(SendTo.Server)] + public void LocalMoveRpc(Vector3 newPosition) + { + transform.localPosition = newPosition; + } [Rpc(SendTo.Server)] public void ScaleRpc(Vector3 newScale) @@ -29,6 +39,12 @@ public void RotateRpc(Quaternion newRotation) transform.rotation = newRotation; } + [Rpc(SendTo.Server)] + public void LocalRotateRpc(Quaternion newRotation) + { + transform.localRotation = newRotation; + } + public bool ShouldSmooth = false; public bool ShouldMove = false; @@ -59,12 +75,22 @@ internal class NetworkTransformAnticipationTests : NetcodeIntegrationTest protected override bool m_SetupIsACoroutine => false; protected override bool m_TearDownIsACoroutine => false; + private GameObject m_TestPrefab; + protected override void OnPlayerPrefabGameObjectCreated() { m_PlayerPrefab.AddComponent(); m_PlayerPrefab.AddComponent(); } + protected override void OnServerAndClientsCreated() + { + m_TestPrefab = CreateNetworkObjectPrefab("child object"); + var transform = m_TestPrefab.AddComponent(); + transform.InLocalSpace = true; + m_TestPrefab.AddComponent(); + } + protected override void OnTimeTravelServerAndClientsConnected() { var serverComponent = GetServerComponent(); @@ -80,6 +106,14 @@ protected override void OnTimeTravelServerAndClientsConnected() otherClientComponent.transform.position = Vector3.zero; otherClientComponent.transform.localScale = Vector3.one; otherClientComponent.transform.rotation = Quaternion.LookRotation(Vector3.forward); + + + var childObject = SpawnObject(m_TestPrefab, m_ServerNetworkManager); + childObject.transform.parent = serverComponent.transform; + childObject.transform.localPosition = Vector3.zero; + childObject.transform.localScale = Vector3.one; + childObject.transform.localRotation = Quaternion.LookRotation(Vector3.forward); + WaitForSpawnedOnAllOrTimeOutWithTimeTravel(childObject.GetComponent()); } public AnticipatedNetworkTransform GetTestComponent() @@ -115,15 +149,35 @@ public AnticipatedNetworkTransform GetOtherClientComponent() return null; } + public AnticipatedNetworkTransform GetChildComponent(AnticipatedNetworkTransform parent) + { + foreach (var tr in parent.GetComponentsInChildren()) + { + if (tr == parent) + { + continue; + } + + return tr; + } + + return null; + } + [Test] public void WhenAnticipating_ValueChangesImmediately() { var testComponent = GetTestComponent(); + var childComponent = GetChildComponent(testComponent); var quaternionComparer = new QuaternionEqualityComparer(0.000001f); testComponent.AnticipateMove(new Vector3(0, 1, 2)); testComponent.AnticipateScale(new Vector3(1, 2, 3)); testComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); + + childComponent.AnticipateMove(new Vector3(0, 1, 2)); + childComponent.AnticipateScale(new Vector3(1, 2, 3)); + childComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); Assert.AreEqual(new Vector3(0, 1, 2), testComponent.transform.position); Assert.AreEqual(new Vector3(1, 2, 3), testComponent.transform.localScale); @@ -132,40 +186,72 @@ public void WhenAnticipating_ValueChangesImmediately() Assert.AreEqual(new Vector3(0, 1, 2), testComponent.AnticipatedState.Position); Assert.AreEqual(new Vector3(1, 2, 3), testComponent.AnticipatedState.Scale); Assert.That(testComponent.AnticipatedState.Rotation, Is.EqualTo(Quaternion.LookRotation(new Vector3(2, 3, 4))).Using(quaternionComparer)); // Quaternion comparer added due to FP precision problems on Android devices. + + + Assert.AreEqual(new Vector3(0, 1, 2), childComponent.transform.localPosition); + Assert.AreEqual(new Vector3(1, 2, 3), childComponent.transform.localScale); + Assert.That(childComponent.transform.localRotation, Is.EqualTo(Quaternion.LookRotation(new Vector3(2, 3, 4))).Using(quaternionComparer)); // Quaternion comparer added due to FP precision problems on Android devices. + + Assert.AreEqual(new Vector3(0, 1, 2), childComponent.AnticipatedState.Position); + Assert.AreEqual(new Vector3(1, 2, 3), childComponent.AnticipatedState.Scale); + Assert.That(childComponent.AnticipatedState.Rotation, Is.EqualTo(Quaternion.LookRotation(new Vector3(2, 3, 4))).Using(quaternionComparer)); // Quaternion comparer added due to FP precision problems on Android devices. } [Test] public void WhenAnticipating_AuthoritativeValueDoesNotChange() { var testComponent = GetTestComponent(); + var childComponent = GetChildComponent(testComponent); var startPosition = testComponent.transform.position; var startScale = testComponent.transform.localScale; var startRotation = testComponent.transform.rotation; + + var childStartPosition = childComponent.transform.localPosition; + var childStartScale = childComponent.transform.localScale; + var childStartRotation = childComponent.transform.localRotation; testComponent.AnticipateMove(new Vector3(0, 1, 2)); testComponent.AnticipateScale(new Vector3(1, 2, 3)); testComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); + + childComponent.AnticipateMove(new Vector3(0, 1, 2)); + childComponent.AnticipateScale(new Vector3(1, 2, 3)); + childComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); Assert.AreEqual(startPosition, testComponent.AuthoritativeState.Position); Assert.AreEqual(startScale, testComponent.AuthoritativeState.Scale); Assert.AreEqual(startRotation, testComponent.AuthoritativeState.Rotation); + + Assert.AreEqual(childStartPosition, childComponent.AuthoritativeState.Position); + Assert.AreEqual(childStartScale, childComponent.AuthoritativeState.Scale); + Assert.AreEqual(childStartRotation, childComponent.AuthoritativeState.Rotation); } [Test] public void WhenAnticipating_ServerDoesNotChange() { var testComponent = GetTestComponent(); + var childComponent = GetChildComponent(testComponent); var startPosition = testComponent.transform.position; var startScale = testComponent.transform.localScale; var startRotation = testComponent.transform.rotation; + + var childStartPosition = childComponent.transform.localPosition; + var childStartScale = childComponent.transform.localScale; + var childStartRotation = childComponent.transform.localRotation; testComponent.AnticipateMove(new Vector3(0, 1, 2)); testComponent.AnticipateScale(new Vector3(1, 2, 3)); testComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); + childComponent.AnticipateMove(new Vector3(0, 1, 2)); + childComponent.AnticipateScale(new Vector3(1, 2, 3)); + childComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); + var serverComponent = GetServerComponent(); + var serverChild = GetChildComponent(serverComponent); Assert.AreEqual(startPosition, serverComponent.AuthoritativeState.Position); Assert.AreEqual(startScale, serverComponent.AuthoritativeState.Scale); @@ -174,6 +260,13 @@ public void WhenAnticipating_ServerDoesNotChange() Assert.AreEqual(startScale, serverComponent.AnticipatedState.Scale); Assert.AreEqual(startRotation, serverComponent.AnticipatedState.Rotation); + Assert.AreEqual(childStartPosition, serverChild.AuthoritativeState.Position); + Assert.AreEqual(childStartScale, serverChild.AuthoritativeState.Scale); + AssertQuaternionsAreEquivalent(childStartRotation, serverChild.AuthoritativeState.Rotation); + Assert.AreEqual(childStartPosition, serverChild.AnticipatedState.Position); + Assert.AreEqual(childStartScale, serverChild.AnticipatedState.Scale); + AssertQuaternionsAreEquivalent(childStartRotation, serverChild.AnticipatedState.Rotation); + TimeTravel(2, 120); Assert.AreEqual(startPosition, serverComponent.AuthoritativeState.Position); @@ -182,22 +275,35 @@ public void WhenAnticipating_ServerDoesNotChange() Assert.AreEqual(startPosition, serverComponent.AnticipatedState.Position); Assert.AreEqual(startScale, serverComponent.AnticipatedState.Scale); Assert.AreEqual(startRotation, serverComponent.AnticipatedState.Rotation); + + Assert.AreEqual(childStartPosition, serverChild.AuthoritativeState.Position); + Assert.AreEqual(childStartScale, serverChild.AuthoritativeState.Scale); + AssertQuaternionsAreEquivalent(childStartRotation, serverChild.AuthoritativeState.Rotation); + Assert.AreEqual(childStartPosition, serverChild.AnticipatedState.Position); + Assert.AreEqual(childStartScale, serverChild.AnticipatedState.Scale); + AssertQuaternionsAreEquivalent(childStartRotation, serverChild.AnticipatedState.Rotation); } [Test] public void WhenAnticipating_OtherClientDoesNotChange() { var testComponent = GetTestComponent(); + var childComponent = GetChildComponent(testComponent); var startPosition = testComponent.transform.position; var startScale = testComponent.transform.localScale; var startRotation = testComponent.transform.rotation; + + var childStartPosition = childComponent.transform.localPosition; + var childStartScale = childComponent.transform.localScale; + var childStartRotation = childComponent.transform.localRotation; testComponent.AnticipateMove(new Vector3(0, 1, 2)); testComponent.AnticipateScale(new Vector3(1, 2, 3)); testComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); var otherClientComponent = GetOtherClientComponent(); + var otherClientChild = GetChildComponent(otherClientComponent); Assert.AreEqual(startPosition, otherClientComponent.AuthoritativeState.Position); Assert.AreEqual(startScale, otherClientComponent.AuthoritativeState.Scale); @@ -206,6 +312,13 @@ public void WhenAnticipating_OtherClientDoesNotChange() Assert.AreEqual(startScale, otherClientComponent.AnticipatedState.Scale); Assert.AreEqual(startRotation, otherClientComponent.AnticipatedState.Rotation); + Assert.AreEqual(childStartPosition, otherClientChild.AuthoritativeState.Position); + Assert.AreEqual(childStartScale, otherClientChild.AuthoritativeState.Scale); + Assert.AreEqual(childStartRotation, otherClientChild.AuthoritativeState.Rotation); + Assert.AreEqual(childStartPosition, otherClientChild.AnticipatedState.Position); + Assert.AreEqual(childStartScale, otherClientChild.AnticipatedState.Scale); + Assert.AreEqual(childStartRotation, otherClientChild.AnticipatedState.Rotation); + TimeTravel(2, 120); Assert.AreEqual(startPosition, otherClientComponent.AuthoritativeState.Position); @@ -214,24 +327,45 @@ public void WhenAnticipating_OtherClientDoesNotChange() Assert.AreEqual(startPosition, otherClientComponent.AnticipatedState.Position); Assert.AreEqual(startScale, otherClientComponent.AnticipatedState.Scale); Assert.AreEqual(startRotation, otherClientComponent.AnticipatedState.Rotation); + + Assert.AreEqual(childStartPosition, otherClientChild.AuthoritativeState.Position); + Assert.AreEqual(childStartScale, otherClientChild.AuthoritativeState.Scale); + Assert.AreEqual(childStartRotation, otherClientChild.AuthoritativeState.Rotation); + Assert.AreEqual(childStartPosition, otherClientChild.AnticipatedState.Position); + Assert.AreEqual(childStartScale, otherClientChild.AnticipatedState.Scale); + Assert.AreEqual(childStartRotation, otherClientChild.AnticipatedState.Rotation); } [Test] public void WhenServerChangesSnapValue_ValuesAreUpdated() { var testComponent = GetTestComponent(); + var testChild = GetChildComponent(testComponent); var serverComponent = GetServerComponent(); + var serverChild = GetChildComponent(serverComponent); serverComponent.Interpolate = false; + serverChild.Interpolate = false; testComponent.AnticipateMove(new Vector3(0, 1, 2)); testComponent.AnticipateScale(new Vector3(1, 2, 3)); testComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); + + testChild.AnticipateMove(new Vector3(0, 1, 2)); + testChild.AnticipateScale(new Vector3(1, 2, 3)); + testChild.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); var rpcComponent = testComponent.GetComponent(); rpcComponent.MoveRpc(new Vector3(2, 3, 4)); + var childRpcComponent = testChild.GetComponent(); + childRpcComponent.LocalMoveRpc(new Vector3(2, 3, 4)); - WaitForMessageReceivedWithTimeTravel(new List { m_ServerNetworkManager }); + WaitForMessagesReceivedWithTimeTravel(new List + { + typeof(RpcMessage), + typeof(RpcMessage), + }, new List { m_ServerNetworkManager }); var otherClientComponent = GetOtherClientComponent(); + var otherClientChild = GetChildComponent(otherClientComponent); WaitForConditionOrTimeOutWithTimeTravel(() => testComponent.AuthoritativeState.Position == serverComponent.transform.position && otherClientComponent.AuthoritativeState.Position == serverComponent.transform.position); @@ -242,6 +376,16 @@ public void WhenServerChangesSnapValue_ValuesAreUpdated() Assert.AreEqual(serverComponent.transform.position, otherClientComponent.transform.position); Assert.AreEqual(serverComponent.transform.position, otherClientComponent.AnticipatedState.Position); Assert.AreEqual(serverComponent.transform.position, otherClientComponent.AuthoritativeState.Position); + + Assert.AreEqual(serverChild.transform.localPosition, testChild.transform.localPosition); + Assert.AreNotEqual(serverChild.transform.localPosition, testChild.transform.position); + Assert.AreEqual(serverChild.transform.localPosition, testChild.AnticipatedState.Position); + Assert.AreEqual(serverChild.transform.localPosition, testChild.AuthoritativeState.Position); + + Assert.AreEqual(serverChild.transform.localPosition, otherClientChild.transform.localPosition); + Assert.AreNotEqual(serverChild.transform.localPosition, otherClientChild.transform.position); + Assert.AreEqual(serverChild.transform.localPosition, otherClientChild.AnticipatedState.Position); + Assert.AreEqual(serverChild.transform.localPosition, otherClientChild.AuthoritativeState.Position); } public void AssertQuaternionsAreEquivalent(Quaternion a, Quaternion b) @@ -254,7 +398,7 @@ public void AssertQuaternionsAreEquivalent(Quaternion a, Quaternion b) } public void AssertVectorsAreEquivalent(Vector3 a, Vector3 b) { - Assert.AreEqual(a.x, b.x, 0.001, $"Vectors were not equal. Expected: {a}, but was {b}"); + Assert.AreEqual(a.x, b.x, 0.001, $"Vectors were not equal. Expected: {a}, but was {b}"); Assert.AreEqual(a.y, b.y, 0.001, $"Vectors were not equal. Expected: {a}, but was {b}"); Assert.AreEqual(a.z, b.z, 0.001, $"Vectors were not equal. Expected: {a}, but was {b}"); } @@ -264,15 +408,23 @@ public void WhenServerChangesSmoothValue_ValuesAreLerped() { var testComponent = GetTestComponent(); var otherClientComponent = GetOtherClientComponent(); + var testChild = GetChildComponent(testComponent); + var otherChild = GetChildComponent(otherClientComponent); testComponent.StaleDataHandling = StaleDataHandling.Ignore; otherClientComponent.StaleDataHandling = StaleDataHandling.Ignore; + testChild.StaleDataHandling = StaleDataHandling.Ignore; + otherChild.StaleDataHandling = StaleDataHandling.Ignore; var serverComponent = GetServerComponent(); + var serverChild = GetChildComponent(serverComponent); serverComponent.Interpolate = false; + serverChild.Interpolate = false; testComponent.GetComponent().ShouldSmooth = true; otherClientComponent.GetComponent().ShouldSmooth = true; + testChild.GetComponent().ShouldSmooth = true; + otherChild.GetComponent().ShouldSmooth = true; var startPosition = testComponent.transform.position; var startScale = testComponent.transform.localScale; @@ -287,17 +439,27 @@ public void WhenServerChangesSmoothValue_ValuesAreLerped() testComponent.AnticipateMove(anticipePosition); testComponent.AnticipateScale(anticipeScale); testComponent.AnticipateRotate(anticipeRotation); + testChild.AnticipateMove(anticipePosition); + testChild.AnticipateScale(anticipeScale); + testChild.AnticipateRotate(anticipeRotation); var rpcComponent = testComponent.GetComponent(); rpcComponent.MoveRpc(serverSetPosition); rpcComponent.RotateRpc(serverSetRotation); rpcComponent.ScaleRpc(serverSetScale); + var childRpcComponent = testChild.GetComponent(); + childRpcComponent.LocalMoveRpc(serverSetPosition); + childRpcComponent.LocalRotateRpc(serverSetRotation); + childRpcComponent.ScaleRpc(serverSetScale); WaitForMessagesReceivedWithTimeTravel(new List { typeof(RpcMessage), typeof(RpcMessage), typeof(RpcMessage), + typeof(RpcMessage), + typeof(RpcMessage), + typeof(RpcMessage), }, new List { m_ServerNetworkManager }); WaitForMessageReceivedWithTimeTravel(m_ClientNetworkManagers.ToList()); @@ -327,6 +489,30 @@ public void WhenServerChangesSmoothValue_ValuesAreLerped() AssertVectorsAreEquivalent(serverSetScale, otherClientComponent.AuthoritativeState.Scale); AssertQuaternionsAreEquivalent(serverSetRotation, otherClientComponent.AuthoritativeState.Rotation); + AssertVectorsAreEquivalent(Vector3.Lerp(anticipePosition, serverSetPosition, percentChanged), testChild.transform.localPosition); + AssertVectorsAreEquivalent(Vector3.Lerp(anticipeScale, serverSetScale, percentChanged), testChild.transform.localScale); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(anticipeRotation, serverSetRotation, percentChanged), testChild.transform.localRotation); + + AssertVectorsAreEquivalent(Vector3.Lerp(anticipePosition, serverSetPosition, percentChanged), testChild.AnticipatedState.Position); + AssertVectorsAreEquivalent(Vector3.Lerp(anticipeScale, serverSetScale, percentChanged), testChild.AnticipatedState.Scale); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(anticipeRotation, serverSetRotation, percentChanged), testChild.AnticipatedState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, testChild.AuthoritativeState.Position); + AssertVectorsAreEquivalent(serverSetScale, testChild.AuthoritativeState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, testChild.AuthoritativeState.Rotation); + + AssertVectorsAreEquivalent(Vector3.Lerp(startPosition, serverSetPosition, percentChanged), otherChild.transform.localPosition); + AssertVectorsAreEquivalent(Vector3.Lerp(startScale, serverSetScale, percentChanged), otherChild.transform.localScale); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(startRotation, serverSetRotation, percentChanged), otherChild.transform.localRotation); + + AssertVectorsAreEquivalent(Vector3.Lerp(startPosition, serverSetPosition, percentChanged), otherChild.AnticipatedState.Position); + AssertVectorsAreEquivalent(Vector3.Lerp(startScale, serverSetScale, percentChanged), otherChild.AnticipatedState.Scale); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(startRotation, serverSetRotation, percentChanged), otherChild.AnticipatedState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, otherChild.AuthoritativeState.Position); + AssertVectorsAreEquivalent(serverSetScale, otherChild.AuthoritativeState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, otherChild.AuthoritativeState.Rotation); + for (var i = 1; i < 60; ++i) { TimeTravel(1f / 60f, 1); @@ -355,6 +541,30 @@ public void WhenServerChangesSmoothValue_ValuesAreLerped() AssertVectorsAreEquivalent(serverSetPosition, otherClientComponent.AuthoritativeState.Position); AssertVectorsAreEquivalent(serverSetScale, otherClientComponent.AuthoritativeState.Scale); AssertQuaternionsAreEquivalent(serverSetRotation, otherClientComponent.AuthoritativeState.Rotation); + + AssertVectorsAreEquivalent(Vector3.Lerp(anticipePosition, serverSetPosition, percentChanged), testChild.transform.localPosition); + AssertVectorsAreEquivalent(Vector3.Lerp(anticipeScale, serverSetScale, percentChanged), testChild.transform.localScale); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(anticipeRotation, serverSetRotation, percentChanged), testChild.transform.localRotation); + + AssertVectorsAreEquivalent(Vector3.Lerp(anticipePosition, serverSetPosition, percentChanged), testChild.AnticipatedState.Position); + AssertVectorsAreEquivalent(Vector3.Lerp(anticipeScale, serverSetScale, percentChanged), testChild.AnticipatedState.Scale); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(anticipeRotation, serverSetRotation, percentChanged), testChild.AnticipatedState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, testChild.AuthoritativeState.Position); + AssertVectorsAreEquivalent(serverSetScale, testChild.AuthoritativeState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, testChild.AuthoritativeState.Rotation); + + AssertVectorsAreEquivalent(Vector3.Lerp(startPosition, serverSetPosition, percentChanged), otherChild.transform.localPosition); + AssertVectorsAreEquivalent(Vector3.Lerp(startScale, serverSetScale, percentChanged), otherChild.transform.localScale); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(startRotation, serverSetRotation, percentChanged), otherChild.transform.localRotation); + + AssertVectorsAreEquivalent(Vector3.Lerp(startPosition, serverSetPosition, percentChanged), otherChild.AnticipatedState.Position); + AssertVectorsAreEquivalent(Vector3.Lerp(startScale, serverSetScale, percentChanged), otherChild.AnticipatedState.Scale); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(startRotation, serverSetRotation, percentChanged), otherChild.AnticipatedState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, otherChild.AuthoritativeState.Position); + AssertVectorsAreEquivalent(serverSetScale, otherChild.AuthoritativeState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, otherChild.AuthoritativeState.Rotation); } TimeTravel(1f / 60f, 1); @@ -381,6 +591,30 @@ public void WhenServerChangesSmoothValue_ValuesAreLerped() AssertVectorsAreEquivalent(serverSetPosition, otherClientComponent.AuthoritativeState.Position); AssertVectorsAreEquivalent(serverSetScale, otherClientComponent.AuthoritativeState.Scale); AssertQuaternionsAreEquivalent(serverSetRotation, otherClientComponent.AuthoritativeState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, testChild.transform.localPosition); + AssertVectorsAreEquivalent(serverSetScale, testChild.transform.localScale); + AssertQuaternionsAreEquivalent(serverSetRotation, testChild.transform.localRotation); + + AssertVectorsAreEquivalent(serverSetPosition, testChild.AnticipatedState.Position); + AssertVectorsAreEquivalent(serverSetScale, testChild.AnticipatedState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, testChild.AnticipatedState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, testChild.AuthoritativeState.Position); + AssertVectorsAreEquivalent(serverSetScale, testChild.AuthoritativeState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, testChild.AuthoritativeState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, otherChild.transform.localPosition); + AssertVectorsAreEquivalent(serverSetScale, otherChild.transform.localScale); + AssertQuaternionsAreEquivalent(serverSetRotation, otherChild.transform.localRotation); + + AssertVectorsAreEquivalent(serverSetPosition, otherChild.AnticipatedState.Position); + AssertVectorsAreEquivalent(serverSetScale, otherChild.AnticipatedState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, otherChild.AnticipatedState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, otherChild.AuthoritativeState.Position); + AssertVectorsAreEquivalent(serverSetScale, otherChild.AuthoritativeState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, otherChild.AuthoritativeState.Rotation); } [Test] diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs index a6f4a83275..cf26f8f41c 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs @@ -1934,6 +1934,23 @@ protected IEnumerator WaitForConditionOrTimeOut(Func checkF }, timeOutHelper); } + /// + /// Waits until the specified condition returns true or a timeout occurs. + /// This overload allows the condition to provide additional error details via a . + /// + /// A delegate that takes a for error details and returns true when the desired condition is met. + /// An optional to control the timeout period. If null, the default timeout is used. + /// An for use in Unity coroutines. + protected void WaitForConditionOrTimeOutWithTimeTravel(Func checkForCondition, int maxTries = 60) + { + WaitForConditionOrTimeOutWithTimeTravel(() => + { + // Clear errorBuilder before each check to ensure the errorBuilder only contains information from the lastest run + m_InternalErrorLog.Clear(); + return checkForCondition(m_InternalErrorLog); + }, maxTries); + } + /// /// Waits until the given NetworkObject is spawned on all clients or a timeout occurs. /// @@ -2009,6 +2026,81 @@ bool ValidateObjectsSpawnedOnAllClients(StringBuilder errorLog) yield return WaitForConditionOrTimeOut(ValidateObjectsSpawnedOnAllClients, timeOutHelper); } + /// + /// Waits until the given NetworkObject is spawned on all clients or a timeout occurs. + /// + /// The id of the to wait for. + /// An optional to control the timeout period. If null, the default timeout is used. + /// An for use in Unity coroutines. + protected void WaitForSpawnedOnAllOrTimeOutWithTimeTravel(ulong networkObjectId, int maxTries = 60) + { + bool ValidateObjectSpawnedOnAllClients(StringBuilder errorLog) + { + foreach (var client in m_NetworkManagers) + { + if (!client.SpawnManager.SpawnedObjects.ContainsKey(networkObjectId)) + { + errorLog.Append($"Client-{client.LocalClientId} has not spawned Object-{networkObjectId}!"); + return false; + } + } + return true; + } + + WaitForConditionOrTimeOutWithTimeTravel(ValidateObjectSpawnedOnAllClients, maxTries); + } + + /// + /// Waits until the given NetworkObject is spawned on all clients or a timeout occurs. + /// + /// The to wait for. + /// An optional to control the timeout period. If null, the default timeout is used. + /// An for use in Unity coroutines. + protected void WaitForSpawnedOnAllOrTimeOutWithTimeTravel(NetworkObject networkObject, int maxTries = 60) + { + var networkObjectId = networkObject.NetworkObjectId; + WaitForSpawnedOnAllOrTimeOutWithTimeTravel(networkObjectId, maxTries); + } + + /// + /// Waits until the given NetworkObject is spawned on all clients or a timeout occurs. + /// + /// The containing a to wait for. + /// An optional to control the timeout period. If null, the default timeout is used. + /// An for use in Unity coroutines. + protected void WaitForSpawnedOnAllOrTimeOutWithTimeTravel(GameObject gameObject, int maxTries = 60) + { + var networkObjectId = gameObject.GetComponent().NetworkObjectId; + WaitForSpawnedOnAllOrTimeOutWithTimeTravel(networkObjectId, maxTries); + } + + /// + /// Waits until all given NetworkObjects are spawned on all clients or a timeout occurs. + /// + /// The list of s to wait for. + /// An optional to control the timeout period. If null, the default timeout is used. + /// An for use in Unity coroutines. + protected void WaitForSpawnedOnAllOrTimeOutWithTimeTravel(List networkObjects, int maxTries = 60) + { + bool ValidateObjectsSpawnedOnAllClients(StringBuilder errorLog) + { + foreach (var client in m_NetworkManagers) + { + foreach (var networkObject in networkObjects) + { + if (!client.SpawnManager.SpawnedObjects.ContainsKey(networkObject.NetworkObjectId)) + { + errorLog.Append($"Client-{client.LocalClientId} has not spawned Object-{networkObject.NetworkObjectId}!"); + return false; + } + } + } + return true; + } + + WaitForConditionOrTimeOutWithTimeTravel(ValidateObjectsSpawnedOnAllClients, maxTries); + } + /// /// Waits until all given NetworkObjects are spawned on all clients or a timeout occurs. /// From 9126581b8e848ab02b83f83ab3e54c5a57a8d7ef Mon Sep 17 00:00:00 2001 From: Kitty Draper Date: Thu, 21 May 2026 13:58:33 -0500 Subject: [PATCH 2/7] changelog --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 0d8fb3a2fb..7a238e951c 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -24,6 +24,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed AnticipatedNetworkTransform not respecting the InLocalSpace flag (#3995) ### Security From 9d1bf1fee0f00a28097ce5f26aad1f96a0affb8a Mon Sep 17 00:00:00 2001 From: Kitty Draper Date: Thu, 21 May 2026 17:10:25 -0500 Subject: [PATCH 3/7] fix formatting --- .../NetworkTransformAnticipationTests.cs | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs index 117e8e3b15..ce969cecf0 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs @@ -20,7 +20,7 @@ public void MoveRpc(Vector3 newPosition) { transform.position = newPosition; } - + [Rpc(SendTo.Server)] public void LocalMoveRpc(Vector3 newPosition) { @@ -106,8 +106,7 @@ protected override void OnTimeTravelServerAndClientsConnected() otherClientComponent.transform.position = Vector3.zero; otherClientComponent.transform.localScale = Vector3.one; otherClientComponent.transform.rotation = Quaternion.LookRotation(Vector3.forward); - - + var childObject = SpawnObject(m_TestPrefab, m_ServerNetworkManager); childObject.transform.parent = serverComponent.transform; childObject.transform.localPosition = Vector3.zero; @@ -174,7 +173,7 @@ public void WhenAnticipating_ValueChangesImmediately() testComponent.AnticipateMove(new Vector3(0, 1, 2)); testComponent.AnticipateScale(new Vector3(1, 2, 3)); testComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); - + childComponent.AnticipateMove(new Vector3(0, 1, 2)); childComponent.AnticipateScale(new Vector3(1, 2, 3)); childComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); @@ -186,8 +185,7 @@ public void WhenAnticipating_ValueChangesImmediately() Assert.AreEqual(new Vector3(0, 1, 2), testComponent.AnticipatedState.Position); Assert.AreEqual(new Vector3(1, 2, 3), testComponent.AnticipatedState.Scale); Assert.That(testComponent.AnticipatedState.Rotation, Is.EqualTo(Quaternion.LookRotation(new Vector3(2, 3, 4))).Using(quaternionComparer)); // Quaternion comparer added due to FP precision problems on Android devices. - - + Assert.AreEqual(new Vector3(0, 1, 2), childComponent.transform.localPosition); Assert.AreEqual(new Vector3(1, 2, 3), childComponent.transform.localScale); Assert.That(childComponent.transform.localRotation, Is.EqualTo(Quaternion.LookRotation(new Vector3(2, 3, 4))).Using(quaternionComparer)); // Quaternion comparer added due to FP precision problems on Android devices. @@ -206,7 +204,7 @@ public void WhenAnticipating_AuthoritativeValueDoesNotChange() var startPosition = testComponent.transform.position; var startScale = testComponent.transform.localScale; var startRotation = testComponent.transform.rotation; - + var childStartPosition = childComponent.transform.localPosition; var childStartScale = childComponent.transform.localScale; var childStartRotation = childComponent.transform.localRotation; @@ -214,7 +212,7 @@ public void WhenAnticipating_AuthoritativeValueDoesNotChange() testComponent.AnticipateMove(new Vector3(0, 1, 2)); testComponent.AnticipateScale(new Vector3(1, 2, 3)); testComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); - + childComponent.AnticipateMove(new Vector3(0, 1, 2)); childComponent.AnticipateScale(new Vector3(1, 2, 3)); childComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); @@ -222,7 +220,7 @@ public void WhenAnticipating_AuthoritativeValueDoesNotChange() Assert.AreEqual(startPosition, testComponent.AuthoritativeState.Position); Assert.AreEqual(startScale, testComponent.AuthoritativeState.Scale); Assert.AreEqual(startRotation, testComponent.AuthoritativeState.Rotation); - + Assert.AreEqual(childStartPosition, childComponent.AuthoritativeState.Position); Assert.AreEqual(childStartScale, childComponent.AuthoritativeState.Scale); Assert.AreEqual(childStartRotation, childComponent.AuthoritativeState.Rotation); @@ -237,7 +235,7 @@ public void WhenAnticipating_ServerDoesNotChange() var startPosition = testComponent.transform.position; var startScale = testComponent.transform.localScale; var startRotation = testComponent.transform.rotation; - + var childStartPosition = childComponent.transform.localPosition; var childStartScale = childComponent.transform.localScale; var childStartRotation = childComponent.transform.localRotation; @@ -249,7 +247,7 @@ public void WhenAnticipating_ServerDoesNotChange() childComponent.AnticipateMove(new Vector3(0, 1, 2)); childComponent.AnticipateScale(new Vector3(1, 2, 3)); childComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); - + var serverComponent = GetServerComponent(); var serverChild = GetChildComponent(serverComponent); @@ -293,7 +291,7 @@ public void WhenAnticipating_OtherClientDoesNotChange() var startPosition = testComponent.transform.position; var startScale = testComponent.transform.localScale; var startRotation = testComponent.transform.rotation; - + var childStartPosition = childComponent.transform.localPosition; var childStartScale = childComponent.transform.localScale; var childStartRotation = childComponent.transform.localRotation; @@ -349,7 +347,7 @@ public void WhenServerChangesSnapValue_ValuesAreUpdated() testComponent.AnticipateMove(new Vector3(0, 1, 2)); testComponent.AnticipateScale(new Vector3(1, 2, 3)); testComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); - + testChild.AnticipateMove(new Vector3(0, 1, 2)); testChild.AnticipateScale(new Vector3(1, 2, 3)); testChild.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); @@ -376,7 +374,7 @@ public void WhenServerChangesSnapValue_ValuesAreUpdated() Assert.AreEqual(serverComponent.transform.position, otherClientComponent.transform.position); Assert.AreEqual(serverComponent.transform.position, otherClientComponent.AnticipatedState.Position); Assert.AreEqual(serverComponent.transform.position, otherClientComponent.AuthoritativeState.Position); - + Assert.AreEqual(serverChild.transform.localPosition, testChild.transform.localPosition); Assert.AreNotEqual(serverChild.transform.localPosition, testChild.transform.position); Assert.AreEqual(serverChild.transform.localPosition, testChild.AnticipatedState.Position); From dff705930449611dcee008119c478b13aeb2a0f3 Mon Sep 17 00:00:00 2001 From: Kitty Draper Date: Thu, 21 May 2026 17:13:45 -0500 Subject: [PATCH 4/7] fix xmldocs --- .../Runtime/TestHelpers/NetcodeIntegrationTest.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs index cf26f8f41c..2c3b94e90a 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs @@ -1939,7 +1939,7 @@ protected IEnumerator WaitForConditionOrTimeOut(Func checkF /// This overload allows the condition to provide additional error details via a . /// /// A delegate that takes a for error details and returns true when the desired condition is met. - /// An optional to control the timeout period. If null, the default timeout is used. + /// the maximum times to check for the condition (default is 60). /// An for use in Unity coroutines. protected void WaitForConditionOrTimeOutWithTimeTravel(Func checkForCondition, int maxTries = 60) { @@ -2030,7 +2030,7 @@ bool ValidateObjectsSpawnedOnAllClients(StringBuilder errorLog) /// Waits until the given NetworkObject is spawned on all clients or a timeout occurs. /// /// The id of the to wait for. - /// An optional to control the timeout period. If null, the default timeout is used. + /// the maximum times to check for the condition (default is 60). /// An for use in Unity coroutines. protected void WaitForSpawnedOnAllOrTimeOutWithTimeTravel(ulong networkObjectId, int maxTries = 60) { @@ -2054,7 +2054,7 @@ bool ValidateObjectSpawnedOnAllClients(StringBuilder errorLog) /// Waits until the given NetworkObject is spawned on all clients or a timeout occurs. /// /// The to wait for. - /// An optional to control the timeout period. If null, the default timeout is used. + /// the maximum times to check for the condition (default is 60). /// An for use in Unity coroutines. protected void WaitForSpawnedOnAllOrTimeOutWithTimeTravel(NetworkObject networkObject, int maxTries = 60) { @@ -2066,7 +2066,7 @@ protected void WaitForSpawnedOnAllOrTimeOutWithTimeTravel(NetworkObject networkO /// Waits until the given NetworkObject is spawned on all clients or a timeout occurs. /// /// The containing a to wait for. - /// An optional to control the timeout period. If null, the default timeout is used. + /// the maximum times to check for the condition (default is 60). /// An for use in Unity coroutines. protected void WaitForSpawnedOnAllOrTimeOutWithTimeTravel(GameObject gameObject, int maxTries = 60) { @@ -2078,7 +2078,7 @@ protected void WaitForSpawnedOnAllOrTimeOutWithTimeTravel(GameObject gameObject, /// Waits until all given NetworkObjects are spawned on all clients or a timeout occurs. /// /// The list of s to wait for. - /// An optional to control the timeout period. If null, the default timeout is used. + /// the maximum times to check for the condition (default is 60). /// An for use in Unity coroutines. protected void WaitForSpawnedOnAllOrTimeOutWithTimeTravel(List networkObjects, int maxTries = 60) { From e7b9b61b91f248b8d407c1613262ab76f77e8f74 Mon Sep 17 00:00:00 2001 From: Kitty Draper Date: Thu, 21 May 2026 17:18:00 -0500 Subject: [PATCH 5/7] fix xmldocs more --- .../Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs index 2c3b94e90a..1beb8a3a66 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs @@ -1940,7 +1940,6 @@ protected IEnumerator WaitForConditionOrTimeOut(Func checkF /// /// A delegate that takes a for error details and returns true when the desired condition is met. /// the maximum times to check for the condition (default is 60). - /// An for use in Unity coroutines. protected void WaitForConditionOrTimeOutWithTimeTravel(Func checkForCondition, int maxTries = 60) { WaitForConditionOrTimeOutWithTimeTravel(() => @@ -2031,7 +2030,6 @@ bool ValidateObjectsSpawnedOnAllClients(StringBuilder errorLog) /// /// The id of the to wait for. /// the maximum times to check for the condition (default is 60). - /// An for use in Unity coroutines. protected void WaitForSpawnedOnAllOrTimeOutWithTimeTravel(ulong networkObjectId, int maxTries = 60) { bool ValidateObjectSpawnedOnAllClients(StringBuilder errorLog) @@ -2055,7 +2053,6 @@ bool ValidateObjectSpawnedOnAllClients(StringBuilder errorLog) /// /// The to wait for. /// the maximum times to check for the condition (default is 60). - /// An for use in Unity coroutines. protected void WaitForSpawnedOnAllOrTimeOutWithTimeTravel(NetworkObject networkObject, int maxTries = 60) { var networkObjectId = networkObject.NetworkObjectId; @@ -2067,7 +2064,6 @@ protected void WaitForSpawnedOnAllOrTimeOutWithTimeTravel(NetworkObject networkO /// /// The containing a to wait for. /// the maximum times to check for the condition (default is 60). - /// An for use in Unity coroutines. protected void WaitForSpawnedOnAllOrTimeOutWithTimeTravel(GameObject gameObject, int maxTries = 60) { var networkObjectId = gameObject.GetComponent().NetworkObjectId; @@ -2079,7 +2075,6 @@ protected void WaitForSpawnedOnAllOrTimeOutWithTimeTravel(GameObject gameObject, /// /// The list of s to wait for. /// the maximum times to check for the condition (default is 60). - /// An for use in Unity coroutines. protected void WaitForSpawnedOnAllOrTimeOutWithTimeTravel(List networkObjects, int maxTries = 60) { bool ValidateObjectsSpawnedOnAllClients(StringBuilder errorLog) From 24f457ef9857e4db87ffc013fa130f87fb1e9c2a Mon Sep 17 00:00:00 2001 From: Kitty Draper Date: Thu, 21 May 2026 17:56:55 -0500 Subject: [PATCH 6/7] fix another format error --- .../Tests/Runtime/NetworkTransformAnticipationTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs index ce969cecf0..95b0825c73 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs @@ -396,7 +396,7 @@ public void AssertQuaternionsAreEquivalent(Quaternion a, Quaternion b) } public void AssertVectorsAreEquivalent(Vector3 a, Vector3 b) { - Assert.AreEqual(a.x, b.x, 0.001, $"Vectors were not equal. Expected: {a}, but was {b}"); + Assert.AreEqual(a.x, b.x, 0.001, $"Vectors were not equal. Expected: {a}, but was {b}"); Assert.AreEqual(a.y, b.y, 0.001, $"Vectors were not equal. Expected: {a}, but was {b}"); Assert.AreEqual(a.z, b.z, 0.001, $"Vectors were not equal. Expected: {a}, but was {b}"); } From f97333c65cbeda1de5dc09f19208d1683b839952 Mon Sep 17 00:00:00 2001 From: Kitty Draper Date: Thu, 21 May 2026 19:19:41 -0500 Subject: [PATCH 7/7] removed unused using directives --- .../Tests/Runtime/NetworkTransformAnticipationTests.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs index 95b0825c73..f1ead09032 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs @@ -1,8 +1,6 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Numerics; using NUnit.Framework; using Unity.Netcode.Components; using Unity.Netcode.TestHelpers.Runtime;