This document is about: FUSION 1
SWITCH TO

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

3 - Movement & Camera

Overview

In Fusion 103 Shared the existing scene gets extended by adding player movement based on player input and a first person camera that follows the correct player object.

Player Movement

In Fusion, gameplay code that updates every tick such as movement should not run in Update / FixedUpdate. Instead, FixedUpdateNetwork should be used. This ensures that the movement is smooth and interpolates correctly on all clients.

Create a new script and name it PlayerMovement. Have it inherit from NetworkBehaviour instead of MonoBehaviour and add the following code to the script:

C#

private CharacterController _controller;

public float PlayerSpeed = 2f;

private void Awake() {
    _controller = GetComponent<CharacterController>();
}

public override void FixedUpdateNetwork()
{
    // Only move own player and not every other player. Each player controls its own player object.
    if (HasStateAuthority == false)
    {
        return;
    }

    Vector3 move = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")) * Runner.DeltaTime * PlayerSpeed;

    
    _controller.Move(move);

    if (move != Vector3.zero)
    {
        gameObject.transform.forward = move;
    }
}

HasStateAuthority can be used to check whether the client controls an object. NetworkTransform only synchronizes changes from the StateAuthority to everyone else. If another client changes the position of the object that change will only be a local change and overridden with data from the network in the future.

Always use Runner.DeltaTime for code running in FixedUpdateNetwork!

Add the PlayerMovement component to the player prefab.

Camera

There are many ways of setting up a camera (third person, first person etc.) and different ways to implement them. In a multiplayer game often a single camera that is following the local player object is desired. There are two common ways to achieve this.

  1. When the local player object gets spawned, have it instantiate a camera.
  2. Have the camera as part of the scene. When the local player object gets spawned have it find the camera and set the target of the camera to it.

In this walkthrough the second approach is used.

Create a new script and name it FirstPersonCamera. Add the following code to it:

C#

using UnityEngine;

public class FirstPersonCamera : MonoBehaviour
{
    public Transform Target;
    public float MouseSensitivity = 10f;

    private float vertialRotation;
    private float horizontalRotation;
    
    void LateUpdate()
    {
        if (Target == null)
        {
            return;
        }

        transform.position = Target.position;
        
        float mouseX = Input.GetAxis("Mouse X");
        float mouseY = Input.GetAxis("Mouse Y");

        vertialRotation -= mouseY * MouseSensitivity;
        vertialRotation = Mathf.Clamp(vertialRotation, -70f, 70f);

        horizontalRotation += mouseX * MouseSensitivity;

        transform.rotation = Quaternion.Euler(vertialRotation, horizontalRotation, 0);
    }
}

This is a very simple first person camera implementation. Note that the code does not contain any multiplayer elements. You can replace this camera with any other camera setup that works with a target object including Cinemachine.

Put the FirstPersonCamera behaviour on the MainCamera GameObject in the scene. There is no need to drag in a target. The target will be set up at runtime by code.

Add Networking

To assign the target, open the PlayerMovement script. Add a variable to store the camera:

C#

public Camera Camera;

Then when the player object gets spawned find and set up the camera if it is the local player.

C#

public override void Spawned()
{
    if (HasStateAuthority)
    {
        Camera = Camera.main;
        Camera.GetComponent<FirstPersonCamera>().Target = GetComponent<NetworkTransform>().InterpolationTarget;
    }
}

Make sure to always use Spawned instead of Awake/Start when initializing NetworkObjects. In Awake/Start the NetworkObject might not be ready to use yet.

HasStateAuthority is only true for objects the player controls, so only the local player object and not other player objects.

Finally, since in first-person games the player character moves in the look direction adjust the movement a bit. Replace the line where the Vector3 move gets calculated with:

C#

var cameraRotationY = Quaternion.Euler(0, Camera.transform.rotation.eulerAngles.y, 0);
Vector3 move = cameraRotationY * new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")) * Runner.DeltaTime * PlayerSpeed;

Handling Button Presses

One time inputs such as GetButtonDown and up are raised in Unity during Update. In Fusion gameplay code such as movement should be executed in FixedUpdateNetwork. This means special handling is necessary for tracking button states accurately.

Note that the same issue also occurs when checking for button presses in FixedUpdate.

There are multiple approaches to capturing inputs like that in FixedUpdateNetwork:

  1. Query for button down in Update. Store the result in a bool and use that for game logic. Clear it at the end of FixedUpdateNetwork.
  2. Use Fusion's NetworkInput with NetworkButtons.
  3. Use the new Unity Input System and set Update Mode to Manual Update and call InputSystem.Update in FixedUpdateNetwork. ()
The above options only apply to shared mode. When running Fusion in server/host mode always use NetworkInput.

Jumping

Let's implement an example for handling button presses by implementing a jump action for the player. In this case option 1 from the options listed in the previous chapter is used.

Start by adding the following variables to PlayerMovement:

C#

    public float JumpForce = 5f;
    public float GravityValue = -9.81f;

    private Vector3 velocity;
    private bool _jumpPressed;

_jumpPressed will be used to track the button state. velocity is for adding artificial gravity to the KCC while airborne.

To poll the button down state each Update add the following code:

C#

void Update()
{
    if (Input.GetButtonDown("Jump"))
    {
        _jumpPressed = true;
    }
}

Then at the end of FixedUpdateNetwork clear the jump state:

C#

_jumpPressed = false;

Replace the line that calls _controller.Move with the following:

C#

_velocity.y += GravityValue * Runner.DeltaTime;
if (_jumpPressed && _controller.isGrounded)
{
    _velocity.y += JumpForce;
}
_controller.Move(move + _velocity * Runner.DeltaTime);

Finally, add the following immediately after the HasStateAuthority check to reset the velocity when on the ground:

C#

if (_controller.isGrounded)
{
    velocity = new Vector3(0, -1, 0);
}

Return to Unity and start the game and press Start Shared Client. Press the Space button and see the character jumping.

Next Shared Mode Basics 4 - Network Properties

Back to top