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
This commit is contained in:
Noel Stephens 2024-09-11 08:40:28 -05:00 коммит произвёл GitHub
Родитель 39818f239d
Коммит 490fb939a5
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
3 изменённых файлов: 130 добавлений и 10 удалений

Просмотреть файл

@ -15,6 +15,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
### Fixed
- Fixed issue where `NetworkAnimator` would send updates to non-observer clients. (#3058)
- Fixed issue where an exception could occur when receiving a universal RPC for a `NetworkObject` that has been despawned. (#3055)
- 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)
- Fixed issue where collections v2.2.x was not supported when using UTP v2.2.x within Unity v2022.3. (#3033)

Просмотреть файл

@ -924,8 +924,14 @@ namespace Unity.Netcode.Components
{
// Just notify all remote clients and not the local server
m_ClientSendList.Clear();
m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds);
m_ClientSendList.Remove(NetworkManager.LocalClientId);
foreach (var clientId in NetworkManager.ConnectedClientsIds)
{
if (clientId == NetworkManager.LocalClientId || !NetworkObject.Observers.Contains(clientId))
{
continue;
}
m_ClientSendList.Add(clientId);
}
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
SendAnimStateClientRpc(m_AnimationMessage, m_ClientRpcParams);
}
@ -1223,9 +1229,14 @@ namespace Unity.Netcode.Components
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);
foreach (var clientId in NetworkManager.ConnectedClientsIds)
{
if (clientId == serverRpcParams.Receive.SenderClientId || clientId == NetworkManager.ServerClientId || !NetworkObject.Observers.Contains(clientId))
{
continue;
}
m_ClientSendList.Add(clientId);
}
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
m_NetworkAnimatorStateChangeHandler.SendParameterUpdate(parametersUpdate, m_ClientRpcParams);
}
@ -1271,9 +1282,14 @@ namespace Unity.Netcode.Components
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);
foreach (var clientId in NetworkManager.ConnectedClientsIds)
{
if (clientId == serverRpcParams.Receive.SenderClientId || clientId == NetworkManager.ServerClientId || !NetworkObject.Observers.Contains(clientId))
{
continue;
}
m_ClientSendList.Add(clientId);
}
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
m_NetworkAnimatorStateChangeHandler.SendAnimationUpdate(animationMessage, m_ClientRpcParams);
}
@ -1322,8 +1338,14 @@ namespace Unity.Netcode.Components
InternalSetTrigger(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet);
m_ClientSendList.Clear();
m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds);
m_ClientSendList.Remove(NetworkManager.ServerClientId);
foreach (var clientId in NetworkManager.ConnectedClientsIds)
{
if (clientId == NetworkManager.ServerClientId || !NetworkObject.Observers.Contains(clientId))
{
continue;
}
m_ClientSendList.Add(clientId);
}
if (IsServerAuthoritative())
{

Просмотреть файл

@ -400,6 +400,103 @@ namespace TestProject.RuntimeTests
AssertOnTimeout($"Timed out waiting for all clients to transition from synchronized cross fade!");
}
private bool AllTriggersDetectedOnObserversOnly(OwnerShipMode ownerShipMode, ulong nonObserverId)
{
if (ownerShipMode == OwnerShipMode.ClientOwner)
{
if (!TriggerTest.ClientsThatTriggered.Contains(m_ServerNetworkManager.LocalClientId))
{
return false;
}
}
foreach (var animatorTestHelper in AnimatorTestHelper.ClientSideInstances)
{
var currentClientId = animatorTestHelper.Value.NetworkManager.LocalClientId;
if (currentClientId == nonObserverId || (ownerShipMode == OwnerShipMode.ClientOwner && currentClientId == animatorTestHelper.Value.OwnerClientId))
{
continue;
}
if (!TriggerTest.ClientsThatTriggered.Contains(currentClientId))
{
return false;
}
}
// Should return false always
return !TriggerTest.ClientsThatTriggered.Contains(nonObserverId);
}
private bool AllObserversSameLayerWeight(OwnerShipMode ownerShipMode, int layer, float targetWeight, ulong nonObserverId)
{
if (ownerShipMode == OwnerShipMode.ClientOwner)
{
if (AnimatorTestHelper.ServerSideInstance.GetLayerWeight(layer) != targetWeight)
{
return false;
}
}
foreach (var animatorTestHelper in AnimatorTestHelper.ClientSideInstances)
{
var currentClientId = animatorTestHelper.Value.NetworkManager.LocalClientId;
if (ownerShipMode == OwnerShipMode.ClientOwner && animatorTestHelper.Value.OwnerClientId == currentClientId)
{
continue;
}
if (currentClientId == nonObserverId)
{
if (animatorTestHelper.Value.GetLayerWeight(layer) == targetWeight)
{
return false;
}
}
else
if (animatorTestHelper.Value.GetLayerWeight(layer) != targetWeight)
{
return false;
}
}
return true;
}
[UnityTest]
public IEnumerator OnlyObserversAnimateTest([Values] OwnerShipMode ownerShipMode, [Values(AuthoritativeMode.ServerAuth, AuthoritativeMode.OwnerAuth)] AuthoritativeMode authoritativeMode)
{
// Spawn our test animator object
var objectInstance = SpawnPrefab(ownerShipMode == OwnerShipMode.ClientOwner, authoritativeMode);
var networkObject = objectInstance.GetComponent<NetworkObject>();
// 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 server-side instance of {GetNetworkAnimatorName(authoritativeMode)} to be spawned!");
var animatorTestHelper = ownerShipMode == OwnerShipMode.ClientOwner ? AnimatorTestHelper.ClientSideInstances[m_ClientNetworkManagers[0].LocalClientId] : AnimatorTestHelper.ServerSideInstance;
networkObject.NetworkHide(m_ClientNetworkManagers[1].LocalClientId);
yield return WaitForConditionOrTimeOut(() => !m_ClientNetworkManagers[1].SpawnManager.SpawnedObjects.ContainsKey(networkObject.NetworkObjectId));
AssertOnTimeout($"Client-{m_ClientNetworkManagers[1].LocalClientId} timed out waiting to hide {networkObject.name}!");
if (authoritativeMode == AuthoritativeMode.ServerAuth)
{
animatorTestHelper = AnimatorTestHelper.ServerSideInstance;
}
animatorTestHelper.SetTrigger();
// Wait for all triggers to fire
yield return WaitForConditionOrTimeOut(() => AllTriggersDetectedOnObserversOnly(ownerShipMode, m_ClientNetworkManagers[1].LocalClientId));
AssertOnTimeout($"Timed out waiting for all triggers to match!");
animatorTestHelper.SetLayerWeight(1, 0.75f);
// Wait for all instances to update their weight value for layer 1
success = WaitForConditionOrTimeOutWithTimeTravel(() => AllObserversSameLayerWeight(ownerShipMode, 1, 0.75f, m_ClientNetworkManagers[1].LocalClientId));
Assert.True(success, $"Timed out waiting for all instances to match weight 0.75 on layer 1!");
}
/// <summary>
/// Verifies that triggers are synchronized with currently connected clients