#if COM_UNITY_MODULES_PHYSICS
using System.Runtime.CompilerServices;
using UnityEngine;
namespace Unity.Netcode.Components
{
///
/// NetworkRigidbodyBase is a integration that helps to synchronize physics motion, collision, and interpolation
/// when used with a .
///
///
/// For a customizable netcode Rigidbody, create your own component from this class and use
/// during instantiation (i.e. invoked from within the Awake method). You can re-initialize after having initialized but only when the is not spawned.
///
public abstract class NetworkRigidbodyBase : NetworkBehaviour
{
#if UNITY_EDITOR
[HideInInspector]
[SerializeField]
internal bool NetworkRigidbodyBaseExpanded;
#endif
///
/// When enabled, the associated will use the Rigidbody to apply and synchronize changes in position, rotation, and
/// allows for the use of Rigidbody interpolation/extrapolation.
///
///
/// If is enabled, non-authoritative instances can only use Rigidbody interpolation. If a network prefab is set to
/// extrapolation and is enabled, then non-authoritative instances will automatically be adjusted to use Rigidbody
/// interpolation while the authoritative instance will still use extrapolation.
///
[Tooltip("When enabled and a NetworkTransform component is attached, the NetworkTransform will use the rigid body for motion and detecting changes in state.")]
public bool UseRigidBodyForMotion;
///
/// When enabled (default), automatically set the Kinematic state of the Rigidbody based on ownership.
/// When disabled, Kinematic state needs to be set by external script(s).
///
public bool AutoUpdateKinematicState = true;
///
/// Primarily applies to the property when disabled but you still want
/// the Rigidbody to be automatically set to Kinematic when despawned.
///
public bool AutoSetKinematicOnDespawn = true;
// Used to cache the authority state of this Rigidbody during the last frame
private bool m_IsAuthority;
protected internal Rigidbody m_InternalRigidbody { get; private set; }
internal NetworkTransform NetworkTransform;
private float m_TickFrequency;
private float m_TickRate;
private enum InterpolationTypes
{
None,
Interpolate,
Extrapolate
}
private InterpolationTypes m_OriginalInterpolation;
///
/// Initializes the networked Rigidbody based on the
/// passed in as a parameter.
///
///
/// Cannot be initialized while the associated is spawned.
///
/// (optional) The to be used
protected void Initialize(NetworkTransform networkTransform = null, Rigidbody rigidbody = null)
{
// Don't initialize if already spawned
if (IsSpawned)
{
Debug.LogError($"[{name}] Attempting to initialize while spawned is not allowed.");
return;
}
m_InternalRigidbody = rigidbody;
NetworkTransform = networkTransform;
if (m_InternalRigidbody == null)
{
m_InternalRigidbody = GetComponent();
}
SetOriginalInterpolation();
if (NetworkTransform == null)
{
NetworkTransform = GetComponent();
}
if (NetworkTransform != null)
{
NetworkTransform.RegisterRigidbody(this);
}
else
{
throw new System.Exception($"[Missing {nameof(NetworkTransform)}] No {nameof(NetworkTransform)} is assigned or can be found during initialization!");
}
if (AutoUpdateKinematicState)
{
SetIsKinematic(true);
}
}
internal Vector3 GetAdjustedPositionThreshold()
{
// Since the threshold is a measurement of unity world space units per tick, we will allow for the maximum threshold
// to be no greater than the threshold measured in unity world space units per second
var thresholdMax = NetworkTransform.PositionThreshold * m_TickRate;
// Get the velocity in unity world space units per tick
var perTickVelocity = GetLinearVelocity() * m_TickFrequency;
// Since a rigid body can have "micro-motion" when allowed to come to rest (based on friction etc), we will allow for
// no less than 1/10th the threshold value.
var minThreshold = NetworkTransform.PositionThreshold * 0.1f;
// Finally, we adjust the threshold based on the body's current velocity
perTickVelocity.x = Mathf.Clamp(Mathf.Abs(perTickVelocity.x), minThreshold, thresholdMax);
perTickVelocity.y = Mathf.Clamp(Mathf.Abs(perTickVelocity.y), minThreshold, thresholdMax);
perTickVelocity.z = Mathf.Clamp(Mathf.Abs(perTickVelocity.z), minThreshold, thresholdMax);
return perTickVelocity;
}
internal Vector3 GetAdjustedRotationThreshold()
{
// Since the rotation threshold is a measurement pf degrees per tick, we get the maximum threshold
// by calculating the threshold in degrees per second.
var thresholdMax = NetworkTransform.RotAngleThreshold * m_TickRate;
// Angular velocity is expressed in radians per second where as the rotation being checked is in degrees.
// Convert the angular velocity to degrees per second and then convert that to degrees per tick.
var rotationPerTick = (GetAngularVelocity() * Mathf.Rad2Deg) * m_TickFrequency;
var minThreshold = NetworkTransform.RotAngleThreshold * m_TickFrequency;
rotationPerTick.x = Mathf.Clamp(Mathf.Abs(rotationPerTick.x), minThreshold, thresholdMax);
rotationPerTick.y = Mathf.Clamp(Mathf.Abs(rotationPerTick.y), minThreshold, thresholdMax);
rotationPerTick.z = Mathf.Clamp(Mathf.Abs(rotationPerTick.z), minThreshold, thresholdMax);
return rotationPerTick;
}
///
/// Sets the linear velocity of the Rigidbody.
///
public void SetLinearVelocity(Vector3 linearVelocity)
{
m_InternalRigidbody.linearVelocity = linearVelocity;
}
///
/// Gets the linear velocity of the Rigidbody.
///
/// as the linear velocity
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3 GetLinearVelocity()
{
return m_InternalRigidbody.linearVelocity;
}
///
/// Sets the angular velocity for the Rigidbody.
///
/// the angular velocity to apply to the body
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetAngularVelocity(Vector3 angularVelocity)
{
m_InternalRigidbody.angularVelocity = angularVelocity;
}
///
/// Gets the angular velocity for the Rigidbody.
///
/// angular velocity as a
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3 GetAngularVelocity()
{
return m_InternalRigidbody.angularVelocity;
}
///
/// Gets the position of the Rigidbody
///
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3 GetPosition()
{
return m_InternalRigidbody.position;
}
///
/// Gets the rotation of the Rigidbody
///
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Quaternion GetRotation()
{
return m_InternalRigidbody.rotation;
}
///
/// Moves the rigid body
///
/// The position to move towards
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MovePosition(Vector3 position)
{
m_InternalRigidbody.MovePosition(position);
}
///
/// Directly applies a position (like teleporting)
///
/// position to apply to the Rigidbody
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetPosition(Vector3 position)
{
m_InternalRigidbody.position = position;
}
///
/// Applies the rotation and position of the 's
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ApplyCurrentTransform()
{
m_InternalRigidbody.position = transform.position;
m_InternalRigidbody.rotation = transform.rotation;
}
// Used for Rigidbody only (see info on normalized below)
private Vector4 m_QuaternionCheck = Vector4.zero;
///
/// Rotatates the Rigidbody towards a specified rotation
///
/// The rotation expressed as a
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MoveRotation(Quaternion rotation)
{
// Evidently we need to check to make sure the quaternion is a perfect
// magnitude of 1.0f when applying the rotation to a rigid body.
m_QuaternionCheck.x = rotation.x;
m_QuaternionCheck.y = rotation.y;
m_QuaternionCheck.z = rotation.z;
m_QuaternionCheck.w = rotation.w;
// If the magnitude is greater than 1.0f (even by a very small fractional value), then normalize the quaternion
if (m_QuaternionCheck.magnitude != 1.0f)
{
rotation.Normalize();
}
m_InternalRigidbody.MoveRotation(rotation);
}
///
/// Applies a rotation to the Rigidbody
///
/// The rotation to apply expressed as a
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetRotation(Quaternion rotation)
{
m_InternalRigidbody.rotation = rotation;
}
///
/// Sets the original interpolation of the Rigidbody while taking the Rigidbody type into consideration
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetOriginalInterpolation()
{
switch (m_InternalRigidbody.interpolation)
{
case RigidbodyInterpolation.None:
{
m_OriginalInterpolation = InterpolationTypes.None;
break;
}
case RigidbodyInterpolation.Interpolate:
{
m_OriginalInterpolation = InterpolationTypes.Interpolate;
break;
}
case RigidbodyInterpolation.Extrapolate:
{
m_OriginalInterpolation = InterpolationTypes.Extrapolate;
break;
}
}
}
///
/// Wakes the Rigidbody if it is sleeping
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WakeIfSleeping()
{
if (m_InternalRigidbody.IsSleeping())
{
m_InternalRigidbody.WakeUp();
}
}
///
/// Puts the Rigidbody to sleep
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SleepRigidbody()
{
m_InternalRigidbody.Sleep();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsKinematic()
{
return m_InternalRigidbody.isKinematic;
}
///
/// Sets the kinematic state of the Rigidbody and handles updating the Rigidbody's
/// interpolation setting based on the Kinematic state.
///
///
/// When using the Rigidbody for motion, this automatically
/// adjusts from extrapolation to interpolation if:
/// - The Rigidbody was originally set to extrapolation
/// - The NetworkTransform is set to interpolate
/// When the two above conditions are true:
/// - When switching from non-kinematic to kinematic this will automatically
/// switch the Rigidbody from extrapolation to interpolate.
/// - When switching from kinematic to non-kinematic this will automatically
/// switch the Rigidbody from interpolation back to extrapolation.
///
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetIsKinematic(bool isKinematic)
{
m_InternalRigidbody.isKinematic = isKinematic;
// If we are not spawned, then exit early
if (!IsSpawned)
{
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)
{
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);
}
}
}
else
{
SetInterpolation(m_IsAuthority ? m_OriginalInterpolation : (NetworkTransform.Interpolate ? InterpolationTypes.None : m_OriginalInterpolation));
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetInterpolation(InterpolationTypes interpolationType)
{
switch (interpolationType)
{
case InterpolationTypes.None:
{
m_InternalRigidbody.interpolation = RigidbodyInterpolation.None;
break;
}
case InterpolationTypes.Interpolate:
{
m_InternalRigidbody.interpolation = RigidbodyInterpolation.Interpolate;
break;
}
case InterpolationTypes.Extrapolate:
{
m_InternalRigidbody.interpolation = RigidbodyInterpolation.Extrapolate;
break;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ResetInterpolation()
{
SetInterpolation(m_OriginalInterpolation);
}
protected override void OnOwnershipChanged(ulong previous, ulong current)
{
UpdateOwnershipAuthority();
base.OnOwnershipChanged(previous, current);
}
///
/// Sets the authority based on whether it is server or owner authoritative
///
///
/// Distributed authority sessions will always be owner authoritative.
///
internal void UpdateOwnershipAuthority()
{
if (NetworkManager.DistributedAuthorityMode)
{
// When in distributed authority mode, always use HasAuthority
m_IsAuthority = HasAuthority;
}
else
{
if (NetworkTransform.IsServerAuthoritative())
{
m_IsAuthority = NetworkManager.IsServer;
}
else
{
m_IsAuthority = IsOwner;
}
}
if (AutoUpdateKinematicState)
{
SetIsKinematic(!m_IsAuthority);
}
}
///
public override void OnNetworkSpawn()
{
m_TickFrequency = 1.0f / NetworkManager.NetworkConfig.TickRate;
m_TickRate = NetworkManager.NetworkConfig.TickRate;
UpdateOwnershipAuthority();
}
///
public override void OnNetworkDespawn()
{
if (UseRigidBodyForMotion && HasAuthority)
{
DetachFromFixedJoint();
NetworkRigidbodyConnections.Clear();
}
// If we are automatically handling the kinematic state...
if (AutoUpdateKinematicState || AutoSetKinematicOnDespawn)
{
// Turn off physics for the rigid body until spawned, otherwise
// non-owners can run fixed updates before the first full
// NetworkTransform update and physics will be applied (i.e. gravity, etc)
SetIsKinematic(true);
}
SetInterpolation(m_OriginalInterpolation);
}
// TODO: Possibly provide a NetworkJoint that allows for more options than fixed.
// Rigidbodies do not have the concept of "local space", and as such using a fixed joint will hold the object
// in place relative to the parent so jitter/stutter does not occur.
// Alternately, users can affix the fixed joint to a child GameObject (without a rigid body) of the parent NetworkObject
// and then add a NetworkTransform to that in order to get the parented child NetworkObject to move around in "local space"
public FixedJoint FixedJoint { get; private set; }
internal System.Collections.Generic.List NetworkRigidbodyConnections = new System.Collections.Generic.List();
internal NetworkRigidbodyBase ParentBody;
private bool m_OriginalGravitySetting;
private float m_OriginalGravityScale;
///
/// When using a custom , this virtual method is invoked when the
/// is created in the event any additional adjustments are needed.
///
protected virtual void OnFixedJointCreated()
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ApplyFixedJoint(NetworkRigidbodyBase bodyToConnectTo, Vector3 position, float connectedMassScale = 0.0f, float massScale = 1.0f, bool useGravity = false, bool zeroVelocity = true)
{
transform.position = position;
m_InternalRigidbody.position = position;
if (zeroVelocity)
{
m_InternalRigidbody.linearVelocity = Vector3.zero;
m_InternalRigidbody.angularVelocity = Vector3.zero;
}
m_OriginalGravitySetting = m_InternalRigidbody.useGravity;
m_InternalRigidbody.useGravity = useGravity;
FixedJoint = gameObject.AddComponent();
FixedJoint.connectedBody = bodyToConnectTo.m_InternalRigidbody;
FixedJoint.connectedMassScale = connectedMassScale;
FixedJoint.massScale = massScale;
OnFixedJointCreated();
}
///
/// Authority Only:
/// When invoked and not already attached to a fixed joint, this will connect two rigid bodies with enabled.
/// Invoke this method on the rigid body you wish to attach to another (i.e. weapon to player, sticky bomb to player/object, etc).
///
///
///
/// Parenting relative:
/// - This instance can be viewed as the child.
/// - The can be viewed as the parent.
///
/// This is the recommended way, as opposed to parenting, to attached/detatch two rigid bodies to one another when is enabled.
/// For more details on using .
///
/// This provides a simple joint solution between two rigid bodies and serves as an example. You can add different joint types by creating a customized/derived
/// version of .
///
/// The target object to attach to.
/// The position of the connection (i.e. where you want the object to be affixed).
/// The target object's mass scale relative to this object being attached.
/// This object's mass scale relative to the target object's.
/// Determines if this object will have gravity applied to it along with the object you are connecting this one to (the default is to not use gravity for this object)
/// When true (the default), both linear and angular velocities of this object are set to zero.
/// When true (the default), this object will teleport itself to the position of connection.
/// true (success) false (failed)
public bool AttachToFixedJoint(NetworkRigidbodyBase objectToConnectTo, Vector3 positionOfConnection, float connectedMassScale = 0.0f, float massScale = 1.0f, bool useGravity = false, bool zeroVelocity = true, bool teleportObject = true)
{
if (!UseRigidBodyForMotion)
{
Debug.LogError($"[{GetType().Name}] {name} does not have {nameof(UseRigidBodyForMotion)} set! Either enable {nameof(UseRigidBodyForMotion)} on this component or do not use a {nameof(FixedJoint)} when parenting under a {nameof(NetworkObject)}.");
return false;
}
if (IsKinematic())
{
Debug.LogError($"[{GetType().Name}] {name} is currently kinematic! You cannot use a {nameof(FixedJoint)} with Kinematic bodies!");
return false;
}
if (objectToConnectTo != null)
{
ApplyFixedJoint(objectToConnectTo, positionOfConnection, connectedMassScale, massScale, useGravity, zeroVelocity);
ParentBody = objectToConnectTo;
ParentBody.NetworkRigidbodyConnections.Add(this);
if (teleportObject)
{
NetworkTransform.SetState(teleportDisabled: false);
}
return true;
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void RemoveFromParentBody()
{
ParentBody.NetworkRigidbodyConnections.Remove(this);
ParentBody = null;
}
///
/// Authority Only:
/// When invoked and already connected to an object via ,
/// this will detach from the fixed joint and destroy the fixed joint component.
///
///
/// This is the recommended way, as opposed to parenting, to attached/detatch two rigid bodies to one another when is enabled.
///
public void DetachFromFixedJoint()
{
if (!HasAuthority)
{
Debug.LogError($"[{name}] Only authority can invoke {nameof(DetachFromFixedJoint)}!");
}
if (UseRigidBodyForMotion)
{
if (FixedJoint != null)
{
FixedJoint.connectedBody = null;
m_InternalRigidbody.useGravity = m_OriginalGravitySetting;
Destroy(FixedJoint);
FixedJoint = null;
ResetInterpolation();
RemoveFromParentBody();
}
}
}
}
}
#endif // COM_UNITY_MODULES_PHYSICS