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 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..f1ead09032 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs @@ -6,6 +6,8 @@ using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.TestTools.Utils; +using Quaternion = UnityEngine.Quaternion; +using Vector3 = UnityEngine.Vector3; namespace Unity.Netcode.RuntimeTests { @@ -17,6 +19,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 +37,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 +73,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 +104,13 @@ 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,16 +146,36 @@ 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); Assert.That(testComponent.transform.rotation, Is.EqualTo(Quaternion.LookRotation(new Vector3(2, 3, 4))).Using(quaternionComparer)); // Quaternion comparer added due to FP precision problems on Android devices. @@ -132,40 +183,71 @@ 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 +256,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 +271,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 +308,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 +323,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 +372,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) @@ -264,15 +404,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 +435,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 +485,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 +537,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 +587,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..1beb8a3a66 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs @@ -1934,6 +1934,22 @@ 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. + /// the maximum times to check for the condition (default is 60). + 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 +2025,77 @@ 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. + /// the maximum times to check for the condition (default is 60). + 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. + /// the maximum times to check for the condition (default is 60). + 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. + /// the maximum times to check for the condition (default is 60). + 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. + /// the maximum times to check for the condition (default is 60). + 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. ///