Spawning
Introduction
For a GameObject to exist across the network, it has to:
- have a
NetworkObject
component; and, - be created using the
Runner.Spawn()
method.
The NetworkObject
is used by Fusion to assign a network-wide unique identifier so all clients can agree on which instance is which and correctly synchronize each networked object's state. The Runner.Spawn()
method tells Fusion when and how to add a new object instance to the collective network state.
IMPORTANT!
Do NOT use Unity's built in GameObject.Instantiate()
method for networked object , this will just create a local game object which is completely detached from the Fusion simulation loop with a broken network state.
Runner.Spawn
The Runner.Spawn()
method on the Fusion NetworkRunner
instance mimics the GameObject.Instantiate()
method in Unity. The parameters that can be provided to the method are:
- a prefab of type
NetworkObject
. - a position
- a rotation
- a
PlayerRef
to identify the client with input authority over the object. - a delegate of type
NetworkRunner.OnBeforeSpawned
to run before replicating the object on the other instances - a prediction key of type
NetworkObjectPredictionKey
in case this is a predicted spawn.
Only the prefab is a mandatory parameter, all others are optional.
var obj = Runner.Spawn(prefab, Vector3.zero, Quaternion.identity, Runner.LocalPlayer, MyOnBeforeSpawnDelegate, key);
Although any client can call Runner.Spawn()
, the results will differ depending on the network topology.
- On the server, in hosted or client/server mode, ownership (State Authority) of the spawned object is assigned to the server, and the object is created and returned immediately. Clients will receive the object as part of the next snapshot.
- On a client in hosted or client/server mode, the default action is that nothing will happen and that the call will return null. However, Fusion supports creation of a temporary local object for instant feedback - this is referred to as "Predictive Spawning".
- In shared mode, State Authority is always assigned to the caller and, again, the instance is returned immediately. Other clients will receive the object eventually.
In either case, Spawn should be called from FixedUpdateNetwork
and it is ok to call during both Forward and Re-simulation stages. Fusion will correctly return the same object during re-simulation of predicted local spawns (assuming the prediction key matches), while authoritative spawns only happen in shared mode or on the Host, neither of which does re-simulation.
Input Authority
The input authority is assigned to a particular client by passing in their PlayerRef
to the method; this is optional. The client to whom input authority was given will be able to provide input data for the object and (in addition to the Host or Server) is allowed to query that input structure in GetInput()
.
If the object does not require input or no client has input authority over it, null
can be passed instead.
OnBeforeSpawned
The NetworkRunner.OnBeforeSpawned
parameter can take a method or lambda expression matching the delegate signature.
public delegate void OnBeforeSpawned(NetworkRunner runner, NetworkObject obj);
This delegate is invoked after the object is created but before it gets synchronized across all instances. This allows the caller to perform additional custom initialization of the object before any other part of the system is able to access it. This is a good place to initialize custom network properties.
private void MySpawnFunction(){
Runner.Spawn(
_objPrefab,
Vector3.zero,
Quaternion.identity,
inputAuthority: null,
InitializeObjBeforeSpawn,
predictionKey: null
);
}
private void InitializeObjBeforeSpawn(NetworkRunner runner, NetworkObject obj)
{
var objSB = obj.GetComponent<ObjSimulationBehaviour>();
objSB.InitializeObjSettings(_currentExplosionForce);
}
It is also possible to use a lambda expression to have the possibility to pass more parameters.
private void MySpawnFunction(){
Runner.Spawn(
_objPrefab,
Vector3.zero,
Quaternion.identity,
inputAuthority: null,
(Runner, NO) => NO.GetComponent<MyCustomBehaviour>().Init(myInt, myParameter)
predictionKey: null
);
}
Spawned
When Fusion creates a new object, it will call the Spawned()
method on that object. The Spawned()
callback is intended for resetting non-networked variables and sub-systems. For instance if the application is using Object Pooling, Spawned()
may need to reset values to their prefab defaults because the object has been recycled and no longer has its default state. All NetworkBehaviour
components implement the ISpawned
interface, thus every component deriving from NetworkBehaviour
can simply override and implement it. SimulationBehaviour
derived components, however, need to explicitly implement ISpawned
and its methods to have their Spawned()
method called.
Spawned()
is called when Fusion first learns of the object's existence. This is not tick aligned or necessarily anywhere near the same tick in which the object was originally spawned by the authoritative peer. For this reason Networked
state should not be initialized in Spawned()
, nor should RPC
s be sent in Spawned()
.
- For spawns on a peer with State Authority,
Spawned()
is called immediately afterRunner.Spawn()
. - For predicted spawns on a peer with Input Authority,
Spawned()
is called when network state is allocated (i.e. when the spawn is confirmed). - For proxies (i.e. object over which the peer has neither Input Authority nor State Authority),
Spawned()
is called when Fusion receives a network state with an object that does not exist locally. In particular a late-joiner will likely receive objects that were spawned a long time ago and the original state from their "birth tick" no longer be relevant.
If a state should be initialized at spawn, use the pre-spawn NetworkRunner.OnBeforeSpawned
callback supplied to Runner.Spawn()
.
Despawn
To remove a network object, the peer with State Authority over the object may call Runner.Despawn()
.
Despawned
Similar to how Runner.Spawn()
triggers the Spawned()
method call for classes implementing ISpawned
, Despawn()
will result in the Despawned()
method to be called for classes implementing IDespawned
.
All NetworkBehaviour
s imlement IDespawned
, whereas SimulationBehaviour
s have to add it explicitly.
Spawn Prediction
Predictive Spawning is named as such because it lets a non-authoritative client predict the future existence of an object and allows it to simulate a local placeholder until the proper networked object's existance has been confirmed as successful or failed.
Two things need to happen for this to work:
- A unique identifier, called Prediction Key, must be created to allow the client to match its temporary local object against a future "actual" instance. This will enable it to upgrade the local object to a first class network object when the spawn is confirmed.
- The client must handle the local object as a non-networked object (= not accessing the object's network state) until it is either confirmed or canceled.
NetworkObjectPredictionKey
The predictionKey
parameter being passed to the Runner.Spawn()
method consists of 4 bytes. The application is free to invent its own keys as long as it considers the following:
- The key must be the same on the client and the authoritative host/server, therefore it cannot be random or based on local data.
- It must be unique for the player and the current tick. It is sensible to include the tick (or some lower end portion of it) as well as the players id in the key.
var predictionKey = new NetworkObjectPredictionKey {Byte0 = (byte) Runner.Simulation.Tick, Byte1 = playerIndex};
N.B.: If multiple networked objects are predictively spawned by a client during the same tick, additional information is need (e.g. a shared counter) to tell the various spawned NetworkObjects
apart from each other.
IPredictedSpawnBehaviour
While the spawned game object is in its predicted state, it will not be part of the Fusion simulation and its networked properties will not work since there is not yet any state for them to access. In order for the predicted object to behave like a real object, some additional logic is needed. This logic has to be implemented using the IPredictedSpawnBehaviour
interface.
public interface IPredictedSpawnBehaviour {
void PredictedSpawnSpawned();
void PredictedSpawnUpdate();
void PredictedSpawnRender();
void PredictedSpawnFailed();
void PredictedSpawnSuccess();
}
Spawned, Update, Render
The three first methods match the Spawned()
, FixedUpdateNetwork()
and Render()
methods found in functional SimulationBehaviour
and NetworkBehaviour
components. They are called under the same conditions as those methods with the difference that these are exclusively and explicitly called when the object is in its "predicted" state.
In fact, one possible implementation of those methods is to simply call Spawned()
, FixedUpdateNetwork()
and Render()
, respectively, though the application needs to consider that network properties are not available. One solution to that is to wrap the networked properties with a check for predictive spawning and maintain two sets of variables, like this:
[Networked] private Vector3 _networkedVelocity { get; set; }
private Vector3 _predictedVelocity;
public Vector3 Velocity
{
get => Object.IsPredictedSpawn ? _predictedVelocity : _networkedVelocity;
set
{
if (Object.IsPredictedSpawn)
_predictedVelocity = value;
else
_networkedVelocity = value;
}
}
Failed And Success
PredictedSpawnFailed()
and PredictedSpawnSuccess
are specific to prediction and triggered when the Server / Host confirms the success or failure of the spawn action. If it prediciton was successful PredictedSpawnSuccess()
is called, otherwise PredictedSpawnFailed()
is triggered.
The application rarely needs to handle successful spawns, since the temporary object is simply promoted to a fully networked object and continues to exist as if nothing had changed; however, prediction failures do need to be handled - the simplest implementation is to just destroy the object.
When using Runner.Despawn()
on a predicted object, it requires an additional boolean parameter to indicate the object in question was in a predicted state.
public void PredictedSpawnFailed()
{
Runner.Despawn(Object, true);
}
Runner.Despawn()
has to be used because Runner.Spawn()
obtained the temporary object through the Fusion Object Pool and thus needs to return it to the pool again.