diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs index 0d0de9afa8..4094e084a8 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs @@ -2,6 +2,7 @@ using System.Runtime.CompilerServices; using UnityEngine; + namespace Unity.Netcode.Components { /// @@ -571,7 +572,7 @@ private void InternalMoveRotation2D(Quaternion rotation) { var quaternion = Quaternion.identity; var angles = quaternion.eulerAngles; - angles.z = m_InternalRigidbody2D.rotation; + angles.z = rotation.z; quaternion.eulerAngles = angles; m_InternalRigidbody2D.MoveRotation(quaternion); } @@ -845,6 +846,28 @@ public void SetIsKinematic(bool isKinematic) PostSetIsKinematic(); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool HasInterpolationTypeSet(InterpolationTypes interpolationType) + { +#if COM_UNITY_MODULES_PHYSICS && COM_UNITY_MODULES_PHYSICS2D + if (m_IsRigidbody2D) + { + return interpolationType == InterpolationTypes.Extrapolate ? m_InternalRigidbody2D.interpolation == RigidbodyInterpolation2D.Extrapolate : m_InternalRigidbody2D.interpolation == RigidbodyInterpolation2D.Interpolate; + } + else + { + return interpolationType == InterpolationTypes.Extrapolate ? m_InternalRigidbody.interpolation == RigidbodyInterpolation.Extrapolate : m_InternalRigidbody.interpolation == RigidbodyInterpolation.Interpolate; + } +#endif +#if COM_UNITY_MODULES_PHYSICS && !COM_UNITY_MODULES_PHYSICS2D + return interpolationType == InterpolationTypes.Extrapolate ? m_InternalRigidbody2D.interpolation == RigidbodyInterpolation2D.Extrapolate : m_InternalRigidbody2D.interpolation == RigidbodyInterpolation2D.Interpolate; +#endif +#if !COM_UNITY_MODULES_PHYSICS && COM_UNITY_MODULES_PHYSICS2D + return interpolationType == InterpolationTypes.Extrapolate ? m_InternalRigidbody.interpolation == RigidbodyInterpolation.Extrapolate : m_InternalRigidbody.interpolation == RigidbodyInterpolation.Interpolate; +#endif + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void PostSetIsKinematic() { @@ -853,26 +876,29 @@ private void PostSetIsKinematic() { return; } + if (UseRigidBodyForMotion) { - // Only if the NetworkTransform is set to interpolate do we need to check for extrapolation - if (NetworkTransform.Interpolate && m_OriginalInterpolation == InterpolationTypes.Extrapolate) + // Exit early if the original interpolation type is not set to extrapolate or NetworkTransform interpolate is disabled + if (m_OriginalInterpolation != InterpolationTypes.Extrapolate || !NetworkTransform.Interpolate) { - if (IsKinematic()) - { - // If not already set to interpolate then set the Rigidbody to interpolate - if (m_InternalRigidbody.interpolation == RigidbodyInterpolation.Extrapolate) - { - // Sleep until the next fixed update when switching from extrapolation to interpolation - SleepRigidbody(); - SetInterpolation(InterpolationTypes.Interpolate); - } - } - else - { - // Switch it back to the original interpolation if non-kinematic (doesn't require sleep). - SetInterpolation(m_OriginalInterpolation); - } + return; + } + + // Otherwise, if this is the active physics object + if (!IsKinematic()) + { + // switch it back to the original interpolation and exit early + SetInterpolation(m_OriginalInterpolation); + return; + } + + // If the Rigidbody or Rigidbody2D is currently configured to extrapolate + if (HasInterpolationTypeSet(InterpolationTypes.Extrapolate)) + { + // sleep until the next fixed update when switching from extrapolation to interpolation + SleepRigidbody(); + SetInterpolation(InterpolationTypes.Interpolate); } } else diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs index 4509797369..4d325613aa 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs @@ -10,104 +10,230 @@ namespace Unity.Netcode.RuntimeTests { - [TestFixture(RigidbodyInterpolation.Interpolate, true, true)] // This should be allowed under all condistions when using Rigidbody motion - [TestFixture(RigidbodyInterpolation.Extrapolate, true, true)] // This should not allow extrapolation on non-auth instances when using Rigidbody motion & NT interpolation - [TestFixture(RigidbodyInterpolation.Extrapolate, false, true)] // This should allow extrapolation on non-auth instances when using Rigidbody & NT has no interpolation - [TestFixture(RigidbodyInterpolation.Interpolate, true, false)] // This should not allow kinematic instances to have Rigidbody interpolation enabled - [TestFixture(RigidbodyInterpolation.Interpolate, false, false)] // Testing that rigid body interpolation remains the same if NT interpolate is disabled + [TestFixture(HostOrServer.Server)] + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.DAHost)] internal class NetworkRigidbodyTest : NetcodeIntegrationTest { protected override int NumberOfClients => 1; - private bool m_NetworkTransformInterpolate; - private bool m_UseRigidBodyForMotion; - private RigidbodyInterpolation m_RigidbodyInterpolation; - public NetworkRigidbodyTest(RigidbodyInterpolation rigidbodyInterpolation, bool networkTransformInterpolate, bool useRigidbodyForMotion) + private List<(RigidbodyInterpolation interpolationType, bool enableInterpolation, bool useRigidbodyForMotion)> m_TestConfigurations = + new List<(RigidbodyInterpolation interpolationType, bool enableInterpolation, bool useRigidbodyForMotion)>() + { + (RigidbodyInterpolation.Interpolate, true, true), // This should be allowed under all condistions when using Rigidbody motion + (RigidbodyInterpolation.Extrapolate, true, true), // This should not allow extrapolation on non-auth instances when using Rigidbody motion & NT interpolation + (RigidbodyInterpolation.Extrapolate, false, true), // This should allow extrapolation on non-auth instances when using Rigidbody & NT has no interpolation + (RigidbodyInterpolation.Interpolate, true, false), // This should not allow kinematic instances to have Rigidbody interpolation enabled + (RigidbodyInterpolation.Interpolate, false, false) // Testing that rigidbody interpolation remains the same if NT interpolate is disabled + }; + + /// + /// The current test configuration applied to the current test running. + /// + private (RigidbodyInterpolation interpolationType, bool enableInterpolation, bool useRigidbodyForMotion) m_CurrentConfiguration; + + public NetworkRigidbodyTest(HostOrServer hostOrServer) : base(hostOrServer) { - m_RigidbodyInterpolation = rigidbodyInterpolation; - m_NetworkTransformInterpolate = networkTransformInterpolate; - m_UseRigidBodyForMotion = useRigidbodyForMotion; } - protected override void OnCreatePlayerPrefab() + /// + /// Base prefab for and + /// + private GameObject m_RigidbodyPrefab; + private NetworkTransform m_3DNetworkTransform; + private Rigidbody m_PrefabRigidbody; + private NetworkRigidbody m_PrefabNetworkRigidbody; + private NetworkObject m_3DAuthorityInstance; + + /// + /// Base prefab for and + /// + private GameObject m_Rigidbody2DPrefab; + private NetworkTransform m_2DNetworkTransform; + private Rigidbody2D m_PrefabRigidbody2D; + private NetworkRigidbody2D m_PrefabNetworkRigidbody2D; + private NetworkObject m_2DAuthorityInstance; + + protected override void OnServerAndClientsCreated() { - var networkTransform = m_PlayerPrefab.AddComponent(); - networkTransform.Interpolate = m_NetworkTransformInterpolate; - var rigidbody = m_PlayerPrefab.AddComponent(); - rigidbody.interpolation = m_RigidbodyInterpolation; - var networkRigidbody = m_PlayerPrefab.AddComponent(); - networkRigidbody.UseRigidBodyForMotion = m_UseRigidBodyForMotion; + m_RigidbodyPrefab = CreateNetworkObjectPrefab("RBTest"); + m_3DNetworkTransform = m_RigidbodyPrefab.AddComponent(); + m_PrefabRigidbody = m_RigidbodyPrefab.AddComponent(); + m_PrefabNetworkRigidbody = m_RigidbodyPrefab.AddComponent(); + + m_Rigidbody2DPrefab = CreateNetworkObjectPrefab("RB2DTest"); + m_2DNetworkTransform = m_Rigidbody2DPrefab.AddComponent(); + m_PrefabRigidbody2D = m_Rigidbody2DPrefab.AddComponent(); + m_PrefabNetworkRigidbody2D = m_Rigidbody2DPrefab.AddComponent(); + + base.OnServerAndClientsCreated(); + } + + private string m_ConfigHeader; + private void ApplyCurrentTestConfiguration() + { + // Configure both 3D and 2D versions based on the current test configuration + m_3DNetworkTransform.Interpolate = m_CurrentConfiguration.enableInterpolation; + m_PrefabRigidbody.interpolation = m_CurrentConfiguration.interpolationType; + m_PrefabNetworkRigidbody.UseRigidBodyForMotion = m_CurrentConfiguration.useRigidbodyForMotion; + m_2DNetworkTransform.Interpolate = m_CurrentConfiguration.enableInterpolation; + m_PrefabRigidbody2D.interpolation = m_CurrentConfiguration.interpolationType == RigidbodyInterpolation.Interpolate ? RigidbodyInterpolation2D.Interpolate : RigidbodyInterpolation2D.Extrapolate; + m_PrefabNetworkRigidbody2D.UseRigidBodyForMotion = m_CurrentConfiguration.useRigidbodyForMotion; + + // Build a header used in assert messages + m_ConfigHeader = $"[{m_CurrentConfiguration.interpolationType}][Interpolate: {m_CurrentConfiguration.enableInterpolation}][RB-Motion: {m_CurrentConfiguration.useRigidbodyForMotion}]"; } /// - /// Tests that a server can destroy a NetworkObject and that it gets despawned correctly. + /// Iterates through the to validate various + /// Rigidbody interpolation settings and kinematic states for authority and non-authority + /// instances. /// - /// [UnityTest] public IEnumerator TestRigidbodyKinematicEnableDisable() { - // This is the *SERVER VERSION* of the *CLIENT PLAYER* - var serverClientPlayerInstance = m_ServerNetworkManager.ConnectedClients[m_ClientNetworkManagers[0].LocalClientId].PlayerObject; + foreach (var configuration in m_TestConfigurations) + { + m_CurrentConfiguration = configuration; + ApplyCurrentTestConfiguration(); - // This is the *CLIENT VERSION* of the *CLIENT PLAYER* - var clientPlayerInstance = m_ClientNetworkManagers[0].LocalClient.PlayerObject; + // Host, Server, DAHost/Session-owner are spawn authority + yield return RunTestConfiguration(); - Assert.IsNotNull(serverClientPlayerInstance, $"{nameof(serverClientPlayerInstance)} is null!"); - Assert.IsNotNull(clientPlayerInstance, $"{nameof(clientPlayerInstance)} is null!"); + // When using distributed authority, swap the session owner with + // the non-session owner client as being the spawn authority. + if (m_DistributedAuthority) + { + yield return RunTestConfiguration(true); + } + } + } + + /// + /// Validates the current applied test configuration. + /// + private IEnumerator RunTestConfiguration(bool swapAuthority = false) + { + // The authority is the "spawn authority". + // Distributed authority runs this a second time with a non-session owner client being the + // spawn authority to validate that scenario works correctly. + var authority = !swapAuthority ? GetAuthorityNetworkManager() : GetNonAuthorityNetworkManager(); + var nonAuthority = !swapAuthority ? GetNonAuthorityNetworkManager() : GetAuthorityNetworkManager(); + + // Spawn instances of both the 3D and 2D prefabs configured for the current test. + m_3DAuthorityInstance = SpawnObject(m_RigidbodyPrefab, authority).GetComponent(); + yield return WaitForSpawnedOnAllOrTimeOut(m_3DAuthorityInstance); + AssertOnTimeout($"Failed to spawn {m_3DAuthorityInstance.name} on all clients!"); + + m_2DAuthorityInstance = SpawnObject(m_Rigidbody2DPrefab, authority).GetComponent(); + yield return WaitForSpawnedOnAllOrTimeOut(m_2DAuthorityInstance); + AssertOnTimeout($"Failed to spawn {m_2DAuthorityInstance.name} on all clients!"); + + // Test 3D Rigidbody + #region 3D Rigidbody validation + var authorityRigidbody = m_3DAuthorityInstance.GetComponent(); + var nonAuthorityInstance = nonAuthority.SpawnManager.SpawnedObjects[m_3DAuthorityInstance.NetworkObjectId]; + var nonAuthorityRigidbody = nonAuthorityInstance.GetComponent(); + var authorityHeader = $"{m_ConfigHeader}[Authority] Client-{authority.LocalClientId}'s instance of {m_3DAuthorityInstance.name}"; + // The authority instance should always be non-kinematic + Assert.False(authorityRigidbody.isKinematic, $"{authorityHeader} is kinematic!"); + + var nonAuthorityHeader = $"{m_ConfigHeader}[Non-Authority] Client-{nonAuthority.LocalClientId}'s instance of {nonAuthorityInstance.name}"; + // Non-authority instances should always be kinematic + Assert.True(nonAuthorityRigidbody.isKinematic, $"{nonAuthorityHeader} is not kinematic!"); + var interpolateCompareNonAuthoritative = RigidbodyInterpolation.None; + + if (m_CurrentConfiguration.useRigidbodyForMotion) + { + // The authoritative instance can be None, Interpolate, or Extrapolate for the Rigidbody interpolation settings. + Assert.AreEqual(m_CurrentConfiguration.interpolationType, authorityRigidbody.interpolation, $"{authorityHeader} interpolation is {authorityRigidbody.interpolation} " + + $"and not {m_CurrentConfiguration.interpolationType}!"); - var serverClientInstanceRigidBody = serverClientPlayerInstance.GetComponent(); - var clientRigidBody = clientPlayerInstance.GetComponent(); + // When using Rigidbody motion, authoritative and non-authoritative Rigidbody interpolation settings should be preserved (except when extrapolation is used + interpolateCompareNonAuthoritative = m_CurrentConfiguration.enableInterpolation ? RigidbodyInterpolation.Interpolate : m_CurrentConfiguration.interpolationType; - if (m_UseRigidBodyForMotion) + } + else { - var interpolateCompareNonAuthoritative = m_NetworkTransformInterpolate ? RigidbodyInterpolation.Interpolate : m_RigidbodyInterpolation; + Assert.AreEqual(RigidbodyInterpolation.Interpolate, authorityRigidbody.interpolation, $"{authorityHeader} interpolation is {authorityRigidbody.interpolation} " + + $"and not {RigidbodyInterpolation.Interpolate}!"); - // Server authoritative NT should yield non-kinematic mode for the server-side player instance - Assert.False(serverClientInstanceRigidBody.isKinematic, $"[Server-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} player's {nameof(Rigidbody)} is kinematic!"); + // client rigidbody has no authority with NT interpolation disabled should allow Rigidbody interpolation + interpolateCompareNonAuthoritative = m_CurrentConfiguration.enableInterpolation ? RigidbodyInterpolation.None : RigidbodyInterpolation.Interpolate; + } + Assert.AreEqual(interpolateCompareNonAuthoritative, nonAuthorityRigidbody.interpolation, $"{nonAuthorityHeader} interpolation is {nonAuthorityRigidbody.interpolation} " + + $"and not {interpolateCompareNonAuthoritative}!"); + #endregion + + // Test 2D Rigidbody + #region 2D Rigidbody validation + var authorityRigidbody2D = m_2DAuthorityInstance.GetComponent(); + var nonAuthorityInstance2D = nonAuthority.SpawnManager.SpawnedObjects[m_2DAuthorityInstance.NetworkObjectId]; + var nonAuthorityRigidbody2D = nonAuthorityInstance2D.GetComponent(); + + authorityHeader = $"{m_ConfigHeader}[Authority] Client-{authority.LocalClientId}'s instance of {m_2DAuthorityInstance.name}"; + // The authority instance should always be non-kinematic + Assert.False(authorityRigidbody2D.bodyType == RigidbodyType2D.Kinematic, $"{authorityHeader} is kinematic!"); + + nonAuthorityHeader = $"{m_ConfigHeader}[Non-Authority] Client-{nonAuthority.LocalClientId}'s instance of {nonAuthorityInstance.name}"; + // Non-authority instances should always be kinematic + Assert.True(nonAuthorityRigidbody2D.bodyType == RigidbodyType2D.Kinematic, $"{nonAuthorityHeader} is not kinematic!"); + var interpolateCompareNonAuthoritative2D = RigidbodyInterpolation2D.None; + var configInterpolation2D = m_CurrentConfiguration.interpolationType == RigidbodyInterpolation.Interpolate ? RigidbodyInterpolation2D.Interpolate : RigidbodyInterpolation2D.Extrapolate; + if (m_CurrentConfiguration.useRigidbodyForMotion) + { // The authoritative instance can be None, Interpolate, or Extrapolate for the Rigidbody interpolation settings. - Assert.AreEqual(m_RigidbodyInterpolation, serverClientInstanceRigidBody.interpolation, $"[Server-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} " + - $"player's {nameof(Rigidbody)}'s interpolation is {serverClientInstanceRigidBody.interpolation} and not {m_RigidbodyInterpolation}!"); - - // Server authoritative NT should yield kinematic mode for the client-side player instance - Assert.True(clientRigidBody.isKinematic, $"[Client-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} player's {nameof(Rigidbody)} is not kinematic!"); + Assert.AreEqual(configInterpolation2D, authorityRigidbody2D.interpolation, $"{authorityHeader} interpolation is {authorityRigidbody2D.interpolation} " + + $"and not {m_CurrentConfiguration.interpolationType}!"); // When using Rigidbody motion, authoritative and non-authoritative Rigidbody interpolation settings should be preserved (except when extrapolation is used - Assert.AreEqual(interpolateCompareNonAuthoritative, clientRigidBody.interpolation, $"[Client-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} " + - $"player's {nameof(Rigidbody)}'s interpolation is {clientRigidBody.interpolation} and not {interpolateCompareNonAuthoritative}!"); + interpolateCompareNonAuthoritative2D = m_CurrentConfiguration.enableInterpolation ? RigidbodyInterpolation2D.Interpolate : configInterpolation2D; } else { - // server rigidbody has authority and should not be kinematic - Assert.False(serverClientInstanceRigidBody.isKinematic, $"[Server-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} player's {nameof(Rigidbody)} is kinematic!"); - Assert.AreEqual(RigidbodyInterpolation.Interpolate, serverClientInstanceRigidBody.interpolation, $"[Server-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} " + - $"player's {nameof(Rigidbody)}'s interpolation is {serverClientInstanceRigidBody.interpolation} and not {nameof(RigidbodyInterpolation.Interpolate)}!"); - - // Server authoritative NT should yield kinematic mode for the client-side player instance - Assert.True(clientRigidBody.isKinematic, $"[Client-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} player's {nameof(Rigidbody)} is not kinematic!"); + Assert.AreEqual(RigidbodyInterpolation2D.Interpolate, authorityRigidbody2D.interpolation, $"{authorityHeader} interpolation is {authorityRigidbody2D.interpolation} " + + $"and not {RigidbodyInterpolation2D.Interpolate}!"); // client rigidbody has no authority with NT interpolation disabled should allow Rigidbody interpolation - if (!m_NetworkTransformInterpolate) - { - Assert.AreEqual(RigidbodyInterpolation.Interpolate, clientRigidBody.interpolation, $"[Client-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} " + - $"player's {nameof(Rigidbody)}'s interpolation is {clientRigidBody.interpolation} and not {nameof(RigidbodyInterpolation.Interpolate)}!"); - } - else - { - Assert.AreEqual(RigidbodyInterpolation.None, clientRigidBody.interpolation, $"[Client-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} " + - $"player's {nameof(Rigidbody)}'s interpolation is {clientRigidBody.interpolation} and not {nameof(RigidbodyInterpolation.None)}!"); - } + interpolateCompareNonAuthoritative2D = m_CurrentConfiguration.enableInterpolation ? RigidbodyInterpolation2D.None : RigidbodyInterpolation2D.Interpolate; } - // despawn the server player (but keep it around on the server) - serverClientPlayerInstance.Despawn(false); + Assert.AreEqual(interpolateCompareNonAuthoritative2D, nonAuthorityRigidbody2D.interpolation, $"{nonAuthorityHeader} interpolation is {nonAuthorityRigidbody2D.interpolation} " + + $"and not {interpolateCompareNonAuthoritative}!"); + #endregion + + var spawnedInstances = new List() { m_3DAuthorityInstance, m_2DAuthorityInstance }; + m_3DAuthorityInstance.Despawn(); + m_2DAuthorityInstance.Despawn(); + yield return WaitForDespawnedOnAllOrTimeOut(spawnedInstances); + AssertOnTimeout($"Failed to de-spawn instances on all clients!"); + m_3DAuthorityInstance = null; + m_2DAuthorityInstance = null; + } + + /// + /// Handle clean up in case of a failed test + /// + protected override IEnumerator OnTearDown() + { + // If either of these are not null then we most likely failed and didn't cleanup. + + // Clean-up m_3DAuthorityInstance + if (m_3DAuthorityInstance) + { + Object.Destroy(m_3DAuthorityInstance); + m_3DAuthorityInstance = null; + } - yield return WaitForConditionOrTimeOut(() => !serverClientPlayerInstance.IsSpawned && !clientPlayerInstance.IsSpawned); - AssertOnTimeout("Timed out waiting for client player to despawn on both server and client!"); + // Clean-up m_2DAuthorityInstance + if (m_2DAuthorityInstance) + { + Object.Destroy(m_2DAuthorityInstance); + m_2DAuthorityInstance = null; + } - // When despawned, we should always be kinematic (i.e. don't apply physics when despawned) - Assert.True(serverClientInstanceRigidBody.isKinematic, $"[Server-Side][Despawned] Client-{m_ClientNetworkManagers[0].LocalClientId} player's {nameof(Rigidbody)} is not kinematic when despawned!"); - Assert.IsTrue(clientPlayerInstance == null, $"[Client-Side] Player {nameof(NetworkObject)} is not null!"); + return base.OnTearDown(); } }