This document is about: FUSION 1
SWITCH TO

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

4 - Physics

Overview

Fusion 104 will examine how Fusion interacts with PhysX in a server authoritative game.

At the end of this section, the project will allow the player to spawn and interact with a physics controlled ball.

Consult the Manual for an in-depth description of the settings for this topic

Setup

By default, Fusion will run the physics simulation on the host and the clients will follow. However, this puts physics objects in a different time compared to local predicted objects which will cause problems for the physics simulation if these objects collide.

Dual time is a complex topic which can be dealt with in a variety of ways - some are downright hacks, others too expensive for general application or does not provide the immediate feedback gamers expect. There is unfortunately not a simple fix that works for all cases.

For this tutorial we will use Fusions built-in ability to predict physics objects locally, placing the physics controlled objects in the same time as the player controlled avatar. This is a solid solution which can be used in production as well; keep in mind that running prediction and resimulation in PhysX is expensive (i.e. multiple times per tick).

To turn on this feature, go to the Fusion menu and select Network Project Config. This opens the Fusion configuration file in the inspector. Under Server Physics Mode choose Client Prediction.

Physics Object

The prefab for a networked, PhysX controlled, object uses the same Rigidbody as a regular Unity PhysX object, but has a different Fusion component to synchronize the visual child object called NetworkRigidbody. This replaces the NetworkTransform as far as networking go.

  1. Create a new empty GameObject in the Unity Editor
  2. Rename GameObject to PhysxBall
  3. Add a new NetworkRigidbody component.
  4. Fusion will show a warning about a missing NetworkObject component, so go ahead and press Add Network Object. Fusion will automatically add a regular Unity RigidBody as well since this is needed for the PhysX simulation.
  5. Change Interpolation Data Source to Predicted and set it to World Space.
  6. Add a Sphere child to PhysxBall
  7. Scale the child down to 0.2 in all directions
  8. Drag the child object into the InterpolationTarget of the NetworkTransform component on the parent object.
  9. Remove the collider from the Sphere
  10. Create a new sphere collider on the parent object with radius 0.1 so that it completely covers the child object.
  11. Add a new script to the game object and call it PhysxBall.cs,
  12. Drag the whole object into the project folder to create a prefab
  13. Save the scene to bake the network object and delete the prefab instance from the scene.
Ball Prefab
Ball Prefab

The PhysxBall Script

Because the ball is driven by PhysX and the NetworkRigidbody takes care of the networked data, it needs less special code to get working than its kinematic cousin. All that needs to be added to PhysxBall.cs is the timer that will despawn the ball after a few seconds (this is exactly the same as for the kinematic ball), as well as a method to set the initial forward velocity.

Both are covered by the Init() method, like this:

C#

using UnityEngine;
using Fusion;

public class PhysxBall : NetworkBehaviour
{
  [Networked] private TickTimer life { get; set; }

  public void Init(Vector3 forward)
  {
    life = TickTimer.CreateFromSeconds(Runner, 5.0f);
    GetComponent<Rigidbody>().velocity = forward;
  }

  public override void FixedUpdateNetwork()
  {
    if(life.Expired(Runner))
      Runner.Despawn(Object);
  }
}

Input

To spawn the ball, the code needs to be extended following the same three steps as for the kinematic ball, but changed to use the second mouse button instead:

1. NetworkInputData

In NetworkInputData.cs Simply add a new button flag:

C#

using Fusion;
using UnityEngine;

public struct NetworkInputData : INetworkInput
{
  public const byte MOUSEBUTTON1 = 0x01;
  public const byte MOUSEBUTTON2 = 0x02;

  public byte buttons;
  public Vector3 direction;
}

2. BasicSpawner

In BasicSpawner.cs Poll the second mouse button in the same fashion as the first, and set the flag accordingly:

C#

  private bool _mouseButton0;
  private bool _mouseButton1;
  private void Update()
  {
    _mouseButton0 = _mouseButton0 || Input.GetMouseButton(0);
    _mouseButton1 = _mouseButton1 || Input.GetMouseButton(1);
  }

  public void OnInput(NetworkRunner runner, NetworkInput input)
  {
    var data = new NetworkInputData();

    ...

    if (_mouseButton1)
      data.buttons |= NetworkInputData.MOUSEBUTTON2;
    _mouseButton1 = false;

    input.Set(data);
  }

3. Player

Player.cs holds the code that spawns the actual ball prefab so in addition to needing a prefab reference like this,

C#

[SerializeField] private PhysxBall _prefabPhysxBall;

the Player must also call spawn and set the velocity (just a constant multiplied to the last forward direction) using the Init() method created earlier.

C#

public override void FixedUpdateNetwork()
{
  if (GetInput(out NetworkInputData data))
  {
    data.direction.Normalize();
    _cc.Move(5*data.direction*Runner.DeltaTime);

    if (data.direction.sqrMagnitude > 0)
      _forward = data.direction;

    if (delay.ExpiredOrNotRunning(Runner))
    {
      if ((data.buttons & NetworkInputData.MOUSEBUTTON1) != 0)
      {
        delay = TickTimer.CreateFromSeconds(Runner, 0.5f);
        Runner.Spawn(_prefabBall, 
          transform.position+_forward,
          Quaternion.LookRotation(_forward), 
          Object.InputAuthority, 
          (runner, o) =>
          {
            // Initialize the Ball before synchronizing it
            o.GetComponent<Ball>().Init();
          });
      }
      else if ((data.buttons & NetworkInputData.MOUSEBUTTON2) != 0)
      {
        delay = TickTimer.CreateFromSeconds(Runner, 0.5f);
        Runner.Spawn(_prefabPhysxBall, 
          transform.position+_forward, 
          Quaternion.LookRotation(_forward),           
          Object.InputAuthority, 
          (runner, o) =>
          {
            o.GetComponent<PhysxBall>().Init( 10*_forward );
          });
      }
    }
  }
}

Finally, to test the new ball, assign the created prefab to the Prefab Physx Ball field on the Player prefab, build and run.

Back to top