Skip to content

Commit 490fb93

Browse files
fix: networkanimator only updates observers backport (#3058)
* fix only send animation updates to observers. * test validates that NetworkAnimator only sends animation updates to observers. * update Added changelog entry
1 parent 39818f2 commit 490fb93

File tree

3 files changed

+130
-10
lines changed

3 files changed

+130
-10
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
1515

1616
### Fixed
1717

18+
- Fixed issue where `NetworkAnimator` would send updates to non-observer clients. (#3058)
1819
- Fixed issue where an exception could occur when receiving a universal RPC for a `NetworkObject` that has been despawned. (#3055)
1920
- Fixed issue where setting a prefab hash value during connection approval but not having a player prefab assigned could cause an exception when spawning a player. (#3046)
2021
- Fixed issue where collections v2.2.x was not supported when using UTP v2.2.x within Unity v2022.3. (#3033)

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

+32-10
Original file line numberDiff line numberDiff line change
@@ -924,8 +924,14 @@ internal void CheckForAnimatorChanges()
924924
{
925925
// Just notify all remote clients and not the local server
926926
m_ClientSendList.Clear();
927-
m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds);
928-
m_ClientSendList.Remove(NetworkManager.LocalClientId);
927+
foreach (var clientId in NetworkManager.ConnectedClientsIds)
928+
{
929+
if (clientId == NetworkManager.LocalClientId || !NetworkObject.Observers.Contains(clientId))
930+
{
931+
continue;
932+
}
933+
m_ClientSendList.Add(clientId);
934+
}
929935
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
930936
SendAnimStateClientRpc(m_AnimationMessage, m_ClientRpcParams);
931937
}
@@ -1223,9 +1229,14 @@ private unsafe void SendParametersUpdateServerRpc(ParametersUpdateMessage parame
12231229
if (NetworkManager.ConnectedClientsIds.Count > (IsHost ? 2 : 1))
12241230
{
12251231
m_ClientSendList.Clear();
1226-
m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds);
1227-
m_ClientSendList.Remove(serverRpcParams.Receive.SenderClientId);
1228-
m_ClientSendList.Remove(NetworkManager.ServerClientId);
1232+
foreach (var clientId in NetworkManager.ConnectedClientsIds)
1233+
{
1234+
if (clientId == serverRpcParams.Receive.SenderClientId || clientId == NetworkManager.ServerClientId || !NetworkObject.Observers.Contains(clientId))
1235+
{
1236+
continue;
1237+
}
1238+
m_ClientSendList.Add(clientId);
1239+
}
12291240
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
12301241
m_NetworkAnimatorStateChangeHandler.SendParameterUpdate(parametersUpdate, m_ClientRpcParams);
12311242
}
@@ -1271,9 +1282,14 @@ private unsafe void SendAnimStateServerRpc(AnimationMessage animationMessage, Se
12711282
if (NetworkManager.ConnectedClientsIds.Count > (IsHost ? 2 : 1))
12721283
{
12731284
m_ClientSendList.Clear();
1274-
m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds);
1275-
m_ClientSendList.Remove(serverRpcParams.Receive.SenderClientId);
1276-
m_ClientSendList.Remove(NetworkManager.ServerClientId);
1285+
foreach (var clientId in NetworkManager.ConnectedClientsIds)
1286+
{
1287+
if (clientId == serverRpcParams.Receive.SenderClientId || clientId == NetworkManager.ServerClientId || !NetworkObject.Observers.Contains(clientId))
1288+
{
1289+
continue;
1290+
}
1291+
m_ClientSendList.Add(clientId);
1292+
}
12771293
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
12781294
m_NetworkAnimatorStateChangeHandler.SendAnimationUpdate(animationMessage, m_ClientRpcParams);
12791295
}
@@ -1322,8 +1338,14 @@ internal void SendAnimTriggerServerRpc(AnimationTriggerMessage animationTriggerM
13221338
InternalSetTrigger(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet);
13231339

13241340
m_ClientSendList.Clear();
1325-
m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds);
1326-
m_ClientSendList.Remove(NetworkManager.ServerClientId);
1341+
foreach (var clientId in NetworkManager.ConnectedClientsIds)
1342+
{
1343+
if (clientId == NetworkManager.ServerClientId || !NetworkObject.Observers.Contains(clientId))
1344+
{
1345+
continue;
1346+
}
1347+
m_ClientSendList.Add(clientId);
1348+
}
13271349

13281350
if (IsServerAuthoritative())
13291351
{

testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs

+97
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,103 @@ public IEnumerator CrossFadeTransitionTests([Values] OwnerShipMode ownerShipMode
400400
AssertOnTimeout($"Timed out waiting for all clients to transition from synchronized cross fade!");
401401
}
402402

403+
private bool AllTriggersDetectedOnObserversOnly(OwnerShipMode ownerShipMode, ulong nonObserverId)
404+
{
405+
if (ownerShipMode == OwnerShipMode.ClientOwner)
406+
{
407+
if (!TriggerTest.ClientsThatTriggered.Contains(m_ServerNetworkManager.LocalClientId))
408+
{
409+
return false;
410+
}
411+
}
412+
413+
foreach (var animatorTestHelper in AnimatorTestHelper.ClientSideInstances)
414+
{
415+
var currentClientId = animatorTestHelper.Value.NetworkManager.LocalClientId;
416+
if (currentClientId == nonObserverId || (ownerShipMode == OwnerShipMode.ClientOwner && currentClientId == animatorTestHelper.Value.OwnerClientId))
417+
{
418+
continue;
419+
}
420+
421+
if (!TriggerTest.ClientsThatTriggered.Contains(currentClientId))
422+
{
423+
return false;
424+
}
425+
}
426+
427+
// Should return false always
428+
return !TriggerTest.ClientsThatTriggered.Contains(nonObserverId);
429+
}
430+
431+
private bool AllObserversSameLayerWeight(OwnerShipMode ownerShipMode, int layer, float targetWeight, ulong nonObserverId)
432+
{
433+
434+
if (ownerShipMode == OwnerShipMode.ClientOwner)
435+
{
436+
if (AnimatorTestHelper.ServerSideInstance.GetLayerWeight(layer) != targetWeight)
437+
{
438+
return false;
439+
}
440+
}
441+
442+
foreach (var animatorTestHelper in AnimatorTestHelper.ClientSideInstances)
443+
{
444+
var currentClientId = animatorTestHelper.Value.NetworkManager.LocalClientId;
445+
if (ownerShipMode == OwnerShipMode.ClientOwner && animatorTestHelper.Value.OwnerClientId == currentClientId)
446+
{
447+
continue;
448+
}
449+
if (currentClientId == nonObserverId)
450+
{
451+
if (animatorTestHelper.Value.GetLayerWeight(layer) == targetWeight)
452+
{
453+
return false;
454+
}
455+
}
456+
else
457+
if (animatorTestHelper.Value.GetLayerWeight(layer) != targetWeight)
458+
{
459+
return false;
460+
}
461+
}
462+
return true;
463+
}
464+
465+
[UnityTest]
466+
public IEnumerator OnlyObserversAnimateTest([Values] OwnerShipMode ownerShipMode, [Values(AuthoritativeMode.ServerAuth, AuthoritativeMode.OwnerAuth)] AuthoritativeMode authoritativeMode)
467+
{
468+
// Spawn our test animator object
469+
var objectInstance = SpawnPrefab(ownerShipMode == OwnerShipMode.ClientOwner, authoritativeMode);
470+
var networkObject = objectInstance.GetComponent<NetworkObject>();
471+
// Wait for it to spawn server-side
472+
var success = WaitForConditionOrTimeOutWithTimeTravel(() => AnimatorTestHelper.ServerSideInstance != null);
473+
Assert.True(success, $"Timed out waiting for the server-side instance of {GetNetworkAnimatorName(authoritativeMode)} to be spawned!");
474+
475+
// Wait for it to spawn client-side
476+
success = WaitForConditionOrTimeOutWithTimeTravel(WaitForClientsToInitialize);
477+
Assert.True(success, $"Timed out waiting for the server-side instance of {GetNetworkAnimatorName(authoritativeMode)} to be spawned!");
478+
479+
var animatorTestHelper = ownerShipMode == OwnerShipMode.ClientOwner ? AnimatorTestHelper.ClientSideInstances[m_ClientNetworkManagers[0].LocalClientId] : AnimatorTestHelper.ServerSideInstance;
480+
481+
networkObject.NetworkHide(m_ClientNetworkManagers[1].LocalClientId);
482+
483+
yield return WaitForConditionOrTimeOut(() => !m_ClientNetworkManagers[1].SpawnManager.SpawnedObjects.ContainsKey(networkObject.NetworkObjectId));
484+
AssertOnTimeout($"Client-{m_ClientNetworkManagers[1].LocalClientId} timed out waiting to hide {networkObject.name}!");
485+
486+
if (authoritativeMode == AuthoritativeMode.ServerAuth)
487+
{
488+
animatorTestHelper = AnimatorTestHelper.ServerSideInstance;
489+
}
490+
animatorTestHelper.SetTrigger();
491+
// Wait for all triggers to fire
492+
yield return WaitForConditionOrTimeOut(() => AllTriggersDetectedOnObserversOnly(ownerShipMode, m_ClientNetworkManagers[1].LocalClientId));
493+
AssertOnTimeout($"Timed out waiting for all triggers to match!");
494+
495+
animatorTestHelper.SetLayerWeight(1, 0.75f);
496+
// Wait for all instances to update their weight value for layer 1
497+
success = WaitForConditionOrTimeOutWithTimeTravel(() => AllObserversSameLayerWeight(ownerShipMode, 1, 0.75f, m_ClientNetworkManagers[1].LocalClientId));
498+
Assert.True(success, $"Timed out waiting for all instances to match weight 0.75 on layer 1!");
499+
}
403500

404501
/// <summary>
405502
/// Verifies that triggers are synchronized with currently connected clients

0 commit comments

Comments
 (0)