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

Network Input

Introduction

Input definition, polling and consumption lies at the core of Fusion.

Back To Top

Input Struct Definition

The input struct can store as simple or as complex data as needed. Fusion will only transmit data that actually changes. Therefore, although it is advantageous to keep data compact (e.g. use flags for buttons), it is fine to include rarely used fields. One such example is collecting input from several local players even if there is mostly just one - if these other players are not actually providing input they will not affect bandwidth usage of the input struct, only local memory.

The input struct has the following constraints:

  • it has to inherit from INetworkInput;
  • it can only contain primitive types and structs;
  • the input struct and any structs it holds have be top-level structs (i.e. cannot be nested in a class); and,
  • for boolean values use NetworkBool instead of bool - C# does not enforce a consistent size for bools across platforms so NetworkBool is used to properly serialize it as a single bit.

Fusion will intelligently map the type of the struct; this permits the use of different structs for different play modes or different parts of the game. When unwrapping the input, Fusion will only return the available input of the correct type.

public struct MyInput : INetworkedInput {
    public Vector3 aimDirection;
}

Back To Top

Buttons

There is a special NetworkButtons type which provides a convenient wrapper for saving button presses in a INetworkInput struct.

To add buttons to an input struct, simply:

  1. Create an enum for the buttons ( Important: it must be explicitly defined and start at 0); and,
  2. Add a NetworkButtons variable to the INetworkedInput.
enum MyButtons {
    Forward = 0,
    Backward = 1,
    Left = 2,
    Right = 3,
}

public struct MyInput : INetworkInput {
    public NetworkButtons buttons;
    public Vector3 aimDirection;
}

The API available for assigning and reading values directly from a NetworkButtons variable is:

  • void Set(int button, bool state): takes the enum value for the button and its state (pressed = true, not pressed = false);
  • bool IsSet(int button): takes takes the enum value for the button and returns its boolean state.

The NetworkButtons type is stateless and therefore does not hold any meta data about the previous state of the buttons. In order to be able to use the next set of methods offered by NetworkButtons, it is necessary to keep track of the previous state of the buttons; this is easily done by creating a [Networked] version of the previous state for each player.

public class PlayerInputExample : NetworkBehaviour {
    [Networked] public NetworkButtons ButtonsPrevious { get; set; }

    ...
}
For the full snippet, see the __GetInput__ section further down.

With this it becomes possible to compare the current state of the buttons with their previous state to evaluate whether the buttons have just been pressed or released.

  • NetworkButtons GetPressed(NetworkButtons previous): returns a set of values for all buttons which have just been pressed.
  • NetworkButtons GetReleased(NetworkButtons previous): returns a set of values for all buttons which have just been released.
  • (NetworkButtons, NetworkButtons) GetPressedOrReleased(NetworkButtons previous): returns a tuple of values for buttons which have been just pressed and released.

IMPORTANT: Only use Input.GetKey() to assign button values. Do NOT use Input.GetKeyDown() or Input.GetKeyUp() as they are not synchronised with the Fusion ticks and thus might be missed.

Back To Top

Poll Input

Fusion collects input by polling the local client and populating a previously defined input struct. The Fusion Runner only ever keeps track of a single input struct, it is therefore strongly advised to implement the input polling in a single place to avoid any unexpected behaviour.

The Fusion Runner polls input by calling the INetworkRunnerCallbacks.OnInput() method. The implementation of OnInput() can populate any struct inheriting from INetworkInput with the chosen data. The populated struct is passed back to Fusion by calling Set() on the provided NetworkInput.

IMPORTANT: Having multiple polling sites will result in all but the last input struct version being overwritten.

Back To Top

SimulationBehaviour

Fusion automatically calls OnInput() on all SimulationBehaviour and NetworkBehaviour components implementing the INetworkRunnerCallbacks interface and over which the client has Input Authority - hence the name.

public class InputProvider : SimulationBehaviour, INetworkRunnerCallbacks {
  public void OnInput(NetworkRunner runner, NetworkInput input) {
    var myInput = new MyInput();

    myInput.Buttons.Set(MyButtons.Forward, Input.GetKey(KeyCode.W));
    myInput.Buttons.Set(MyButtons.Backward, Input.GetKey(KeyCode.S));
    myInput.Buttons.Set(MyButtons.Left, Input.GetKey(KeyCode.A));
    myInput.Buttons.Set(MyButtons.Right, Input.GetKey(KeyCode.D));
    myInput.Buttons.Set(MyButtons.Jump, Input.GetKey(KeyCode.Space));

    input.Set(myInput);
  }
}

Back To Top

MonoBehaviour And Pure CSharp

To poll input from a regular CSharp script or a MonoBehaviour, follow these steps:

  1. Implement INetworkRunnerCallbacks and OnInput(); and,
  2. Register the script with the NetworkRunner by calling AddCallbacks() on it.

public class InputProvider : Monobehaviour, INetworkRunnerCallbacks {

  public void OnEnable(){
    var myNetworkRunner = FindObjectOfType<NetworkRunner>();
    myNetworkRunner.AddCallbacks( this );
  }

  public void OnInput(NetworkRunner runner, NetworkInput input) {
    // Same as in the snippet for SimulationBehaviour and NetworkBehaviour.
  }

  public void OnDisable(){
    var myNetworkRunner = FindObjectOfType<NetworkRunner>();
    myNetworkRunner.RemoveCallbacks( this );
  }
}

Back To Top

Read Input

The input can be read by the simulation to modify the existing networked state from its current state to the new one based on the previously polled input. Fusion synchronizes the input struct across the network and makes it available during simulation on the client who has Input Authority and the one who has State Authority (the host).

Contrary to polling input, reading input can be done at as many different places as necessary.

N.B.: The player input is only available for the client with Input Authoriy and State Authority. In the HostMode and ServerMode this means the player client and the host / server, whereas in SharedMode this is one and the same client.

It is not possible to read the input one client on another. Therefore, any changes relying on input need to be saved as a [Networked] state in order for it to be replicated on other clients.

Back To Top

GetInput()

To get the input struct, call GetInput(out T input) in the FixedUpdateNetwork() of on any NetworkBehaviour which has Input Authority over the object in question (e.g. the component controlling the player's movement). The call to GetInput() provides the same input struct that was previously populated in OnInput().

The call to GetInput() will return false if:

  • the client does not have State Authority or Input Authority
  • the requested type of input does not exist in the simulation
using Fusion;
using UnityEngine;

public class PlayerInputExample : NetworkBehaviour {

  [Networked] public NetworkButtons ButtonsPrevious { get; set; }

  public override void FixedUpdateNetwork() {
    NetworkButtons buttons = default;

    if (GetInput<MyInput>(out var input)) {
      buttons = input.Buttons;
    }

    // compute pressed/released state
    var pressed = buttons.GetPressed(ButtonsPrevious);
    var released = buttons.GetReleased(ButtonsPrevious);

    // store latest input as 'previous' state we had
    ButtonsPrevious = buttons;

    // movement (check for down)
    var vector = default(Vector3);

    if (buttons.IsSet(MyButtons.Forward)) { vector.z += 1; }
    if (buttons.IsSet(MyButtons.Backward)) { vector.z -= 1; }

    if (buttons.IsSet(MyButtons.Left)) { vector.x  -= 1; }
    if (buttons.IsSet(MyButtons.Right)) { vector.x += 1; }

    DoMove(vector);

    // jump (check for pressed)
    if (pressed.IsSet(MyButtons.Jump)) {
      DoJump();
    }
  }

  void DoMove(Vector3 vector) {
    // dummy method with no logic in it
  }

  void DoJump() {
    // dummy method with no logic in it
  }
}

Back To Top

Runner.TryGetInputForPlayer()

It is possible to read the input from outside a NetworkBehaviour by calling NetworkRunner.TryGetInputForPlayer<T>(PlayerRef playerRef, out var input). In addition to the INetworkInput type, it requires specifying the player for which the input should be retrieved. N.B.: The limitations as for GetInput() apply; i.e. on the client with input authority or Server / Host can get the input for the specified player.

var myNetworkRunner = FindObjectOfType<NetworkRunner>();

// Example for local player if script runs only on the client
if(myNetworkRunner.TryGetInputForPlayer<MyInput>(myNetworkRunner.LocalPlayer, out var input)){
    // do logic
}

Back To Top

A Note On Authority

To guarantee full simulation authority, it is key to only collect input values in OnInput() when populating the input struct. The logic based to be executed based on the input should be done completely in the GetInput().

For instance, the following split would be used for firing a bullet:

  • OnInput(): Save the value of the firing button for the player.
  • GetInput(): Check if the firing button was pressed and shot the bullet if it was.

To Document Top