#if COM_UNITY_MODULES_ANIMATION
using System.Collections.Generic;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;
namespace Unity.Netcode.Components
{
internal class NetworkAnimatorStateChangeHandler : INetworkUpdateSystem
{
private NetworkAnimator m_NetworkAnimator;
///
/// This removes sending RPCs from within RPCs when the
/// server is forwarding updates from clients to clients
/// As well this handles newly connected client synchronization
/// of the existing Animator's state.
///
private void FlushMessages()
{
foreach (var clientId in m_ClientsToSynchronize)
{
m_NetworkAnimator.ServerSynchronizeNewPlayer(clientId);
}
m_ClientsToSynchronize.Clear();
foreach (var sendEntry in m_SendParameterUpdates)
{
m_NetworkAnimator.SendParametersUpdateClientRpc(sendEntry.ParametersUpdateMessage, sendEntry.ClientRpcParams);
}
m_SendParameterUpdates.Clear();
foreach (var sendEntry in m_SendTriggerUpdates)
{
m_NetworkAnimator.SendAnimTriggerClientRpc(sendEntry.AnimationTriggerMessage, sendEntry.ClientRpcParams);
}
m_SendTriggerUpdates.Clear();
}
///
public void NetworkUpdate(NetworkUpdateStage updateStage)
{
switch (updateStage)
{
case NetworkUpdateStage.PreUpdate:
{
// Only the server forwards messages and synchronizes players
if (m_NetworkAnimator.NetworkManager.IsServer)
{
// Flush any pending messages
FlushMessages();
}
// Everyone applies any parameters updated
foreach (var parameterUpdate in m_ProcessParameterUpdates)
{
m_NetworkAnimator.UpdateParameters(parameterUpdate);
}
m_ProcessParameterUpdates.Clear();
// Only owners check for Animator changes
if (m_NetworkAnimator.IsOwner && !m_NetworkAnimator.IsServerAuthoritative() || m_NetworkAnimator.IsServerAuthoritative() && m_NetworkAnimator.NetworkManager.IsServer)
{
m_NetworkAnimator.CheckForAnimatorChanges();
}
break;
}
}
}
///
/// Clients that need to be synchronized to the relative Animator
///
private List m_ClientsToSynchronize = new List();
///
/// When a new client is connected, they are added to the
/// m_ClientsToSynchronize list.
///
internal void SynchronizeClient(ulong clientId)
{
m_ClientsToSynchronize.Add(clientId);
}
///
/// A pending outgoing Animation update for (n) clients
///
private struct AnimationUpdate
{
public ClientRpcParams ClientRpcParams;
public NetworkAnimator.AnimationMessage AnimationMessage;
}
private List m_SendAnimationUpdates = new List();
///
/// Invoked when a server needs to forwarding an update to the animation state
///
internal void SendAnimationUpdate(NetworkAnimator.AnimationMessage animationMessage, ClientRpcParams clientRpcParams = default)
{
m_SendAnimationUpdates.Add(new AnimationUpdate() { ClientRpcParams = clientRpcParams, AnimationMessage = animationMessage });
}
private struct ParameterUpdate
{
public ClientRpcParams ClientRpcParams;
public NetworkAnimator.ParametersUpdateMessage ParametersUpdateMessage;
}
private List m_SendParameterUpdates = new List();
///
/// Invoked when a server needs to forwarding an update to the parameter state
///
internal void SendParameterUpdate(NetworkAnimator.ParametersUpdateMessage parametersUpdateMessage, ClientRpcParams clientRpcParams = default)
{
m_SendParameterUpdates.Add(new ParameterUpdate() { ClientRpcParams = clientRpcParams, ParametersUpdateMessage = parametersUpdateMessage });
}
private List m_ProcessParameterUpdates = new List();
internal void ProcessParameterUpdate(NetworkAnimator.ParametersUpdateMessage parametersUpdateMessage)
{
m_ProcessParameterUpdates.Add(parametersUpdateMessage);
}
private struct TriggerUpdate
{
public ClientRpcParams ClientRpcParams;
public NetworkAnimator.AnimationTriggerMessage AnimationTriggerMessage;
}
private List m_SendTriggerUpdates = new List();
///
/// Invoked when a server needs to forward an update to a Trigger state
///
internal void SendTriggerUpdate(NetworkAnimator.AnimationTriggerMessage animationTriggerMessage, ClientRpcParams clientRpcParams = default)
{
m_SendTriggerUpdates.Add(new TriggerUpdate() { ClientRpcParams = clientRpcParams, AnimationTriggerMessage = animationTriggerMessage });
}
internal void DeregisterUpdate()
{
NetworkUpdateLoop.UnregisterNetworkUpdate(this, NetworkUpdateStage.PreUpdate);
}
internal NetworkAnimatorStateChangeHandler(NetworkAnimator networkAnimator)
{
m_NetworkAnimator = networkAnimator;
NetworkUpdateLoop.RegisterNetworkUpdate(this, NetworkUpdateStage.PreUpdate);
}
}
///
/// NetworkAnimator enables remote synchronization of state for on network objects.
///
[AddComponentMenu("Netcode/" + nameof(NetworkAnimator))]
[RequireComponent(typeof(Animator))]
public class NetworkAnimator : NetworkBehaviour
{
internal struct AnimationMessage : INetworkSerializable
{
// state hash per layer. if non-zero, then Play() this animation, skipping transitions
internal int StateHash;
internal float NormalizedTime;
internal int Layer;
internal float Weight;
public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref StateHash);
serializer.SerializeValue(ref NormalizedTime);
serializer.SerializeValue(ref Layer);
serializer.SerializeValue(ref Weight);
}
}
internal struct ParametersUpdateMessage : INetworkSerializable
{
internal byte[] Parameters;
public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref Parameters);
}
}
internal struct AnimationTriggerMessage : INetworkSerializable
{
internal int Hash;
internal bool IsTriggerSet;
public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref Hash);
serializer.SerializeValue(ref IsTriggerSet);
}
}
[SerializeField] private Animator m_Animator;
public Animator Animator
{
get { return m_Animator; }
set
{
m_Animator = value;
}
}
internal bool IsServerAuthoritative()
{
return OnIsServerAuthoritative();
}
///
/// Override this method and return false to switch to owner authoritative mode
///
protected virtual bool OnIsServerAuthoritative()
{
return true;
}
// Animators only support up to 32 params
private const int k_MaxAnimationParams = 32;
private int[] m_TransitionHash;
private int[] m_AnimationHash;
private float[] m_LayerWeights;
private static byte[] s_EmptyArray = new byte[] { };
private NetworkAnimatorStateChangeHandler m_NetworkAnimatorStateChangeHandler;
private unsafe struct AnimatorParamCache
{
internal int Hash;
internal int Type;
internal fixed byte Value[4]; // this is a max size of 4 bytes
}
// 128 bytes per Animator
private FastBufferWriter m_ParameterWriter = new FastBufferWriter(k_MaxAnimationParams * sizeof(float), Allocator.Persistent);
private NativeArray m_CachedAnimatorParameters;
// We cache these values because UnsafeUtility.EnumToInt uses direct IL that allows a non-boxing conversion
private struct AnimationParamEnumWrapper
{
internal static readonly int AnimatorControllerParameterInt;
internal static readonly int AnimatorControllerParameterFloat;
internal static readonly int AnimatorControllerParameterBool;
internal static readonly int AnimatorControllerParameterTriggerBool;
static AnimationParamEnumWrapper()
{
AnimatorControllerParameterInt = UnsafeUtility.EnumToInt(AnimatorControllerParameterType.Int);
AnimatorControllerParameterFloat = UnsafeUtility.EnumToInt(AnimatorControllerParameterType.Float);
AnimatorControllerParameterBool = UnsafeUtility.EnumToInt(AnimatorControllerParameterType.Bool);
AnimatorControllerParameterTriggerBool = UnsafeUtility.EnumToInt(AnimatorControllerParameterType.Trigger);
}
}
private void Cleanup()
{
if (m_NetworkAnimatorStateChangeHandler != null)
{
m_NetworkAnimatorStateChangeHandler.DeregisterUpdate();
m_NetworkAnimatorStateChangeHandler = null;
}
if (IsServer)
{
NetworkManager.OnClientConnectedCallback -= OnClientConnectedCallback;
}
if (m_CachedAnimatorParameters != null && m_CachedAnimatorParameters.IsCreated)
{
m_CachedAnimatorParameters.Dispose();
}
if (m_ParameterWriter.IsInitialized)
{
m_ParameterWriter.Dispose();
}
}
public override void OnDestroy()
{
Cleanup();
base.OnDestroy();
}
private List m_ParametersToUpdate;
private List m_ClientSendList;
private ClientRpcParams m_ClientRpcParams;
public override void OnNetworkSpawn()
{
if (IsOwner || IsServer)
{
int layers = m_Animator.layerCount;
m_TransitionHash = new int[layers];
m_AnimationHash = new int[layers];
m_LayerWeights = new float[layers];
if (IsServer)
{
NetworkManager.OnClientConnectedCallback += OnClientConnectedCallback;
}
// Store off our current layer weights
for (int layer = 0; layer < m_Animator.layerCount; layer++)
{
float layerWeightNow = m_Animator.GetLayerWeight(layer);
if (layerWeightNow != m_LayerWeights[layer])
{
m_LayerWeights[layer] = layerWeightNow;
}
}
if (IsServer)
{
m_ClientSendList = new List(128);
m_ClientRpcParams = new ClientRpcParams();
m_ClientRpcParams.Send = new ClientRpcSendParams();
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
}
}
var parameters = m_Animator.parameters;
m_CachedAnimatorParameters = new NativeArray(parameters.Length, Allocator.Persistent);
m_ParametersToUpdate = new List(parameters.Length);
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),
Hash = parameter.nameHash
};
unsafe
{
switch (parameter.type)
{
case AnimatorControllerParameterType.Float:
var value = m_Animator.GetFloat(cacheParam.Hash);
UnsafeUtility.WriteArrayElement(cacheParam.Value, 0, value);
break;
case AnimatorControllerParameterType.Int:
var valueInt = m_Animator.GetInteger(cacheParam.Hash);
UnsafeUtility.WriteArrayElement(cacheParam.Value, 0, valueInt);
break;
case AnimatorControllerParameterType.Bool:
var valueBool = m_Animator.GetBool(cacheParam.Hash);
UnsafeUtility.WriteArrayElement(cacheParam.Value, 0, valueBool);
break;
default:
break;
}
}
m_CachedAnimatorParameters[i] = cacheParam;
}
m_NetworkAnimatorStateChangeHandler = new NetworkAnimatorStateChangeHandler(this);
}
public override void OnNetworkDespawn()
{
Cleanup();
}
///
/// Synchronizes newly joined players
///
internal void ServerSynchronizeNewPlayer(ulong playerId)
{
m_ClientSendList.Clear();
m_ClientSendList.Add(playerId);
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
// With synchronization we send all parameters
m_ParametersToUpdate.Clear();
for (int i = 0; i < m_CachedAnimatorParameters.Length; i++)
{
m_ParametersToUpdate.Add(i);
}
SendParametersUpdate(m_ClientRpcParams);
for (int layer = 0; layer < m_Animator.layerCount; layer++)
{
AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(layer);
var stateHash = st.fullPathHash;
var normalizedTime = st.normalizedTime;
var totalSpeed = st.speed * st.speedMultiplier;
var adjustedNormalizedMaxTime = totalSpeed > 0.0f ? 1.0f / totalSpeed : 0.0f;
// NOTE:
// When synchronizing, for now we will just complete the transition and
// synchronize the player to the next state being transitioned into
if (m_Animator.IsInTransition(layer))
{
var tt = m_Animator.GetAnimatorTransitionInfo(layer);
var nextState = m_Animator.GetNextAnimatorStateInfo(layer);
if (nextState.length > 0)
{
var nextStateTotalSpeed = nextState.speed * nextState.speedMultiplier;
var nextStateAdjustedLength = nextState.length * nextStateTotalSpeed;
// TODO: We need to get the transition curve for the target state as well as some
// reasonable RTT estimate in order to get a more precise normalized synchronization time
var transitionTime = Mathf.Min(tt.duration, tt.duration * tt.normalizedTime) * 0.5f;
normalizedTime = Mathf.Min(1.0f, transitionTime > 0.0f ? transitionTime / nextStateAdjustedLength : 0.0f);
}
else
{
normalizedTime = 0.0f;
}
stateHash = nextState.fullPathHash;
}
else
if (st.normalizedTime >= adjustedNormalizedMaxTime)
{
continue;
}
var animMsg = new AnimationMessage
{
StateHash = stateHash,
NormalizedTime = normalizedTime,
Layer = layer,
Weight = m_LayerWeights[layer]
};
// Server always send via client RPC
SendAnimStateClientRpc(animMsg, m_ClientRpcParams);
}
}
private void OnClientConnectedCallback(ulong playerId)
{
m_NetworkAnimatorStateChangeHandler.SynchronizeClient(playerId);
}
internal void CheckForAnimatorChanges()
{
if (!IsOwner && !IsServerAuthoritative() || IsServerAuthoritative() && !IsServer)
{
return;
}
if (CheckParametersChanged())
{
SendParametersUpdate();
}
if (m_Animator.runtimeAnimatorController == null)
{
return;
}
int stateHash;
float normalizedTime;
// This sends updates only if a layer change or transition is happening
for (int layer = 0; layer < m_Animator.layerCount; layer++)
{
AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(layer);
var totalSpeed = st.speed * st.speedMultiplier;
var adjustedNormalizedMaxTime = totalSpeed > 0.0f ? 1.0f / totalSpeed : 0.0f;
// determine if we have reached the end of our state time, if so we can skip
if (st.normalizedTime >= adjustedNormalizedMaxTime)
{
continue;
}
if (!CheckAnimStateChanged(out stateHash, out normalizedTime, layer))
{
continue;
}
var animMsg = new AnimationMessage
{
StateHash = stateHash,
NormalizedTime = normalizedTime,
Layer = layer,
Weight = m_LayerWeights[layer]
};
if (!IsServer && IsOwner)
{
SendAnimStateServerRpc(animMsg);
}
else
{
SendAnimStateClientRpc(animMsg);
}
}
}
private void SendParametersUpdate(ClientRpcParams clientRpcParams = default, bool sendDirect = false)
{
m_ParameterWriter.Seek(0);
m_ParameterWriter.Truncate();
WriteParameters(m_ParameterWriter, sendDirect);
var parametersMessage = new ParametersUpdateMessage
{
Parameters = m_ParameterWriter.ToArray()
};
if (!IsServer)
{
SendParametersUpdateServerRpc(parametersMessage);
}
else
{
if (sendDirect)
{
SendParametersUpdateClientRpc(parametersMessage, clientRpcParams);
}
else
{
m_NetworkAnimatorStateChangeHandler.SendParameterUpdate(parametersMessage, clientRpcParams);
}
}
}
///
/// Helper function to get the cached value
///
unsafe private T GetValue(ref AnimatorParamCache animatorParamCache)
{
T currentValue;
fixed (void* value = animatorParamCache.Value)
{
currentValue = UnsafeUtility.ReadArrayElement(value, 0);
}
return currentValue;
}
///
/// Checks if any of the Animator's parameters have changed
/// If so, it fills out m_ParametersToUpdate with the indices of the parameters
/// that have changed. Returns true if any parameters changed.
///
unsafe private bool CheckParametersChanged()
{
m_ParametersToUpdate.Clear();
for (int i = 0; i < m_CachedAnimatorParameters.Length; i++)
{
ref var cacheValue = ref UnsafeUtility.ArrayElementAsRef(m_CachedAnimatorParameters.GetUnsafePtr(), i);
var hash = cacheValue.Hash;
if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterInt)
{
var valueInt = m_Animator.GetInteger(hash);
var currentValue = GetValue(ref cacheValue);
if (currentValue != valueInt)
{
m_ParametersToUpdate.Add(i);
continue;
}
}
else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterBool)
{
var valueBool = m_Animator.GetBool(hash);
var currentValue = GetValue(ref cacheValue);
if (currentValue != valueBool)
{
m_ParametersToUpdate.Add(i);
continue;
}
}
else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterFloat)
{
var valueFloat = m_Animator.GetFloat(hash);
var currentValue = GetValue(ref cacheValue);
if (currentValue != valueFloat)
{
m_ParametersToUpdate.Add(i);
continue;
}
}
}
return m_ParametersToUpdate.Count > 0;
}
///
/// Checks if any of the Animator's states have changed
///
private unsafe 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;
}
///
/// Writes all of the Animator's parameters
/// This uses the m_ParametersToUpdate list to write out only
/// the parameters that have changed
///
private unsafe void WriteParameters(FastBufferWriter writer, bool sendCacheState)
{
// Write how many parameter entries we are going to write
BytePacker.WriteValuePacked(writer, (uint)m_ParametersToUpdate.Count);
foreach (var parameterIndex in m_ParametersToUpdate)
{
ref var cacheValue = ref UnsafeUtility.ArrayElementAsRef(m_CachedAnimatorParameters.GetUnsafePtr(), parameterIndex);
var hash = cacheValue.Hash;
BytePacker.WriteValuePacked(writer, (uint)parameterIndex);
if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterInt)
{
var valueInt = m_Animator.GetInteger(hash);
fixed (void* value = cacheValue.Value)
{
UnsafeUtility.WriteArrayElement(value, 0, valueInt);
BytePacker.WriteValuePacked(writer, (uint)valueInt);
}
}
else // Note: Triggers are treated like boolean values
if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterBool)
{
var valueBool = m_Animator.GetBool(hash);
fixed (void* value = cacheValue.Value)
{
UnsafeUtility.WriteArrayElement(value, 0, valueBool);
BytePacker.WriteValuePacked(writer, valueBool);
}
}
else
if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterFloat)
{
var valueFloat = m_Animator.GetFloat(hash);
fixed (void* value = cacheValue.Value)
{
UnsafeUtility.WriteArrayElement(value, 0, valueFloat);
BytePacker.WriteValuePacked(writer, valueFloat);
}
}
}
}
///
/// Reads all parameters that were updated and applies the values
///
private unsafe void ReadParameters(FastBufferReader reader)
{
ByteUnpacker.ReadValuePacked(reader, out uint totalParametersToRead);
var totalParametersRead = 0;
while (totalParametersRead < totalParametersToRead)
{
ByteUnpacker.ReadValuePacked(reader, out uint parameterIndex);
ref var cacheValue = ref UnsafeUtility.ArrayElementAsRef(m_CachedAnimatorParameters.GetUnsafePtr(), (int)parameterIndex);
var hash = cacheValue.Hash;
if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterInt)
{
ByteUnpacker.ReadValuePacked(reader, out uint newValue);
m_Animator.SetInteger(hash, (int)newValue);
fixed (void* value = cacheValue.Value)
{
UnsafeUtility.WriteArrayElement(value, 0, newValue);
}
}
else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterBool)
{
ByteUnpacker.ReadValuePacked(reader, out bool newBoolValue);
m_Animator.SetBool(hash, newBoolValue);
fixed (void* value = cacheValue.Value)
{
UnsafeUtility.WriteArrayElement(value, 0, newBoolValue);
}
}
else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterFloat)
{
ByteUnpacker.ReadValuePacked(reader, out float newFloatValue);
m_Animator.SetFloat(hash, newFloatValue);
fixed (void* value = cacheValue.Value)
{
UnsafeUtility.WriteArrayElement(value, 0, newFloatValue);
}
}
totalParametersRead++;
}
}
///
/// Applies the ParametersUpdateMessage state to the Animator
///
internal unsafe void UpdateParameters(ParametersUpdateMessage parametersUpdate)
{
if (parametersUpdate.Parameters != null && parametersUpdate.Parameters.Length != 0)
{
// We use a fixed value here to avoid the copy of data from the byte buffer since we own the data
fixed (byte* parameters = parametersUpdate.Parameters)
{
var reader = new FastBufferReader(parameters, Allocator.None, parametersUpdate.Parameters.Length);
ReadParameters(reader);
}
}
}
///
/// Applies the AnimationMessage state to the Animator
///
private unsafe void UpdateAnimationState(AnimationMessage animationState)
{
if (animationState.StateHash != 0)
{
m_Animator.Play(animationState.StateHash, animationState.Layer, animationState.NormalizedTime);
}
m_Animator.SetLayerWeight(animationState.Layer, animationState.Weight);
}
///
/// Server-side animator parameter update request
/// The server sets its local parameters and then forwards the message to the remaining clients
///
[ServerRpc]
private unsafe void SendParametersUpdateServerRpc(ParametersUpdateMessage parametersUpdate, ServerRpcParams serverRpcParams = default)
{
if (IsServerAuthoritative())
{
m_NetworkAnimatorStateChangeHandler.SendParameterUpdate(parametersUpdate);
}
else
{
if (serverRpcParams.Receive.SenderClientId != OwnerClientId)
{
return;
}
UpdateParameters(parametersUpdate);
if (NetworkManager.ConnectedClientsIds.Count - 2 > 0)
{
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.SendParameterUpdate(parametersUpdate, m_ClientRpcParams);
}
}
}
///
/// Updates the client's animator's parameters
///
[ClientRpc]
internal unsafe void SendParametersUpdateClientRpc(ParametersUpdateMessage parametersUpdate, ClientRpcParams clientRpcParams = default)
{
var isServerAuthoritative = IsServerAuthoritative();
if (!isServerAuthoritative && !IsOwner || isServerAuthoritative)
{
m_NetworkAnimatorStateChangeHandler.ProcessParameterUpdate(parametersUpdate);
}
}
///
/// Server-side animation state update request
/// The server sets its local state and then forwards the message to the remaining clients
///
[ServerRpc]
private unsafe void SendAnimStateServerRpc(AnimationMessage animSnapshot, ServerRpcParams serverRpcParams = default)
{
if (IsServerAuthoritative())
{
m_NetworkAnimatorStateChangeHandler.SendAnimationUpdate(animSnapshot);
}
else
{
if (serverRpcParams.Receive.SenderClientId != OwnerClientId)
{
return;
}
UpdateAnimationState(animSnapshot);
if (NetworkManager.ConnectedClientsIds.Count - 2 > 0)
{
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.SendAnimationUpdate(animSnapshot, m_ClientRpcParams);
}
}
}
///
/// Internally-called RPC client receiving function to update some animation state on a client
///
[ClientRpc]
private unsafe void SendAnimStateClientRpc(AnimationMessage animSnapshot, ClientRpcParams clientRpcParams = default)
{
var isServerAuthoritative = IsServerAuthoritative();
if (!isServerAuthoritative && !IsOwner || isServerAuthoritative)
{
UpdateAnimationState(animSnapshot);
}
}
///
/// Server-side trigger state update request
/// The server sets its local state and then forwards the message to the remaining clients
///
[ServerRpc]
private void SendAnimTriggerServerRpc(AnimationTriggerMessage animationTriggerMessage, ServerRpcParams serverRpcParams = default)
{
if (IsServerAuthoritative())
{
m_NetworkAnimatorStateChangeHandler.SendTriggerUpdate(animationTriggerMessage);
}
else
{
if (serverRpcParams.Receive.SenderClientId != OwnerClientId)
{
return;
}
// trigger the animation locally on the server...
m_Animator.SetBool(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet);
if (NetworkManager.ConnectedClientsIds.Count - 2 > 0)
{
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.SendTriggerUpdate(animationTriggerMessage, m_ClientRpcParams);
}
}
}
///
/// Internally-called RPC client receiving function to update a trigger when the server wants to forward
/// a trigger for a client to play / reset
///
/// the payload containing the trigger data to apply
/// unused
[ClientRpc]
internal void SendAnimTriggerClientRpc(AnimationTriggerMessage animationTriggerMessage, ClientRpcParams clientRpcParams = default)
{
var isServerAuthoritative = IsServerAuthoritative();
if (!isServerAuthoritative && !IsOwner || isServerAuthoritative)
{
m_Animator.SetBool(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet);
}
}
///
/// Sets the trigger for the associated animation
///
/// The string name of the trigger to activate
public void SetTrigger(string triggerName)
{
SetTrigger(Animator.StringToHash(triggerName));
}
///
/// The hash for the trigger to activate
/// sets (true) or resets (false) the trigger. The default is to set it (true).
public void SetTrigger(int hash, bool setTrigger = true)
{
var isServerAuthoritative = IsServerAuthoritative();
if (IsOwner && !isServerAuthoritative || IsServer && isServerAuthoritative)
{
var animTriggerMessage = new AnimationTriggerMessage() { Hash = hash, IsTriggerSet = setTrigger };
if (IsServer)
{
SendAnimTriggerClientRpc(animTriggerMessage);
}
else
{
SendAnimTriggerServerRpc(animTriggerMessage);
}
// trigger the animation locally on the server...
m_Animator.SetBool(hash, setTrigger);
}
}
///
/// Resets the trigger for the associated animation. See SetTrigger for more on how triggers are special
///
/// The string name of the trigger to reset
public void ResetTrigger(string triggerName)
{
ResetTrigger(Animator.StringToHash(triggerName));
}
///
/// The hash for the trigger to activate
public void ResetTrigger(int hash)
{
SetTrigger(hash, false);
}
}
}
#endif // COM_UNITY_MODULES_ANIMATION