This page is a work in progress and could be pending updates.

Fusion Animations

Level Advanced

Overview

The Fusion Animations sample showcases six different approaches to handling animation networking using Fusion.

Overview

For introduction to animations in Fusion as well as differences between render accurate animations and tick accurate animations please refer to Animation documentation page in Fusion Manual.

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.

Hitboxes

Download

Version Release Date Download
1.1.3 Dec 14, 2022 Fusion Animations 1.1.3 Build 86

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.

Start

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.

Hitboxes

Note: Perfect and most reliable animation precision is achieved with tick accurate solution (Example 6) but it comes with a large drawback of being most complex to set up correctly and maintain. Make sure you understand the differences between render and tick accurate animation solutions before going down this road.

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).

HitboxDraw

*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.

Clumsy

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.

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

Note: Notice how the locomotion animation can be very much out of sync on start after the two other clients join. This is because animation time is not synchronized so animation starts to play from zero on all clients even though the character on state authority is playing the animation already for several seconds. This will get automatically corrected with next animation action (e.g. Jump) however if such behavior is unacceptable, synchronization of animation time is needed - see Example 3.

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).

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);
}

Example

Make sure your game benefits from the increased precision as for many projects usage of interpolators can be an unnecessary complication and solution from Example 1 will produce sufficient results.

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.

Example

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.

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

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.

Disclaimer: Animancer is a third party Unity asset developed, maintained and supported by Kybernetik. At the time of writing it can be used in combination with Fusion. Photon does not provide an out-of-the-box Animancer-Fusion integration.

Example

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.

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).

public override void FixedUpdateNetwork()
{
    if (IsProxy == true)
        return;

    if (_controller.HasJumped == true)
    {
        _fullBodyMachine.TryActivateState<PlayerJumpState>();
    }
}
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.

Note: Child machines are not showcased in this example but it is essentially enough to provide a child machine in the OnCollectChildStateMachines method of the desired parent state and then control the state machine as usual.

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.

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 in the experimental state and lacking web documentation. Understanding requires intermediate coding skills.

Fusion Animation Controller is a tick accurate animation solution built directly on top of Unity’s low level Playables API.

Please ensure you understand limitations of the custom animation solution before using it in your game. Read more about tick accurate animations in Animation documentation.

Example

Similarly to Mecanim, 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.

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. Therefore for a more elaborate animation example please check BR200.


To Document Top