diff --git a/com.unity.netcode.gameobjects/Documentation~/ghostobjects.md b/com.unity.netcode.gameobjects/Documentation~/ghostobjects.md
index 333d3e6f8d..fa9fe14402 100644
--- a/com.unity.netcode.gameobjects/Documentation~/ghostobjects.md
+++ b/com.unity.netcode.gameobjects/Documentation~/ghostobjects.md
@@ -86,6 +86,13 @@ To convert a hybrid object back into a prefab that uses only one package, remove
> [!NOTE]
> Unity might prevent you from removing a **GhostObject** or **NetworkObject** component while the GameObject has `GhostBehaviour` or `NetworkBehaviour` components that depend on it. Remove the dependent components first.
+## Mixed mode objects and physics
+
+To avoid visual anomalies, NGO automatically adjusts some Rigidbody settings based on the **Physics Run Mode** of the `NetcodeConfig` asset:
+
+- **Always Run**: In a session that has hybrid prefabs, non-hybrid NGO prefabs with a NetworkRigidbody and a 3D Rigidbody have the Rigidbody's **Interpolate** value set to **Extrapolate** on the authority instance and to **None** on all non-authority instances. The authority instance is the server or host for a server-authoritative NetworkTransform, or the owner for an owner-authoritative one. The value follows authority when ownership changes, and the original value is restored when the instance despawns.
+- **User Controlled**: Hybrid NGO prefabs with a NetworkRigidbody have **Full Tick Smoothing** set to **Disabled**.
+
## Additional resources
- [GhostObjects in Unity Netcode](https://docs.unity3d.com/Packages/com.unity.netcode@latest?subfolder=/manual/ghostobject/ghostobject.html)
diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs
index d55b2f30e8..cbc4b3a5ad 100644
--- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs
@@ -298,6 +298,25 @@ private bool InitializeHybrid()
return true;
}
+ // Overrides the authored interpolation while spawned when the NetcodeConfig physics mode requires it.
+ private bool m_UsePhysicsModeInterpolation;
+
+ ///
+ /// Prevents the jitter an interpolated Rigidbody that is not a GhostObject shows in a hybrid session with .
+ ///
+ private void SetPhysicsModeInterpolation()
+ {
+ var netcodeConfig = NetcodeConfig.Global;
+ m_UsePhysicsModeInterpolation = !m_IsRigidbody2D && m_LocalNetworkManager.NetworkConfig.Prefabs.HasGhostPrefabs
+ && netcodeConfig != null && netcodeConfig.PhysicsRunMode == PhysicGroupRunMode.AlwaysRun;
+ }
+
+ // The authority extrapolates and every other peer does not interpolate.
+ private void ApplyPhysicsModeInterpolation()
+ {
+ SetInterpolation(m_IsAuthority ? InterpolationTypes.Extrapolate : InterpolationTypes.None);
+ }
+
///
/// Kinematic on every peer except the server. Called at spawn because an in-scene placed instance has no session during Awake.
///
@@ -956,6 +975,14 @@ private void PostSetIsKinematic()
return;
}
+#if UNIFIED_NETCODE
+ if (m_UsePhysicsModeInterpolation)
+ {
+ ApplyPhysicsModeInterpolation();
+ return;
+ }
+#endif
+
if (UseRigidBodyForMotion)
{
// Exit early if the original interpolation type is not set to extrapolate or NetworkTransform interpolate is disabled
@@ -1103,6 +1130,12 @@ internal void UpdateOwnershipAuthority()
{
SetIsKinematic(!m_IsAuthority);
}
+#if UNIFIED_NETCODE
+ else if (m_UsePhysicsModeInterpolation)
+ {
+ ApplyPhysicsModeInterpolation();
+ }
+#endif
}
internal override void InternalOnNetworkPreSpawn(ref NetworkManager networkManager)
@@ -1122,6 +1155,9 @@ public override void OnNetworkSpawn()
#endif
m_TickFrequency = 1.0f / m_LocalNetworkManager.NetworkConfig.TickRate;
m_TickRate = m_LocalNetworkManager.NetworkConfig.TickRate;
+#if UNIFIED_NETCODE
+ SetPhysicsModeInterpolation();
+#endif
UpdateOwnershipAuthority();
}
diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefab.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefab.cs
index 68725f3bbd..3a03a59bd8 100644
--- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefab.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefab.cs
@@ -179,6 +179,18 @@ public bool Validate(int index = -1)
#if UNIFIED_NETCODE
// Mark this network prefab as having to be registered via the unified API
HasGhost = networkObject.HasGhost;
+
+#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D
+ // With UserControlled physics, full tick smoothing holds a hybrid Rigidbody in place on the host.
+ var netcodeConfig = NetcodeConfig.Global;
+ if (HasGhost && netcodeConfig != null && netcodeConfig.PhysicsRunMode == PhysicGroupRunMode.UserControlled
+ && networkObject.GhostObject.FullTickSmoothing != FullTickSmoothingMode.Disabled
+ && networkObject.GetComponent() != null)
+ {
+ NetworkLog.LogWarning(new Logging.Context(LogLevel.Developer, $"Disabling full tick smoothing on {networkObject.name} because the {nameof(NetcodeConfig)} physics mode is {PhysicGroupRunMode.UserControlled}.").AddNetworkObject(networkObject));
+ networkObject.GhostObject.FullTickSmoothing = FullTickSmoothingMode.Disabled;
+ }
+#endif
#endif
if (networkObject.InScenePlaced)
{