This commit is contained in:
2025-01-10 15:22:38 +01:00
371 changed files with 1211 additions and 53454 deletions

View File

@@ -1,543 +0,0 @@
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Netcode.Components
{
#pragma warning disable IDE0001
/// <summary>
/// A subclass of <see cref="NetworkTransform"/> that supports basic client anticipation - the client
/// can set a value on the belief that the server will update it to reflect the same value in a future update
/// (i.e., as the result of an RPC call). This value can then be adjusted as new updates from the server come in,
/// in three basic modes:
///
/// <list type="bullet">
///
/// <item><b>Snap:</b> In this mode (with <see cref="StaleDataHandling"/> set to
/// <see cref="StaleDataHandling.Ignore"/> and no <see cref="NetworkBehaviour.OnReanticipate"/> callback),
/// the moment a more up-to-date value is received from the authority, it will simply replace the anticipated value,
/// resulting in a "snap" to the new value if it is different from the anticipated value.</item>
///
/// <item><b>Smooth:</b> In this mode (with <see cref="StaleDataHandling"/> set to
/// <see cref="Netcode.StaleDataHandling.Ignore"/> and an <see cref="NetworkBehaviour.OnReanticipate"/> callback that calls
/// <see cref="Smooth"/> from the anticipated value to the authority value with an appropriate
/// <see cref="Mathf.Lerp"/>-style smooth function), when a more up-to-date value is received from the authority,
/// it will interpolate over time from an incorrect anticipated value to the correct authoritative value.</item>
///
/// <item><b>Constant Reanticipation:</b> In this mode (with <see cref="StaleDataHandling"/> set to
/// <see cref="Netcode.StaleDataHandling.Reanticipate"/> and an <see cref="NetworkBehaviour.OnReanticipate"/> that calculates a
/// new anticipated value based on the current authoritative value), when a more up-to-date value is received from
/// the authority, user code calculates a new anticipated value, possibly calling <see cref="Smooth"/> to interpolate
/// between the previous anticipation and the new anticipation. This is useful for values that change frequently and
/// need to constantly be re-evaluated, as opposed to values that change only in response to user action and simply
/// need a one-time anticipation when the user performs that action.</item>
///
/// </list>
///
/// Note that these three modes may be combined. For example, if an <see cref="NetworkBehaviour.OnReanticipate"/> callback
/// does not call either <see cref="Smooth"/> or one of the Anticipate methods, the result will be a snap to the
/// authoritative value, enabling for a callback that may conditionally call <see cref="Smooth"/> when the
/// difference between the anticipated and authoritative values is within some threshold, but fall back to
/// snap behavior if the difference is too large.
/// </summary>
#pragma warning restore IDE0001
[DisallowMultipleComponent]
[AddComponentMenu("Netcode/Anticipated Network Transform")]
public class AnticipatedNetworkTransform : NetworkTransform
{
#if UNITY_EDITOR
internal override bool HideInterpolateValue => true;
#endif
public struct TransformState
{
public Vector3 Position;
public Quaternion Rotation;
public Vector3 Scale;
}
private TransformState m_AuthoritativeTransform = new TransformState();
private TransformState m_AnticipatedTransform = new TransformState();
private TransformState m_PreviousAnticipatedTransform = new TransformState();
private ulong m_LastAnticipaionCounter;
private ulong m_LastAuthorityUpdateCounter;
private TransformState m_SmoothFrom;
private TransformState m_SmoothTo;
private float m_SmoothDuration;
private float m_CurrentSmoothTime;
private bool m_OutstandingAuthorityChange = false;
#if UNITY_EDITOR
private void Reset()
{
// Anticipation + smoothing is a form of interpolation, and adding NetworkTransform's buffered interpolation
// makes the anticipation get weird, so we default it to false.
Interpolate = false;
}
#endif
#pragma warning disable IDE0001
/// <summary>
/// Defines what the behavior should be if we receive a value from the server with an earlier associated
/// time value than the anticipation time value.
/// <br/><br/>
/// If this is <see cref="Netcode.StaleDataHandling.Ignore"/>, the stale data will be ignored and the authoritative
/// value will not replace the anticipated value until the anticipation time is reached. <see cref="OnAuthoritativeValueChanged"/>
/// and <see cref="OnReanticipate"/> will also not be invoked for this stale data.
/// <br/><br/>
/// If this is <see cref="Netcode.StaleDataHandling.Reanticipate"/>, the stale data will replace the anticipated data and
/// <see cref="OnAuthoritativeValueChanged"/> and <see cref="OnReanticipate"/> will be invoked.
/// In this case, the authoritativeTime value passed to <see cref="OnReanticipate"/> will be lower than
/// the anticipationTime value, and that callback can be used to calculate a new anticipated value.
/// </summary>
#pragma warning restore IDE0001
public StaleDataHandling StaleDataHandling = StaleDataHandling.Reanticipate;
/// <summary>
/// Contains the current state of this transform on the server side.
/// Note that, on the server side, this gets updated at the end of the frame, and will not immediately reflect
/// changes to the transform.
/// </summary>
public TransformState AuthoritativeState => m_AuthoritativeTransform;
/// <summary>
/// Contains the current anticipated state, which will match the values of this object's
/// actual <see cref="MonoBehaviour.transform"/>. When a server
/// update arrives, this value will be overwritten by the new
/// server value (unless stale data handling is set to "Ignore"
/// and the update is determined to be stale). This value will
/// be duplicated in <see cref="PreviousAnticipatedState"/>, which
/// will NOT be overwritten in server updates.
/// </summary>
public TransformState AnticipatedState => m_AnticipatedTransform;
/// <summary>
/// Indicates whether this transform currently needs
/// reanticipation. If this is true, the anticipated value
/// has been overwritten by the authoritative value from the
/// server; the previous anticipated value is stored in <see cref="PreviousAnticipatedState"/>
/// </summary>
public bool ShouldReanticipate
{
get;
private set;
}
/// <summary>
/// Holds the most recent anticipated state, whatever was
/// most recently set using the Anticipate methods. Unlike
/// <see cref="AnticipatedState"/>, this does not get overwritten
/// when a server update arrives.
/// </summary>
public TransformState PreviousAnticipatedState => m_PreviousAnticipatedTransform;
/// <summary>
/// Anticipate that, at the end of one round trip to the server, this transform will be in the given
/// <see cref="newPosition"/>
/// </summary>
/// <param name="newPosition"></param>
public void AnticipateMove(Vector3 newPosition)
{
if (NetworkManager.ShutdownInProgress || !NetworkManager.IsListening)
{
return;
}
transform.position = newPosition;
m_AnticipatedTransform.Position = newPosition;
if (CanCommitToTransform)
{
m_AuthoritativeTransform.Position = newPosition;
}
m_PreviousAnticipatedTransform = m_AnticipatedTransform;
m_LastAnticipaionCounter = NetworkManager.AnticipationSystem.AnticipationCounter;
m_SmoothDuration = 0;
m_CurrentSmoothTime = 0;
}
/// <summary>
/// Anticipate that, at the end of one round trip to the server, this transform will have the given
/// <see cref="newRotation"/>
/// </summary>
/// <param name="newRotation"></param>
public void AnticipateRotate(Quaternion newRotation)
{
if (NetworkManager.ShutdownInProgress || !NetworkManager.IsListening)
{
return;
}
transform.rotation = newRotation;
m_AnticipatedTransform.Rotation = newRotation;
if (CanCommitToTransform)
{
m_AuthoritativeTransform.Rotation = newRotation;
}
m_PreviousAnticipatedTransform = m_AnticipatedTransform;
m_LastAnticipaionCounter = NetworkManager.AnticipationSystem.AnticipationCounter;
m_SmoothDuration = 0;
m_CurrentSmoothTime = 0;
}
/// <summary>
/// Anticipate that, at the end of one round trip to the server, this transform will have the given
/// <see cref="newScale"/>
/// </summary>
/// <param name="newScale"></param>
public void AnticipateScale(Vector3 newScale)
{
if (NetworkManager.ShutdownInProgress || !NetworkManager.IsListening)
{
return;
}
transform.localScale = newScale;
m_AnticipatedTransform.Scale = newScale;
if (CanCommitToTransform)
{
m_AuthoritativeTransform.Scale = newScale;
}
m_PreviousAnticipatedTransform = m_AnticipatedTransform;
m_LastAnticipaionCounter = NetworkManager.AnticipationSystem.AnticipationCounter;
m_SmoothDuration = 0;
m_CurrentSmoothTime = 0;
}
/// <summary>
/// Anticipate that, at the end of one round trip to the server, the transform will have the given
/// <see cref="newState"/>
/// </summary>
/// <param name="newState"></param>
public void AnticipateState(TransformState newState)
{
if (NetworkManager.ShutdownInProgress || !NetworkManager.IsListening)
{
return;
}
var transform_ = transform;
transform_.position = newState.Position;
transform_.rotation = newState.Rotation;
transform_.localScale = newState.Scale;
m_AnticipatedTransform = newState;
if (CanCommitToTransform)
{
m_AuthoritativeTransform = newState;
}
m_PreviousAnticipatedTransform = m_AnticipatedTransform;
m_SmoothDuration = 0;
m_CurrentSmoothTime = 0;
}
private void ProcessSmoothing()
{
// If not spawned or this instance has authority, exit early
if (!IsSpawned)
{
return;
}
if (m_CurrentSmoothTime < m_SmoothDuration)
{
m_CurrentSmoothTime += NetworkManager.RealTimeProvider.DeltaTime;
var transform_ = transform;
var pct = math.min(m_CurrentSmoothTime / m_SmoothDuration, 1f);
m_AnticipatedTransform = new TransformState
{
Position = Vector3.Lerp(m_SmoothFrom.Position, m_SmoothTo.Position, pct),
Rotation = Quaternion.Lerp(m_SmoothFrom.Rotation, m_SmoothTo.Rotation, pct),
Scale = Vector3.Lerp(m_SmoothFrom.Scale, m_SmoothTo.Scale, pct)
};
m_PreviousAnticipatedTransform = m_AnticipatedTransform;
if (!CanCommitToTransform)
{
transform_.position = m_AnticipatedTransform.Position;
transform_.localScale = m_AnticipatedTransform.Scale;
transform_.rotation = m_AnticipatedTransform.Rotation;
}
}
}
// TODO: This does not handle OnFixedUpdate
// This requires a complete overhaul in this class to switch between using
// NetworkRigidbody's position and rotation values.
public override void OnUpdate()
{
ProcessSmoothing();
// Do not call the base class implementation...
// AnticipatedNetworkTransform applies its authoritative state immediately rather than waiting for update
// This is because AnticipatedNetworkTransforms may need to reference each other in reanticipating
// and we will want all reanticipation done before anything else wants to reference the transform in
// OnUpdate()
//base.OnUpdate();
}
/// <summary>
/// Since authority does not subscribe to updates (OnUpdate or OnFixedUpdate),
/// we have to update every frame to assure authority processes soothing.
/// </summary>
private void Update()
{
if (CanCommitToTransform && IsSpawned)
{
ProcessSmoothing();
}
}
internal class AnticipatedObject : IAnticipationEventReceiver, IAnticipatedObject
{
public AnticipatedNetworkTransform Transform;
public void SetupForRender()
{
if (Transform.CanCommitToTransform)
{
var transform_ = Transform.transform;
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.
// Otherwise we'll go ahead and make the visual and actual locations
// match.
Transform.m_AnticipatedTransform = Transform.m_AuthoritativeTransform;
}
transform_.position = Transform.m_AnticipatedTransform.Position;
transform_.rotation = Transform.m_AnticipatedTransform.Rotation;
transform_.localScale = Transform.m_AnticipatedTransform.Scale;
}
}
public void SetupForUpdate()
{
if (Transform.CanCommitToTransform)
{
var transform_ = Transform.transform;
transform_.position = Transform.m_AuthoritativeTransform.Position;
transform_.rotation = Transform.m_AuthoritativeTransform.Rotation;
transform_.localScale = Transform.m_AuthoritativeTransform.Scale;
}
}
public void Update()
{
// No need to do this, it's handled by NetworkTransform.OnUpdate
}
public void ResetAnticipation()
{
Transform.ShouldReanticipate = false;
}
public NetworkObject OwnerObject => Transform.NetworkObject;
}
private AnticipatedObject m_AnticipatedObject = null;
private void ResetAnticipatedState()
{
var transform_ = transform;
m_AuthoritativeTransform = new TransformState
{
Position = transform_.position,
Rotation = transform_.rotation,
Scale = transform_.localScale
};
m_AnticipatedTransform = m_AuthoritativeTransform;
m_PreviousAnticipatedTransform = m_AnticipatedTransform;
m_SmoothDuration = 0;
m_CurrentSmoothTime = 0;
}
/// <summary>
/// (This replaces the first OnSynchronize for NetworkTransforms)
/// This is needed to initialize when fully synchronized since non-authority instances
/// don't apply the initial synchronization (new client synchronization) until after
/// everything has been spawned and synchronized.
/// </summary>
protected internal override void InternalOnNetworkSessionSynchronized()
{
var wasSynchronizing = SynchronizeState.IsSynchronizing;
base.InternalOnNetworkSessionSynchronized();
if (!CanCommitToTransform && wasSynchronizing && !SynchronizeState.IsSynchronizing)
{
m_OutstandingAuthorityChange = true;
ApplyAuthoritativeState();
ResetAnticipatedState();
m_AnticipatedObject = new AnticipatedObject { Transform = this };
NetworkManager.AnticipationSystem.RegisterForAnticipationEvents(m_AnticipatedObject);
NetworkManager.AnticipationSystem.AllAnticipatedObjects.Add(m_AnticipatedObject);
}
}
/// <summary>
/// (This replaces the any subsequent OnSynchronize for NetworkTransforms post client synchronization)
/// This occurs on already connected clients when dynamically spawning a NetworkObject for
/// non-authoritative instances.
/// </summary>
protected internal override void InternalOnNetworkPostSpawn()
{
base.InternalOnNetworkPostSpawn();
if (!CanCommitToTransform && NetworkManager.IsConnectedClient && !SynchronizeState.IsSynchronizing)
{
m_OutstandingAuthorityChange = true;
ApplyAuthoritativeState();
ResetAnticipatedState();
m_AnticipatedObject = new AnticipatedObject { Transform = this };
NetworkManager.AnticipationSystem.RegisterForAnticipationEvents(m_AnticipatedObject);
NetworkManager.AnticipationSystem.AllAnticipatedObjects.Add(m_AnticipatedObject);
}
}
public override void OnNetworkSpawn()
{
if (NetworkManager.DistributedAuthorityMode)
{
Debug.LogWarning($"This component is not currently supported in distributed authority.");
}
base.OnNetworkSpawn();
// Non-authoritative instances exit early if the synchronization has yet to
// be applied at this point
if (SynchronizeState.IsSynchronizing && !CanCommitToTransform)
{
return;
}
m_OutstandingAuthorityChange = true;
ApplyAuthoritativeState();
ResetAnticipatedState();
m_AnticipatedObject = new AnticipatedObject { Transform = this };
NetworkManager.AnticipationSystem.RegisterForAnticipationEvents(m_AnticipatedObject);
NetworkManager.AnticipationSystem.AllAnticipatedObjects.Add(m_AnticipatedObject);
}
public override void OnNetworkDespawn()
{
if (m_AnticipatedObject != null)
{
NetworkManager.AnticipationSystem.DeregisterForAnticipationEvents(m_AnticipatedObject);
NetworkManager.AnticipationSystem.AllAnticipatedObjects.Remove(m_AnticipatedObject);
NetworkManager.AnticipationSystem.ObjectsToReanticipate.Remove(m_AnticipatedObject);
m_AnticipatedObject = null;
}
ResetAnticipatedState();
base.OnNetworkDespawn();
}
public override void OnDestroy()
{
if (m_AnticipatedObject != null)
{
NetworkManager.AnticipationSystem.DeregisterForAnticipationEvents(m_AnticipatedObject);
NetworkManager.AnticipationSystem.AllAnticipatedObjects.Remove(m_AnticipatedObject);
NetworkManager.AnticipationSystem.ObjectsToReanticipate.Remove(m_AnticipatedObject);
m_AnticipatedObject = null;
}
base.OnDestroy();
}
/// <summary>
/// Interpolate between the transform represented by <see cref="from"/> to the transform represented by
/// <see cref="to"/> over <see cref="durationSeconds"/> of real time. The duration uses
/// <see cref="Time.deltaTime"/>, so it is affected by <see cref="Time.timeScale"/>.
/// </summary>
/// <param name="from"></param>
/// <param name="to"></param>
/// <param name="durationSeconds"></param>
public void Smooth(TransformState from, TransformState to, float durationSeconds)
{
var transform_ = transform;
if (durationSeconds <= 0)
{
m_AnticipatedTransform = to;
m_PreviousAnticipatedTransform = m_AnticipatedTransform;
transform_.position = to.Position;
transform_.rotation = to.Rotation;
transform_.localScale = to.Scale;
m_SmoothDuration = 0;
m_CurrentSmoothTime = 0;
return;
}
m_AnticipatedTransform = from;
m_PreviousAnticipatedTransform = m_AnticipatedTransform;
m_SmoothFrom = from;
m_SmoothTo = to;
m_SmoothDuration = durationSeconds;
m_CurrentSmoothTime = 0;
}
protected override void OnBeforeUpdateTransformState()
{
// this is called when new data comes from the server
m_LastAuthorityUpdateCounter = NetworkManager.AnticipationSystem.LastAnticipationAck;
m_OutstandingAuthorityChange = true;
}
protected override void OnNetworkTransformStateUpdated(ref NetworkTransformState oldState, ref NetworkTransformState newState)
{
base.OnNetworkTransformStateUpdated(ref oldState, ref newState);
ApplyAuthoritativeState();
}
protected override void OnTransformUpdated()
{
if (CanCommitToTransform || m_AnticipatedObject == null)
{
return;
}
// this is called pretty much every frame and will change the transform
// If we've overridden the transform with an anticipated state, we need to be able to change it back
// to the anticipated state (while updating the authority state accordingly) or else
// mark this transform for reanticipation
var transform_ = transform;
// 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 (!m_OutstandingAuthorityChange)
{
// Keep the anticipated value unchanged, we have no updates from the server at all.
return;
}
if (StaleDataHandling == StaleDataHandling.Ignore && m_LastAnticipaionCounter > m_LastAuthorityUpdateCounter)
{
// Keep the anticipated value unchanged because it is more recent than the authoritative one.
return;
}
m_SmoothDuration = 0;
m_CurrentSmoothTime = 0;
m_OutstandingAuthorityChange = false;
m_AnticipatedTransform = m_AuthoritativeTransform;
ShouldReanticipate = true;
NetworkManager.AnticipationSystem.ObjectsToReanticipate.Add(m_AnticipatedObject);
}
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 5abfce83aadd948498d4990c645a017b

View File

@@ -230,6 +230,7 @@ namespace Unity.Netcode.Components
}
}
#if UNITY_EDITOR
private void ParseStateMachineStates(int layerIndex, ref AnimatorController animatorController, ref AnimatorStateMachine stateMachine)
{
@@ -263,7 +264,6 @@ namespace Unity.Netcode.Components
{
case AnimatorControllerParameterType.Trigger:
{
if (transition.destinationStateMachine != null)
{
var destinationStateMachine = transition.destinationStateMachine;
@@ -297,18 +297,12 @@ namespace Unity.Netcode.Components
}
}
}
#endif
/// <summary>
/// Creates the TransitionStateInfoList table
/// </summary>
private void BuildTransitionStateInfoList()
{
#if UNITY_EDITOR
if (UnityEditor.EditorApplication.isUpdating || UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode)
{
return;
}
if (m_Animator == null)
{
return;
@@ -326,9 +320,18 @@ namespace Unity.Netcode.Components
var stateMachine = animatorController.layers[x].stateMachine;
ParseStateMachineStates(x, ref animatorController, ref stateMachine);
}
#endif
}
/// <summary>
/// In-Editor Only
/// Virtual OnValidate method for custom derived NetworkAnimator classes.
/// </summary>
protected virtual void OnValidate()
{
BuildTransitionStateInfoList();
}
#endif
public void OnAfterDeserialize()
{
BuildDestinationToTransitionInfoTable();
@@ -336,7 +339,7 @@ namespace Unity.Netcode.Components
public void OnBeforeSerialize()
{
BuildTransitionStateInfoList();
// Do nothing when serializing (handled during OnValidate)
}
internal struct AnimationState : INetworkSerializable
@@ -419,8 +422,8 @@ namespace Unity.Netcode.Components
internal bool HasBeenProcessed;
// This is preallocated/populated in OnNetworkSpawn for all instances in the event ownership or
// authority changes. When serializing, IsDirtyCount determines how many AnimationState entries
// should be serialized from the list. When deserializing the list is created and populated with
// authority changes. When serializing, IsDirtyCount determines how many AnimationState entries
// should be serialized from the list. When deserializing the list is created and populated with
// only the number of AnimationStates received which is dictated by the deserialized IsDirtyCount.
internal List<AnimationState> AnimationStates;
@@ -496,7 +499,7 @@ namespace Unity.Netcode.Components
}
/// <summary>
/// Override this method and return false to switch to owner authoritative mode
/// Override this method and return false to switch to owner authoritative mode.
/// </summary>
/// <remarks>
/// When using a distributed authority network topology, this will default to
@@ -507,10 +510,6 @@ namespace Unity.Netcode.Components
return NetworkManager ? !NetworkManager.DistributedAuthorityMode : true;
}
// Animators only support up to 32 parameters
// TODO: Look into making this a range limited property
private const int k_MaxAnimationParams = 32;
private int[] m_TransitionHash;
private int[] m_AnimationHash;
private float[] m_LayerWeights;
@@ -534,7 +533,7 @@ namespace Unity.Netcode.Components
}
// 128 bytes per Animator
private FastBufferWriter m_ParameterWriter = new FastBufferWriter(k_MaxAnimationParams * sizeof(float), Allocator.Persistent);
private FastBufferWriter m_ParameterWriter;
private NativeArray<AnimatorParamCache> m_CachedAnimatorParameters;
@@ -586,6 +585,14 @@ namespace Unity.Netcode.Components
protected virtual void Awake()
{
if (!m_Animator)
{
#if !UNITY_EDITOR
Debug.LogError($"{nameof(NetworkAnimator)} {name} does not have an {nameof(UnityEngine.Animator)} assigned to it. The {nameof(NetworkAnimator)} will not initialize properly.");
#endif
return;
}
int layers = m_Animator.layerCount;
// Initializing the below arrays for everyone handles an issue
// when running in owner authoritative mode and the owner changes.
@@ -615,6 +622,9 @@ namespace Unity.Netcode.Components
}
}
// The total initialization size calculated for the m_ParameterWriter write buffer.
var totalParameterSize = sizeof(uint);
// Build our reference parameter values to detect when they change
var parameters = m_Animator.parameters;
m_CachedAnimatorParameters = new NativeArray<AnimatorParamCache>(parameters.Length, Allocator.Persistent);
@@ -655,7 +665,37 @@ namespace Unity.Netcode.Components
}
m_CachedAnimatorParameters[i] = cacheParam;
// Calculate parameter sizes (index + type size)
switch (parameter.type)
{
case AnimatorControllerParameterType.Int:
{
totalParameterSize += sizeof(int) * 2;
break;
}
case AnimatorControllerParameterType.Bool:
case AnimatorControllerParameterType.Trigger:
{
// Bool is serialized to 1 byte
totalParameterSize += sizeof(int) + 1;
break;
}
case AnimatorControllerParameterType.Float:
{
totalParameterSize += sizeof(int) + sizeof(float);
break;
}
}
}
if (m_ParameterWriter.IsInitialized)
{
m_ParameterWriter.Dispose();
}
// Create our parameter write buffer for serialization
m_ParameterWriter = new FastBufferWriter(totalParameterSize, Allocator.Persistent);
}
/// <summary>
@@ -697,7 +737,7 @@ namespace Unity.Netcode.Components
}
/// <summary>
/// Wries all parameter and state information needed to initially synchronize a client
/// Writes all parameter and state information needed to initially synchronize a client
/// </summary>
private void WriteSynchronizationData<T>(ref BufferSerializer<T> serializer) where T : IReaderWriter
{
@@ -772,8 +812,10 @@ namespace Unity.Netcode.Components
}
}
animationState.Transition = isInTransition; // The only time this could be set to true
animationState.StateHash = stateHash; // When a transition, this is the originating/starting state
// The only time this could be set to true
animationState.Transition = isInTransition;
// When a transition, this is the originating/starting state
animationState.StateHash = stateHash;
animationState.NormalizedTime = normalizedTime;
animationState.Layer = layer;
animationState.Weight = m_LayerWeights[layer];
@@ -847,7 +889,8 @@ namespace Unity.Netcode.Components
{
m_TransitionHash[layer] = nt.fullPathHash;
m_AnimationHash[layer] = 0;
animState.DestinationStateHash = nt.fullPathHash; // Next state is the destination state for cross fade
// Next state is the destination state for cross fade
animState.DestinationStateHash = nt.fullPathHash;
animState.CrossFade = true;
animState.Transition = true;
animState.Duration = tt.duration;
@@ -865,7 +908,8 @@ namespace Unity.Netcode.Components
// first time in this transition for this layer
m_TransitionHash[layer] = tt.fullPathHash;
m_AnimationHash[layer] = 0;
animState.StateHash = tt.fullPathHash; // Transitioning from state
// Transitioning from state
animState.StateHash = tt.fullPathHash;
animState.CrossFade = false;
animState.Transition = true;
animState.NormalizedTime = tt.normalizedTime;
@@ -1081,7 +1125,7 @@ namespace Unity.Netcode.Components
{
writer.Seek(0);
writer.Truncate();
// Write how many parameter entries we are going to write
// Write out how many parameter entries to read
BytePacker.WriteValuePacked(writer, (uint)m_ParametersToUpdate.Count);
foreach (var parameterIndex in m_ParametersToUpdate)
{
@@ -1230,10 +1274,11 @@ namespace Unity.Netcode.Components
NetworkLog.LogError($"[DestinationState To Transition Info] Layer ({animationState.Layer}) sub-table does not contain destination state ({animationState.DestinationStateHash})!");
}
}
else if (NetworkManager.LogLevel == LogLevel.Developer)
{
NetworkLog.LogError($"[DestinationState To Transition Info] Layer ({animationState.Layer}) does not exist!");
}
// For reference, it is valid to have no transition information
//else if (NetworkManager.LogLevel == LogLevel.Developer)
//{
// NetworkLog.LogError($"[DestinationState To Transition Info] Layer ({animationState.Layer}) does not exist!");
//}
}
else if (animationState.Transition && animationState.CrossFade)
{
@@ -1436,7 +1481,7 @@ namespace Unity.Netcode.Components
/// <summary>
/// Distributed Authority: Internally-called RPC client receiving function to update a trigger when the server wants to forward
/// a trigger for a client to play / reset
/// a trigger to a client
/// </summary>
/// <param name="animationTriggerMessage">the payload containing the trigger data to apply</param>
[Rpc(SendTo.NotAuthority)]
@@ -1447,7 +1492,7 @@ namespace Unity.Netcode.Components
/// <summary>
/// Client Server: Internally-called RPC client receiving function to update a trigger when the server wants to forward
/// a trigger for a client to play / reset
/// a trigger to a client
/// </summary>
/// <param name="animationTriggerMessage">the payload containing the trigger data to apply</param>
/// <param name="clientRpcParams">unused</param>
@@ -1513,7 +1558,7 @@ namespace Unity.Netcode.Components
}
/// <summary>
/// Resets the trigger for the associated animation. See <see cref="SetTrigger(string)">SetTrigger</see> for more on how triggers are special
/// Resets the trigger for the associated animation. See <see cref="SetTrigger(string)">SetTrigger</see> for more on how triggers are special
/// </summary>
/// <param name="triggerName">The string name of the trigger to reset</param>
public void ResetTrigger(string triggerName)
@@ -1529,4 +1574,5 @@ namespace Unity.Netcode.Components
}
}
}
#endif // COM_UNITY_MODULES_ANIMATION
// COM_UNITY_MODULES_ANIMATION
#endif

View File

@@ -1,603 +0,0 @@
#if COM_UNITY_MODULES_PHYSICS
using System.Runtime.CompilerServices;
using UnityEngine;
namespace Unity.Netcode.Components
{
/// <summary>
/// NetworkRigidbodyBase is a <see cref="Rigidbody"/> integration that helps to synchronize physics motion, collision, and interpolation
/// when used with a <see cref="NetworkTransform"/>.
/// </summary>
/// <remarks>
/// For a customizable netcode Rigidbody, create your own component from this class and use <see cref="Initialize(NetworkTransform, Rigidbody)"/>
/// during instantiation (i.e. invoked from within the Awake method). You can re-initialize after having initialized but only when the <see cref="NetworkObject"/> is not spawned.
/// </remarks>
public abstract class NetworkRigidbodyBase : NetworkBehaviour
{
#if UNITY_EDITOR
[HideInInspector]
[SerializeField]
internal bool NetworkRigidbodyBaseExpanded;
#endif
/// <summary>
/// When enabled, the associated <see cref="NetworkTransform"/> will use the Rigidbody to apply and synchronize changes in position, rotation, and
/// allows for the use of Rigidbody interpolation/extrapolation.
/// </summary>
/// <remarks>
/// If <see cref="NetworkTransform.Interpolate"/> is enabled, non-authoritative instances can only use Rigidbody interpolation. If a network prefab is set to
/// extrapolation and <see cref="NetworkTransform.Interpolate"/> is enabled, then non-authoritative instances will automatically be adjusted to use Rigidbody
/// interpolation while the authoritative instance will still use extrapolation.
/// </remarks>
[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;
/// <summary>
/// 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).
/// </summary>
public bool AutoUpdateKinematicState = true;
/// <summary>
/// Primarily applies to the <see cref="AutoUpdateKinematicState"/> property when disabled but you still want
/// the Rigidbody to be automatically set to Kinematic when despawned.
/// </summary>
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;
/// <summary>
/// Initializes the networked Rigidbody based on the <see cref="RigidbodyTypes"/>
/// passed in as a parameter.
/// </summary>
/// <remarks>
/// Cannot be initialized while the associated <see cref="NetworkObject"/> is spawned.
/// </remarks>
/// <param name="rigidbody">(optional) The <see cref="Rigidbody"/> to be used</param>
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<Rigidbody>();
}
SetOriginalInterpolation();
if (NetworkTransform == null)
{
NetworkTransform = GetComponent<NetworkTransform>();
}
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;
}
/// <summary>
/// Sets the linear velocity of the Rigidbody.
/// </summary>
public void SetLinearVelocity(Vector3 linearVelocity)
{
m_InternalRigidbody.linearVelocity = linearVelocity;
}
/// <summary>
/// Gets the linear velocity of the Rigidbody.
/// </summary>
/// <returns><see cref="Vector3"/> as the linear velocity</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3 GetLinearVelocity()
{
return m_InternalRigidbody.linearVelocity;
}
/// <summary>
/// Sets the angular velocity for the Rigidbody.
/// </summary>
/// <param name="angularVelocity">the angular velocity to apply to the body</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetAngularVelocity(Vector3 angularVelocity)
{
m_InternalRigidbody.angularVelocity = angularVelocity;
}
/// <summary>
/// Gets the angular velocity for the Rigidbody.
/// </summary>
/// <returns>angular velocity as a <see cref="Vector3"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3 GetAngularVelocity()
{
return m_InternalRigidbody.angularVelocity;
}
/// <summary>
/// Gets the position of the Rigidbody
/// </summary>
/// <returns><see cref="Vector3"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3 GetPosition()
{
return m_InternalRigidbody.position;
}
/// <summary>
/// Gets the rotation of the Rigidbody
/// </summary>
/// <returns><see cref="Quaternion"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Quaternion GetRotation()
{
return m_InternalRigidbody.rotation;
}
/// <summary>
/// Moves the rigid body
/// </summary>
/// <param name="position">The <see cref="Vector3"/> position to move towards</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MovePosition(Vector3 position)
{
m_InternalRigidbody.MovePosition(position);
}
/// <summary>
/// Directly applies a position (like teleporting)
/// </summary>
/// <param name="position"><see cref="Vector3"/> position to apply to the Rigidbody</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetPosition(Vector3 position)
{
m_InternalRigidbody.position = position;
}
/// <summary>
/// Applies the rotation and position of the <see cref="GameObject"/>'s <see cref="Transform"/>
/// </summary>
[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;
/// <summary>
/// Rotatates the Rigidbody towards a specified rotation
/// </summary>
/// <param name="rotation">The rotation expressed as a <see cref="Quaternion"/></param>
[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);
}
/// <summary>
/// Applies a rotation to the Rigidbody
/// </summary>
/// <param name="rotation">The rotation to apply expressed as a <see cref="Quaternion"/></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetRotation(Quaternion rotation)
{
m_InternalRigidbody.rotation = rotation;
}
/// <summary>
/// Sets the original interpolation of the Rigidbody while taking the Rigidbody type into consideration
/// </summary>
[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;
}
}
}
/// <summary>
/// Wakes the Rigidbody if it is sleeping
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WakeIfSleeping()
{
if (m_InternalRigidbody.IsSleeping())
{
m_InternalRigidbody.WakeUp();
}
}
/// <summary>
/// Puts the Rigidbody to sleep
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SleepRigidbody()
{
m_InternalRigidbody.Sleep();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsKinematic()
{
return m_InternalRigidbody.isKinematic;
}
/// <summary>
/// Sets the kinematic state of the Rigidbody and handles updating the Rigidbody's
/// interpolation setting based on the Kinematic state.
/// </summary>
/// <remarks>
/// When using the Rigidbody for <see cref="NetworkTransform"/> 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.
/// </remarks>
/// <param name="isKinematic"></param>
[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);
}
/// <summary>
/// Sets the authority based on whether it is server or owner authoritative
/// </summary>
/// <remarks>
/// Distributed authority sessions will always be owner authoritative.
/// </remarks>
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);
}
}
/// <inheritdoc />
public override void OnNetworkSpawn()
{
m_TickFrequency = 1.0f / NetworkManager.NetworkConfig.TickRate;
m_TickRate = NetworkManager.NetworkConfig.TickRate;
UpdateOwnershipAuthority();
}
/// <inheritdoc />
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<NetworkRigidbodyBase> NetworkRigidbodyConnections = new System.Collections.Generic.List<NetworkRigidbodyBase>();
internal NetworkRigidbodyBase ParentBody;
private bool m_OriginalGravitySetting;
private float m_OriginalGravityScale;
/// <summary>
/// When using a custom <see cref="NetworkRigidbodyBase"/>, this virtual method is invoked when the
/// <see cref="FixedJoint"/> is created in the event any additional adjustments are needed.
/// </summary>
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>();
FixedJoint.connectedBody = bodyToConnectTo.m_InternalRigidbody;
FixedJoint.connectedMassScale = connectedMassScale;
FixedJoint.massScale = massScale;
OnFixedJointCreated();
}
/// <summary>
/// Authority Only:
/// When invoked and not already attached to a fixed joint, this will connect two rigid bodies with <see cref="UseRigidBodyForMotion"/> 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).
/// <seealso cref="FixedJoint"/>
/// </summary>
/// <remarks>
/// Parenting relative:
/// - This instance can be viewed as the child.
/// - The <param name="objectToConnectTo"/> can be viewed as the parent.
/// <br/>
/// This is the recommended way, as opposed to parenting, to attached/detatch two rigid bodies to one another when <see cref="UseRigidBodyForMotion"/> is enabled.
/// For more details on using <see cref="UnityEngine.FixedJoint"/>.
/// <br/>
/// 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 <see cref="NetworkRigidbodyBase"/>.
/// </remarks>
/// <param name="objectToConnectTo">The target object to attach to.</param>
/// <param name="positionOfConnection">The position of the connection (i.e. where you want the object to be affixed).</param>
/// <param name="connectedMassScale">The target object's mass scale relative to this object being attached.</param>
/// <param name="massScale">This object's mass scale relative to the target object's.</param>
/// <param name="useGravity">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)</param>
/// <param name="zeroVelocity">When true (the default), both linear and angular velocities of this object are set to zero.</param>
/// <param name="teleportObject">When true (the default), this object will teleport itself to the position of connection.</param>
/// <returns>true (success) false (failed)</returns>
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;
}
/// <summary>
/// Authority Only:
/// When invoked and already connected to an object via <see cref="FixedJoint"/>,
/// this will detach from the fixed joint and destroy the fixed joint component.
/// </summary>
/// <remarks>
/// This is the recommended way, as opposed to parenting, to attached/detatch two rigid bodies to one another when <see cref="UseRigidBodyForMotion"/> is enabled.
/// </remarks>
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

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 8c4434f0563fb7f42b3b2993c97ae81a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,24 +0,0 @@
#if COM_UNITY_MODULES_PHYSICS
using UnityEngine;
namespace Unity.Netcode.Components
{
/// <summary>
/// NetworkRigidbody allows for the use of <see cref="Rigidbody"/> on network objects. By controlling the kinematic
/// mode of the <see cref="Rigidbody"/> and disabling it on all peers but the authoritative one.
/// </summary>
[RequireComponent(typeof(NetworkTransform))]
[RequireComponent(typeof(Rigidbody))]
[AddComponentMenu("Netcode/Network Rigidbody")]
public class NetworkRigidbody : NetworkRigidbodyBase
{
public Rigidbody Rigidbody => m_InternalRigidbody;
protected virtual void Awake()
{
Initialize();
}
}
}
#endif // COM_UNITY_MODULES_PHYSICS

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: f6c0be61502bb534f922ebb746851216
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,22 +0,0 @@
#if COM_UNITY_MODULES_PHYSICS2D
using UnityEngine;
namespace Unity.Netcode.Components
{
/// <summary>
/// NetworkRigidbody allows for the use of <see cref="Rigidbody2D"/> on network objects. By controlling the kinematic
/// mode of the rigidbody and disabling it on all peers but the authoritative one.
/// </summary>
[RequireComponent(typeof(NetworkTransform))]
[RequireComponent(typeof(Rigidbody2D))]
[AddComponentMenu("Netcode/Network Rigidbody 2D")]
public class NetworkRigidbody2D : NetworkRigidbodyBase
{
public Rigidbody2D Rigidbody2D => m_InternalRigidbody2D;
protected virtual void Awake()
{
Initialize(RigidbodyTypes.Rigidbody2D);
}
}
}
#endif // COM_UNITY_MODULES_PHYSICS2D

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 80d7c879794dfda4687da0e400131852
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -32,7 +32,8 @@ namespace Unity.Netcode.Components
/// </summary>
public struct NetworkTransformState : INetworkSerializable
{
private const int k_InLocalSpaceBit = 0x00000001; // Persists between state updates (authority dictates if this is set)
// Persists between state updates (authority dictates if this is set)
private const int k_InLocalSpaceBit = 0x00000001;
private const int k_PositionXBit = 0x00000002;
private const int k_PositionYBit = 0x00000004;
private const int k_PositionZBit = 0x00000008;
@@ -43,18 +44,25 @@ namespace Unity.Netcode.Components
private const int k_ScaleYBit = 0x00000100;
private const int k_ScaleZBit = 0x00000200;
private const int k_TeleportingBit = 0x00000400;
private const int k_Interpolate = 0x00000800; // Persists between state updates (authority dictates if this is set)
private const int k_QuaternionSync = 0x00001000; // Persists between state updates (authority dictates if this is set)
private const int k_QuaternionCompress = 0x00002000; // Persists between state updates (authority dictates if this is set)
private const int k_UseHalfFloats = 0x00004000; // Persists between state updates (authority dictates if this is set)
// Persists between state updates (authority dictates if this is set)
private const int k_Interpolate = 0x00000800;
// Persists between state updates (authority dictates if this is set)
private const int k_QuaternionSync = 0x00001000;
// Persists between state updates (authority dictates if this is set)
private const int k_QuaternionCompress = 0x00002000;
// Persists between state updates (authority dictates if this is set)
private const int k_UseHalfFloats = 0x00004000;
private const int k_Synchronization = 0x00008000;
private const int k_PositionSlerp = 0x00010000; // Persists between state updates (authority dictates if this is set)
private const int k_IsParented = 0x00020000; // When parented and synchronizing, we need to have both lossy and local scale due to varying spawn order
// Persists between state updates (authority dictates if this is set)
private const int k_PositionSlerp = 0x00010000;
// When parented and synchronizing, we need to have both lossy and local scale due to varying spawn order
private const int k_IsParented = 0x00020000;
private const int k_SynchBaseHalfFloat = 0x00040000;
private const int k_ReliableSequenced = 0x00080000;
private const int k_UseUnreliableDeltas = 0x00100000;
private const int k_UnreliableFrameSync = 0x00200000;
private const int k_TrackStateId = 0x10000000; // (Internal Debugging) When set each state update will contain a state identifier
// (Internal Debugging) When set each state update will contain a state identifier
private const int k_TrackStateId = 0x10000000;
// Stores persistent and state relative flags
private uint m_Bitset;
@@ -409,8 +417,8 @@ namespace Unity.Netcode.Components
}
/// <summary>
/// Returns whether this state update was a frame synchronization when
/// UseUnreliableDeltas is enabled. When set, the entire transform will
/// Returns whether this state update was a frame synchronization when
/// UseUnreliableDeltas is enabled. When set, the entire transform will
/// be or has been synchronized.
/// </summary>
public bool IsUnreliableFrameSync()
@@ -929,8 +937,6 @@ namespace Unity.Netcode.Components
#endregion
#region PROPERTIES AND GENERAL METHODS
public enum AuthorityModes
{
Server,
@@ -1370,7 +1376,8 @@ namespace Unity.Netcode.Components
private BufferedLinearInterpolatorVector3 m_PositionInterpolator;
private BufferedLinearInterpolatorVector3 m_ScaleInterpolator;
private BufferedLinearInterpolatorQuaternion m_RotationInterpolator; // rotation is a single Quaternion since each Euler axis will affect the quaternion's final value
// rotation is a single Quaternion since each Euler axis will affect the quaternion's final value
private BufferedLinearInterpolatorQuaternion m_RotationInterpolator;
// The previous network state
private NetworkTransformState m_OldState = new NetworkTransformState();
@@ -1643,11 +1650,11 @@ namespace Unity.Netcode.Components
Debug.LogException(ex);
}
// The below is part of assuring we only send a frame synch, when sending unreliable deltas, if
// The below is part of assuring we only send a frame synch, when sending unreliable deltas, if
// we have already sent at least one unreliable delta state update. At this point in the callstack,
// a delta state update has just been sent in the above UpdateTransformState() call and as long as
// we didn't send a frame synch and we are not synchronizing then we know at least one unreliable
// delta has been sent. Under this scenario, we should start checking for this instance's alloted
// delta has been sent. Under this scenario, we should start checking for this instance's alloted
// frame synch "tick slot". Once we send a frame synch, if no other deltas occur after that
// (i.e. the object is at rest) then we will stop sending frame synch's until the object begins
// moving, rotating, or scaling again.
@@ -1964,7 +1971,7 @@ namespace Unity.Netcode.Components
networkState.NetworkDeltaPosition = m_HalfPositionState;
// If ownership offset is greater or we are doing an axial synchronization then synchronize the base position
// If ownership offset is greater or we are doing an axial synchronization then synchronize the base position
if ((m_HalfFloatTargetTickOwnership > m_CachedNetworkManager.ServerTime.Tick || isAxisSync) && !networkState.IsTeleportingNextFrame)
{
networkState.SynchronizeBaseHalfFloat = true;
@@ -3403,7 +3410,7 @@ namespace Unity.Netcode.Components
/// - Local space to local space (<see cref="NetworkObject"/> parent to <see cref="NetworkObject"/> parent)
/// Will all smoothly transition while interpolation is enabled.
/// (Does not work if using a <see cref="Rigidbody"/> or <see cref="Rigidbody2D"/> for motion)
///
///
/// When a parent changes, non-authoritative instances should:<br />
/// - Apply the resultant position, rotation, and scale from the parenting action.<br />
/// - Clear interpolators (even if not enabled on this frame)<br />
@@ -3575,7 +3582,7 @@ namespace Unity.Netcode.Components
var transformToCommit = transform;
// Explicit set states are cumulative during a fractional tick period of time (i.e. each SetState invocation will
// Explicit set states are cumulative during a fractional tick period of time (i.e. each SetState invocation will
// update the axial deltas to whatever changes are applied). As such, we need to preserve the dirty and explicit
// state flags.
var stateWasDirty = m_LocalAuthoritativeNetworkState.IsDirty;
@@ -3658,7 +3665,7 @@ namespace Unity.Netcode.Components
var serverTime = m_CachedNetworkManager.ServerTime;
var cachedServerTime = serverTime.Time;
//var offset = (float)serverTime.TickOffset;
// var offset = (float)serverTime.TickOffset;
#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D
var cachedDeltaTime = m_UseRigidbodyForMotion ? m_CachedNetworkManager.RealTimeProvider.FixedDeltaTime : m_CachedNetworkManager.RealTimeProvider.DeltaTime;
#else
@@ -3669,7 +3676,7 @@ namespace Unity.Netcode.Components
// is to make their cachedRenderTime run 2 ticks behind.
// TODO: This could most likely just always be 2
//var ticksAgo = ((!IsServerAuthoritative() && !IsServer) || m_CachedNetworkManager.DistributedAuthorityMode) && !m_CachedNetworkManager.DAHost ? 2 : 1;
// var ticksAgo = ((!IsServerAuthoritative() && !IsServer) || m_CachedNetworkManager.DistributedAuthorityMode) && !m_CachedNetworkManager.DAHost ? 2 : 1;
var ticksAgo = 2;
var cachedRenderTime = serverTime.TimeTicksAgo(ticksAgo).Time;
@@ -3746,7 +3753,7 @@ namespace Unity.Netcode.Components
/// <summary>
/// Determines whether the <see cref="NetworkTransform"/> is <see cref="AuthorityModes.Server"/> or <see cref="AuthorityModes.Owner"/> based on the <see cref="AuthorityMode"/> property.
/// You can override this method to control this logic.
/// You can override this method to control this logic.
/// </summary>
/// <returns><see cref="true"/> or <see cref="false"/></returns>
protected virtual bool OnIsServerAuthoritative()
@@ -3772,7 +3779,6 @@ namespace Unity.Netcode.Components
return OnIsServerAuthoritative();
}
}
#endregion
#region MESSAGE HANDLING
@@ -3964,7 +3970,7 @@ namespace Unity.Netcode.Components
{
return 2 * m_TickFrequency;
// TODO: We need an RTT that updates regularly and not just when the client sends packets
//return Mathf.Max(1.0f, TicksAgo) * m_TickFrequency;
// return Mathf.Max(1.0f, TicksAgo) * m_TickFrequency;
}
/// <summary>
@@ -3974,25 +3980,25 @@ namespace Unity.Netcode.Components
private void TickUpdate()
{
// TODO: We need an RTT that updates regularly and not just when the client sends packets
//if (m_UnityTransport != null)
//{
// // Determine the desired ticks ago by the RTT (this really should be the combination of the
// // authority and non-authority 1/2 RTT but in the end anything beyond 300ms is considered very poor
// // network quality so latent interpolation is going to be expected).
// var rtt = Mathf.Max(m_TickInMS, m_UnityTransport.GetCurrentRtt(NetworkManager.ServerClientId));
// m_TicksAgoSamples[m_TickSampleIndex] = Mathf.Max(1, (int)(rtt * m_TickFrequency));
// var tickAgoSum = 0.0f;
// foreach (var tickAgo in m_TicksAgoSamples)
// {
// tickAgoSum += tickAgo;
// }
// m_PreviousTicksAgo = TicksAgo;
// TicksAgo = Mathf.Lerp(m_PreviousTicksAgo, tickAgoSum / m_TickRate, m_TickFrequency);
// m_TickSampleIndex = (m_TickSampleIndex + 1) % m_TickRate;
// // Get the partial tick value for when this is all calculated to provide an offset for determining
// // the relative starting interpolation point for the next update
// Offset = m_OffsetTickFrequency * (Mathf.Max(2, TicksAgo) - (int)TicksAgo);
//}
// if (m_UnityTransport != null)
// {
// // Determine the desired ticks ago by the RTT (this really should be the combination of the
// // authority and non-authority 1/2 RTT but in the end anything beyond 300ms is considered very poor
// // network quality so latent interpolation is going to be expected).
// var rtt = Mathf.Max(m_TickInMS, m_UnityTransport.GetCurrentRtt(NetworkManager.ServerClientId));
// m_TicksAgoSamples[m_TickSampleIndex] = Mathf.Max(1, (int)(rtt * m_TickFrequency));
// var tickAgoSum = 0.0f;
// foreach (var tickAgo in m_TicksAgoSamples)
// {
// tickAgoSum += tickAgo;
// }
// m_PreviousTicksAgo = TicksAgo;
// TicksAgo = Mathf.Lerp(m_PreviousTicksAgo, tickAgoSum / m_TickRate, m_TickFrequency);
// m_TickSampleIndex = (m_TickSampleIndex + 1) % m_TickRate;
// // Get the partial tick value for when this is all calculated to provide an offset for determining
// // the relative starting interpolation point for the next update
// Offset = m_OffsetTickFrequency * (Mathf.Max(2, TicksAgo) - (int)TicksAgo);
// }
// TODO FIX: The local NetworkTickSystem can invoke with the same network tick as before
if (m_NetworkManager.ServerTime.Tick <= m_LastTick)
@@ -4012,13 +4018,13 @@ namespace Unity.Netcode.Components
private UnityTransport m_UnityTransport;
private float m_TickFrequency;
//private float m_OffsetTickFrequency;
//private ulong m_TickInMS;
//private int m_TickSampleIndex;
// private float m_OffsetTickFrequency;
// private ulong m_TickInMS;
// private int m_TickSampleIndex;
private int m_TickRate;
public float TicksAgo { get; private set; }
//public float Offset { get; private set; }
//private float m_PreviousTicksAgo;
// public float Offset { get; private set; }
// private float m_PreviousTicksAgo;
private List<float> m_TicksAgoSamples = new List<float>();
@@ -4032,16 +4038,16 @@ namespace Unity.Netcode.Components
//// For the offset, it uses the fractional remainder of the tick to determine the offset.
//// In order to keep within tick boundaries, we increment the tick rate by 1 to assure it
//// will always be < the tick frequency.
//m_OffsetTickFrequency = 1.0f / (m_TickRate + 1);
//m_TickInMS = (ulong)(1000 * m_TickFrequency);
//m_UnityTransport = m_NetworkManager.NetworkConfig.NetworkTransport as UnityTransport;
// m_OffsetTickFrequency = 1.0f / (m_TickRate + 1);
// m_TickInMS = (ulong)(1000 * m_TickFrequency);
// m_UnityTransport = m_NetworkManager.NetworkConfig.NetworkTransport as UnityTransport;
//// Fill the sample with a starting value of 1
//for (int i = 0; i < m_TickRate; i++)
//{
// m_TicksAgoSamples.Add(1f);
//}
// for (int i = 0; i < m_TickRate; i++)
// {
// m_TicksAgoSamples.Add(1f);
// }
TicksAgo = 2f;
//m_PreviousTicksAgo = 1f;
// m_PreviousTicksAgo = 1f;
if (networkManager.IsServer)
{
networkManager.OnServerStopped += OnNetworkManagerStopped;

View File

@@ -27,7 +27,7 @@ namespace Unity.Netcode.Components
}
/// <summary>
/// Default implementation required to register a <see cref="Rigidbody"/> with a <see cref="RigidbodyContactEventManager"/> instance.
/// Default implementation required to register a <see cref="Rigidbody"/> with a <see cref="RigidbodyContactEventManager"/> instance.
/// </summary>
/// <remarks>
/// Recommended to implement this method on a <see cref="NetworkBehaviour"/> component
@@ -52,7 +52,7 @@ namespace Unity.Netcode.Components
}
/// <summary>
/// This is an extended version of <see cref="IContactEventHandler"/> and can be used to register a <see cref="Rigidbody"/> with a <see cref="RigidbodyContactEventManager"/> instance. <br />
/// This is an extended version of <see cref="IContactEventHandler"/> and can be used to register a <see cref="Rigidbody"/> with a <see cref="RigidbodyContactEventManager"/> instance.<br />
/// This provides additional <see cref="ContactEventHandlerInfo"/> information to the <see cref="RigidbodyContactEventManager"/> for each set of contact events it is processing.
/// </summary>
public interface IContactEventHandlerWithInfo : IContactEventHandler
@@ -66,9 +66,9 @@ namespace Unity.Netcode.Components
/// <summary>
/// Add this component to an in-scene placed GameObject to provide faster collision event processing between <see cref="Rigidbody"/> instances and optionally static colliders.
/// <see cref="IContactEventHandler"/> <br />
/// <see cref="IContactEventHandlerWithInfo"/> <br />
/// <see cref="ContactEventHandlerInfo"/> <br />
/// <see cref="IContactEventHandler"/><br />
/// <see cref="IContactEventHandlerWithInfo"/><br />
/// <see cref="ContactEventHandlerInfo"/><br />
/// </summary>
[AddComponentMenu("Netcode/Rigidbody Contact Event Manager")]
public class RigidbodyContactEventManager : MonoBehaviour