Skip to content
Merged
2 changes: 1 addition & 1 deletion com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
### Removed

### Fixed

- Fixed `NetworkAnimator` could not run in the server authoritative mode. (#2003)
- Fixed issue where `NetworkBehaviourReference` would throw a type cast exception if using `NetworkBehaviourReference.TryGet` and the component type was not found. (#1984)
- Fixed `NetworkSceneManager` was not sending scene event notifications for the currently active scene and any additively loaded scenes when loading a new scene in `LoadSceneMode.Single` mode. (#1975)
- Fixed issue where one or more clients disconnecting during a scene event would cause `LoadEventCompleted` or `UnloadEventCompleted` to wait until the `NetworkConfig.LoadSceneTimeOut` period before being triggered. (#1973)
Expand Down
146 changes: 102 additions & 44 deletions com.unity.netcode.gameobjects/Components/NetworkAnimator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,10 @@ public void NetworkUpdate(NetworkUpdateStage updateStage)
m_ProcessParameterUpdates.Clear();

// Only owners check for Animator changes
if (m_NetworkAnimator.IsOwner)
if (m_NetworkAnimator.IsOwner && !m_NetworkAnimator.IsServerAuthoritative || m_NetworkAnimator.IsServerAuthoritative && m_NetworkAnimator.NetworkManager.IsServer)
{
m_NetworkAnimator.CheckForAnimatorChanges();
}

break;
}
}
Expand Down Expand Up @@ -209,6 +208,42 @@ public Animator Animator
}
}

[Tooltip("When enabled, the NetworkAnimator will operate in server authoritative mode.")]
[SerializeField]
private bool m_IsServerAuthoritative;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be set to true by default as to not create a breaking change?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. If we want to make server authoritative the default that is easy enough to do.
Do we want users to default to the server authoritative model or client authoritative model?
(both approaches have pro's and con's)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for making server-auth the default to not change the current default.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NetworkAnimator follows the same pattern as NetworkTransform to make it client/owner authoritative.


/// <summary>
/// Server-Side Only:
/// Can be used to switch between server and owner authoritative modes during runtime.
/// Note: Only when the associated NetworkObject is spawned.
/// </summary>
public bool IsServerAuthoritative
Copy link
Copy Markdown
Contributor

@0xFA11 0xFA11 Jun 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to keep this API similar to NetworkTransform's, I think we should go for something similar to (protected) NetworkTransform::OnIsServerAuthoritative() & (internal) NetworkTransform::IsServerAuthoritative()

{
get
{
return m_IsServerAuthoritative;
}
set
{
if (!IsSpawned)
{
m_IsServerAuthoritative = value;
}
else if (IsSpawned && IsServer)
{
m_IsServerAuthoritative = value;
m_ServerAuthoritativeMode.Value = value;
}
}
}

private NetworkVariable<bool> m_ServerAuthoritativeMode = new NetworkVariable<bool>();

private void OnServerAuthoritativeModeChanged(bool previous, bool next)
{
m_IsServerAuthoritative = next;
}

// Animators only support up to 32 params
private const int k_MaxAnimationParams = 32;

Expand Down Expand Up @@ -276,13 +311,10 @@ public override void OnDestroy()
base.OnDestroy();
}


private List<int> m_ParametersToUpdate;

private List<ulong> m_ClientSendList;
private ClientRpcParams m_ClientRpcParams;


public override void OnNetworkSpawn()
{
if (IsOwner || IsServer)
Expand All @@ -295,6 +327,7 @@ public override void OnNetworkSpawn()
if (IsServer)
{
NetworkManager.OnClientConnectedCallback += OnClientConnectedCallback;
m_ServerAuthoritativeMode.Value = m_IsServerAuthoritative;
}

// Store off our current layer weights
Expand All @@ -316,6 +349,11 @@ public override void OnNetworkSpawn()
}
}

if (!IsServer)
{
m_ServerAuthoritativeMode.OnValueChanged = OnServerAuthoritativeModeChanged;
}

var parameters = m_Animator.parameters;
m_CachedAnimatorParameters = new NativeArray<AnimatorParamCache>(parameters.Length, Allocator.Persistent);
m_ParametersToUpdate = new List<int>(parameters.Length);
Expand Down Expand Up @@ -439,7 +477,7 @@ private void OnClientConnectedCallback(ulong playerId)

internal void CheckForAnimatorChanges()
{
if (!IsOwner)
if (!IsOwner && !IsServerAuthoritative || m_IsServerAuthoritative && !IsServer)
{
return;
}
Expand Down Expand Up @@ -753,19 +791,26 @@ private unsafe void UpdateAnimationState(AnimationMessage animationState)
[ServerRpc]
private unsafe void SendParametersUpdateServerRpc(ParametersUpdateMessage parametersUpdate, ServerRpcParams serverRpcParams = default)
{
if (serverRpcParams.Receive.SenderClientId != OwnerClientId)
if (IsServerAuthoritative)
{
return;
m_NetworkAnimatorStateChangeHandler.SendParameterUpdate(parametersUpdate);
}
UpdateParameters(parametersUpdate);
if (NetworkManager.ConnectedClientsIds.Count - 2 > 0)
else
{
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.SendParameterUpdate(parametersUpdate, m_ClientRpcParams);
if (serverRpcParams.Receive.SenderClientId != OwnerClientId)
{
return;
}
UpdateParameters(parametersUpdate);
if (NetworkManager.ConnectedClientsIds.Count - 2 > 0)
{
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.SendParameterUpdate(parametersUpdate, m_ClientRpcParams);
}
}
}

Expand All @@ -775,7 +820,7 @@ private unsafe void SendParametersUpdateServerRpc(ParametersUpdateMessage parame
[ClientRpc]
internal unsafe void SendParametersUpdateClientRpc(ParametersUpdateMessage parametersUpdate, ClientRpcParams clientRpcParams = default)
{
if (!IsOwner)
if (!IsServerAuthoritative && !IsOwner || IsServerAuthoritative)
{
m_NetworkAnimatorStateChangeHandler.ProcessParameterUpdate(parametersUpdate);
}
Expand All @@ -788,20 +833,26 @@ internal unsafe void SendParametersUpdateClientRpc(ParametersUpdateMessage param
[ServerRpc]
private unsafe void SendAnimStateServerRpc(AnimationMessage animSnapshot, ServerRpcParams serverRpcParams = default)
{
if (serverRpcParams.Receive.SenderClientId != OwnerClientId)
if (IsServerAuthoritative)
{
return;
m_NetworkAnimatorStateChangeHandler.SendAnimationUpdate(animSnapshot);
}

UpdateAnimationState(animSnapshot);
if (NetworkManager.ConnectedClientsIds.Count - 2 > 0)
else
{
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.SendAnimationUpdate(animSnapshot, m_ClientRpcParams);
if (serverRpcParams.Receive.SenderClientId != OwnerClientId)
{
return;
}
UpdateAnimationState(animSnapshot);
if (NetworkManager.ConnectedClientsIds.Count - 2 > 0)
{
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.SendAnimationUpdate(animSnapshot, m_ClientRpcParams);
}
}
}

Expand All @@ -811,7 +862,7 @@ private unsafe void SendAnimStateServerRpc(AnimationMessage animSnapshot, Server
[ClientRpc]
private unsafe void SendAnimStateClientRpc(AnimationMessage animSnapshot, ClientRpcParams clientRpcParams = default)
{
if (!IsOwner)
if (!IsServerAuthoritative && !IsOwner || IsServerAuthoritative)
{
UpdateAnimationState(animSnapshot);
}
Expand All @@ -824,23 +875,30 @@ private unsafe void SendAnimStateClientRpc(AnimationMessage animSnapshot, Client
[ServerRpc]
private void SendAnimTriggerServerRpc(AnimationTriggerMessage animationTriggerMessage, ServerRpcParams serverRpcParams = default)
{
if (serverRpcParams.Receive.SenderClientId != OwnerClientId)
if (IsServerAuthoritative)
{
return;
m_NetworkAnimatorStateChangeHandler.SendTriggerUpdate(animationTriggerMessage);
}

// trigger the animation locally on the server...
m_Animator.SetBool(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet);

if (NetworkManager.ConnectedClientsIds.Count - 2 > 0)
else
{
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.SendTriggerUpdate(animationTriggerMessage, m_ClientRpcParams);
if (serverRpcParams.Receive.SenderClientId != OwnerClientId)
{
return;
}
// trigger the animation locally on the server...
m_Animator.SetBool(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet);

if (NetworkManager.ConnectedClientsIds.Count - 2 > 0)
{
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.SendTriggerUpdate(animationTriggerMessage, m_ClientRpcParams);
}
}

}

/// <summary>
Expand All @@ -852,7 +910,7 @@ private void SendAnimTriggerServerRpc(AnimationTriggerMessage animationTriggerMe
[ClientRpc]
internal void SendAnimTriggerClientRpc(AnimationTriggerMessage animationTriggerMessage, ClientRpcParams clientRpcParams = default)
{
if (!IsOwner)
if (!IsServerAuthoritative && !IsOwner || IsServerAuthoritative)
{
m_Animator.SetBool(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet);
}
Expand All @@ -872,7 +930,7 @@ public void SetTrigger(string triggerName)
/// <param name="reset">If true, resets the trigger</param>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs update, reset renamed as setTrigger and logic changed too.

public void SetTrigger(int hash, bool setTrigger = true)
{
if (IsOwner)
if (IsOwner && !IsServerAuthoritative || IsServer && IsServerAuthoritative)
{
var animTriggerMessage = new AnimationTriggerMessage() { Hash = hash, IsTriggerSet = setTrigger };
if (IsServer)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class AnimatedCubeController : NetworkBehaviour
private bool m_Rotate;
private NetworkAnimator m_NetworkAnimator;


private void Awake()
{
m_Animator = GetComponent<Animator>();
Expand All @@ -22,27 +23,90 @@ private void Awake()

public override void OnNetworkSpawn()
{
if (!IsOwner)
if (HasAuthority())
{
enabled = false;
}
}


private bool HasAuthority()
{
if (IsOwnerAuthority() || IsServerAuthority())
{
return true;
}
return false;
}

private bool IsServerAuthority()
{
if (IsServer && m_NetworkAnimator.IsServerAuthoritative)
{
return true;
}
return false;
}

private bool IsOwnerAuthority()
{
if (IsOwner && m_NetworkAnimator.IsServerAuthoritative)
{
return true;
}
return false;
}


[ServerRpc(RequireOwnership = false)]
private void ToggleRotateAnimationServerRpc(bool rotate)
{
m_Rotate = rotate;
m_Animator.SetBool("Rotate", m_Rotate);
}

internal void ToggleRotateAnimation()
{
m_Rotate = !m_Rotate;
m_Animator.SetBool("Rotate", m_Rotate);
if (m_NetworkAnimator.IsServerAuthoritative)
{
if (!IsServer && IsOwner)
{
ToggleRotateAnimationServerRpc(m_Rotate);
}
else if (IsServer && IsOwner)
{
m_Animator.SetBool("Rotate", m_Rotate);
}
}
else if (IsOwner)
{
m_Animator.SetBool("Rotate", m_Rotate);
}
}

internal void PlayPulseAnimation(bool useNetworkAnimator = true)
[ServerRpc(RequireOwnership = false)]
private void PlayPulseAnimationServerRpc(bool rotate)
{
if (useNetworkAnimator)
m_NetworkAnimator.SetTrigger("Pulse");
}

internal void PlayPulseAnimation()
{
if (m_NetworkAnimator.IsServerAuthoritative)
{
m_NetworkAnimator.SetTrigger("Pulse");
if (!IsServer && IsOwner)
{
PlayPulseAnimationServerRpc(m_Rotate);
}
else if (IsServer && IsOwner)
{
m_NetworkAnimator.SetTrigger("Pulse");
}
}
else
else if (IsOwner)
{
m_Animator.SetBool("Pulse", true);
m_NetworkAnimator.SetTrigger("Pulse");
}
}

Expand Down
Loading