This document is about: FUSION 2
SWITCH TO

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.

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, and others are too expensive for general application or do 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 Fusion's physics addon 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, but keep in mind that running prediction and resimulation in PhysX is expensive (i.e. multiple times per tick).

To import the physics addon, head to `Assets/FusionAddons/Photon-Fusion-Physics-Addon.. and import the Unity package into the project.

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 goes.

  1. Create a new empty GameObject in the Unity Editor
  2. Rename GameObject to PhysxBall
  3. Add a new NetworkRigidbody3D 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. Add a Sphere child to PhysxBall
  6. Scale the child down to 0.2 in all directions
  7. Drag the child object into the InterpolationTarget of the NetworkRigidbody3D component on the parent object.
  8. Remove the collider from the Sphere
  9. Create a new sphere collider on the parent object with radius 0.1 so that it completely covers the child object.
  10. Add a new script to the game object and call it PhysxBall.cs,
  11. Drag the whole object into the project folder to create a prefab
  12. Save the scene to bake the network object and delete the prefab instance from the scene.
ball prefab
Ball Prefab

For physics objects separating the visuals and assigning them as the Interpolation Target is recommended but not always necessary. The collider and the rigidbody have to be on the parent object together with the NetworkRigidbody.

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 MOUSEBUTTON0 = 1;
  public const byte MOUSEBUTTON1 = 2;

  public NetworkButtons 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();

    ...

    data.buttons.Set(NetworkInputData.MOUSEBUTTON0, _mouseButton0);
    _mouseButton0 = false;
    data.buttons.Set(NetworkInputData.MOUSEBUTTON1, _mouseButton1);
    _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 (HasStateAuthority && delay.ExpiredOrNotRunning(Runner))
    {
      if (data.buttons.IsSet(NetworkInputData.MOUSEBUTTON0))
      {
        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.IsSet(NetworkInputData.MOUSEBUTTON1))
      {
        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 );
          });
      }
    }
  }
}

For networked physics to work a RunnerSimulatePhysics3D component is needed on the runner object. So open the BasicSpawner.cs and add the following line after the line which adds the runner component in the StartGame function:

C#

gameObject.AddComponent<RunnerSimulatePhysics3D>();

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

Next Host Mode Basics 5 - Property Changes

Back to top