This document is about: FUSION 2
SWITCH TO

Advanced Spawning

Overview

Spawning in Fusion consists of calling Runner.Spawn(), Runner.SpawnAsync(), or Runner.TrySpawn(). This call creates a network entity that is replicated to all other peers.

  • In Client-Server Modes (Server/Host/Single) only the Server can spawn objects, and that spawn is replicated to all clients.
  • In Shared Mode any client may call Spawn(), but that call generates a message to the Shared Server which then initiates the actual spawn.

When Spawn() is called or replicated, a Network Object (a Unity GameObject with a NetworkObject component) is produced in response. How that game object is produced is determined by the Object Provider associated with the NetworkRunner.

Object Provider

The Object Provider is an INetworkObjectProvider implementation that defines how Runner.Spawn() and Runner.Despawn() operations commission and decommission Unity GameObjects. Every active NetworkRunner has an associated instance of an INetworkObjectProvider. By default this will be an instance of NetworkObjectProviderDefault.

Custom Object Provider Uses

Creating your own customized INetworkObjectProvider class allows precise control over the lifecycle of the actual GameObjects which are spawned, and can be useful cases such as;

  • Pooling
  • Dynamically creating Network Objects at runtime
  • Advanced handling for migration to and from single player modes

Assigning the Object Provider

There are several ways to set the Object Provider for a Network Runner.

  • Pass an instance of INetworkObjectProvider when calling Runner.GameStart().

C#

runner.StartGame(new StartGameArgs(){ ObjectProvider = new MyObjectProvider()});
  • or; Add a component which implements INetworkObjectProvider to the Network Runner game object.
  • or; Do neither, and the Runner will automatically add NetworkObjectProviderDefault to its GameObject.

INetworkObjectProvider Implementation

The INetworkObjectProvider interface requires the implementation of the following two methods:

  • AcquireInstance(): used to acquire an object from the pool when NetworkRunner.Spawn() has been called.
  • ReleaseInstance(): used to release and return an object from the pool when NetworkRunner.Despawn() has been called.

NetworkObjectProviderDefault

NetworkObjectProviderDefault is the default fallback implementation of INetworkObjectProvider. If you do not specify a provider in the StartArgs passed in Runner.StartGame(), the Runner will search its game object for a component which implements INetworkObjectProvider. If no implementation is found, the Runner will then fallback to automatically adding a NetworkObjectProviderDefault component to its game object - which is then used as the Object Provider.

NetworkObjectProviderDefault is a very basic implementation that will produce Objects in response to Spawn() by instantiating a copy of a passed prefab - without any pooling. Conversely, Despawn() calls are handled with Destroy().

Custom Pooling

Object pooling is a common pattern used to minimize memory fragmentation as well as lower the burden placed on the CPU and the garbage collector.

For the same reasons it is advisable, albeit not necessary, to implement pooling of NetworkObjects in your INetworkObjectProvider implementation.

The simplest way to add pooling is to derive from NetworkObjectProviderDefault and override the InstantiatePrefab() and DestroyPrefabInstance() methods to take instances from and return instances to a pool. For a complete INetworkObjectProvider pooling implementation, refer to the Fusion Object Pooling sample.

Reset Local State

When borrowing an object from the pool or returning it, make sure to reset any local (non-networked) state on the object, e.g. rigidbody velocities, animators, particle and trail effects, timers and other plain C# fields. Otherwise, that state bleeds through the lifetimes of the pooled object into its next use. Networked state does not need to be reset manually, it is reset by Fusion on its own.

Providing Custom Objects at Runtime

In order to dynamically generate game objects at runtime (instead of spawning using Prefabs), you will need to create a custom INetworkObjectProvider class, and override the AcquireInstance() method to:

  1. Create a GameObject using Instantiate(), new GameObject(), or GameObject.CreatePrimitive().
  2. Add a NetworkObject component if one does not exist already on the GameObject.
  3. Add any desired child GameObjects and NetworkBehaviours.
  4. NetworkObjectBaker.Bake() the Network Object (this must be done last after all changes to the Network Object structure).
  5. Return the NetworkObject instance as the result.

It is possible (and in most cases recommended) to use NetworkObjectProviderDefault as a base class when making your own custom INetworkObjectProvider implementation.

IMPORTANT: Once spawned and attached, Network Objects CANNOT have Network Behaviours added or removed. Any customization of the Network Behaviours on a Network Object must be done before spawning. It is of course still possible to add and remove any non-networked components at any time.

Custom Object Provider Example

This example code demonstrates creating a custom Network Object at runtime, as an alternative to spawning with pre-made Prefabs.

C#

public class BakingObjectProvider : NetworkObjectProviderDefault
{
  // For this sample, we are using very high flag values to indicate custom.
  // Other values will fall through the default instantiation handling.
  public const int CUSTOM_PREFAB_FLAG = 100000;

  // The NetworkObjectBaker class can be reused and is Runner independent.
  private static NetworkObjectBaker _baker;
  private static NetworkObjectBaker Baker => _baker ??= new NetworkObjectBaker();

  public override NetworkObjectAcquireResult AcquireInstance(NetworkRunner runner, in NetworkObjectAcquireContext context, out NetworkObject result) 
  {
    // Detect if this is a custom spawn by its high prefabID value we are passing.
    // The Spawn call will need to pass this value instead of a prefab.
    if (context.TypeId.IsPrefab && context.TypeId.AsPrefabId.RawValue == CUSTOM_PREFAB_FLAG) 
    {
      var go = GameObject.CreatePrimitive(PrimitiveType.Cube);
      var no = go.AddComponent<NetworkObject>();
      go.AddComponent<NetworkTransform>();
      go.name = $"Custom Object"; 
    
      // Baking is required for the NetworkObject to be valid for spawning.
      Baker.Bake(go);
    
      // Move the object to the applicable Runner Scene/PhysicsScene/DontDestroyOnLoad
      // These implementations exist in the INetworkSceneManager assigned to the runner.
      if (context.DontDestroyOnLoad) 
      {
        runner.MakeDontDestroyOnLoad(go);
      }  
      else 
      {
        runner.MoveToRunnerScene(go);
      }

      // We are finished. Return the NetworkObject and report success.
      result = no;
      return NetworkObjectAcquireResult.Success;        
    }
    
    // For all other spawns (regular prefabs AND scene objects, provided via context.AttachableInstance),
    // use the default behaviour.
    return base.AcquireInstance(runner, context, out result);
  }
}

Last updated on

Back to top