Skip to content

Commit 31c30e5

Browse files
fix: networkanimator only updates observers (#3057)
* fix Only send updates to observers. * test Includes validation test that it only sends updates to observers. * update adding changelog entry.
1 parent 1d22804 commit 31c30e5

File tree

3 files changed

+131
-11
lines changed

3 files changed

+131
-11
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
2121

2222
### Fixed
2323

24+
- Fixed issue where `NetworkAnimator` would send updates to non-observer clients. (#3057)
2425
- Fixed issue where an exception could occur when receiving a universal RPC for a `NetworkObject` that has been despawned. (#3052)
2526
- Fixed issue where a NetworkObject hidden from a client that is then promoted to be session owner was not being synchronized with newly joining clients.(#3051)
2627
- Fixed issue where clients could have a wrong time delta on `NetworkVariableBase` which could prevent from sending delta state updates. (#3045)

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

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -952,8 +952,14 @@ internal void CheckForAnimatorChanges()
952952
{
953953
// Just notify all remote clients and not the local server
954954
m_ClientSendList.Clear();
955-
m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds);
956-
m_ClientSendList.Remove(NetworkManager.LocalClientId);
955+
foreach (var clientId in NetworkManager.ConnectedClientsIds)
956+
{
957+
if (clientId == NetworkManager.LocalClientId || !NetworkObject.Observers.Contains(clientId))
958+
{
959+
continue;
960+
}
961+
m_ClientSendList.Add(clientId);
962+
}
957963
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
958964
SendAnimStateClientRpc(m_AnimationMessage, m_ClientRpcParams);
959965
}
@@ -1264,9 +1270,15 @@ private unsafe void SendParametersUpdateServerRpc(ParametersUpdateMessage parame
12641270
if (NetworkManager.ConnectedClientsIds.Count > (IsHost ? 2 : 1))
12651271
{
12661272
m_ClientSendList.Clear();
1267-
m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds);
1268-
m_ClientSendList.Remove(serverRpcParams.Receive.SenderClientId);
1269-
m_ClientSendList.Remove(NetworkManager.ServerClientId);
1273+
foreach (var clientId in NetworkManager.ConnectedClientsIds)
1274+
{
1275+
if (clientId == serverRpcParams.Receive.SenderClientId || clientId == NetworkManager.ServerClientId || !NetworkObject.Observers.Contains(clientId))
1276+
{
1277+
continue;
1278+
}
1279+
m_ClientSendList.Add(clientId);
1280+
}
1281+
12701282
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
12711283
m_NetworkAnimatorStateChangeHandler.SendParameterUpdate(parametersUpdate, m_ClientRpcParams);
12721284
}
@@ -1321,9 +1333,14 @@ private void SendAnimStateServerRpc(AnimationMessage animationMessage, ServerRpc
13211333
if (NetworkManager.ConnectedClientsIds.Count > (IsHost ? 2 : 1))
13221334
{
13231335
m_ClientSendList.Clear();
1324-
m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds);
1325-
m_ClientSendList.Remove(serverRpcParams.Receive.SenderClientId);
1326-
m_ClientSendList.Remove(NetworkManager.ServerClientId);
1336+
foreach (var clientId in NetworkManager.ConnectedClientsIds)
1337+
{
1338+
if (clientId == serverRpcParams.Receive.SenderClientId || clientId == NetworkManager.ServerClientId || !NetworkObject.Observers.Contains(clientId))
1339+
{
1340+
continue;
1341+
}
1342+
m_ClientSendList.Add(clientId);
1343+
}
13271344
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
13281345
m_NetworkAnimatorStateChangeHandler.SendAnimationUpdate(animationMessage, m_ClientRpcParams);
13291346
}
@@ -1390,9 +1407,14 @@ internal void SendAnimTriggerServerRpc(AnimationTriggerMessage animationTriggerM
13901407
InternalSetTrigger(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet);
13911408

13921409
m_ClientSendList.Clear();
1393-
m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds);
1394-
m_ClientSendList.Remove(NetworkManager.ServerClientId);
1395-
1410+
foreach (var clientId in NetworkManager.ConnectedClientsIds)
1411+
{
1412+
if (clientId == NetworkManager.ServerClientId || !NetworkObject.Observers.Contains(clientId))
1413+
{
1414+
continue;
1415+
}
1416+
m_ClientSendList.Add(clientId);
1417+
}
13961418
if (IsServerAuthoritative())
13971419
{
13981420
m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animationTriggerMessage, m_ClientRpcParams);

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

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,103 @@ public IEnumerator CrossFadeTransitionTests()
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()
467+
{
468+
// Spawn our test animator object
469+
var objectInstance = SpawnPrefab(m_OwnerShipMode == OwnerShipMode.ClientOwner, m_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(m_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(m_AuthoritativeMode)} to be spawned!");
478+
479+
var animatorTestHelper = m_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 (m_AuthoritativeMode == AuthoritativeMode.ServerAuth)
487+
{
488+
animatorTestHelper = AnimatorTestHelper.ServerSideInstance;
489+
}
490+
animatorTestHelper.SetTrigger();
491+
// Wait for all triggers to fire
492+
yield return WaitForConditionOrTimeOut(() => AllTriggersDetectedOnObserversOnly(m_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(m_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)