com.unity.netcode.gameobjects@1.4.0

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).

## [1.4.0] - 2023-04-10

### Added

- Added a way to access the GlobalObjectIdHash via PrefabIdHash for use in the Connection Approval Callback. (#2437)
- Added `OnServerStarted` and `OnServerStopped` events that will trigger only on the server (or host player) to notify that the server just started or is no longer active (#2420)
- Added `OnClientStarted` and `OnClientStopped` events that will trigger only on the client (or host player) to notify that the client just started or is no longer active (#2420)
- Added `NetworkTransform.UseHalfFloatPrecision` property that, when enabled, will use half float values for position, rotation, and scale. This yields a 50% bandwidth savings a the cost of precision. (#2388)
- Added `NetworkTransform.UseQuaternionSynchronization` property that, when enabled, will synchronize the entire quaternion. (#2388)
- Added `NetworkTransform.UseQuaternionCompression` property that, when enabled, will use a smallest three implementation reducing a full quaternion synchronization update to the size of an unsigned integer. (#2388)
- Added `NetworkTransform.SlerpPosition` property that, when enabled along with interpolation being enabled, will interpolate using `Vector3.Slerp`. (#2388)
- Added `BufferedLinearInterpolatorVector3` that replaces the float version, is now used by `NetworkTransform`, and provides the ability to enable or disable `Slerp`. (#2388)
- Added `HalfVector3` used for scale when half float precision is enabled. (#2388)
- Added `HalfVector4` used for rotation when half float precision and quaternion synchronization is enabled. (#2388)
- Added `HalfVector3DeltaPosition` used for position when half float precision is enabled. This handles loss in position precision by updating only the delta position as opposed to the full position. (#2388)
- Added `NetworkTransform.GetSpaceRelativePosition` and `NetworkTransform.GetSpaceRelativeRotation` helper methods to return the proper values depending upon whether local or world space. (#2388)
- Added `NetworkTransform.OnAuthorityPushTransformState` virtual method that is invoked just prior to sending the `NetworkTransformState` to non-authoritative instances. This provides users with the ability to obtain more precise delta values for prediction related calculations. (#2388)
- Added `NetworkTransform.OnNetworkTransformStateUpdated` virtual method that is invoked just after the authoritative `NetworkTransformState` is applied. This provides users with the ability to obtain more precise delta values for prediction related calculations. (#2388)
- Added `NetworkTransform.OnInitialize`virtual method that is invoked after the `NetworkTransform` has been initialized or re-initialized when ownership changes. This provides for a way to make adjustments when `NetworkTransform` is initialized (i.e. resetting client prediction etc) (#2388)
- Added `NetworkObject.SynchronizeTransform` property (default is true) that provides users with another way to help with bandwidth optimizations where, when set to false, the `NetworkObject`'s associated transform will not be included when spawning and/or synchronizing late joining players. (#2388)
- Added `NetworkSceneManager.ActiveSceneSynchronizationEnabled` property, disabled by default, that enables client synchronization of server-side active scene changes. (#2383)
- Added `NetworkObject.ActiveSceneSynchronization`, disabled by default, that will automatically migrate a `NetworkObject` to a newly assigned active scene. (#2383)
- Added `NetworkObject.SceneMigrationSynchronization`, enabled by default, that will synchronize client(s) when a `NetworkObject` is migrated into a new scene on the server side via `SceneManager.MoveGameObjectToScene`. (#2383)

### Changed

- Made sure the `CheckObjectVisibility` delegate is checked and applied, upon `NetworkShow` attempt. Found while supporting (#2454), although this is not a fix for this (already fixed) issue. (#2463)
- Changed `NetworkTransform` authority handles delta checks on each new network tick and no longer consumes processing cycles checking for deltas for all frames in-between ticks. (#2388)
- Changed the `NetworkTransformState` structure is now public and now has public methods that provide access to key properties of the `NetworkTransformState` structure. (#2388)
- Changed `NetworkTransform` interpolation adjusts its interpolation "ticks ago" to be 2 ticks latent if it is owner authoritative and the instance is not the server or 1 tick latent if the instance is the server and/or is server authoritative. (#2388)
- Updated `NetworkSceneManager` to migrate dynamically spawned `NetworkObject`s with `DestroyWithScene` set to false into the active scene if their current scene is unloaded. (#2383)
- Updated the server to synchronize its local `NetworkSceneManager.ClientSynchronizationMode` during the initial client synchronization. (#2383)

### Fixed

- Fixed issue where during client synchronization the synchronizing client could receive a ObjectSceneChanged message before the client-side NetworkObject instance had been instantiated and spawned. (#2502)
- Fixed issue where `NetworkAnimator` was building client RPC parameters to exclude the host from sending itself messages but was not including it in the ClientRpc parameters. (#2492)
- Fixed issue where `NetworkAnimator` was not properly detecting and synchronizing cross fade initiated transitions. (#2481)
- Fixed issue where `NetworkAnimator` was not properly synchronizing animation state updates. (#2481)
- Fixed float NetworkVariables not being rendered properly in the inspector of NetworkObjects. (#2441)
- Fixed an issue where Named Message Handlers could remove themselves causing an exception when the metrics tried to access the name of the message.(#2426)
- Fixed registry of public `NetworkVariable`s in derived `NetworkBehaviour`s (#2423)
- Fixed issue where runtime association of `Animator` properties to `AnimationCurve`s would cause `NetworkAnimator` to attempt to update those changes. (#2416)
- Fixed issue where `NetworkAnimator` would not check if its associated `Animator` was valid during serialization and would spam exceptions in the editor console. (#2416)
- Fixed issue with a child's rotation rolling over when interpolation is enabled on a `NetworkTransform`. Now using half precision or full quaternion synchronization will always update all axis. (#2388)
- Fixed issue where `NetworkTransform` was not setting the teleport flag when the `NetworkTransform.InLocalSpace` value changed. This issue only impacted `NetworkTransform` when interpolation was enabled. (#2388)
- Fixed issue when the `NetworkSceneManager.ClientSynchronizationMode` is `LoadSceneMode.Additive` and the server changes the currently active scene prior to a client connecting then upon a client connecting and being synchronized the NetworkSceneManager would clear its internal ScenePlacedObjects list that could already be populated. (#2383)
- Fixed issue where a client would load duplicate scenes of already preloaded scenes during the initial client synchronization and `NetworkSceneManager.ClientSynchronizationMode` was set to `LoadSceneMode.Additive`. (#2383)
This commit is contained in:
Unity Technologies
2023-04-10 00:00:00 +00:00
parent 8060718e04
commit b5abc3ff7c
138 changed files with 7892 additions and 1852 deletions

View File

@@ -23,6 +23,13 @@ namespace Unity.Netcode.Components
/// </summary>
private void FlushMessages()
{
foreach (var animationUpdate in m_SendAnimationUpdates)
{
m_NetworkAnimator.SendAnimStateClientRpc(animationUpdate.AnimationMessage, animationUpdate.ClientRpcParams);
}
m_SendAnimationUpdates.Clear();
foreach (var sendEntry in m_SendParameterUpdates)
{
m_NetworkAnimator.SendParametersUpdateClientRpc(sendEntry.ParametersUpdateMessage, sendEntry.ClientRpcParams);
@@ -64,9 +71,11 @@ namespace Unity.Netcode.Components
m_NetworkAnimator.UpdateParameters(ref parameterUpdate);
}
m_ProcessParameterUpdates.Clear();
var isServerAuthority = m_NetworkAnimator.IsServerAuthoritative();
// Only owners check for Animator changes
if (m_NetworkAnimator.IsOwner && !m_NetworkAnimator.IsServerAuthoritative() || m_NetworkAnimator.IsServerAuthoritative() && m_NetworkAnimator.NetworkManager.IsServer)
// owners when owner authoritative or the server when server authoritative are the only instances that
// checks for Animator changes
if ((!isServerAuthority && m_NetworkAnimator.IsOwner) || (isServerAuthority && m_NetworkAnimator.IsServer))
{
m_NetworkAnimator.CheckForAnimatorChanges();
}
@@ -157,11 +166,11 @@ namespace Unity.Netcode.Components
[AddComponentMenu("Netcode/Network Animator")]
[RequireComponent(typeof(Animator))]
public class NetworkAnimator : NetworkBehaviour, ISerializationCallbackReceiver
{
[Serializable]
internal class TransitionStateinfo
{
public bool IsCrossFadeExit;
public int Layer;
public int OriginatingState;
public int DestinationState;
@@ -279,6 +288,11 @@ namespace Unity.Netcode.Components
{
return;
}
if (m_Animator == null)
{
return;
}
TransitionStateInfoList = new List<TransitionStateinfo>();
var animatorController = m_Animator.runtimeAnimatorController as AnimatorController;
if (animatorController == null)
@@ -312,9 +326,19 @@ namespace Unity.Netcode.Components
internal float NormalizedTime;
internal int Layer;
internal float Weight;
internal float Duration;
// For synchronizing transitions
internal bool Transition;
internal bool CrossFade;
// Flags for bool states
private const byte k_IsTransition = 0x01;
private const byte k_IsCrossFade = 0x02;
// Used to serialize the bool states
private byte m_StateFlags;
// The StateHash is where the transition starts
// and the DestinationStateHash is the destination state
internal int DestinationStateHash;
@@ -324,65 +348,46 @@ namespace Unity.Netcode.Components
if (serializer.IsWriter)
{
var writer = serializer.GetFastBufferWriter();
var writeSize = FastBufferWriter.GetWriteSize(Transition);
writeSize += FastBufferWriter.GetWriteSize(StateHash);
writeSize += FastBufferWriter.GetWriteSize(NormalizedTime);
writeSize += FastBufferWriter.GetWriteSize(Layer);
writeSize += FastBufferWriter.GetWriteSize(Weight);
m_StateFlags = 0x00;
if (Transition)
{
writeSize += FastBufferWriter.GetWriteSize(DestinationStateHash);
m_StateFlags |= k_IsTransition;
}
if (!writer.TryBeginWrite(writeSize))
if (CrossFade)
{
throw new OverflowException($"[{GetType().Name}] Could not serialize: Out of buffer space.");
m_StateFlags |= k_IsCrossFade;
}
serializer.SerializeValue(ref m_StateFlags);
writer.WriteValue(Transition);
writer.WriteValue(StateHash);
writer.WriteValue(NormalizedTime);
writer.WriteValue(Layer);
writer.WriteValue(Weight);
BytePacker.WriteValuePacked(writer, StateHash);
BytePacker.WriteValuePacked(writer, Layer);
if (Transition)
{
writer.WriteValue(DestinationStateHash);
BytePacker.WriteValuePacked(writer, DestinationStateHash);
}
}
else
{
var reader = serializer.GetFastBufferReader();
// Begin reading the Transition flag
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(Transition)))
{
throw new OverflowException($"[{GetType().Name}] Could not deserialize: Out of buffer space.");
}
reader.ReadValue(out Transition);
serializer.SerializeValue(ref m_StateFlags);
Transition = (m_StateFlags & k_IsTransition) == k_IsTransition;
CrossFade = (m_StateFlags & k_IsCrossFade) == k_IsCrossFade;
// Now determine what remains to be read
var readSize = FastBufferWriter.GetWriteSize(StateHash);
readSize += FastBufferWriter.GetWriteSize(NormalizedTime);
readSize += FastBufferWriter.GetWriteSize(Layer);
readSize += FastBufferWriter.GetWriteSize(Weight);
ByteUnpacker.ReadValuePacked(reader, out StateHash);
ByteUnpacker.ReadValuePacked(reader, out Layer);
if (Transition)
{
readSize += FastBufferWriter.GetWriteSize(DestinationStateHash);
ByteUnpacker.ReadValuePacked(reader, out DestinationStateHash);
}
}
// Now read the remaining information about this AnimationState
if (!reader.TryBeginRead(readSize))
{
throw new OverflowException($"[{GetType().Name}] Could not deserialize: Out of buffer space.");
}
serializer.SerializeValue(ref NormalizedTime);
serializer.SerializeValue(ref Weight);
reader.ReadValue(out StateHash);
reader.ReadValue(out NormalizedTime);
reader.ReadValue(out Layer);
reader.ReadValue(out Weight);
if (Transition)
{
reader.ReadValue(out DestinationStateHash);
}
// Cross fading includes the duration of the cross fade.
if (CrossFade)
{
serializer.SerializeValue(ref Duration);
}
}
}
@@ -565,8 +570,10 @@ namespace Unity.Netcode.Components
// We initialize the m_AnimationMessage for all instances in the event that
// ownership or authority changes during runtime.
m_AnimationMessage = new AnimationMessage();
m_AnimationMessage.AnimationStates = new List<AnimationState>();
m_AnimationMessage = new AnimationMessage
{
AnimationStates = new List<AnimationState>()
};
// Store off our current layer weights and create our animation
// state entries per layer.
@@ -588,17 +595,13 @@ namespace Unity.Netcode.Components
m_CachedAnimatorParameters = new NativeArray<AnimatorParamCache>(parameters.Length, Allocator.Persistent);
m_ParametersToUpdate = new List<int>(parameters.Length);
// Include all parameters including any controlled by an AnimationCurve as this could change during runtime.
// We ignore changes to any parameter controlled by an AnimationCurve when we are checking for changes in
// the Animator's parameters.
for (var i = 0; i < parameters.Length; i++)
{
var parameter = parameters[i];
if (m_Animator.IsParameterControlledByCurve(parameter.nameHash))
{
// we are ignoring parameters that are controlled by animation curves - syncing the layer
// states indirectly syncs the values that are driven by the animation curves
continue;
}
var cacheParam = new AnimatorParamCache
{
Type = UnsafeUtility.EnumToInt(parameter.type),
@@ -643,12 +646,22 @@ namespace Unity.Netcode.Components
/// <inheritdoc/>
public override void OnNetworkSpawn()
{
// If there is no assigned Animator then generate a server network warning (logged locally and if applicable on the server-host side as well).
if (m_Animator == null)
{
NetworkLog.LogWarningServer($"[{gameObject.name}][{nameof(NetworkAnimator)}] {nameof(Animator)} is not assigned! Animation synchronization will not work for this instance!");
}
if (IsServer)
{
m_ClientSendList = new List<ulong>(128);
m_ClientRpcParams = new ClientRpcParams();
m_ClientRpcParams.Send = new ClientRpcSendParams();
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
m_ClientRpcParams = new ClientRpcParams
{
Send = new ClientRpcSendParams
{
TargetClientIds = m_ClientSendList
}
};
}
// Create a handler for state changes
@@ -691,10 +704,7 @@ namespace Unity.Netcode.Components
for (int layer = 0; layer < m_Animator.layerCount; layer++)
{
var synchronizationStateInfo = m_Animator.GetCurrentAnimatorStateInfo(layer);
if (SynchronizationStateInfo != null)
{
SynchronizationStateInfo.Add(synchronizationStateInfo);
}
SynchronizationStateInfo?.Add(synchronizationStateInfo);
var stateHash = synchronizationStateInfo.fullPathHash;
var normalizedTime = synchronizationStateInfo.normalizedTime;
var isInTransition = m_Animator.IsInTransition(layer);
@@ -767,11 +777,97 @@ namespace Unity.Netcode.Components
else
{
var parameters = new ParametersUpdateMessage();
var animationStates = new AnimationMessage();
var animationMessage = new AnimationMessage();
serializer.SerializeValue(ref parameters);
UpdateParameters(ref parameters);
serializer.SerializeValue(ref animationStates);
HandleAnimStateUpdate(ref animationStates);
serializer.SerializeValue(ref animationMessage);
foreach (var animationState in animationMessage.AnimationStates)
{
UpdateAnimationState(animationState);
}
}
}
/// <summary>
/// Checks for animation state changes in:
/// -Layer weights
/// -Cross fades
/// -Transitions
/// -Layer AnimationStates
/// </summary>
private void CheckForStateChange(int layer)
{
var stateChangeDetected = false;
var animState = m_AnimationMessage.AnimationStates[m_AnimationMessage.IsDirtyCount];
float layerWeightNow = m_Animator.GetLayerWeight(layer);
animState.CrossFade = false;
animState.Transition = false;
animState.NormalizedTime = 0.0f;
animState.Layer = layer;
animState.Duration = 0.0f;
animState.Weight = m_LayerWeights[layer];
animState.DestinationStateHash = 0;
if (layerWeightNow != m_LayerWeights[layer])
{
m_LayerWeights[layer] = layerWeightNow;
stateChangeDetected = true;
animState.Weight = layerWeightNow;
}
AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(layer);
if (m_Animator.IsInTransition(layer))
{
AnimatorTransitionInfo tt = m_Animator.GetAnimatorTransitionInfo(layer);
AnimatorStateInfo nt = m_Animator.GetNextAnimatorStateInfo(layer);
if (tt.anyState && tt.fullPathHash == 0 && m_TransitionHash[layer] != nt.fullPathHash)
{
m_TransitionHash[layer] = nt.fullPathHash;
m_AnimationHash[layer] = 0;
animState.DestinationStateHash = nt.fullPathHash; // Next state is the destination state for cross fade
animState.CrossFade = true;
animState.Transition = true;
animState.Duration = tt.duration;
animState.NormalizedTime = tt.normalizedTime;
stateChangeDetected = true;
//Debug.Log($"[Cross-Fade] To-Hash: {nt.fullPathHash} | TI-Duration: ({tt.duration}) | TI-Norm: ({tt.normalizedTime}) | From-Hash: ({m_AnimationHash[layer]}) | SI-FPHash: ({st.fullPathHash}) | SI-Norm: ({st.normalizedTime})");
}
else
if (!tt.anyState && tt.fullPathHash != m_TransitionHash[layer])
{
// first time in this transition for this layer
m_TransitionHash[layer] = tt.fullPathHash;
m_AnimationHash[layer] = 0;
animState.StateHash = tt.fullPathHash; // Transitioning from state
animState.CrossFade = false;
animState.Transition = true;
animState.NormalizedTime = tt.normalizedTime;
stateChangeDetected = true;
//Debug.Log($"[Transition] TI-Duration: ({tt.duration}) | TI-Norm: ({tt.normalizedTime}) | From-Hash: ({m_AnimationHash[layer]}) |SI-FPHash: ({st.fullPathHash}) | SI-Norm: ({st.normalizedTime})");
}
}
else
{
if (st.fullPathHash != m_AnimationHash[layer])
{
m_TransitionHash[layer] = 0;
m_AnimationHash[layer] = st.fullPathHash;
// first time in this animation state
if (m_AnimationHash[layer] != 0)
{
// came from another animation directly - from Play()
animState.StateHash = st.fullPathHash;
animState.NormalizedTime = st.normalizedTime;
}
stateChangeDetected = true;
//Debug.Log($"[State] From-Hash: ({m_AnimationHash[layer]}) |SI-FPHash: ({st.fullPathHash}) | SI-Norm: ({st.normalizedTime})");
}
}
if (stateChangeDetected)
{
m_AnimationMessage.AnimationStates[m_AnimationMessage.IsDirtyCount] = animState;
m_AnimationMessage.IsDirtyCount++;
}
}
@@ -784,11 +880,6 @@ namespace Unity.Netcode.Components
/// </remarks>
internal void CheckForAnimatorChanges()
{
if (!IsSpawned || (!IsOwner && !IsServerAuthoritative()) || (IsServerAuthoritative() && !IsServer))
{
return;
}
if (CheckParametersChanged())
{
SendParametersUpdate();
@@ -803,9 +894,6 @@ namespace Unity.Netcode.Components
return;
}
int stateHash;
float normalizedTime;
// Reset the dirty count before checking for AnimationState updates
m_AnimationMessage.IsDirtyCount = 0;
@@ -815,26 +903,7 @@ namespace Unity.Netcode.Components
AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(layer);
var totalSpeed = st.speed * st.speedMultiplier;
var adjustedNormalizedMaxTime = totalSpeed > 0.0f ? 1.0f / totalSpeed : 0.0f;
if (!CheckAnimStateChanged(out stateHash, out normalizedTime, layer))
{
continue;
}
// If we made it here, then we need to synchronize this layer's animation state.
// Get one of the preallocated AnimationState entries and populate it with the
// current layer's state.
var animationState = m_AnimationMessage.AnimationStates[m_AnimationMessage.IsDirtyCount];
animationState.Transition = false; // Only used during synchronization
animationState.StateHash = stateHash;
animationState.NormalizedTime = normalizedTime;
animationState.Layer = layer;
animationState.Weight = m_LayerWeights[layer];
// Apply the changes
m_AnimationMessage.AnimationStates[m_AnimationMessage.IsDirtyCount] = animationState;
m_AnimationMessage.IsDirtyCount++;
CheckForStateChange(layer);
}
// Send an AnimationMessage only if there are dirty AnimationStates to send
@@ -851,7 +920,7 @@ namespace Unity.Netcode.Components
m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds);
m_ClientSendList.Remove(NetworkManager.LocalClientId);
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
SendAnimStateClientRpc(m_AnimationMessage);
SendAnimStateClientRpc(m_AnimationMessage, m_ClientRpcParams);
}
}
}
@@ -885,7 +954,7 @@ namespace Unity.Netcode.Components
/// <summary>
/// Helper function to get the cached value
/// </summary>
unsafe private T GetValue<T>(ref AnimatorParamCache animatorParamCache)
private unsafe T GetValue<T>(ref AnimatorParamCache animatorParamCache)
{
T currentValue;
fixed (void* value = animatorParamCache.Value)
@@ -900,12 +969,20 @@ namespace Unity.Netcode.Components
/// If so, it fills out m_ParametersToUpdate with the indices of the parameters
/// that have changed. Returns true if any parameters changed.
/// </summary>
unsafe private bool CheckParametersChanged()
private unsafe bool CheckParametersChanged()
{
m_ParametersToUpdate.Clear();
for (int i = 0; i < m_CachedAnimatorParameters.Length; i++)
{
ref var cacheValue = ref UnsafeUtility.ArrayElementAsRef<AnimatorParamCache>(m_CachedAnimatorParameters.GetUnsafePtr(), i);
// If a parameter gets controlled by a curve during runtime after initialization of NetworkAnimator
// then ignore changes to this parameter. We are not removing the parameter in the event that
// it no longer is controlled by a curve.
if (m_Animator.IsParameterControlledByCurve(cacheValue.Hash))
{
continue;
}
var hash = cacheValue.Hash;
if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterInt)
{
@@ -941,52 +1018,6 @@ namespace Unity.Netcode.Components
return m_ParametersToUpdate.Count > 0;
}
/// <summary>
/// Checks if any of the Animator's states have changed
/// </summary>
private bool CheckAnimStateChanged(out int stateHash, out float normalizedTime, int layer)
{
stateHash = 0;
normalizedTime = 0;
float layerWeightNow = m_Animator.GetLayerWeight(layer);
if (layerWeightNow != m_LayerWeights[layer])
{
m_LayerWeights[layer] = layerWeightNow;
return true;
}
if (m_Animator.IsInTransition(layer))
{
AnimatorTransitionInfo tt = m_Animator.GetAnimatorTransitionInfo(layer);
if (tt.fullPathHash != m_TransitionHash[layer])
{
// first time in this transition for this layer
m_TransitionHash[layer] = tt.fullPathHash;
m_AnimationHash[layer] = 0;
return true;
}
}
else
{
AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(layer);
if (st.fullPathHash != m_AnimationHash[layer])
{
// first time in this animation state
if (m_AnimationHash[layer] != 0)
{
// came from another animation directly - from Play()
stateHash = st.fullPathHash;
normalizedTime = st.normalizedTime;
}
m_TransitionHash[layer] = 0;
m_AnimationHash[layer] = st.fullPathHash;
return true;
}
}
return false;
}
/// <summary>
/// Writes all of the Animator's parameters
/// This uses the m_ParametersToUpdate list to write out only
@@ -1110,14 +1141,14 @@ namespace Unity.Netcode.Components
}
// If there is no state transition then return
if (animationState.StateHash == 0)
if (animationState.StateHash == 0 && !animationState.Transition)
{
return;
}
var currentState = m_Animator.GetCurrentAnimatorStateInfo(animationState.Layer);
// If it is a transition, then we are synchronizing transitions in progress when a client late joins
if (animationState.Transition)
if (animationState.Transition && !animationState.CrossFade)
{
// We should have all valid entries for any animation state transition update
// Verify the AnimationState's assigned Layer exists
@@ -1150,9 +1181,14 @@ namespace Unity.Netcode.Components
NetworkLog.LogError($"[DestinationState To Transition Info] Layer ({animationState.Layer}) does not exist!");
}
}
else if (animationState.Transition && animationState.CrossFade)
{
m_Animator.CrossFade(animationState.DestinationStateHash, animationState.Duration, animationState.Layer, animationState.NormalizedTime);
}
else
{
if (currentState.fullPathHash != animationState.StateHash)
// Make sure we are not just updating the weight of a layer.
if (currentState.fullPathHash != animationState.StateHash && m_Animator.HasState(animationState.Layer, animationState.StateHash))
{
m_Animator.Play(animationState.StateHash, animationState.Layer, animationState.NormalizedTime);
}
@@ -1237,23 +1273,11 @@ namespace Unity.Netcode.Components
}
}
internal void HandleAnimStateUpdate(ref AnimationMessage animationMessage)
{
var isServerAuthoritative = IsServerAuthoritative();
if (!isServerAuthoritative && !IsOwner || isServerAuthoritative)
{
foreach (var animationState in animationMessage.AnimationStates)
{
UpdateAnimationState(animationState);
}
}
}
/// <summary>
/// Internally-called RPC client receiving function to update some animation state on a client
/// </summary>
[ClientRpc]
private unsafe void SendAnimStateClientRpc(AnimationMessage animationMessage, ClientRpcParams clientRpcParams = default)
internal unsafe void SendAnimStateClientRpc(AnimationMessage animationMessage, ClientRpcParams clientRpcParams = default)
{
// This should never happen
if (IsHost)
@@ -1264,7 +1288,10 @@ namespace Unity.Netcode.Components
}
return;
}
HandleAnimStateUpdate(ref animationMessage);
foreach (var animationState in animationMessage.AnimationStates)
{
UpdateAnimationState(animationState);
}
}
/// <summary>
@@ -1274,44 +1301,31 @@ namespace Unity.Netcode.Components
[ServerRpc]
internal void SendAnimTriggerServerRpc(AnimationTriggerMessage animationTriggerMessage, ServerRpcParams serverRpcParams = default)
{
// If it is server authoritative
// Ignore if a non-owner sent this.
if (serverRpcParams.Receive.SenderClientId != OwnerClientId)
{
if (NetworkManager.LogLevel == LogLevel.Developer)
{
NetworkLog.LogWarning($"[Owner Authoritative] Detected the a non-authoritative client is sending the server animation trigger updates. If you recently changed ownership of the {name} object, then this could be the reason.");
}
return;
}
// set the trigger locally on the server
InternalSetTrigger(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet);
m_ClientSendList.Clear();
m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds);
m_ClientSendList.Remove(NetworkManager.ServerClientId);
if (IsServerAuthoritative())
{
// The only condition where this should (be allowed to) happen is when the owner sends the server a trigger message
if (OwnerClientId == serverRpcParams.Receive.SenderClientId)
{
m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animationTriggerMessage);
}
else if (NetworkManager.LogLevel == LogLevel.Developer)
{
NetworkLog.LogWarning($"[Server Authoritative] Detected the a non-authoritative client is sending the server animation trigger updates. If you recently changed ownership of the {name} object, then this could be the reason.");
}
m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animationTriggerMessage, m_ClientRpcParams);
}
else
else if (NetworkManager.ConnectedClientsIds.Count > (IsHost ? 2 : 1))
{
// Ignore if a non-owner sent this.
if (serverRpcParams.Receive.SenderClientId != OwnerClientId)
{
if (NetworkManager.LogLevel == LogLevel.Developer)
{
NetworkLog.LogWarning($"[Owner Authoritative] Detected the a non-authoritative client is sending the server animation trigger updates. If you recently changed ownership of the {name} object, then this could be the reason.");
}
return;
}
// set the trigger locally on the server
InternalSetTrigger(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet);
// send the message to all non-authority clients excluding the server and the owner
if (NetworkManager.ConnectedClientsIds.Count > (IsHost ? 2 : 1))
{
m_ClientSendList.Clear();
m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds);
m_ClientSendList.Remove(serverRpcParams.Receive.SenderClientId);
m_ClientSendList.Remove(NetworkManager.ServerClientId);
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animationTriggerMessage, m_ClientRpcParams);
}
m_ClientSendList.Remove(serverRpcParams.Receive.SenderClientId);
m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animationTriggerMessage, m_ClientRpcParams);
}
}