diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/network-prefab-handler.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/network-prefab-handler.md index 5adfb60839..47331d1d37 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/network-prefab-handler.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/network-prefab-handler.md @@ -22,11 +22,11 @@ Prefab handlers are classes which implement on of the Netcode for GameObjects pr - **INetworkPrefabInstanceHandler**: This is the simplest interface for custom prefab handlers. - **NetworkPrefabInstanceHandlerWithData**: This specialized handler receives custom data from the authority during spawning, enabling dynamic prefab customization. -Netcode will use the `Instantiate` and `Destroy` methods in place of default spawn handlers for the `NetworkObject` used during spawning and despawning. Because the message to instantiate a new `NetworkObject` originates from the [authority](../terms-concepts/authority.md), not all game clients will have the Instantiate method. All non-authority clients will have the instantiate method invoked if the `INetworkPrefabInstanceHandler` implementation is registered with `NetworkPrefabHandler` (`NetworkManager.PrefabHandler`) and the authority spawns the registered/associated `NetworkObject`. +Netcode will use the `Instantiate` and `Destroy` methods in place of default spawn handlers for the `NetworkObject` used during spawning and de-spawning. The authority instance will use the traditional spawning approach where it will, via user script, instantiate and spawn a network prefab (even for those registered with a prefab handler). However, all non-authority clients will automatically use the instantiate method defined by the `INetworkPrefabInstanceHandler` implementation if the network prefab spawned has a registered `INetworkPrefabInstanceHandler` implementation with the `NetworkPrefabHandler` (`NetworkManager.PrefabHandler`). ### INetworkPrefabInstanceHandler -This is the simplest prefab handler description. Use the `INetworkPrefabInstanceHandler` for situations where the prefab override behaviour is consistent and known. +When all you want to do is handle overriding a network prefab, implementing the `INetworkPrefabInstanceHandler` interface and registering an instance of that implementation with the `NetworkPrefabHandler` (`NetworkManager.PrefabHandler`) is all you would need to do. Use the `INetworkPrefabInstanceHandler` for situations where the prefab override behavior is consistent and known. ```csharp public interface INetworkPrefabInstanceHandler @@ -38,7 +38,7 @@ This is the simplest prefab handler description. Use the `INetworkPrefabInstance ### NetworkPrefabInstanceHandlerWithData -the `NetworkPrefabInstanceHandlerWithData` allows for sending custom data from the authority during object spawning. This extra data can then be used to change the behavior of the `Instantiate` method. An implementation of `NetworkPrefabInstanceHandlerWithData` allows for sending any custom type that is serializable using [INetworkSerializable](advanced-topics/serialization/inetworkserializable.md). +If you are looking to provide serialized data to be used during the instantiation process, then the `NetworkPrefabInstanceHandlerWithData` class is what you would want to derive from and register with the `NetworkPrefabHandler` (`NetworkManager.PrefabHandler`). The `NetworkPrefabInstanceHandlerWithData` class allows for sending custom data from the authority during object spawning. This extra data can then be used to change the behavior of the `Instantiate` method. An implementation of `NetworkPrefabInstanceHandlerWithData` allows for sending any custom type that is serializable using [INetworkSerializable](advanced-topics/serialization/inetworkserializable.md). ```csharp public abstract class NetworkPrefabInstanceHandlerWithData : INetworkPrefabInstanceHandlerWithData @@ -51,7 +51,7 @@ public abstract class NetworkPrefabInstanceHandlerWithData : INetworkPrefabIn ## Prefab handler registration -Once you have created a class to be your prefab handler, you can then register the class with the network prefab handler system using `NetworkManager.PrefabHandler.AddHandler`. Prefab handlers are registered against a NetworkObject's [GlobalObjectIdHash](../basics/networkobject.md#using-networkobjects). +Once you have created your prefab handler (_derived class or interface implementation_), you will need to register any new instance of your prefab handler with the network prefab handler system using `NetworkManager.PrefabHandler.AddHandler`. Prefab handlers are registered against a NetworkObject's [GlobalObjectIdHash](../basics/networkobject.md#using-networkobjects). ```csharp public class GameManager : NetworkBehaviour @@ -66,7 +66,7 @@ public class GameManager : NetworkBehaviour } ``` -Prefab handlers can be unregistered using `NetworkManager.PrefabHandler.RemoveHandler`. +In order to un-register a prefab handler, you can [invoke the `NetworkManager.PrefabHandler.RemoveHandler` method](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.4/api/Unity.Netcode.NetworkPrefabHandler.html#Unity_Netcode_NetworkPrefabHandler_RemoveHandler_System_UInt32_) (_There are several override versions of this method_). ## Object spawning with prefab handlers @@ -76,68 +76,151 @@ Note that the `Initialize` method is only called on non-authority clients. To cu ### Object spawning with custom data -For handlers that support custom data, the data to send needs to be manually set. To do this, call `SetInstantiationData` before calling the `Spawn` method. If `SetInstantiationData` is not called, the `default` implementation will be sent to the `Instantiate` call. +When using a handler derived from `NetworkPrefabInstanceHandlerWithData`, you must manually set the instantiation data after instantiating the instance but before spawning. To do this, invoke the `NetworkPrefabInstanceHandlerWithData.SetInstantiationData` method before invoking the `NetworkObject.Spawn` method. If `SetInstantiationData` is not called, the `default` implementation will be sent to the `Instantiate` call. + +#### Example + +Below we can find an example script where the `InstantiateData` structure implements the `INetworkSerializable` interface and it will be used to serialize the instantiation data for the network prefab defined within the below `SpawnPrefabWithColor` NetworkBehaviour. ```csharp -public struct SpawnData : INetworkSerializable +/// +/// The instantiation data that is serialzied and sent with the +/// spawn object message and provided prior to instantiating. +/// +public struct InstantiateData : INetworkSerializable { - public int version; - public string name; + // For example purposes, the color of the material of a MeshRenderer + public Color Color; + + // Add additional pre-spawn configuration fields here: public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { - serializer.SerializeValue(ref version); - serializer.SerializeValue(ref name); + serializer.SerializeValue(ref Color); + + // Add addition (field) value serialization here for each new field: } } +``` + +The below `SpawnPrefabWithColor` is a very simple example of a NetworkBehavior component that is to be placed on an in-scene placed NetworkObject. This allows you to configure which network prefab is going to be used to register the `SpawnWithColorSystem`and has a `SpawnWithColorSystem.SpawnObject` method that can be used to instantiate the network prefab instance with instantiation data containing the color to be applied. We can also see that each client/server instance of the `SpawnPrefabWithColor` component will create a `SpawnWithColorHandler` instance. -public class CountingObject : NetworkBehaviour +_While there are much easier ways to synchronize the color of MeshRenderer instances across clients, this is only for example purposes._ + +```csharp +/// +/// Add to an in-scene placed . +/// +public class SpawnPrefabWithColor : NetworkBehaviour { - [SerializeField] private GameObject prefabToSpawn; - public string currentName + /// + /// The network prefab used to register the handler. + /// + public GameObject NetworkPrefab; - public void SpawnObject(int objectTypeToSpawn) - { - var instance = Instantiate(prefabToSpawn); + /// + /// The handler instance. + /// + private SpawnWithColorHandler m_SpawnWithColorHandler; - // Set data before spawning - var customSpawnData = new SpawnData { version: objectTypeToSpawn, name: currentName} - NetworkManager.Singleton.PrefabHandler.SetInstantiationData(instance, customSpawnData); + protected override void OnNetworkPreSpawn(ref NetworkManager networkManager) + { + m_SpawnWithColorHandler = new SpawnWithColorHandler(networkManager, NetworkPrefab); + base.OnNetworkPreSpawn(ref networkManager); + } - instance.Spawn(); + /// + /// Invoked by some other component or additional script logic controls + /// when an object is spawned. + /// + /// The color to apply (pseudo example purposes) + /// The spawned instance + public NetworkObject SpawnObject(Vector3 position, Quaternion rotation, Color color) + { + if (!IsSpawned || !HasAuthority || m_SpawnWithColorHandler == null) + { + return null; + } + // Instantiate, set the instantiation data, and then spawn the network prefab. + return m_SpawnWithColorHandler.InstantiateSetDataAndSpawn(position, rotation, new InstantiateData() { Color = color }); } } ``` +Above, we can see the `SpawnWithColorSystem` invokes the `SpawnWithColorHandler.InstantiateSetDataAndSpawn` method to create a new network prefab instance, set the instantiation data, and then spawn the network prefab instance. -All non-authority clients will then receive this data when `Instantiate` is called. +Below we will find the more complex of the three scripts for this example. The `SpawnWithColorHandler` constructor automatically registers itself with the `NetworkManager`. ```csharp -public class SpawnWithDataSystem : NetworkPrefabInstanceHandlerWithData +/// +/// The prefan instance handler that uses instantiation data to handle updating +/// the instance's s material's color. (example purposes only) +/// +public class SpawnWithColorHandler : NetworkPrefabInstanceHandlerWithData { - public override NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation, SpawnData data) + private GameObject m_RegisteredPrefabToSpawn; + private NetworkManager m_NetworkManager; + + /// + /// Constructor + /// + /// The prefab used to register this handler instance. + /// The prefab group this handler will be able to spawn. + public SpawnWithColorSystem(NetworkManager networkManager, GameObject registeredPrefab) { - // Create the client-side prefab using the spawn data from the authority - var prefabToSpawn = GetPrefabForVersion(data.version); - var instance = Instantiate(prefabToSpawn); + m_NetworkManager = networkManager; + m_RegisteredPrefabToSpawn = registeredPrefab; + + // Register this handler with the NetworkPrefabHandler + m_NetworkManager.PrefabHandler.AddHandler(m_RegisteredPrefabToSpawn, this); + } + + /// + /// Used by the server or a client when using a distributed authority network topology, + /// instantiate the prefab, set the instantiation data, and then spawn. + /// + public NetworkObject InstantiateSetDataAndSpawn(Vector3 position, Quaternion rotation, InstantiateData instantiateData) + { + var instance = GetPrefabInstance(position, rotation, instantiateData); - var obj = instance.GetComponent(); - obj.currentName = data.name; + // Set the spawndata before spawning + m_NetworkManager.PrefabHandler.SetInstantiationData(instance, instantiateData); + + instance.GetComponent().Spawn(); + return instance; + } + + /// + /// Returns an instance of the registered prefab (no instantiation data set yet) + /// + public NetworkObject GetPrefabInstance(Vector3 position, Quaternion rotation, InstantiateData instantiateData) + { + // Optional to include your own position and/or rotation within the InstantiateData. + var instance = Object.Instantiate(m_RegisteredPrefabToSpawn, position, rotation); + var meshRenderers = instance.GetComponentsInChildren(); + foreach (var renderer in meshRenderers) + { + // Assign the color to each MeshRenderer (just a psuedo example) + renderer.material.color = instantiateData.Color; + } return instance.GetComponent(); } - public override void Destroy(NetworkObject networkObject) + /// + public override NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation, InstantiateData instantiateData) { - Object.DestroyImmediate(networkObject.gameObject); + // For non-authority instances, we can just get an instance based off of the passed in InstantiateData + return GetPrefabInstance(position, rotation, instantiateData); } - private GameObject GetPrefabForVersion(int version) + /// + public override void Destroy(NetworkObject networkObject) { - // Here you can implement logic to return a different client-side game object - // depending on the information sent from the server. + Object.DestroyImmediate(networkObject.gameObject); } } ``` +When instantiating from user script for a host, server, or distributed authority client, the above `InstantiateSetDataAndSpawn` method is used. When instantiating on non-authority instances the `GetPrefabInstance` is used since the authority provides the instantiation data. ## Further Reading