com.unity.netcode.gameobjects@1.0.0-pre.10

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.0.0-pre.10] - 2022-06-21

### Added

- Added a new `OnTransportFailure` callback to `NetworkManager`. This callback is invoked when the manager's `NetworkTransport` encounters an unrecoverable error. Transport failures also cause the `NetworkManager` to shut down. Currently, this is only used by `UnityTransport` to signal a timeout of its connection to the Unity Relay servers. (#1994)
- Added `NetworkEvent.TransportFailure`, which can be used by implementations of `NetworkTransport` to signal to `NetworkManager` that an unrecoverable error was encountered. (#1994)
- Added test to ensure a warning occurs when nesting NetworkObjects in a NetworkPrefab (#1969)
- Added `NetworkManager.RemoveNetworkPrefab(...)` to remove a prefab from the prefabs list (#1950)

### Changed

- Updated `UnityTransport` dependency on `com.unity.transport` to 1.1.0. (#2025)
- (API Breaking) `ConnectionApprovalCallback` is no longer an `event` and will not allow more than 1 handler registered at a time. Also, `ConnectionApprovalCallback` is now a `Func<>` taking `ConnectionApprovalRequest` in and returning `ConnectionApprovalResponse` back out (#1972)

### Removed

### Fixed
- Fixed issue where dynamically spawned `NetworkObject`s could throw an exception if the scene of origin handle was zero (0) and the `NetworkObject` was already spawned. (#2017)
- Fixed issue where `NetworkObject.Observers` was not being cleared when despawned. (#2009)
- Fixed `NetworkAnimator` could not run in the server authoritative mode. (#2003)
- Fixed issue where late joining clients would get a soft synchronization error if any in-scene placed NetworkObjects were parented under another `NetworkObject`. (#1985)
- Fixed issue where `NetworkBehaviourReference` would throw a type cast exception if using `NetworkBehaviourReference.TryGet` and the component type was not found. (#1984)
- Fixed `NetworkSceneManager` was not sending scene event notifications for the currently active scene and any additively loaded scenes when loading a new scene in `LoadSceneMode.Single` mode. (#1975)
- Fixed issue where one or more clients disconnecting during a scene event would cause `LoadEventCompleted` or `UnloadEventCompleted` to wait until the `NetworkConfig.LoadSceneTimeOut` period before being triggered. (#1973)
- Fixed issues when multiple `ConnectionApprovalCallback`s were registered (#1972)
- Fixed a regression in serialization support: `FixedString`, `Vector2Int`, and `Vector3Int` types can now be used in NetworkVariables and RPCs again without requiring a `ForceNetworkSerializeByMemcpy<>` wrapper. (#1961)
- Fixed generic types that inherit from NetworkBehaviour causing crashes at compile time. (#1976)
- Fixed endless dialog boxes when adding a `NetworkBehaviour` to a `NetworkManager` or vice-versa. (#1947)
- Fixed `NetworkAnimator` issue where it was only synchronizing parameters if the layer or state changed or was transitioning between states. (#1946)
- Fixed `NetworkAnimator` issue where when it did detect a parameter had changed it would send all parameters as opposed to only the parameters that changed. (#1946)
- Fixed `NetworkAnimator` issue where it was not always disposing the `NativeArray` that is allocated when spawned. (#1946)
- Fixed `NetworkAnimator` issue where it was not taking the animation speed or state speed multiplier into consideration. (#1946)
- Fixed `NetworkAnimator` issue where it was not properly synchronizing late joining clients if they joined while `Animator` was transitioning between states. (#1946)
- Fixed `NetworkAnimator` issue where the server was not relaying changes to non-owner clients when a client was the owner. (#1946)
- Fixed issue where the `PacketLoss` metric for tools would return the packet loss over a connection lifetime instead of a single frame. (#2004)
This commit is contained in:
Unity Technologies
2022-06-21 00:00:00 +00:00
parent 5b1fc203ed
commit 0f7a30d285
62 changed files with 3763 additions and 1286 deletions

View File

@@ -1,10 +1,157 @@
#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;
/// <summary>
/// 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.
/// </summary>
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;
}
}
}
/// <summary>
/// Clients that need to be synchronized to the relative Animator
/// </summary>
private List<ulong> m_ClientsToSynchronize = new List<ulong>();
/// <summary>
/// When a new client is connected, they are added to the
/// m_ClientsToSynchronize list.
/// </summary>
internal void SynchronizeClient(ulong clientId)
{
m_ClientsToSynchronize.Add(clientId);
}
/// <summary>
/// A pending outgoing Animation update for (n) clients
/// </summary>
private struct AnimationUpdate
{
public ClientRpcParams ClientRpcParams;
public NetworkAnimator.AnimationMessage AnimationMessage;
}
private List<AnimationUpdate> m_SendAnimationUpdates = new List<AnimationUpdate>();
/// <summary>
/// Invoked when a server needs to forwarding an update to the animation state
/// </summary>
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<ParameterUpdate> m_SendParameterUpdates = new List<ParameterUpdate>();
/// <summary>
/// Invoked when a server needs to forwarding an update to the parameter state
/// </summary>
internal void SendParameterUpdate(NetworkAnimator.ParametersUpdateMessage parametersUpdateMessage, ClientRpcParams clientRpcParams = default)
{
m_SendParameterUpdates.Add(new ParameterUpdate() { ClientRpcParams = clientRpcParams, ParametersUpdateMessage = parametersUpdateMessage });
}
private List<NetworkAnimator.ParametersUpdateMessage> m_ProcessParameterUpdates = new List<NetworkAnimator.ParametersUpdateMessage>();
internal void ProcessParameterUpdate(NetworkAnimator.ParametersUpdateMessage parametersUpdateMessage)
{
m_ProcessParameterUpdates.Add(parametersUpdateMessage);
}
private struct TriggerUpdate
{
public ClientRpcParams ClientRpcParams;
public NetworkAnimator.AnimationTriggerMessage AnimationTriggerMessage;
}
private List<TriggerUpdate> m_SendTriggerUpdates = new List<TriggerUpdate>();
/// <summary>
/// Invoked when a server needs to forward an update to a Trigger state
/// </summary>
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);
}
}
/// <summary>
/// NetworkAnimator enables remote synchronization of <see cref="UnityEngine.Animator"/> state for on network objects.
/// </summary>
@@ -19,7 +166,6 @@ namespace Unity.Netcode.Components
internal float NormalizedTime;
internal int Layer;
internal float Weight;
internal byte[] Parameters;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
@@ -27,6 +173,14 @@ namespace Unity.Netcode.Components
serializer.SerializeValue(ref NormalizedTime);
serializer.SerializeValue(ref Layer);
serializer.SerializeValue(ref Weight);
}
}
internal struct ParametersUpdateMessage : INetworkSerializable
{
internal byte[] Parameters;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref Parameters);
}
}
@@ -34,12 +188,12 @@ namespace Unity.Netcode.Components
internal struct AnimationTriggerMessage : INetworkSerializable
{
internal int Hash;
internal bool Reset;
internal bool IsTriggerSet;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref Hash);
serializer.SerializeValue(ref Reset);
serializer.SerializeValue(ref IsTriggerSet);
}
}
@@ -54,7 +208,18 @@ namespace Unity.Netcode.Components
}
}
private bool m_SendMessagesAllowed = false;
internal bool IsServerAuthoritative()
{
return OnIsServerAuthoritative();
}
/// <summary>
/// Override this method and return false to switch to owner authoritative mode
/// </summary>
protected virtual bool OnIsServerAuthoritative()
{
return true;
}
// Animators only support up to 32 params
private const int k_MaxAnimationParams = 32;
@@ -62,6 +227,8 @@ namespace Unity.Netcode.Components
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
{
@@ -72,6 +239,7 @@ namespace Unity.Netcode.Components
// 128 bytes per Animator
private FastBufferWriter m_ParameterWriter = new FastBufferWriter(k_MaxAnimationParams * sizeof(float), Allocator.Persistent);
private NativeArray<AnimatorParamCache> m_CachedAnimatorParameters;
// We cache these values because UnsafeUtility.EnumToInt uses direct IL that allows a non-boxing conversion
@@ -80,40 +248,86 @@ namespace Unity.Netcode.Components
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()
{
if (m_CachedAnimatorParameters.IsCreated)
{
m_CachedAnimatorParameters.Dispose();
}
m_ParameterWriter.Dispose();
Cleanup();
base.OnDestroy();
}
private List<int> m_ParametersToUpdate;
private List<ulong> m_ClientSendList;
private ClientRpcParams m_ClientRpcParams;
public override void OnNetworkSpawn()
{
if (IsServer)
if (IsOwner || IsServer)
{
m_SendMessagesAllowed = true;
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<ulong>(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<AnimatorParamCache>(parameters.Length, Allocator.Persistent);
m_ParametersToUpdate = new List<int>(parameters.Length);
for (var i = 0; i < parameters.Length; i++)
{
var parameter = parameters[i];
@@ -142,13 +356,11 @@ namespace Unity.Netcode.Components
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;
case AnimatorControllerParameterType.Trigger:
default:
break;
}
@@ -156,24 +368,117 @@ namespace Unity.Netcode.Components
m_CachedAnimatorParameters[i] = cacheParam;
}
m_NetworkAnimatorStateChangeHandler = new NetworkAnimatorStateChangeHandler(this);
}
public override void OnNetworkDespawn()
{
m_SendMessagesAllowed = false;
Cleanup();
}
private void FixedUpdate()
/// <summary>
/// Synchronizes newly joined players
/// </summary>
internal void ServerSynchronizeNewPlayer(ulong playerId)
{
if (!m_SendMessagesAllowed || !m_Animator || !m_Animator.enabled)
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++)
{
int stateHash;
float normalizedTime;
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;
@@ -187,29 +492,120 @@ namespace Unity.Netcode.Components
Weight = m_LayerWeights[layer]
};
m_ParameterWriter.Seek(0);
m_ParameterWriter.Truncate();
WriteParameters(m_ParameterWriter);
animMsg.Parameters = m_ParameterWriter.ToArray();
SendAnimStateClientRpc(animMsg);
if (!IsServer && IsOwner)
{
SendAnimStateServerRpc(animMsg);
}
else
{
SendAnimStateClientRpc(animMsg);
}
}
}
private bool CheckAnimStateChanged(out int stateHash, out float normalizedTime, int layer)
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);
}
}
}
/// <summary>
/// Helper function to get the cached value
/// </summary>
unsafe private T GetValue<T>(ref AnimatorParamCache animatorParamCache)
{
T currentValue;
fixed (void* value = animatorParamCache.Value)
{
currentValue = UnsafeUtility.ReadArrayElement<T>(value, 0);
}
return currentValue;
}
/// <summary>
/// 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.
/// </summary>
unsafe private 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);
var hash = cacheValue.Hash;
if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterInt)
{
var valueInt = m_Animator.GetInteger(hash);
var currentValue = GetValue<int>(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<bool>(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<float>(ref cacheValue);
if (currentValue != valueFloat)
{
m_ParametersToUpdate.Add(i);
continue;
}
}
}
return m_ParametersToUpdate.Count > 0;
}
/// <summary>
/// Checks if any of the Animator's states have changed
/// </summary>
private unsafe bool CheckAnimStateChanged(out int stateHash, out float normalizedTime, int layer)
{
bool shouldUpdate = false;
stateHash = 0;
normalizedTime = 0;
float layerWeightNow = m_Animator.GetLayerWeight(layer);
if (!Mathf.Approximately(layerWeightNow, m_LayerWeights[layer]))
if (layerWeightNow != m_LayerWeights[layer])
{
m_LayerWeights[layer] = layerWeightNow;
shouldUpdate = true;
return true;
}
if (m_Animator.IsInTransition(layer))
{
AnimatorTransitionInfo tt = m_Animator.GetAnimatorTransitionInfo(layer);
@@ -218,7 +614,7 @@ namespace Unity.Netcode.Components
// first time in this transition for this layer
m_TransitionHash[layer] = tt.fullPathHash;
m_AnimationHash[layer] = 0;
shouldUpdate = true;
return true;
}
}
else
@@ -235,26 +631,26 @@ namespace Unity.Netcode.Components
}
m_TransitionHash[layer] = 0;
m_AnimationHash[layer] = st.fullPathHash;
shouldUpdate = true;
return true;
}
}
return shouldUpdate;
return false;
}
/* $AS TODO: Right now we are not checking for changed values this is because
the read side of this function doesn't have similar logic which would cause
an overflow read because it doesn't know if the value is there or not. So
there needs to be logic to track which indexes changed in order for there
to be proper value change checking. Will revist in 1.1.0.
*/
private unsafe void WriteParameters(FastBufferWriter writer)
/// <summary>
/// Writes all of the Animator's parameters
/// This uses the m_ParametersToUpdate list to write out only
/// the parameters that have changed
/// </summary>
private unsafe void WriteParameters(FastBufferWriter writer, bool sendCacheState)
{
for (int i = 0; i < m_CachedAnimatorParameters.Length; i++)
// 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<AnimatorParamCache>(m_CachedAnimatorParameters.GetUnsafePtr(), i);
ref var cacheValue = ref UnsafeUtility.ArrayElementAsRef<AnimatorParamCache>(m_CachedAnimatorParameters.GetUnsafePtr(), parameterIndex);
var hash = cacheValue.Hash;
BytePacker.WriteValuePacked(writer, (uint)parameterIndex);
if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterInt)
{
var valueInt = m_Animator.GetInteger(hash);
@@ -264,39 +660,46 @@ namespace Unity.Netcode.Components
BytePacker.WriteValuePacked(writer, (uint)valueInt);
}
}
else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterBool)
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);
writer.WriteValueSafe(valueBool);
BytePacker.WriteValuePacked(writer, valueBool);
}
}
else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterFloat)
else
if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterFloat)
{
var valueFloat = m_Animator.GetFloat(hash);
fixed (void* value = cacheValue.Value)
{
UnsafeUtility.WriteArrayElement(value, 0, valueFloat);
writer.WriteValueSafe(valueFloat);
BytePacker.WriteValuePacked(writer, valueFloat);
}
}
}
}
/// <summary>
/// Reads all parameters that were updated and applies the values
/// </summary>
private unsafe void ReadParameters(FastBufferReader reader)
{
for (int i = 0; i < m_CachedAnimatorParameters.Length; i++)
{
ref var cacheValue = ref UnsafeUtility.ArrayElementAsRef<AnimatorParamCache>(m_CachedAnimatorParameters.GetUnsafePtr(), i);
var hash = cacheValue.Hash;
ByteUnpacker.ReadValuePacked(reader, out uint totalParametersToRead);
var totalParametersRead = 0;
while (totalParametersRead < totalParametersToRead)
{
ByteUnpacker.ReadValuePacked(reader, out uint parameterIndex);
ref var cacheValue = ref UnsafeUtility.ArrayElementAsRef<AnimatorParamCache>(m_CachedAnimatorParameters.GetUnsafePtr(), (int)parameterIndex);
var hash = cacheValue.Hash;
if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterInt)
{
ByteUnpacker.ReadValuePacked(reader, out int newValue);
m_Animator.SetInteger(hash, newValue);
ByteUnpacker.ReadValuePacked(reader, out uint newValue);
m_Animator.SetInteger(hash, (int)newValue);
fixed (void* value = cacheValue.Value)
{
UnsafeUtility.WriteArrayElement(value, 0, newValue);
@@ -304,7 +707,7 @@ namespace Unity.Netcode.Components
}
else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterBool)
{
reader.ReadValueSafe(out bool newBoolValue);
ByteUnpacker.ReadValuePacked(reader, out bool newBoolValue);
m_Animator.SetBool(hash, newBoolValue);
fixed (void* value = cacheValue.Value)
{
@@ -313,42 +716,164 @@ namespace Unity.Netcode.Components
}
else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterFloat)
{
reader.ReadValueSafe(out float newFloatValue);
ByteUnpacker.ReadValuePacked(reader, out float newFloatValue);
m_Animator.SetFloat(hash, newFloatValue);
fixed (void* value = cacheValue.Value)
{
UnsafeUtility.WriteArrayElement(value, 0, newFloatValue);
}
}
totalParametersRead++;
}
}
/// <summary>
/// Internally-called RPC client receiving function to update some animation parameters on a client when
/// the server wants to update them
/// Applies the ParametersUpdateMessage state to the Animator
/// </summary>
/// <param name="animSnapshot">the payload containing the parameters to apply</param>
/// <param name="clientRpcParams">unused</param>
[ClientRpc]
private unsafe void SendAnimStateClientRpc(AnimationMessage animSnapshot, ClientRpcParams clientRpcParams = default)
internal unsafe void UpdateParameters(ParametersUpdateMessage parametersUpdate)
{
if (animSnapshot.StateHash != 0)
{
m_Animator.Play(animSnapshot.StateHash, animSnapshot.Layer, animSnapshot.NormalizedTime);
}
m_Animator.SetLayerWeight(animSnapshot.Layer, animSnapshot.Weight);
if (animSnapshot.Parameters != null && animSnapshot.Parameters.Length != 0)
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 = animSnapshot.Parameters)
fixed (byte* parameters = parametersUpdate.Parameters)
{
var reader = new FastBufferReader(parameters, Allocator.None, animSnapshot.Parameters.Length);
var reader = new FastBufferReader(parameters, Allocator.None, parametersUpdate.Parameters.Length);
ReadParameters(reader);
}
}
}
/// <summary>
/// Applies the AnimationMessage state to the Animator
/// </summary>
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);
}
/// <summary>
/// Server-side animator parameter update request
/// The server sets its local parameters and then forwards the message to the remaining clients
/// </summary>
[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);
}
}
}
/// <summary>
/// Updates the client's animator's parameters
/// </summary>
[ClientRpc]
internal unsafe void SendParametersUpdateClientRpc(ParametersUpdateMessage parametersUpdate, ClientRpcParams clientRpcParams = default)
{
var isServerAuthoritative = IsServerAuthoritative();
if (!isServerAuthoritative && !IsOwner || isServerAuthoritative)
{
m_NetworkAnimatorStateChangeHandler.ProcessParameterUpdate(parametersUpdate);
}
}
/// <summary>
/// Server-side animation state update request
/// The server sets its local state and then forwards the message to the remaining clients
/// </summary>
[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);
}
}
}
/// <summary>
/// Internally-called RPC client receiving function to update some animation state on a client
/// </summary>
[ClientRpc]
private unsafe void SendAnimStateClientRpc(AnimationMessage animSnapshot, ClientRpcParams clientRpcParams = default)
{
var isServerAuthoritative = IsServerAuthoritative();
if (!isServerAuthoritative && !IsOwner || isServerAuthoritative)
{
UpdateAnimationState(animSnapshot);
}
}
/// <summary>
/// Server-side trigger state update request
/// The server sets its local state and then forwards the message to the remaining clients
/// </summary>
[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);
}
}
}
/// <summary>
/// Internally-called RPC client receiving function to update a trigger when the server wants to forward
/// a trigger for a client to play / reset
@@ -356,24 +881,17 @@ namespace Unity.Netcode.Components
/// <param name="animSnapshot">the payload containing the trigger data to apply</param>
/// <param name="clientRpcParams">unused</param>
[ClientRpc]
private void SendAnimTriggerClientRpc(AnimationTriggerMessage animSnapshot, ClientRpcParams clientRpcParams = default)
internal void SendAnimTriggerClientRpc(AnimationTriggerMessage animationTriggerMessage, ClientRpcParams clientRpcParams = default)
{
if (animSnapshot.Reset)
var isServerAuthoritative = IsServerAuthoritative();
if (!isServerAuthoritative && !IsOwner || isServerAuthoritative)
{
m_Animator.ResetTrigger(animSnapshot.Hash);
}
else
{
m_Animator.SetTrigger(animSnapshot.Hash);
m_Animator.SetBool(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet);
}
}
/// <summary>
/// Sets the trigger for the associated animation
/// Note, triggers are special vs other kinds of parameters. For all the other parameters we watch for changes
/// in FixedUpdate and users can just set them normally off of Animator. But because triggers are transitory
/// and likely to come and go between FixedUpdate calls, we require users to set them here to guarantee us to
/// catch it...then we forward it to the Animator component
/// </summary>
/// <param name="triggerName">The string name of the trigger to activate</param>
public void SetTrigger(string triggerName)
@@ -383,31 +901,23 @@ namespace Unity.Netcode.Components
/// <inheritdoc cref="SetTrigger(string)" />
/// <param name="hash">The hash for the trigger to activate</param>
/// <param name="reset">If true, resets the trigger</param>
public void SetTrigger(int hash, bool reset = false)
/// <param name="setTrigger">sets (true) or resets (false) the trigger. The default is to set it (true).</param>
public void SetTrigger(int hash, bool setTrigger = true)
{
var animMsg = new AnimationTriggerMessage();
animMsg.Hash = hash;
animMsg.Reset = reset;
if (IsServer)
var isServerAuthoritative = IsServerAuthoritative();
if (IsOwner && !isServerAuthoritative || IsServer && isServerAuthoritative)
{
// trigger the animation locally on the server...
if (reset)
var animTriggerMessage = new AnimationTriggerMessage() { Hash = hash, IsTriggerSet = setTrigger };
if (IsServer)
{
m_Animator.ResetTrigger(hash);
SendAnimTriggerClientRpc(animTriggerMessage);
}
else
{
m_Animator.SetTrigger(hash);
SendAnimTriggerServerRpc(animTriggerMessage);
}
// ...then tell all the clients to do the same
SendAnimTriggerClientRpc(animMsg);
}
else
{
Debug.LogWarning("Trying to call NetworkAnimator.SetTrigger on a client...ignoring");
// trigger the animation locally on the server...
m_Animator.SetBool(hash, setTrigger);
}
}
@@ -424,7 +934,7 @@ namespace Unity.Netcode.Components
/// <param name="hash">The hash for the trigger to activate</param>
public void ResetTrigger(int hash)
{
SetTrigger(hash, true);
SetTrigger(hash, false);
}
}
}