Animations
Overview
The Fusion Animations sample showcases six different approaches to handling animation networking using Fusion.
Examples 1 to 5 use render accurate approaches, while example 6 represents a tick accurate approach. Each example demonstrates the resulting precision between server characters and proxy characters by drawing leg and arm hitboxes in roughly one second intervals. The animation precision is especially important when the game is using lag compensation.
Download
Version | Release Date | Download | |
---|---|---|---|
2.0.9 | Aug 27, 2024 | Fusion Animations 2.0.9 Build 638 |
Folder Structure
Project is structured as a set of 6 small independent examples.
/01_AnimatorSimple | Example 1 - Networked State with Animator |
/02_AnimatorInterpolated | Example 2 - Interpolated Networked State with Animator |
/03_AnimatorStateSync | Example 3 - State Synchronization with Animator |
/04_NetworkMecanimAnimator | Example 4 - Network Mecanim Animator |
/05_AnimancerFSM | Example 5 - Network FSM with Animancer |
/06_FusionAnimationController | Example 6 - Fusion Animation Controller |
/Common | Prefabs, scripts and materials common to all examples |
/ThirdParty | Third party assets (models, animations, Animancer Lite) |
How To
Starting the game
Each example has its own scene file which can be opened and played. Alternatively all examples can be started from the Start scene (/Common/Start).
For demonstration purposes, Multi-Peer mode with a Host plus 2 Clients is recommended (Start Host + 2 Clients button). This allows you to see and compare the State Authority, Input Authority and Proxy results in the editor.
Controls
All examples use just three animation states. The idle animation plays by default, pressing the Up Arrow key starts the character run animation, and pressing the Space key triggers the jump animation.
While playing with multiple peers, the Num 0, 1, 2, and 3 keys can be used to switch between them. Alternatively peers can be switched from the Runner Visibility Controls window (top menu Fusion > Windows > Runner Visibility Controls).
Understanding animation precision
Accurate animation synchronization between peers can be crucial for specific game applications, such as accurate lag compensation. When animations are in perfect sync it is ensured that a hit recognized on one client will also be recognized on the server, resulting in a good player experience. This of course applies only if character hitboxes are influenced by animations. Check the Animation page in Fusion Manual where important concepts are explained in detail.
All examples show the expected position of leg and arm hitboxes as seen by the local player (hitboxes of proxy* characters are showcased as blue) and as they appear on the server (colored green). The higher the animation precision, the more closely the boxes align.
Hitbox drawing is performed by HitboxDraw
script placed on the Player prefabs. To better compare the difference, hitboxes are drawn in 50 ticks intervals (roughly a second).
*Proxy Object = Network object instance that does not have Input Authority (is not controlled by the local player) nor State Authority (instance does not have authority over their network state, usually instance on the server). Simply put when playing as a client, proxy characters are other players that the player can see.
Simulating network conditions
Some changes for increased animation precision (like usage of interpolated data) are better observed with bad network conditions. To quickly simulate such conditions tools like Clumsy can be used. See more in the Simulating Network Conditions section.
Example 1 - Networked State with Animator
Render accurate approach
This example demonstrates the simplest solution which uses the latest network state to control Animator parameters.
Usually an already existing network state (e.g. _controller.Speed
) can be used to control the animations. Render accurate animations are set based on networked data in the Render
method or from OnChanged
callbacks.
C#
private CharacterController _controller;
private Animator _animator;
public override void Render()
{
if (_lastVisibleJump < _controller.JumpCount)
{
_animator.SetTrigger("Jump");
}
_lastVisibleJump = _controller.JumpCount;
_animator.SetFloat("Speed", _controller.Speed);
}
Example 2 - Interpolated Networked State with Animator
Render accurate approach
This example is a step up from the previous example by using interpolated network data as opposed to just the latest. Using interpolated data makes animations more precise and preserves this precision even when the network conditions are not ideal (see Simulating Network Conditions section).
C#
private CharacterController _controller;
private Animator _animator;
public override void Render()
{
int jumpCount = _useInterpolation == true ? _controller.InterpolatedJumpCount : _controller.JumpCount;
if (_lastVisibleJump < jumpCount)
{
_animator.SetTrigger("Jump");
}
_lastVisibleJump = jumpCount;
float speed = _useInterpolation == true ? _controller.InterpolatedSpeed : _controller.Speed;
_animator.SetFloat("Speed", speed);
}
To compare usage of interpolated data vs latest data, a simple checkbox is present on the Player component. Locate Player_AnimatorInterpolated prefab and turn off Use Interpolation. Start the game with two additional clients, simulate a bad network (Clumsy - Lag 100 ms, Drop 20%) and compare the difference.
Example 3 - State Synchronization with Animator
Render accurate approach
This example adds a special component AnimatorStateSync
that periodically synchronizes current player animator states and their time. State synchronization ensures that proxy animations will be in sync even after a player joins the game or when a proxy object enters the Area of Interest.
State synchronization can be especially important for longer running animations (e.g. locomotion) or for correcting mistakes that can happen with a render accurate approach.
Example: When a local player joins the game, the remote player is already performing running animation for 10 seconds. However when animation time is not synchronized, proxy characters on the local player machine will just start the running animation from the beginning resulting in a different animation timing. The timing will self correct with the next animation action (e.g. Jump) but until then the animations will be out of sync.
Example 4 - Network Mecanim Animator
Render accurate approach
A simple solution for synchronizing animations through the NetworkMecanimAnimator
(NMA) component that comes bundled with the Fusion SDK.
Network Mecanim Animator does not use interpolated networked data therefore the animation precision is lower than what is possible with solution from Example 3. Keep in mind that NMA does not use existing network data but rather copies current Animator parameters to its own networked data structure therefore it will be more bandwidth intensive especially if synchronized parameters change a lot.
See more about Network Mecanim Animator in the Animation documentation.
C#
private CharacterController _controller;
private NetworkMecanimAnimator _networkAnimator;
public override void FixedUpdateNetwork()
{
if (IsProxy == true)
return;
if (Runner.IsForward == false)
return;
if (_controller.HasJumped == true)
{
_networkAnimator.SetTrigger("Jump", true);
}
_networkAnimator.Animator.SetFloat("Speed", _controller.Speed);
}
Example 5 - Network FSM with Animancer
Render accurate approach
This example uses a popular third party animation asset Animancer and the Network FSM addon that is currently in preview just in this sample. Keep in mind that Animancer can also be used without the FSM, in a similar way as in the first examples.
Network FSM requires the StateMachineController
component to be placed on the game object, and a user script (e.g. Player) that holds the state machine and implements the IStateMachineOwner
interface. StateMachineController
will then automatically synchronize necessary data to all peers and update registered machines.
C#
public class Player : NetworkBehaviour, IStateMachineOwner
{
private PlayerBehaviourMachine _fullBodyMachine;
void IStateMachineOwner.CollectStateMachines(List<IStateMachine> stateMachines)
{
var states = GetComponentsInChildren<PlayerStateBehaviour>();
var animancer = GetComponentInChildren<AnimancerComponent>();
_fullBodyMachine = new PlayerBehaviourMachine("Full Body", _controller, animancer, states);
stateMachines.Add(_fullBodyMachine);
}
}
Animations are played based on state changes. A state represents a single player behavior that is either an object in the player hierarchy with StateBehaviour
script attached (e.g. Jump State, Locomotion State), or a plain class that inherits from State
script (not showcased in this example).
C#
public override void FixedUpdateNetwork()
{
if (IsProxy == true)
return;
if (_controller.HasJumped == true)
{
_fullBodyMachine.TryActivateState<PlayerJumpState>();
}
}
C#
public class PlayerJumpState : PlayerStateBehaviour
{
[SerializeField]
private ClipTransition _jumpClip;
protected override void OnEnterStateRender()
{
Animancer.Play(_jumpClip);
}
protected override void OnFixedUpdate()
{
if (Machine.StateTime >= _jumpClip.Length * _jumpClip.Speed)
{
// Jump animation should be finished, let's leave this state
Machine.TryDeactivateState(StateId);
}
}
}
It is possible to create sub-state machines (child machines) controlled from a parent state, effectively creating an HFSM (Hierarchical Finite State Machine). For example, a child machine could be an airborne machine with states like Jump, Fall and Land that is controlled from an Airborne parent state. Such separation is useful when controlling complex animation setups with many states.
It is worth mentioning that Network FSM by default switches states on proxies based on the interpolated data and provides current state time (Machine.StateTime
) that is also interpolated when checked from Render calls. This ensures fine animation precision without much effort.
C#
public class PlayerLocomotionState : PlayerStateBehaviour
{
[SerializeField]
private LinearMixerTransition _moveMixer;
protected override void OnEnterStateRender()
{
Animancer.Play(_moveMixer);
// Update the animation time based on the state time
_moveMixer.State.Time = Machine.StateTime;
}
protected override void OnRender()
{
_moveMixer.State.Parameter = Controller.InterpolatedSpeed;
}
}
Example 6 - Fusion Animation Controller
Tick accurate approach
Fusion Animation Controller is a tick accurate animation solution built directly on top of Unity’s low level Playables API.
Similarly to Mecanim, Fusion Animation Controller operates with Animation Layers. Every layer contains one or more Animation States. Both layers and states are simply represented by objects in the object hierarchy (see Player_FusionAnimationController
prefab). Animations are controlled from code via the AnimationController
component.
C#
private CharacterController _controller;
private PlayerLocomotionState _locomotionState;
private PlayerJumpState _jumpState;
public override void FixedUpdateNetwork()
{
if (IsProxy == true)
return;
if (_controller.HasJumped == true)
{
_jumpState.Activate(0.15f);
}
else if (_jumpState.IsPlaying() == false || _jumpState.IsFinished(-0.15f, false) == true)
{
_locomotionState.Activate(0.15f);
}
}
Fusion Animation Controller is a refined version of the animation controller used in BR200.
3rd Party Assets
The Animations Sample includes several assets provided courtesy of their respective creators. The full packages can be acquired for your own projects at their respective site:
- Animancer LITE by Kybernetik
IMPORTANT: To use them in a commercial project, it is required to purchase a license from the respective creators.
Back to top- Overview
- Download
- Folder Structure
- How To
- Example 1 - Networked State with Animator
- Example 2 - Interpolated Networked State with Animator
- Example 3 - State Synchronization with Animator
- Example 4 - Network Mecanim Animator
- Example 5 - Network FSM with Animancer
- Example 6 - Fusion Animation Controller
- 3rd Party Assets