Prefabs
Overview
Any prefab with NetworkObject
script on its root GameObject
and with the Is Spawnable
checkbox ticked can be spawned at runtime.
Fusion avoids creating hard references to NetworkObject
prefabs whenever possible. If a prefab is addressable or a Resource, Fusion will not reference it directly and store the address/resource path instead. In other case, a hard reference to the prefab will be stored in the config.
To make sure user code does not form hard references as well, use NetworkPrefabRef
type to reference prefabs. NetworkPrefabRef
is based on the prefab's GUID, so it is resilient to renaming/moving prefabs in the project.
Registering Prefabs
Fusion needs prefabs being registered before it is able to perform a networked spawn. Prefabs GUIDs and data needed to load them (address, resource path or a static reference) are stored in NetworkPrefabTable
accessible with NetworkProjectConfig.PrefabTable
property.
All the spawnable NetworkObject
prefabs in the project present at build time are detected and registered automatically. You can verify this by inspecting Prefabs
property of the NetworkProjectConfig
.
[A]
stands for Addressable prefabs.[R]
stands for Resources prefabs.[S]
stands for Statically-referenced prefabs.
Is Spawnable
checkbox on the prefab instead.Alternatively, you can inspect Prefab Source
label field of a prefab.
If either the table is incomplete or the Prefab Source
displays an invalid value, use Fusion > Rebuild Prefab Table
menu item or Rebuild Prefab Table
button in the config inspector.
All the spawnable prefabs are tagged with FusionPrefab
label as well. This means l:FusionPrefab
can be used to as a term in the Project Window search box:
Prefabs that are not present at built time (e.g. procedurally generated or new ones added with Addressables during a build's lifetime) can be registered at runtime with NetworkPrefabTable.TryAdd
.
C#
// this will register an Addressable prefab with a know guid without having to load it first
NetworkPrefabId RegisterAddressablePrefab(NetworkProjectConfig config, NetworkObjectGuid guid)
{
var source = ScriptableObject.CreateInstance<NetworkPrefabSourceUnityAddressable>();
source.Address = new AssetReferenceGameObject(guid.ToUnityGuidString());
if (config.PrefabTable.TryAdd(guid, source, out var id)) {
return id;
}
throw new ArgumentException($"Failed to register addressable guid: {guid}");
}
// this will register any prefab, but it needs to be loaded first (from whatever the source)
NetworkPrefabId RegisterPrefab(NetworkProjectConfig config, NetworkObject prefab)
{
var source = new NetworkPrefabSourceStatic {
PrefabReference = prefab
};
if (config.PrefabTable.TryAdd(prefab.NetworkGuid, source, out var id)) {
return id;
}
throw new ArgumentException($"Failed to register prefab with guid: {prefab.NetworkGuid}");
}
NetworkPrefabId
assignment is sequential). Make sure a prefab is registered on all the clients before there is an attempt to spawn it (e.g. register it before starting a runner).Spawning Prefabs
Using NetworkPrefabRef
To spawn prefabs use one of NetworkRunner.Spawn
methods. The recommended way is to use NetworkPrefabRef
fields to reference and to spawn prefabs, as it prevents hard references from being formed.
C#
public class SpawnWithNetworkPrefabRef : NetworkBehaviour {
public NetworkPrefabRef PrefabRef;
public NetworkObject SpawnPrefab() {
return Runner.Spawn(PrefabRef);
}
}
If a prefab is addressable or a Resource, Fusion will load the actual prefab before returning from Spawn
. In case of Addressables, AsyncOperationHandle.WaitForCompletion
will be used to ensure the prefab has been loaded. In case of Resources, Resources.Load
is used. If this introduces a significant delay, consider preloading prefabs - FusionAddressablePrefabsPreloader
provides a sample implementation.
Using Prefab References
Alternatively, NetworkObject
, GameObject
or SimulationBehaviour
subclass references can be used. Spawn
will throw an InvalidOperationException
if the reference passed does not belong to the spawnable prefab's root GameObject
. In case of NetworkObject
and GameObject
references, [NetworkPrefab]
attribute can be used to limit the inspector selection to only spawnable prefabs.
C#
public class SpawnWithReferences : NetworkBehaviour {
[NetworkPrefab]
public GameObject GameObjectRef;
[NetworkPrefab]
public NetworkObject NetworkObjectRef;
public NetworkTransform SimulationBehaviourSubclassRef;
public NetworkObject SpawnWithGameObjectRef() {
return Runner.Spawn(GameObjectRef);
}
public NetworkObject SpawnWithNetworkObjectRef() {
return Runner.Spawn(NetworkObjectRef);
}
public NetworkTransform SpawnWithSimulationBehaviourSubclassRef() {
return Runner.Spawn(SimulationBehaviourSubclassRef);
}
}
Using NetworkPrefabId
After a prefab is registered with NetworkPrefabTable
, a NetworkPrefabId
is returned. This type is size and network-optimized and meant to be used in runtime only, as its value for a prefab might be different once another prefab is added to/removed from the project. It is especially useful when registering additional prefabs manually with NetworkPrefabTable.TryAdd
.
C#
public class SpawnWithNetworkPrefabId : NetworkBehaviour {
[NonSerialized]
public NetworkPrefabId PrefabId;
public UnityEngine.AddressableAssets.AssetReferenceGameObject PrefabRef;
public void RegisterPrefab() {
var source = ScriptableObject.CreateInstance<NetworkPrefabSourceUnityAddressable>();
source.Address = PrefabRef;
if (!Runner.Config.PrefabTable.TryAdd(NetworkObjectGuid.Parse(PrefabRef.AssetGUID), source, out PrefabId)) {
Debug.LogError($"Failed to register {PrefabRef}");
}
}
public NetworkObject SpawnPrefab() {
Debug.Assert(PrefabId.IsValid);
return Runner.Spawn(PrefabId);
}
}
Dynamically registered prefabs can be spawned with their GUIDs or object references (if they have been loaded already) as well.
Custom
It is possible to use other means of referencing prefabs. Existing projects that use Addressables and AssetReferenceGameObject
fields already can instead first load the prefab and then spawn it, using the resulting reference:
C#
public class SpawnWithAddressables : NetworkBehaviour {
public UnityEngine.AddressableAssets.AssetReferenceGameObject AddressablePrefabRef;
public void SpawnAsync() {
AddressablePrefabRef.LoadAssetAsync().Completed += asyncOpHandle => {
Runner.Spawn(asyncOpHandle.Result); // asyncOpHandle.Result is of GameObject type
};
}
}
Similar approach can be used for projects already using Resources paths to reference prefabs.
Custom Content Delivery
If a project depends on a custom content delivery system, implementing custom prefab source should be considered. This allows the user to keep using NetworkPrefabRef
, asset references and editor prefab auto-discovery regardless of how the prefab is loaded at runtime. What follows is a simplified sample of how custom prefab source based on Asset Bundles can be implemented.
First, a runtime class implementing INetworkPrefabSource
is needed. The interface represents data and logic needed to load a single prefab. Instead of implementing INetworkPrefabSource
directly, the following snippet uses NetworkPrefabSourceUnityBase
- a ScriptableObject
-based base class that provides the basic implementation.
C#
public class NetworkPrefabSourceUnityAssetBundles : NetworkPrefabSourceUnityBase {
// name of the asset bundle
public string AssetBundleName;
// name of the asset in the asset bundle
public string AssetName;
private GameObject _prefab;
// this is what gets displayed in prefab's Prefab Source label field
public override string EditorSummary => $"[AssetBundle: {AssetBundleName}[{AssetName}]]";
public override void Load(in NetworkPrefabLoadContext context) {
// load the bundle, if not yet loaded
var bundle = AssetBundle.GetAllLoadedAssetBundles()
.FirstOrDefault(x => x.name == AssetBundleName);
if (bundle == null) {
var bundlePath = System.IO.Path.Combine(Application.streamingAssetsPath, AssetBundleName);
bundle = AssetBundle.LoadFromFile(bundlePath);
}
if (bundle == null) {
context.Error(new InvalidOperationException($"Unable to load asset bundle {AssetBundleName}"));
} else {
// actually load the prefab
_prefab = bundle.LoadAsset<GameObject>(AssetName);
context.Loaded(_prefab);
}
}
public override void Unload() {
if (_prefab) {
Destroy(_prefab);
_prefab = null;
}
}
}
Next, an editor-only implementation of INetworkPrefabSourceFactory
is needed. The resulting class detects whether a spawnable prefab at assetPath
should use custom asset loading - if yes, then it returns an initialized instance of NetworkPrefabSourceUnityAssetBundles
. There is no need to register the factory itself, as it is automatically detected by Fusion editor scripts.
C#
public class NetworkPrefabAssetFactoryAssetBundles : INetworkPrefabSourceFactory {
int INetworkPrefabSourceFactory.Order => 0;
Type INetworkPrefabSourceFactory.SourceType => typeof(NetworkPrefabSourceUnityAssetBundles);
NetworkPrefabSourceUnityBase INetworkPrefabSourceFactory.TryCreate(string assetPath) {
var prefabBundle = AssetDatabase.GetImplicitAssetBundleName(assetPath);
if (!string.IsNullOrEmpty(prefabBundle)) {
var result = ScriptableObject.CreateInstance<NetworkPrefabSourceUnityAssetBundles>();
result.AssetBundleName = prefabBundle;
result.AssetName = System.IO.Path.GetFileName(assetPath);
return result;
}
// returning null instructs Fusion to use other factories with greater Order
return null;
}
}
With these in place, any spawnable prefab assigned to an Asset Bundle should display its Prefab Source
as Asset Bundle and the entry in the Prefabs
property in the config inspector should be prefixed with [AB]
. If that is not the case immediately, consider using the Fusion > Rebuild Prefab Table
menu item.
Rebuild Prefab Table
repeatedly, some editor integration code is needed to listen to Asset Bundle assignment changes (check NetworkPrefabAssetFactoryAddressable
).