This document is about: FUSION 2-SHARED
SWITCH TO

5 - Car Adjustments

Overview

In Part 5 we will be making some slight adjustments to the CarControl script created as part of the Unity Wheel Collider tutorial in order to improve car position and movement prediction.

Position synchronisation

The Car position is synchronised by the NetworkTransform we added to it in the previous section. Additionally the Physics Forecast system performs some extrapolation beyond remote time which helps predict car positions. When Forecast Physics is disabled the positions are synchronised in remote time instead.

Confirm Physics Forecast is enabled by ensuring the box is ticked in the Network Project Config Asset.

Physics Forecast tick box in the Network Project Config Asset
Physics Forecast tick box in the Network Project Config Asset.

FixedUpdate()

For NetworkTransforms using physics ensure the car movement logic happens in FixedUpdate() rather than Update() or FixedUpdateNetwork().

This is unlike non-physics NetworkObject, which should use FixedUpdateNetwork().

For Forecast physics we use Unity's interpolation of RigidBody for smooth motion rather than the our network interpolation, which is tied to FixedUpdateNetwork().

Ensure the Interpolation mode of the RigidBody for the car is set to Interpolate in order to benefit from Unity's interpolation.

Setting the interpolation mode of the rigid body to Interpolate
Setting the interpolation mode of the rigid body to Interpolate.

Improving car movement prediction

In this step we will be adding support for networking the current acceleration state and turning amount for each car. This allows each client to predict the movement of cars based on the latest values it has received for them.

For example: If Client A is holding "left", then Client B will predict that Client A's car turning state will continue to be "left" until it receives the next update. This allows Client B to predict Client A's car movement more realistically than not taking any turning amount into consideration.

Rather than MonoBehaviour our script needs to inherit from NetworkBehaviour in order to use Networked Properties to synchronise the car acceleration and turning state.

To create a "Networked Property", simply use a C# auto-implemented property for the value and add Fusion's [Networked] attribute.

Only the StateAuthority can change the value of a Networked Property. Just set it and Fusion automatically synchronizes the value from the StateAuthority to all other clients.

The code snippet below shows that the vInput (acceleration) and hInput (turning) values have been moved to Networked Properties.

C#

public class CarControl : NetworkBehaviour
{
    public float motorTorque = 2000;
    public float brakeTorque = 2000;
    public float maxSpeed = 20;
    public float steeringRange = 30;
    public float steeringRangeAtMaxSpeed = 10;
    public float centreOfGravityOffset = -1f;
    [Networked] private float vInput { get; set; }
    [Networked] private float hInput { get; set; }

Note that Networked Properties have to be auto-implemented properties ({get; set;}), regular fields are not supported.

This section of code has been changed at the top of FixedUpdate():

C#

    void FixedUpdate()
    {
        if (Object.HasStateAuthority)
        {
            vInput = Input.GetAxis("Vertical");
            hInput = Input.GetAxis("Horizontal");
        }

The vInput (acceleration) and hInput (turning) values are set in FixedUpdate(), but can only be set by the player that has StateAuthority. All clients will now receive all acceleration and turning values for all cars that they do not have authority over, and only the players that do have authority over those cars can set the properties.

The rest of FixedUpdate() on all clients uses the latest acceleration and turning information to act on the car wheel colliders. Adjusting the wheel colliders results in changes to the car's position. On non-authority clients, this provides a more accurate prediction of the cars movement than simply relying on the physics forecast system. This is because the physics forecast system is unaware of the additional acceleration or turning forces that may be being applied. The end result of applying the last know acceleration and turning information in addition to using the physics forecast system for smooth corrections results in a more seamless and accurate player experience.

Complete updated CarControl.cs script:

C#

using Fusion;
using UnityEngine;

public class CarControl : NetworkBehaviour
{
    public float motorTorque = 2000;
    public float brakeTorque = 2000;
    public float maxSpeed = 20;
    public float steeringRange = 30;
    public float steeringRangeAtMaxSpeed = 10;
    public float centreOfGravityOffset = -1f;
    [Networked] private float vInput { get; set; }
    [Networked] private float hInput { get; set; }

    WheelControl[] wheels;
    Rigidbody rigidBody;

    // Start is called before the first frame update
    void Start()
    {
        rigidBody = GetComponent<Rigidbody>();

        // Adjust center of mass vertically, to help prevent the car from rolling
        rigidBody.centerOfMass += Vector3.up * centreOfGravityOffset;

        // Find all child GameObjects that have the WheelControl script attached
        wheels = GetComponentsInChildren<WheelControl>();
    }

    // Update is called once per frame
    void FixedUpdate()
    {
        if (Object.HasStateAuthority)
        {
            vInput = Input.GetAxis("Vertical");
            hInput = Input.GetAxis("Horizontal");
        }

        // Calculate current speed in relation to the forward direction of the car
        // (this returns a negative number when traveling backwards)
        float forwardSpeed = Vector3.Dot(transform.forward, rigidBody.velocity);


        // Calculate how close the car is to top speed
        // as a number from zero to one
        float speedFactor = Mathf.InverseLerp(0, maxSpeed, forwardSpeed);

        // Use that to calculate how much torque is available 
        // (zero torque at top speed)
        float currentMotorTorque = Mathf.Lerp(motorTorque, 0, speedFactor);

        // and to calculate how much to steer 
        // (the car steers more gently at top speed)
        float currentSteerRange = Mathf.Lerp(steeringRange, steeringRangeAtMaxSpeed, speedFactor);

        // Check whether the user input is in the same direction 
        // as the car's velocity
        bool isAccelerating = Mathf.Sign(vInput) == Mathf.Sign(forwardSpeed);

        foreach (var wheel in wheels)
        {
            if (!wheel.WheelCollider)
            {
                continue;
            }

            // Apply steering to Wheel colliders that have "Steerable" enabled
            if (wheel.steerable)
            {
                wheel.WheelCollider.steerAngle = hInput * currentSteerRange;
            }

            if (isAccelerating)
            {
                // Apply torque to Wheel colliders that have "Motorized" enabled
                if (wheel.motorized)
                {
                    wheel.WheelCollider.motorTorque = vInput * currentMotorTorque;
                }
                wheel.WheelCollider.brakeTorque = 0;
            }
            else
            {
                // If the user is trying to go in the opposite direction
                // apply brakes to all wheels
                wheel.WheelCollider.brakeTorque = Mathf.Abs(vInput) * brakeTorque;
                wheel.WheelCollider.motorTorque = 0;
            }
        }
    }
}

Playing the Game

Congratulations! You have successfully completed this tutorial on adding Shared Mode Fusion networking to an existing car game. To ensure everything is functioning properly, it's now time to play and test the game.

The simplest way to test with two players connected is to make a build of the game and launch two instances at once. After starting each client should be in control of their own car.

Testing two players playing together
Testing two players playing together

Next: Essential Forecast Vehicle Physics Tutorial 6 - Where to go next

Back to top