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 Server/Client 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 callingRunner.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 whenNetworkRunner.Spawn()
has been called.ReleaseInstance()
: used to release and return an object from the pool wheNetworkRunner.Destroy()
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 NetworkObject
s in your INetworkObjectProvider
implementation.
The object pool to be used for a Game Session needs to implement INetworkObjectPool
. Furthermore, it has to be known before it is started and assigned to the StartGameArgs.ObjectPool
parameter which is passed to the NetworkRunner.StartGame()
method. If no object pool is specified, then the NetworkObjectPoolDefault
will be used.
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 AcquirePrefabInstance()
method to:
- Create a GameObject using
Instantiate()
,new GameObject()
, orGameObject.CreatePrimitive()
. - Add a
NetworkObject
component if one does not exist already on the GameObject. - Add any desired child
GameObjects
andNetworkBehaviours
. NetworkObjectBaker.Bake()
the Network Object (this must be done last after all changes to the Network Object structure).- 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 AcquirePrefabInstance(NetworkRunner runner, in NetworkPrefabAcquireContext 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.PrefabId.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, use the default spawning.
return base.AcquirePrefabInstance(runner, context, out result);
}
}