From 4ebfb5f59b74277c9db97a6f27445c4e7e6d46f0 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 5 Apr 2023 09:57:52 -0500 Subject: [PATCH 1/8] fix: NetworkAnimator not detecting and synchronizing cross fade initiated transitions (#2481) * fix For client authoritative, the server now actually sends any pending animation updates to any non-authoritative clients. Trigger not updating properly on server when server authoritative but was the owner sending the trigger update. Improve detection of the type of animation state change. This fixes the issue where cross fades were not being handled by NetworkAnimator. This is a first pass fix and the serialization could use an overhaul to reduce bandwidth costs. * update Making sure we don't toggle between transition and crossfade. Packing values for AnimationState Updating the manual test used to get cross fading to AnimationStates without transitions working. Making the serialization of the two bools one byte instead of two bytes. * test Adding some cross fade tests to the NetworkAnimatorTests. Also fixed an issue with the coroutineRunner being destroyed before the IntegrationTestSceneHandler was. This is the final test for cross fade initiated transitions and whether it is detected and synchronized or not. --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 + .../Components/NetworkAnimator.cs | 334 +++++++++--------- .../Runtime/IntegrationTestSceneHandler.cs | 6 +- .../AnimatedCubeController.cs | 23 ++ .../CrossFadeTransitionDetect.cs | 89 +++++ .../CrossFadeTransitionDetect.cs.meta | 11 + .../CubeAnimatorController.controller | 65 ++++ .../Runtime/Animation/AnimatorTestHelper.cs | 7 + .../Runtime/Animation/NetworkAnimatorTests.cs | 72 +++- 9 files changed, 437 insertions(+), 172 deletions(-) create mode 100644 testproject/Assets/Tests/Manual/NetworkAnimatorTests/CrossFadeTransitionDetect.cs create mode 100644 testproject/Assets/Tests/Manual/NetworkAnimatorTests/CrossFadeTransitionDetect.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index cab4d32e55..fb4714179e 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -41,6 +41,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- 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 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) diff --git a/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs index 8fee157c17..d3799ea4c7 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs @@ -23,6 +23,13 @@ internal class NetworkAnimatorStateChangeHandler : INetworkUpdateSystem /// 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 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) 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 @@ internal NetworkAnimatorStateChangeHandler(NetworkAnimator networkAnimator) [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; @@ -317,9 +326,19 @@ internal struct AnimationState : INetworkSerializable 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; @@ -329,65 +348,46 @@ internal struct AnimationState : INetworkSerializable 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); } } } @@ -777,11 +777,97 @@ protected override void OnSynchronize(ref BufferSerializer serializer) 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); + } + } + } + + /// + /// Checks for animation state changes in: + /// -Layer weights + /// -Cross fades + /// -Transitions + /// -Layer AnimationStates + /// + 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++; } } @@ -794,11 +880,6 @@ protected override void OnSynchronize(ref BufferSerializer serializer) /// internal void CheckForAnimatorChanges() { - if (!IsSpawned || (!IsOwner && !IsServerAuthoritative()) || (IsServerAuthoritative() && !IsServer)) - { - return; - } - if (CheckParametersChanged()) { SendParametersUpdate(); @@ -813,9 +894,6 @@ internal void CheckForAnimatorChanges() return; } - int stateHash; - float normalizedTime; - // Reset the dirty count before checking for AnimationState updates m_AnimationMessage.IsDirtyCount = 0; @@ -825,26 +903,7 @@ internal void CheckForAnimatorChanges() 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 @@ -959,52 +1018,6 @@ private unsafe bool CheckParametersChanged() return m_ParametersToUpdate.Count > 0; } - /// - /// Checks if any of the Animator's states have changed - /// - 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; - } - /// /// Writes all of the Animator's parameters /// This uses the m_ParametersToUpdate list to write out only @@ -1128,14 +1141,14 @@ internal void UpdateAnimationState(AnimationState animationState) } // 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 @@ -1168,9 +1181,14 @@ internal void UpdateAnimationState(AnimationState animationState) 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); } @@ -1255,23 +1273,11 @@ private unsafe void SendAnimStateServerRpc(AnimationMessage animationMessage, Se } } - internal void HandleAnimStateUpdate(ref AnimationMessage animationMessage) - { - var isServerAuthoritative = IsServerAuthoritative(); - if (!isServerAuthoritative && !IsOwner || isServerAuthoritative) - { - foreach (var animationState in animationMessage.AnimationStates) - { - UpdateAnimationState(animationState); - } - } - } - /// /// Internally-called RPC client receiving function to update some animation state on a client /// [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) @@ -1282,7 +1288,10 @@ private unsafe void SendAnimStateClientRpc(AnimationMessage animationMessage, Cl } return; } - HandleAnimStateUpdate(ref animationMessage); + foreach (var animationState in animationMessage.AnimationStates) + { + UpdateAnimationState(animationState); + } } /// @@ -1292,44 +1301,31 @@ private unsafe void SendAnimStateClientRpc(AnimationMessage animationMessage, Cl [ServerRpc] internal void SendAnimTriggerServerRpc(AnimationTriggerMessage animationTriggerMessage, ServerRpcParams serverRpcParams = default) { - // If it is server authoritative - if (IsServerAuthoritative()) + // Ignore if a non-owner sent this. + if (serverRpcParams.Receive.SenderClientId != OwnerClientId) { - // 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) + 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."); + 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; } - else - { - // 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); + // 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.Clear(); + m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds); + m_ClientSendList.Remove(NetworkManager.ServerClientId); + + if (IsServerAuthoritative()) + { + m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animationTriggerMessage, m_ClientRpcParams); + } + else if (NetworkManager.ConnectedClientsIds.Count > (IsHost ? 2 : 1)) + { + m_ClientSendList.Remove(serverRpcParams.Receive.SenderClientId); + m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animationTriggerMessage, m_ClientRpcParams); } } diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs index 2bcbf8dcb3..c5b559587e 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs @@ -941,7 +941,11 @@ public void Dispose() } } QueuedSceneJobs.Clear(); - Object.Destroy(CoroutineRunner.gameObject); + if (CoroutineRunner != null && CoroutineRunner.gameObject != null) + { + Object.Destroy(CoroutineRunner.gameObject); + } + } } } diff --git a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimatedCubeController.cs b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimatedCubeController.cs index 8a784df0aa..badeb189ff 100644 --- a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimatedCubeController.cs +++ b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimatedCubeController.cs @@ -189,6 +189,24 @@ private float GetLayerWeight(int layer) return m_Animator.GetLayerWeight(layer); } + [ServerRpc] + private void TestCrossFadeServerRpc() + { + m_Animator.CrossFade("CrossFadeState", 0.25f, 0); + } + + private void TestCrossFade() + { + if (!IsServer && m_IsServerAuthoritative) + { + TestCrossFadeServerRpc(); + } + else + { + m_Animator.CrossFade("CrossFadeState", 0.25f, 0); + } + } + private void LateUpdate() { @@ -209,6 +227,11 @@ private void LateUpdate() DisplayTestIntValueIfChanged(); + if (Input.GetKeyDown(KeyCode.G)) + { + TestCrossFade(); + } + // Rotates the cube if (Input.GetKeyDown(KeyCode.C)) { diff --git a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CrossFadeTransitionDetect.cs b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CrossFadeTransitionDetect.cs new file mode 100644 index 0000000000..808fb5689d --- /dev/null +++ b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CrossFadeTransitionDetect.cs @@ -0,0 +1,89 @@ +using System.Collections.Generic; +using Unity.Netcode; +using UnityEngine; + +/// +/// This StateMachineBehaviour is used to detect an initiated transition +/// for integration test purposes. +/// +public class CrossFadeTransitionDetect : StateMachineBehaviour +{ + public static Dictionary> StatesEntered = new Dictionary>(); + public static bool IsVerboseDebug; + + public static string CurrentTargetStateName { get; private set; } + public static int CurrentTargetStateHash { get; private set; } + + public static List ClientIds = new List(); + + public static void ResetTest() + { + ClientIds.Clear(); + StatesEntered.Clear(); + IsVerboseDebug = false; + } + + private void Log(string logMessage) + { + if (!IsVerboseDebug) + { + return; + } + Debug.Log($"[CrossFadeDetect] {logMessage}"); + } + + public static bool AllClientsTransitioned() + { + foreach (var clientId in ClientIds) + { + if (!StatesEntered.ContainsKey(clientId)) + { + return false; + } + + if (!StatesEntered[clientId].ContainsKey(CurrentTargetStateHash)) + { + return false; + } + } + return true; + } + + public static void SetTargetAnimationState(string animationStateName) + { + CurrentTargetStateName = animationStateName; + CurrentTargetStateHash = Animator.StringToHash(animationStateName); + } + + public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) + { + if (stateInfo.shortNameHash != CurrentTargetStateHash) + { + Log($"[Ignoring State][Layer-{layerIndex}] Incoming: ({stateInfo.fullPathHash}) | Targeting: ({CurrentTargetStateHash})"); + return; + } + + var networkObject = animator.GetComponent(); + if (networkObject == null || networkObject.NetworkManager == null || !networkObject.IsSpawned) + { + return; + } + + var clientId = networkObject.NetworkManager.LocalClientId; + if (!StatesEntered.ContainsKey(clientId)) + { + StatesEntered.Add(clientId, new Dictionary()); + } + + if (!StatesEntered[clientId].ContainsKey(stateInfo.shortNameHash)) + { + StatesEntered[clientId].Add(stateInfo.shortNameHash, stateInfo); + } + else + { + StatesEntered[clientId][stateInfo.shortNameHash] = stateInfo; + } + + Log($"[{layerIndex}][STATE-ENTER][{clientId}] {networkObject.NetworkManager.name} entered state {stateInfo.shortNameHash}!"); + } +} diff --git a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CrossFadeTransitionDetect.cs.meta b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CrossFadeTransitionDetect.cs.meta new file mode 100644 index 0000000000..6da43d9a7c --- /dev/null +++ b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CrossFadeTransitionDetect.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 53314cafa8e073c40b0b35dd25485c42 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CubeAnimatorController.controller b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CubeAnimatorController.controller index 27d6b21286..3434711a59 100644 --- a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CubeAnimatorController.controller +++ b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CubeAnimatorController.controller @@ -188,6 +188,34 @@ AnimatorState: m_MirrorParameter: m_CycleOffsetParameter: m_TimeParameter: +--- !u!1102 &-7324519211837832008 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: CrossFadeState + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: + - {fileID: -5899436739107315318} + m_StateMachineBehaviours: + - {fileID: 8360333217518347423} + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 7400000, guid: bd13c1363af7aaf4db0ffb085ac89d77, type: 2} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: --- !u!1101 &-6396453490711135124 AnimatorStateTransition: m_ObjectHideFlags: 1 @@ -238,6 +266,28 @@ AnimatorStateTransition: m_InterruptionSource: 0 m_OrderedInterruption: 1 m_CanTransitionToSelf: 1 +--- !u!1101 &-5899436739107315318 +AnimatorStateTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: [] + m_DstStateMachine: {fileID: 0} + m_DstState: {fileID: -1676030328622575462} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 3 + m_TransitionDuration: 0.25 + m_TransitionOffset: 0 + m_ExitTime: 0.75 + m_HasExitTime: 1 + m_HasFixedDuration: 1 + m_InterruptionSource: 0 + m_OrderedInterruption: 1 + m_CanTransitionToSelf: 1 --- !u!1102 &-5552815716159021554 AnimatorState: serializedVersion: 6 @@ -629,6 +679,9 @@ AnimatorStateMachine: - serializedVersion: 1 m_State: {fileID: -1603678049383302394} m_Position: {x: 440, y: 190, z: 0} + - serializedVersion: 1 + m_State: {fileID: -7324519211837832008} + m_Position: {x: 570, y: -140, z: 0} m_ChildStateMachines: [] m_AnyStateTransitions: [] m_EntryTransitions: [] @@ -1266,3 +1319,15 @@ AnimatorStateMachine: m_ExitPosition: {x: 800, y: 120, z: 0} m_ParentStateMachinePosition: {x: 800, y: 20, z: 0} m_DefaultState: {fileID: 5205197960406981613} +--- !u!114 &8360333217518347423 +MonoBehaviour: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 53314cafa8e073c40b0b35dd25485c42, type: 3} + m_Name: + m_EditorClassIdentifier: diff --git a/testproject/Assets/Tests/Runtime/Animation/AnimatorTestHelper.cs b/testproject/Assets/Tests/Runtime/Animation/AnimatorTestHelper.cs index b8686ea32e..e8130c68b4 100644 --- a/testproject/Assets/Tests/Runtime/Animation/AnimatorTestHelper.cs +++ b/testproject/Assets/Tests/Runtime/Animation/AnimatorTestHelper.cs @@ -122,6 +122,13 @@ public void SetTrigger(string name = "TestTrigger", bool monitorTrigger = false) } } + public const string TargetCrossFadeState = "CrossFadeState"; + + public void TestCrossFade() + { + m_Animator.CrossFade(TargetCrossFadeState, 0.25f, 0); + } + public void SetBool(string name, bool valueToSet) { m_Animator.SetBool(name, valueToSet); diff --git a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs index 56c8f6b4d2..14e299b443 100644 --- a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs +++ b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs @@ -257,6 +257,76 @@ private bool WaitForClientsToInitialize() return true; } + private bool AllClientsTransitioningAnyState() + { + foreach (var networkManager in m_ClientNetworkManagers) + { + var clientId = networkManager.LocalClientId; + if (!AnimatorTestHelper.ClientSideInstances.ContainsKey(clientId)) + { + return false; + } + var animator = AnimatorTestHelper.ClientSideInstances[clientId].GetComponent(); + if (!animator.isInitialized) + { + return false; + } + var transitionInfo = animator.GetAnimatorTransitionInfo(0); + if (!transitionInfo.anyState) + { + return false; + } + VerboseDebug($"{networkManager.name} transitioning from AnyState or CrossFade."); + } + return true; + } + + /// + /// Verifies that cross fading is synchronized with currently connected clients + /// + [UnityTest] + public IEnumerator CrossFadeTransitionTests([Values] OwnerShipMode ownerShipMode, [Values] AuthoritativeMode authoritativeMode) + { + CrossFadeTransitionDetect.ResetTest(); + CrossFadeTransitionDetect.SetTargetAnimationState(AnimatorTestHelper.TargetCrossFadeState); + VerboseDebug($" ++++++++++++++++++ Cross Fade Transition Test [{ownerShipMode}] Starting ++++++++++++++++++ "); + CrossFadeTransitionDetect.IsVerboseDebug = m_EnableVerboseDebug; + + // Spawn our test animator object + var objectInstance = SpawnPrefab(ownerShipMode == OwnerShipMode.ClientOwner, authoritativeMode); + + // Wait for it to spawn server-side + var success = WaitForConditionOrTimeOutWithTimeTravel(() => AnimatorTestHelper.ServerSideInstance != null); + Assert.True(success, $"Timed out waiting for the server-side instance of {GetNetworkAnimatorName(authoritativeMode)} to be spawned!"); + + // Wait for it to spawn client-side + success = WaitForConditionOrTimeOutWithTimeTravel(WaitForClientsToInitialize); + Assert.True(success, $"Timed out waiting for the client-side instance of {GetNetworkAnimatorName(authoritativeMode)} to be spawned!"); + var animatorTestHelper = ownerShipMode == OwnerShipMode.ClientOwner ? AnimatorTestHelper.ClientSideInstances[m_ClientNetworkManagers[0].LocalClientId] : AnimatorTestHelper.ServerSideInstance; + var layerCount = animatorTestHelper.GetAnimator().layerCount; + + var animationStateCount = animatorTestHelper.GetAnimatorStateCount(); + Assert.True(layerCount == animationStateCount, $"AnimationState count {animationStateCount} does not equal the layer count {layerCount}!"); + + if (authoritativeMode == AuthoritativeMode.ServerAuth) + { + animatorTestHelper = AnimatorTestHelper.ServerSideInstance; + } + + CrossFadeTransitionDetect.ClientIds.Add(m_ServerNetworkManager.LocalClientId); + foreach (var client in m_ClientNetworkManagers) + { + CrossFadeTransitionDetect.ClientIds.Add(client.LocalClientId); + } + + animatorTestHelper.TestCrossFade(); + + // Verify the host and all clients performed a cross fade transition + yield return WaitForConditionOrTimeOut(CrossFadeTransitionDetect.AllClientsTransitioned); + AssertOnTimeout($"Timed out waiting for all clients to transition from synchronized cross fade!"); + } + + /// /// Verifies that triggers are synchronized with currently connected clients /// @@ -331,8 +401,6 @@ public IEnumerator TriggerUpdateTests([Values] OwnerShipMode ownerShipMode, [Val // Verify we only entered each state once success = WaitForConditionOrTimeOutWithTimeTravel(() => CheckStateEnterCount.AllStatesEnteredMatch(clientIdList)); Assert.True(success, $"Timed out waiting for all states entered to match!"); - // Since the com.unity.netcode.components does not allow test project to access its internals - // during runtime, this is only used when running test runner from within the editor // Now, update some states for several seconds to assure the AnimationState count does not grow var waitForSeconds = new WaitForSeconds(0.25f); From ea85f1503a988e42340bb3d26a7aa8e89b1a8f0a Mon Sep 17 00:00:00 2001 From: Simon Lemay Date: Thu, 6 Apr 2023 12:27:28 -0400 Subject: [PATCH 2/8] docs: Mention that PEM format includes the begin/end markers (#2491) --- .../Runtime/Transports/UTP/UnityTransport.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index 95f2b6624c..c40376dff8 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -1432,6 +1432,10 @@ private void ConfigureSimulatorForUtp1() private string m_ClientCaCertificate; /// Set the server parameters for encryption. + /// + /// The public certificate and private key are expected to be in the PEM format, including + /// the begin/end markers like -----BEGIN CERTIFICATE-----. + /// /// Public certificate for the server (PEM format). /// Private key for the server (PEM format). public void SetServerSecrets(string serverCertificate, string serverPrivateKey) @@ -1442,9 +1446,15 @@ public void SetServerSecrets(string serverCertificate, string serverPrivateKey) /// Set the client parameters for encryption. /// + /// /// If the CA certificate is not provided, validation will be done against the OS/browser /// certificate store. This is what you'd want if using certificates from a known provider. /// For self-signed certificates, the CA certificate needs to be provided. + /// + /// + /// The CA certificate (if provided) is expected to be in the PEM format, including the + /// begin/end markers like -----BEGIN CERTIFICATE-----. + /// /// /// Common name of the server (typically hostname). /// CA certificate used to validate the server's authenticity. From b9f9cebfcd135ae73a7d07309f57c14a2b424022 Mon Sep 17 00:00:00 2001 From: Kitty Draper <284434+ShadauxCat@users.noreply.github.com> Date: Thu, 6 Apr 2023 13:05:33 -0500 Subject: [PATCH 3/8] chore: release 1.3.1 merge to develop (#2490) --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 +- com.unity.netcode.gameobjects/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index fb4714179e..585a59d408 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -53,7 +53,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - 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) - Fixed float NetworkVariables not being rendered properly in the inspector of NetworkObjects. (#2441) -## [1.3.0] - 2023-03-20 +## [1.3.1] - 2023-03-27 ### Added diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index a6525aeaa6..79627fe965 100644 --- a/com.unity.netcode.gameobjects/package.json +++ b/com.unity.netcode.gameobjects/package.json @@ -2,7 +2,7 @@ "name": "com.unity.netcode.gameobjects", "displayName": "Netcode for GameObjects", "description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.", - "version": "1.3.0", + "version": "1.3.1", "unity": "2020.3", "dependencies": { "com.unity.nuget.mono-cecil": "1.10.1", From 0096ba3ac8641edb386d672073bac221c4c189b5 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity <73188597+NoelStephensUnity@users.noreply.github.com> Date: Thu, 6 Apr 2023 13:28:05 -0500 Subject: [PATCH 4/8] fix Actually pass the m_ClientRpcParams to avoid the host sending messages to itself. --- com.unity.netcode.gameobjects/Components/NetworkAnimator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs index d3799ea4c7..64f6017da8 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs @@ -920,7 +920,7 @@ internal void CheckForAnimatorChanges() m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds); m_ClientSendList.Remove(NetworkManager.LocalClientId); m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList; - SendAnimStateClientRpc(m_AnimationMessage); + SendAnimStateClientRpc(m_AnimationMessage, m_ClientRpcParams); } } } From a005e80fe2e9a133abae71db55813c4ca126e282 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity <73188597+NoelStephensUnity@users.noreply.github.com> Date: Thu, 6 Apr 2023 13:30:06 -0500 Subject: [PATCH 5/8] update updating the changelog. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 585a59d408..9d408f7196 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -41,6 +41,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- 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. - 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 an issue where Named Message Handlers could remove themselves causing an exception when the metrics tried to access the name of the message.(#2426) From f3feb6b4426bb3b1f03ba915e6c34a2eb81d1652 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity <73188597+NoelStephensUnity@users.noreply.github.com> Date: Thu, 6 Apr 2023 15:04:37 -0500 Subject: [PATCH 6/8] update adjusting changelog entry order. --- com.unity.netcode.gameobjects/CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 32e62ff58c..2d31e2d1bf 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,6 +10,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- 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 a way to access the GlobalObjectIdHash via PrefabIdHash for use in the Connection Approval Callback. (#2437) - 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) @@ -27,17 +29,15 @@ Additional documentation and release notes are available at [Multiplayer Documen - 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) -- 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) ### 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) -- 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) ### Fixed From 6528f5de4858d7515f72dc3789e986fba7f64315 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity <73188597+NoelStephensUnity@users.noreply.github.com> Date: Thu, 6 Apr 2023 15:07:35 -0500 Subject: [PATCH 7/8] update missed one entry. --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 2d31e2d1bf..e4e0884225 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -44,6 +44,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - 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) @@ -52,7 +53,6 @@ Additional documentation and release notes are available at [Multiplayer Documen - 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) -- Fixed float NetworkVariables not being rendered properly in the inspector of NetworkObjects. (#2441) ## [1.3.1] - 2023-03-27 From d0fa155fbfff501201782f4c67f726dc7330aa25 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity <73188597+NoelStephensUnity@users.noreply.github.com> Date: Thu, 6 Apr 2023 15:09:14 -0500 Subject: [PATCH 8/8] update argg! One more --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index e4e0884225..dc03775601 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,9 +10,9 @@ Additional documentation and release notes are available at [Multiplayer Documen ### 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 a way to access the GlobalObjectIdHash via PrefabIdHash for use in the Connection Approval Callback. (#2437) - 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)