Skip to content

Commit bdf229d

Browse files
fix: weighted layer support in NetworkAnimator MTT-2698 (#1765)
* fix: weighted layer support in NetworkAnimator MTT-2698 * more integration tests
1 parent d762083 commit bdf229d

File tree

6 files changed

+326
-49
lines changed

6 files changed

+326
-49
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ Additional documentation and release notes are available at [Multiplayer Documen
99
## [Unreleased]
1010

1111
### Added
12-
13-
- Added first set of tests for NetworkAnimator - parameter syncing, trigger set / reset, override network animator (#7135)
12+
- NetworkAnimator now properly synchrhonizes all animation layers as well as runtime-adjusted weighting between them (#1765)
13+
- Added first set of tests for NetworkAnimator - parameter syncing, trigger set / reset, override network animator (#1735)
1414

1515
### Changed
1616

com.unity.netcode.gameobjects/Components/NetworkAnimator.cs

Lines changed: 69 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using Unity.Collections;
22
using Unity.Collections.LowLevel.Unsafe;
3-
43
using UnityEngine;
54

65
namespace Unity.Netcode.Components
@@ -14,14 +13,19 @@ public class NetworkAnimator : NetworkBehaviour
1413
{
1514
internal struct AnimationMessage : INetworkSerializable
1615
{
17-
public int StateHash; // if non-zero, then Play() this animation, skipping transitions
16+
// state hash per layer. if non-zero, then Play() this animation, skipping transitions
17+
public int StateHash;
1818
public float NormalizedTime;
19+
public int Layer;
20+
public float Weight;
1921
public byte[] Parameters;
2022

2123
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
2224
{
2325
serializer.SerializeValue(ref StateHash);
2426
serializer.SerializeValue(ref NormalizedTime);
27+
serializer.SerializeValue(ref Layer);
28+
serializer.SerializeValue(ref Weight);
2529
serializer.SerializeValue(ref Parameters);
2630
}
2731
}
@@ -54,10 +58,9 @@ public Animator Animator
5458
// Animators only support up to 32 params
5559
public static int K_MaxAnimationParams = 32;
5660

57-
private int m_TransitionHash;
58-
59-
private int m_AnimationHash;
60-
public int AnimationHash { get => m_AnimationHash; }
61+
private int[] m_TransitionHash;
62+
private int[] m_AnimationHash;
63+
private float[] m_LayerWeights;
6164

6265
private unsafe struct AnimatorParamCache
6366
{
@@ -100,12 +103,16 @@ public override void OnNetworkSpawn()
100103
if (IsServer)
101104
{
102105
m_SendMessagesAllowed = true;
106+
int layers = m_Animator.layerCount;
107+
108+
m_TransitionHash = new int[layers];
109+
m_AnimationHash = new int[layers];
110+
m_LayerWeights = new float[layers];
103111
}
112+
104113
var parameters = m_Animator.parameters;
105114
m_CachedAnimatorParameters = new NativeArray<AnimatorParamCache>(parameters.Length, Allocator.Persistent);
106115

107-
m_AnimationHash = -1;
108-
109116
for (var i = 0; i < parameters.Length; i++)
110117
{
111118
var parameter = parameters[i];
@@ -157,66 +164,81 @@ public override void OnNetworkDespawn()
157164

158165
private void FixedUpdate()
159166
{
160-
if (!m_SendMessagesAllowed)
167+
if (!m_SendMessagesAllowed || !m_Animator || !m_Animator.enabled)
161168
{
162169
return;
163170
}
164171

165-
int stateHash;
166-
float normalizedTime;
167-
if (!CheckAnimStateChanged(out stateHash, out normalizedTime))
172+
for (int layer = 0; layer < m_Animator.layerCount; layer++)
168173
{
169-
return;
170-
}
174+
int stateHash;
175+
float normalizedTime;
176+
if (!CheckAnimStateChanged(out stateHash, out normalizedTime, layer))
177+
{
178+
continue;
179+
}
171180

172-
var animMsg = new AnimationMessage
173-
{
174-
StateHash = stateHash,
175-
NormalizedTime = normalizedTime
176-
};
181+
var animMsg = new AnimationMessage
182+
{
183+
StateHash = stateHash,
184+
NormalizedTime = normalizedTime,
185+
Layer = layer,
186+
Weight = m_LayerWeights[layer]
187+
};
177188

178-
m_ParameterWriter.Seek(0);
179-
m_ParameterWriter.Truncate();
189+
m_ParameterWriter.Seek(0);
190+
m_ParameterWriter.Truncate();
180191

181-
WriteParameters(m_ParameterWriter);
182-
animMsg.Parameters = m_ParameterWriter.ToArray();
192+
WriteParameters(m_ParameterWriter);
193+
animMsg.Parameters = m_ParameterWriter.ToArray();
183194

184-
SendAnimStateClientRpc(animMsg);
195+
SendAnimStateClientRpc(animMsg);
196+
}
185197
}
186198

187-
private bool CheckAnimStateChanged(out int stateHash, out float normalizedTime)
199+
private bool CheckAnimStateChanged(out int stateHash, out float normalizedTime, int layer)
188200
{
201+
bool shouldUpdate = false;
189202
stateHash = 0;
190203
normalizedTime = 0;
191204

192-
if (m_Animator.IsInTransition(0))
205+
float layerWeightNow = m_Animator.GetLayerWeight(layer);
206+
207+
if (!Mathf.Approximately(layerWeightNow, m_LayerWeights[layer]))
208+
{
209+
m_LayerWeights[layer] = layerWeightNow;
210+
shouldUpdate = true;
211+
}
212+
if (m_Animator.IsInTransition(layer))
193213
{
194-
AnimatorTransitionInfo tt = m_Animator.GetAnimatorTransitionInfo(0);
195-
if (tt.fullPathHash != m_TransitionHash)
214+
AnimatorTransitionInfo tt = m_Animator.GetAnimatorTransitionInfo(layer);
215+
if (tt.fullPathHash != m_TransitionHash[layer])
196216
{
197-
// first time in this transition
198-
m_TransitionHash = tt.fullPathHash;
199-
m_AnimationHash = 0;
200-
return true;
217+
// first time in this transition for this layer
218+
m_TransitionHash[layer] = tt.fullPathHash;
219+
m_AnimationHash[layer] = 0;
220+
shouldUpdate = true;
201221
}
202-
return false;
203222
}
204-
205-
AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(0);
206-
if (st.fullPathHash != m_AnimationHash)
223+
else
207224
{
208-
// first time in this animation state
209-
if (m_AnimationHash != 0)
225+
AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(layer);
226+
if (st.fullPathHash != m_AnimationHash[layer])
210227
{
211-
// came from another animation directly - from Play()
212-
stateHash = st.fullPathHash;
213-
normalizedTime = st.normalizedTime;
228+
// first time in this animation state
229+
if (m_AnimationHash[layer] != 0)
230+
{
231+
// came from another animation directly - from Play()
232+
stateHash = st.fullPathHash;
233+
normalizedTime = st.normalizedTime;
234+
}
235+
m_TransitionHash[layer] = 0;
236+
m_AnimationHash[layer] = st.fullPathHash;
237+
shouldUpdate = true;
214238
}
215-
m_TransitionHash = 0;
216-
m_AnimationHash = st.fullPathHash;
217-
return true;
218239
}
219-
return false;
240+
241+
return shouldUpdate;
220242
}
221243

222244
/* $AS TODO: Right now we are not checking for changed values this is because
@@ -311,9 +333,9 @@ private unsafe void SendAnimStateClientRpc(AnimationMessage animSnapshot, Client
311333
{
312334
if (animSnapshot.StateHash != 0)
313335
{
314-
m_AnimationHash = animSnapshot.StateHash;
315-
m_Animator.Play(animSnapshot.StateHash, 0, animSnapshot.NormalizedTime);
336+
m_Animator.Play(animSnapshot.StateHash, animSnapshot.Layer, animSnapshot.NormalizedTime);
316337
}
338+
m_Animator.SetLayerWeight(animSnapshot.Layer, animSnapshot.Weight);
317339

318340
if (animSnapshot.Parameters != null && animSnapshot.Parameters.Length != 0)
319341
{

com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/NetworkAnimatorTests.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,53 @@ public IEnumerator AnimationStateSyncTest()
129129
Assert.False(s_GloabalTimeoutHelper.TimedOut, "Client failed to sync its animation state from the server");
130130
}
131131

132+
[UnityTest]
133+
public IEnumerator AnimationLayerStateSyncTest()
134+
{
135+
int layer = 1;
136+
// check that we have started in the default state
137+
Assert.True(m_PlayerOnServerAnimator.GetCurrentAnimatorStateInfo(layer).IsName("DefaultStateLayer2"));
138+
Assert.True(m_PlayerOnClientAnimator.GetCurrentAnimatorStateInfo(layer).IsName("DefaultStateLayer2"));
139+
140+
// cause a change to the AlphaState state by setting AlphaParameter, which is
141+
// the variable bound to the transition from default to AlphaState (see the TestAnimatorController asset)
142+
m_PlayerOnServerAnimator.SetBool("Layer2AlphaParameter", true);
143+
144+
// ...and now we should be in the AlphaState having triggered the AlphaParameter
145+
yield return WaitForConditionOrTimeOut(() => m_PlayerOnServerAnimator.GetCurrentAnimatorStateInfo(layer).IsName("Layer2AlphaState"));
146+
Assert.False(s_GloabalTimeoutHelper.TimedOut, "Server failed to reach its animation state");
147+
148+
// ...and now the client should also have sync'd and arrived at the correct state
149+
yield return WaitForConditionOrTimeOut(() => m_PlayerOnClientAnimator.GetCurrentAnimatorStateInfo(layer).IsName("Layer2AlphaState"));
150+
Assert.False(s_GloabalTimeoutHelper.TimedOut, "Client failed to sync its animation state from the server");
151+
}
152+
153+
[UnityTest]
154+
public IEnumerator AnimationLayerWeightTest()
155+
{
156+
int layer = 1;
157+
float targetWeight = 0.333f;
158+
159+
// check that we have started in the default state
160+
Assert.True(Mathf.Approximately(m_PlayerOnServerAnimator.GetLayerWeight(layer), 1f));
161+
Assert.True(Mathf.Approximately(m_PlayerOnClientAnimator.GetLayerWeight(layer), 1f));
162+
163+
m_PlayerOnServerAnimator.SetLayerWeight(layer, targetWeight);
164+
165+
// ...and now we should be in the AlphaState having triggered the AlphaParameter
166+
yield return WaitForConditionOrTimeOut(() =>
167+
Mathf.Approximately(m_PlayerOnServerAnimator.GetLayerWeight(layer), targetWeight)
168+
);
169+
Assert.False(s_GloabalTimeoutHelper.TimedOut, "Server failed to reach its animation state");
170+
171+
// ...and now the client should also have sync'd and arrived at the correct state
172+
yield return WaitForConditionOrTimeOut(() =>
173+
Mathf.Approximately(m_PlayerOnClientAnimator.GetLayerWeight(layer), targetWeight)
174+
);
175+
Assert.False(s_GloabalTimeoutHelper.TimedOut, "Server failed to reach its animation state");
176+
}
177+
178+
132179
[UnityTest]
133180
public IEnumerator AnimationStateSyncTriggerTest([Values(true, false)] bool asHash)
134181
{
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
%YAML 1.1
2+
%TAG !u! tag:unity3d.com,2011:
3+
--- !u!74 &7400000
4+
AnimationClip:
5+
m_ObjectHideFlags: 0
6+
m_CorrespondingSourceObject: {fileID: 0}
7+
m_PrefabInstance: {fileID: 0}
8+
m_PrefabAsset: {fileID: 0}
9+
m_Name: Layer2Animation
10+
serializedVersion: 6
11+
m_Legacy: 0
12+
m_Compressed: 0
13+
m_UseHighQualityCurve: 1
14+
m_RotationCurves: []
15+
m_CompressedRotationCurves: []
16+
m_EulerCurves: []
17+
m_PositionCurves: []
18+
m_ScaleCurves: []
19+
m_FloatCurves: []
20+
m_PPtrCurves: []
21+
m_SampleRate: 60
22+
m_WrapMode: 0
23+
m_Bounds:
24+
m_Center: {x: 0, y: 0, z: 0}
25+
m_Extent: {x: 0, y: 0, z: 0}
26+
m_ClipBindingConstant:
27+
genericBindings: []
28+
pptrCurveMapping: []
29+
m_AnimationClipSettings:
30+
serializedVersion: 2
31+
m_AdditiveReferencePoseClip: {fileID: 0}
32+
m_AdditiveReferencePoseTime: 0
33+
m_StartTime: 0
34+
m_StopTime: 1
35+
m_OrientationOffsetY: 0
36+
m_Level: 0
37+
m_CycleOffset: 0
38+
m_HasAdditiveReferencePose: 0
39+
m_LoopTime: 0
40+
m_LoopBlend: 0
41+
m_LoopBlendOrientation: 0
42+
m_LoopBlendPositionY: 0
43+
m_LoopBlendPositionXZ: 0
44+
m_KeepOriginalOrientation: 0
45+
m_KeepOriginalPositionY: 1
46+
m_KeepOriginalPositionXZ: 0
47+
m_HeightFromFeet: 0
48+
m_Mirror: 0
49+
m_EditorCurves: []
50+
m_EulerEditorCurves: []
51+
m_HasGenericRootTransform: 0
52+
m_HasMotionFloatCurves: 0
53+
m_Events: []

com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/Layer2Animation.anim.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)