This document is about: FUSION 2
SWITCH TO

3 - Movement & Camera

Overview

In Part 3 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 and add the following code to the script:

C#

using Fusion;
using UnityEngine;

public class PlayerMovement : NetworkBehaviour
{
    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;
        }
    }
}

Note that it inherits from NetworkBehaviour instead of MonoBehaviour which provides FixedUpdateNetwork and allows for the use of Networked Properties later on.

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!

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 section is used.

Replace the code in the PlayerMovement script with the following:

C#

using Fusion;
using UnityEngine;

public class PlayerMovement : NetworkBehaviour
{
    private Vector3 _velocity;
    private bool _jumpPressed;
    
    private CharacterController _controller;

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

    private void Awake()
    {
        _controller = GetComponent<CharacterController>();
    }
    
    void Update()
    {
        if (Input.GetButtonDown("Jump"))
        {
            _jumpPressed = true;
        }
    }

    public override void FixedUpdateNetwork()
    {
        // Only move own player and not every other player. Each player controls its own player object.
        if (HasStateAuthority == false)
        {
            return;
        }
        
        if (_controller.isGrounded)
        {
            _velocity = new Vector3(0, -1, 0);
        }
        
        Vector3 move = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")) * Runner.DeltaTime * PlayerSpeed;

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

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

This code adds a jump function and velocity/gravity handling to the KCC.

_jumpPressed is used to track the button state. The button is polled each Update to make sure no press is missed and reset at the end of FixedUpdateNetwork.

C#

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

Is used to make sure the KCC remain fixed to the ground even when walking over small slopes and that only jumping allows the player to leave the ground.

Play Testing

With the PlayerMovement script ready, return to Unity and add the PlayerMovement component to the PlayerCharacter prefab.

Now it is time to test the player movement. To have two clients available start by creating a Unity build.

Go to File > Build Settings and add the current scene to the list. Then press the Player Settings button on the bottom left of the window.

In the Player Settings under Resolution and Presentation set the Fullscreen Mode to Windowed and ensure Run In Background is checked.

download the sdk
Copy the App Id.

Return to the Build Settings window and select Build and Run to create a build. Enter play mode in Unity once the build is complete, so that two clients are available.

On both clients press the Start Shared Client button. Two players appear, each of them controlled by one of the clients. Test that walking around and jumping works.

Camera

To complete the player setup let's add a 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 attach the camera to itself.

In this tutorial 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 verticalRotation;
    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");

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

        horizontalRotation += mouseX * MouseSensitivity;

        transform.rotation = Quaternion.Euler(verticalRotation, 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 = transform;
    }
}

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#

Quaternion 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;

Next Shared Mode Basics 4 - Network Properties

Back to top