diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 0e1bbc0de0..c28d573905 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -29,7 +29,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Added serializer for `Pose` (#3546) - Added methods `GetDefaultNetworkSettings` and `GetDefaultPipelineConfigurations` to `UnityTransport`. These can be used to retrieve the default settings and pipeline stages that are used by `UnityTransport`. This is useful when providing a custom driver constructor through `UnityTransport.s_DriverConstructor`, since it allows reusing or tuning the existing configuration instead of trying to recreate it. This means a transport with a custom driver can now easily benefit from most of the features of `UnityTransport`, like integration with the Network Simulator and Network Profiler from the multiplayer tools package. (#3501) - Added mappings between `ClientId` and `TransportId`. (#3516) -- Added `NetworkPrefabInstanceHandlerWithData`, a variant of `INetworkPrefabInstanceHandler` that provides access to custom instantiation data directly within the `Instantiate()` method. (#3430) +- Added `NetworkPrefabInstanceHandlerWithData`, a variant of `INetworkPrefabInstanceHandler` that provides access to custom instantiation data directly within the `Instantiate()` method. (#3497) ### Fixed diff --git a/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md b/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md index f784cc5148..bf749ce60b 100644 --- a/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md +++ b/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md @@ -34,6 +34,7 @@ * [Ownership race conditions](basics/race-conditions.md) * [Spawning and despawning](spawn-despawn.md) * [Object spawning](basics/object-spawning.md) + * [Network prefab handler](advanced-topics/network-prefab-handler.md) * [Object pooling](advanced-topics/object-pooling.md) * [Object visibility](basics/object-visibility.md) * [Spawning synchronization](basics/spawning-synchronization.md) @@ -100,4 +101,4 @@ * [NetworkObject parenting](samples/bossroom/networkobject-parenting.md) * [Optimizing Boss Room](samples/bossroom/optimizing-bossroom.md) * [NetworkRigidbody](samples/bossroom/networkrigidbody.md) - * [Spawn NetworkObjects](samples/bossroom/spawn-networkobjects.md) \ No newline at end of file + * [Spawn NetworkObjects](samples/bossroom/spawn-networkobjects.md) 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 new file mode 100644 index 0000000000..6608534375 --- /dev/null +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/network-prefab-handler.md @@ -0,0 +1,258 @@ +# Network prefab handler + +The network prefab handler system provides advanced control over how network prefabs are instantiated and destroyed during runtime. You can use it to override the default Netcode for GameObjects [object spawning](../basics/object-spawning.md) behavior by implementing custom prefab handlers. + +The network prefab handler system is accessible from the [NetworkManager](../components/networkmanager.md) as `NetworkManager.PrefabHandler`. + +## When to use a prefab handler + +For an overview of the default object spawning behavior, refer to the [object spawning page](../basics/object-spawning.md). The default spawning behavior is designed to cover the majority of use cases, however there are some scenarios where you may need more control: + +- **Object pooling**: Reusing objects to reduce memory allocation and initialization costs. +- **Performance optimization**: Using different prefab variants on different platforms (such as using a simpler object for server simulation). +- **Custom initialization**: Setting up objects with game client specific data or configurations. +- **Conditional spawning**: Initializing different prefab variants based on runtime conditions. + +The prefab handler system addresses these needs through an interface-based architecture. The system relies on two key methods: `Instantiate` and `Destroy`. `Instantiate` is called on non-authority clients when an [authority](../terms-concepts/authority.md) spawns a new [NetworkObject](../basics/networkobject.md) that has a registered network prefab handler. `Destroy` is called on all game clients whenever a registered [NetworkObject](../basics/networkobject.md) is destroyed. + +## Create a prefab handler + +Prefab handlers are classes that implement one of the Netcode for GameObjects prefab handler descriptions. There are currently two such descriptions: + +- [**INetworkPrefabInstanceHandler**](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.INetworkPrefabInstanceHandler.html): This is the simplest interface for custom prefab handlers. +- [**NetworkPrefabInstanceHandlerWithData**](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkPrefabInstanceHandlerWithData.html): This specialized handler receives custom data from the authority during spawning, enabling dynamic prefab customization. + +When using a prefab handler, Netcode for GameObjects uses the `Instantiate` and `Destroy` methods instead of default spawn handlers for the NetworkObject during spawning and despawning. The authority instance uses 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` + +For the simple use case of overriding a network prefab, implement the `INetworkPrefabInstanceHandler` interface and register an instance of that implementation with the `NetworkPrefabHandler` (`NetworkManager.PrefabHandler`). + +Use the `INetworkPrefabInstanceHandler` for situations where the prefab override behavior is consistent and known. + +```csharp + public interface INetworkPrefabInstanceHandler + { + NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation); + void Destroy(NetworkObject networkObject); + } +``` + +### `NetworkPrefabInstanceHandlerWithData` + +If you want to provide additional serialized data during the instantiation process, you can derive from the `NetworkPrefabInstanceHandlerWithData` class. + +The `NetworkPrefabInstanceHandlerWithData` class allows you to send custom data from the authority during object spawning. This extra data can then be used to change the behavior of the `Instantiate` method. Using `NetworkPrefabInstanceHandlerWithData`, you can send any custom type that is serializable using [`INetworkSerializable`](serialization/inetworkserializable.md). + +```csharp +public abstract class NetworkPrefabInstanceHandlerWithData : INetworkPrefabInstanceHandlerWithData + where T : struct, INetworkSerializable +{ + public abstract NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation, T instantiationData); + public abstract void Destroy(NetworkObject networkObject); +} +``` + +## Register a prefab handler + +Once you've [created a prefab handler](#create-a-prefab-handler), whether by implementing or deriving, you need to register any new instance of that 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 +{ + [SerializeField] private GameObject prefabToSpawn; + + void Start() + { + var customHandler = new MyPrefabHandler(); + NetworkManager.PrefabHandler.AddHandler(prefabToSpawn, customHandler); + } +} +``` + +To un-register a prefab handler, you can [invoke the `NetworkManager.PrefabHandler.RemoveHandler` method](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkPrefabHandler.html#Unity_Netcode_NetworkPrefabHandler_RemoveHandler_System_UInt32_). There are several override versions of this method. + +## Object spawning with prefab handlers + +Once a prefab handler is registered, Netcode for GameObjects automatically uses the defined `Initialize` and `Destroy` methods to manage the object lifecycle. [Spawn the network prefab as usual](../basics/object-spawning.md#spawning-a-network-prefab-overview) and the `Initialize` method will be called on whichever handler is registered with the spawned network prefab. + +Note that the `Initialize` method is only called on non-authority clients. To customize network prefab behavior on the authority, you can use [prefab overrides](../basics/object-spawning.md#taking-prefab-overrides-into-consideration). + +### Object spawning with custom data + +When using a handler derived from `NetworkPrefabInstanceHandlerWithData`, you must manually set the instantiation data after instantiating the instance (but before spawning) by invoking 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. + +#### Examples + +The first example here is a pseudo-script where the `InstantiateData` structure implements the `INetworkSerializable` interface and is used to serialize instantiation data for a network prefab defined within the `SpawnPrefabWithColor` NetworkBehaviour. + +```csharp +/// +/// The instantiation data that is serialized and sent with the +/// spawn object message and provided prior to instantiating. +/// +public struct InstantiateData : INetworkSerializable +{ + // 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 Color); + + // Add addition (field) value serialization here for each new field: + } +} +``` + +The second example, `SpawnPrefabWithColor`, is an example of a NetworkBehavior component, to be placed on an in-scene placed NetworkObject, that handles the instantiation of a prefab handler (`SpawnWithColorHandler`) and the means to configure the network prefab to register with the handler. It also has a `SpawnWithColorSystem.SpawnObject` method that can instantiate an instance of the assigned network prefab instance that will also have instantiation data associated with it that contains the color to be applied to the instance's MeshRenderers. + +There are 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 +{ + /// + /// The network prefab used to register the handler. + /// + public GameObject NetworkPrefab; + + /// + /// The handler instance. + /// + private SpawnWithColorHandler m_SpawnWithColorHandler; + + protected override void OnNetworkPreSpawn(ref NetworkManager networkManager) + { + m_SpawnWithColorHandler = new SpawnWithColorHandler(networkManager, NetworkPrefab); + base.OnNetworkPreSpawn(ref networkManager); + } + + /// + /// 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 }); + } +} +``` +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. + +The third example is the most complex: `SpawnWithColorHandler` with a constructor that automatically registers itself and the network prefab with the `NetworkManager.PrefabHandler`: + +```csharp +/// +/// The prefab instance handler that uses instantiation data to handle updating +/// the instance's s material's color. (example purposes only) +/// +public class SpawnWithColorHandler : NetworkPrefabInstanceHandlerWithData +{ + 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) + { + 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); + + // Set the instantiate data 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 pseudo example) + renderer.material.color = instantiateData.Color; + } + return instance.GetComponent(); + } + + /// + public override NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation, InstantiateData instantiateData) + { + // For non-authority instances, we can just get an instance based off of the passed in InstantiateData + return GetPrefabInstance(position, rotation, instantiateData); + } + + /// + public override void Destroy(NetworkObject networkObject) + { + 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` method is used, since the authority provides the instantiation data. + +While setting the color of a MeshRenderer doesn't really provide a broad spectrum use case scenario for `NetworkPrefabInstanceHandlerWithData`, the above example does provide you with a simple implementation to understand:: + +- Creating a serializable structure to be serialized with the spawned network prefab. +- Creating a component (in this case a NetworkBehaviour) that's used to instantiate the handler and configure the network prefab to be registered. +- Creating a `NetworkPrefabInstanceHandlerWithData` derived class that: + - Handles instantiating and destroying instances of the registered prefab. + - Provides an example of instantiating the network prefab, setting the instantiation data for the instance, and then spawning the instance. + - Provides an example of taking the de-serialized instantiation data and using one (or more) fields to configure the network prefab instance prior to it being spawned. + +### Pre-instantiation data and spawn data + +You can use `NetworkPrefabInstanceHandlerWithData` to include pre-spawn instantiation data that you can then use to define the instance before the instance is instantiated and/or spawned. However, the are some subtle differences between pre-spawn serialized data and spawn serialized data. + +- *Pre-spawn serialized data*: + - Is included in the NetworkObject serialized data. + - Is extracted and de-serialized prior to invoking the `NetworkPrefabInstanceHandlerWithData.Instantiate` method. + - Can be used to identify pre-instantiated objects and/or the type of network prefab to be spawned. + - Has no context for the network prefab that's going to be spawned, unless you provide that in the instantiation data. + - Can include any kind of serialized data types supported by Netcode for GameObjects. + - Can be used to spawn pre-determined instances that are created prior to connecting to the session or created while synchronizing with a session. +- *Spawn serialized data*: + - Isn't available until the network prefab is already instantiated and is in the middle of or has finished the spawn process. + - Can't be used to define what network prefab to instantiate. + - Typically will include other netcode related states such as NetworkVariables and RPCs. + +When it comes to including instantiation data, you should be cautious about including data from already spawned objects. You need to ensure that the serialized information of already spawned objects, like a `NetworkBehaviourReference` or `NetworkObjectReference`, exists prior to being used. + +## Additional resources + +- [Object pooling](./object-pooling.md) +- [Authority prefab overrides](../basics/object-spawning.md#taking-prefab-overrides-into-consideration) diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/object-pooling.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/object-pooling.md index 32e7add573..0d44441f31 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/object-pooling.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/object-pooling.md @@ -2,19 +2,9 @@ Netcode for GameObjects (Netcode) provides built-in support for Object Pooling, which allows you to override the default Netcode destroy and spawn handlers with your own logic. This allows you to store destroyed network objects in a pool to reuse later. This is useful for often used objects, such as projectiles, and is a way to increase the application's overall performance. By pre-instantiating and reusing the instances of those objects, object pooling removes the need to create or destroy objects at runtime, which can save a lot of work for the CPU. This means that instead of creating or destroying the same object over and over again, it's simply deactivated after use, then, when another object is needed, the pool recycles one of the deactivated objects and reactivates it. -See [Introduction to Object Pooling](https://learn.unity.com/tutorial/introduction-to-object-pooling) to learn more about the importance of pooling objects. +Refer to [Introduction to Object Pooling](https://learn.unity.com/tutorial/introduction-to-object-pooling) to learn more about the importance of pooling objects. -## NetworkPrefabInstanceHandler - -You can register your own spawn handlers by including the `INetworkPrefabInstanceHandler` interface and registering with the `NetworkPrefabHandler`. -```csharp - public interface INetworkPrefabInstanceHandler - { - NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation); - void Destroy(NetworkObject networkObject); - } -``` -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 a Host or Server, both won't have the Instantiate method invoked. All clients (excluding a Host) will have the instantiate method invoked if the `INetworkPrefabInstanceHandler` implementation is registered with `NetworkPrefabHandler` (`NetworkManager.PrefabHandler`) and a Host or Server spawns the registered/associated `NetworkObject`. +To learn how to override the default Netcode destroy and spawn handlers, refer to the [Network prefab handler page](./network-prefab-handler.md). \ No newline at end of file +--> diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/object-spawning.md b/com.unity.netcode.gameobjects/Documentation~/basics/object-spawning.md index ffab9107fa..d9b7bff794 100644 --- a/com.unity.netcode.gameobjects/Documentation~/basics/object-spawning.md +++ b/com.unity.netcode.gameobjects/Documentation~/basics/object-spawning.md @@ -1,47 +1,49 @@ # Object spawning -In Unity, you typically create a new game object using the `Instantiate` function. Creating a game object with `Instantiate` will only create that object on the local machine. `Spawning` in Netcode for GameObjects (Netcode) means to instantiate and/or spawn the object that is synchronized between all clients by the server. +In Unity, you typically create a new game object using the `Instantiate` function. Creating a game object with `Instantiate` will only create that object on the local machine. `Spawning` in Netcode for GameObjects (Netcode) means to instantiate and/or spawn the object that is synchronized between all game clients. -## Network Prefabs +## Network prefabs -A network Prefab is any unity Prefab asset that has one `NetworkObject` component attached to a `GameObject` within the prefab. More commonly, the `NetworkObject` component is attached to the root `GameObject` of the Prefab asset because this allows any child `GameObject` to have `NetworkBehaviour` components automatically assigned to the `NetworkObject`. The reason for this is that a `NetworkObject` component attached to a `GameObject` will be assigned (associated with) any `NetworkBehaviour` components on: +A network prefab is any unity prefab asset that has one `NetworkObject` component attached to a `GameObject` within the prefab. More commonly, the `NetworkObject` component is attached to the root `GameObject` of the prefab asset because this allows any child `GameObject` to have `NetworkBehaviour` components automatically assigned to the `NetworkObject`. The reason for this is that a `NetworkObject` component attached to a `GameObject` will be assigned (associated with) any `NetworkBehaviour` components on: - the same `GameObject` that the `NetworkObject` component is attached to - any child or children of the `GameObject` that the `NetworkObject` is attached to. > [!NOTE] -> A caveat of the above two rules is when one of the children `GameObject`s also has a `NetworkObject` component assigned to it (a.k.a. "Nested NetworkObjects"). Because nested `NetworkObject` components aren't permited in network prefabs, Netcode for GameObjects will notify you in the editor if you are trying to add more than one `NetworkObject` to a Prefab and won't allow you to do this. +> A caveat of the above two rules is when one of the children `GameObject`s also has a `NetworkObject` component assigned to it (a.k.a. "Nested NetworkObjects"). Because nested `NetworkObject` components aren't permited in network prefabs, Netcode for GameObjects will notify you in the editor if you are trying to add more than one `NetworkObject` to a prefab and won't allow you to do this. When a `NetworkBehaviour` is assigned to a `NetworkObject`, the `NetworkObject.NetworkObjectId` is used to help determine which `NetworkBehaviour` component instance will receive an update to a `NetworkVariable` or where to invoke an RPC. A `NetworkObject` component can have one or more `NetworkBehaviour` components assigned to it. -### Registering a Network Prefab +### Registering a network prefab -You must register a Network Prefab instance with a `NetworkManager` using a `NetworkedPrefabsList` scriptable object. -There are four steps to registering a network Prefab with a `NetworkManager`: +You must register a Network prefab instance with a `NetworkManager` using a `NetworkedprefabsList` scriptable object. +There are four steps to registering a network prefab with a `NetworkManager`: -1. Create a Network Prefab by creating a Prefab with a `NetworkObject` component attached to the root `GameObject`. -2. Create a scriptable object called `NetworkedPrefabsList` by right-clicking the project window, then: `Create/Netcode/NetworkedPrefabsList`. -3. Add your Network Prefab to the `NetworkPrefabsList`. -4. Add the `NetworkPrefabsList` to the Network Prefabs Lists that's associated with a `NetworkManager`. +1. Create a Network prefab by creating a prefab with a `NetworkObject` component attached to the root `GameObject`. +2. Create a scriptable object called `NetworkedprefabsList` by right-clicking the project window, then: `Create/Netcode/NetworkedprefabsList`. +3. Add your Network prefab to the `NetworkprefabsList`. +4. Add the `NetworkprefabsList` to the Network prefabs Lists that's associated with a `NetworkManager`. -### Spawning a Network Prefab (Overview) +### Spawning a network prefab (overview) -Netcode uses a server authoritative networking model so spawning netcode objects can only be done on a server or host. To spawn a network prefab, you must first create an instance of the network Prefab and then invoke the spawn method on the `NetworkObject` component of the instance you created. -_In most cases, you will want to keep the `NetworkObject` component attached to the root `GameObject` of the network prefab._ +When using a [server authoritative networking model](../terms-concepts/authority.md#server-authority) only the server or host can spawn netcode objects. Under a [distributed authority networking model](../terms-concepts/authority.md#distributed-authority), any game client can spawn netcode objects. The game client that spawned the network object then becomes the [authority](../terms-concepts/authority.md) of that object. -By default a newly spawned network Prefab instance is owned by the server unless otherwise specified. +To spawn a network prefab, you must first create an instance of the network prefab and then invoke the spawn method on the NetworkObject component of the instance you created. In most cases, you will want to keep the NetworkObject component attached to the root GameObject of the network prefab. + +By default, a newly spawned network prefab instance is owned by the authority unless otherwise specified. See [Ownership](networkobject.md#ownership) for more information. -The following is a basic example of how to spawn a network Prefab instance (with the default server ownership): +The following is a basic example of how to spawn a network prefab instance: ```csharp -var instance = Instantiate(myPrefab); +var instance = Instantiate(myprefab); var instanceNetworkObject = instance.GetComponent(); instanceNetworkObject.Spawn(); ``` The `NetworkObject.Spawn` method takes 1 optional parameter that defaults to `true`: + ```csharp public void Spawn(bool destroyWithScene = true); ``` @@ -53,45 +55,51 @@ When you set the destroyWithScene property to `false` it will be treated the sam > [!NOTE] > You might find it useful to add a `GameObject` property in a `NetworkBehaviour`-derived component to use when assigning a network prefab instance for dynamically spawning. You need to make sure to instantiate a new instance **prior** to spawning. If you attempt to just spawn the actual network prefab instance it can result in unexpected results. -### Taking Prefab Overrides Into Consideration -Sometimes, you might want to make a simpler prefab instance to be spawned on server version the override for clients. You should take this into consideration when dynamically spawning a network prefab. If you're running as a host, you want the override to spawn since a host is both a server and a client. However, if you also want to have the ability to run as a dedicated server, you might want to spawn the source network prefab. +### Taking prefab overrides into consideration + +Sometimes, you might want to use a different prefab instance on the authority compared to other clients. You should take this into consideration when dynamically spawning a network prefab. If you're running as a host, you want the override to spawn since a host is both a server and a client. However, if you also want to have the ability to run as a dedicated server, you might want to spawn the source network prefab. There are two ways you can accomplish this, as explained below. -#### Get The Network Prefab Override First +#### Get the network prefab override first + This option provides you with the overall view of getting the network prefab override, instantiating it, and then spawning it. ```csharp -var instance = Instantiate(NetworkManager.GetNetworkPrefabOverride(myPrefab)); +var instance = Instantiate(NetworkManager.GetNetworkprefabOverride(myprefab)); var instanceNetworkObject = instance.GetComponent(); instanceNetworkObject.Spawn(); ``` -In the above script, we get the prefab override using the `NetworkManager.GetNetworkPrefabOverride` method. Then we create an instance of the network prefab override, and finally we spawn the network prefab override instance's `NetworkObject`. + +In the above script, we get the prefab override using the `NetworkManager.GetNetworkprefabOverride` method. Then we create an instance of the network prefab override, and finally we spawn the network prefab override instance's `NetworkObject`. #### Using InstantiateAndSpawn + The second option is to leverage the `NetworkSpawnManager.InstantiateAndSpawn` method that handles whether or not to spawn an override for you. The below script is written as if it's being invoked within a `NetworkBehaviour`. ```csharp -var networkObject = NetworkManager.SpawnManager.InstantiateAndSpawn(myPrefab, ownerId); +var networkObject = NetworkManager.SpawnManager.InstantiateAndSpawn(myprefab, ownerId); ``` -We pass in the overridden source network prefab we want to have instantiated and spawned, and then it returns the instantiated and spawned `NetworkObject` of the spawned object. The default behavior of `InstantiateAndSpawn` is to spawn the override if running as a host and the original source prefab if running as a server. + +We pass in the overridden source network prefab we want to have instantiated and spawned, and then it returns the instantiated and spawned `NetworkObject` of the spawned object. The default behavior of `InstantiateAndSpawn` is to spawn the original source prefab if running as a server and the override otherwise. `InstantiateAndSpawn` has several parameters to provide more control over this process: ```csharp -InstantiateAndSpawn(NetworkObject networkPrefab, ulong ownerClientId = NetworkManager.ServerClientId, bool destroyWithScene = false, bool isPlayerObject = false, bool forceOverride = false, Vector3 position = default, Quaternion rotation = default) +InstantiateAndSpawn(NetworkObject networkprefab, ulong ownerClientId = NetworkManager.ServerClientId, bool destroyWithScene = false, bool isPlayerObject = false, bool forceOverride = false, Vector3 position = default, Quaternion rotation = default) ``` Looking at the parameters, we can see it defaults to the server as the owner, ensures that the instantiated `NetworkObject` won't be destroyed if the scene is unloaded, is not spawned as a player, has a `forceOverride` parameter, and provides a way to set the position and rotation of the newly instantiated `NetworkObject`. -The `forceOverride` parameter, when set to true, will use the override whether you're running as either server or host. +The `forceOverride` parameter, when set to true, will always use the override. +To override prefabs on non-authority game clients, refer to the [network prefab handler page](../advanced-topics/network-prefab-handler.md). -## Destroying / Despawning +## Destroying and despawning -By default, a spawned network Prefab instance that is destroyed on the server/host will be automatically destroyed on all clients. +By default, a spawned network prefab instance that is destroyed on the authority will be automatically destroyed on all clients. -When a client disconnects, all network Prefab instances created during the network session will be destroyed on the client-side by default. If you don't want that to happen, set the `DontDestroyWithOwner` field on `NetworkObject` to true before despawning. +When a client disconnects, all network prefab instances dynamically created during the network session will be destroyed on the client-side by default. If you don't want that to happen, set the `DontDestroyWithOwner` field on `NetworkObject` to true before despawning. To do this at runtime: @@ -104,59 +112,58 @@ To make this the default from the editor Inspector view: ![image](../images/DontDestroyWithOwner.png) -As an alternative way, you can make the `NetworkObject.DontDestroyWithOwner` property default to `true` by setting it on the `NetworkObject` itself like in the above screenshot. +As an alternative, you can make the `NetworkObject.DontDestroyWithOwner` property default to `true` by setting it on the `NetworkObject` itself like in the above screenshot. ### Despawning -Only a server can despawn a `NetworkObject`, and the default despawn behavior is to destroy the associated GameObject. to despawn but not destroy a `NetworkObject`, you should call `NetworkObject.Despawn` and pass false as the parameter. Clients will always be notified and will mirror the despawn behavior. If you despawn and destroy on the server then all clients will despawn and then destroy the `GameObject` that the `NetworkObjet` component is attached to. +Only the authority can despawn a NetworkObject, and the default despawn behavior is to destroy the associated GameObject. To despawn but not destroy a NetworkObject, call `NetworkObject.Despawn` and pass false as the parameter. Non-authority clients will always be notified and will mirror the despawn behavior. If you despawn and destroy on the authority then all other connected clients will despawn and then destroy the GameObject that the NetworkObject component is attached to. -On the client side, you should never call `Object.Destroy` on any `GameObject` with a `NetworkObject` component attached to it (this isn't supported and will cause an exception to be thrown). If you want to use a more client authority model, have the client with ownership invoke an RPC to defer the despawning on server side. +On the non-authority side, you should never call `Object.Destroy` on any GameObject with a NetworkObject component attached to it (this isn't supported and will cause an exception to be thrown). To allow non-authority clients to destroy objects they do not own, have the relevant client invoke an RPC to defer the despawning on the authority side. -The only way to despawn `NetworkObject` for a specific client is to use `NetworkObject.NetworkHide`. -See: [Object Visibility](object-visibility.md) for more information on this. +The only way to despawn a NetworkObject for a specific client is to use `NetworkObject.NetworkHide`. Refer to the [object visibility page](object-visibility.md) for more information. > [!NOTE] > If you have `GameObject` children, with `NetworkBehaviour` components attached, of a parent `GameObject`, with a `NetworkObject` component attached, you can't disable the `GameObject` children before spawning or despawning. Doing so, in v1.0.0, can cause unexpected results and it's recommended to make sure all children are enabled in the hierarchy before spawning or despawning. -## Dynamically Spawned Network Prefabs +## Dynamically spawned network prefabs -Netcode for GameObjects uses the term "dynamically spawned" to convey that the `NetworkObject` is being spawned via user specific code. Whereas a player or in-scene placed `NetworkObject` (with scene management enabled) is typically spawned by Netcode for GameObjects. There are several ways to spawn a network Prefab via code: +Netcode for GameObjects uses the term "dynamically spawned" to convey that the `NetworkObject` is being spawned via user specific code. Whereas a player or in-scene placed `NetworkObject` (with scene management enabled) is typically spawned by Netcode for GameObjects. There are several ways to spawn a network prefab via code: -### Dynamic Spawning (non-pooled): +### Dynamic spawning (non-pooled): -This type of dynamically spawned `NetworkObject` typically is a simple wrapper class that holds a reference to the Prefab asset. In the example below, the `NonPooledDynamicSpawner.PrefabToSpawn` property holds a reference to the network prefab: +This type of dynamically spawned `NetworkObject` typically is a simple wrapper class that holds a reference to the prefab asset. In the example below, the `NonPooledDynamicSpawner.prefabToSpawn` property holds a reference to the network prefab: ```csharp public class NonPooledDynamicSpawner : NetworkBehaviour { - public GameObject PrefabToSpawn; + public GameObject prefabToSpawn; public bool DestroyWithSpawner; - private GameObject m_PrefabInstance; + private GameObject m_prefabInstance; private NetworkObject m_SpawnedNetworkObject; public override void OnNetworkSpawn() { - // Only the server spawns, clients will disable this component on their side - enabled = IsServer; - if (!enabled || PrefabToSpawn == null) + // Only the authority spawns, other clients will disable this component on their side + enabled = HasAuthority; + if (!enabled || prefabToSpawn == null) { return; } // Instantiate the GameObject Instance - m_PrefabInstance = Instantiate(PrefabToSpawn); + m_prefabInstance = Instantiate(prefabToSpawn); // Optional, this example applies the spawner's position and rotation to the new instance - m_PrefabInstance.transform.position = transform.position; - m_PrefabInstance.transform.rotation = transform.rotation; + m_prefabInstance.transform.position = transform.position; + m_prefabInstance.transform.rotation = transform.rotation; // Get the instance's NetworkObject and Spawn - m_SpawnedNetworkObject = m_PrefabInstance.GetComponent(); + m_SpawnedNetworkObject = m_prefabInstance.GetComponent(); m_SpawnedNetworkObject.Spawn(); } public override void OnNetworkDespawn() { - if (IsServer && DestroyWithSpawner && m_SpawnedNetworkObject != null && m_SpawnedNetworkObject.IsSpawned) + if (HasAuthority && DestroyWithSpawner && m_SpawnedNetworkObject != null && m_SpawnedNetworkObject.IsSpawned) { m_SpawnedNetworkObject.Despawn(); } @@ -171,36 +178,36 @@ Consumable and/or items that can be picked up by a player or NPC(that is, a weap > While the NonPooledDynamicSpawner example is one of the simplest ways to spawn a NetworkObject, there is a memory allocation cost associated with instantiating and destroying the GameObject and all attached components. This design pattern can sometimes be all you need for the netcode game asset you are working with, and other times you might want to respawn/re-use the object instance. When performance is a concern and you want to spawn more than just one `NetworkObject` during the lifetime of the spawner or want to repeatedly respawn a single `NetworkObject`, the less proccessor and memory allocation intensive technique is to use [pooled dynamic spawning](#pooled-dynamic-spawning). > [!NOTE] -> Really, when we use the term "non-pooled" more often than not we are referring to the concept that a `GameObject` will be instantiated on both the server and the clients each time an instance is spawned. +> Generally, the term "non-pooled" refers to the concept that a GameObject will be instantiated on all game clients each time an instance is spawned. -### Pooled Dynamic Spawning +### Pooled dynamic spawning -Pooled dynamic spawning is when netcode objects (`GameObject` with one `NetworkObject` component) aren't destroyed on the server or the client when despawned. Instead, specific components are just disabled (or the `GameObject` itself) when a netcode object is despawned. A pooled dynamically spawned netcode object is typically instantiated during an already memory allocation heavy period of time (like when a scene is loaded or even at the start of your application before even establishing a network connection). Pooled dynamically spawned netcode objects are more commonly thought of as more than one netcode object that can be re-used without incurring the memory allocation and initialization costs. However, you might also run into scenarios where you need just one dynamically spawned netcode object to be treated like a pooled dynmically spawned netcode object. +Pooled dynamic spawning is when netcode objects (`GameObject` with one `NetworkObject` component) aren't destroyed on game clients when despawned. Instead, specific components are just disabled (or the `GameObject` itself) when a netcode object is despawned. A pooled dynamically spawned netcode object is typically instantiated during an already memory allocation heavy period of time (like when a scene is loaded or even at the start of your application before even establishing a network connection). Pooled dynamically spawned netcode objects are more commonly thought of as more than one netcode object that can be re-used without incurring the memory allocation and initialization costs. However, you might also run into scenarios where you need just one dynamically spawned netcode object to be treated like a pooled dynmically spawned netcode object. -Fortunately, Netcode for GameObjects provides you with a way to be in control over the instatiation and destruction process for one or many netcode objects by via the `INetworkPrefabInstanceHandler` interface. Any `INetworkPrefabInstanceHandler`implementation should be registered with the `NetworkPrefabHandler`(for multiple netcode objects see [Object Pooling](../advanced-topics/object-pooling.md)) to accomplish this. +Fortunately, Netcode for GameObjects provides you with a way to be in control over the instatiation and destruction process for one or many netcode objects by via the `INetworkprefabInstanceHandler` interface. Any `INetworkprefabInstanceHandler`implementation should be registered with the `NetworkprefabHandler`(for multiple netcode objects see [Object Pooling](../advanced-topics/object-pooling.md)) to accomplish this. -The easiest way to not destroy a network Prefab instance is to have something, other than the instance itself, keeping a reference to the instance. This way you can simply set the root `GameObject` to be inactive when it's despawned while still being able to set it active when the same network Prefab type needs to be respawned. Below is one example of how you can accomplish this for a single netcode object instance: +The easiest way to not destroy a network prefab instance is to have something, other than the instance itself, keeping a reference to the instance. This way you can simply set the root `GameObject` to be inactive when it's despawned while still being able to set it active when the same network prefab type needs to be respawned. Below is one example of how you can accomplish this for a single netcode object instance: ```csharp -public class SinglePooledDynamicSpawner : NetworkBehaviour, INetworkPrefabInstanceHandler +public class SinglePooledDynamicSpawner : NetworkBehaviour, INetworkprefabInstanceHandler { - public GameObject PrefabToSpawn; - public bool SpawnPrefabAutomatically; + public GameObject prefabToSpawn; + public bool SpawnprefabAutomatically; - private GameObject m_PrefabInstance; + private GameObject m_prefabInstance; private NetworkObject m_SpawnedNetworkObject; private void Start() { - // Instantiate our instance when we start (for both clients and server) - m_PrefabInstance = Instantiate(PrefabToSpawn); + // Instantiate our instance when we start (for all connected game clients) + m_prefabInstance = Instantiate(prefabToSpawn); - // Get the NetworkObject component assigned to the Prefab instance - m_SpawnedNetworkObject = m_PrefabInstance.GetComponent(); + // Get the NetworkObject component assigned to the prefab instance + m_SpawnedNetworkObject = m_prefabInstance.GetComponent(); // Set it to be inactive - m_PrefabInstance.SetActive(false); + m_prefabInstance.SetActive(false); } private IEnumerator DespawnTimer() @@ -219,37 +226,37 @@ public class SinglePooledDynamicSpawner : NetworkBehaviour, INetworkPrefabInstan } /// - /// Invoked only on clients and not server or host - /// INetworkPrefabInstanceHandler.Instantiate implementation + /// Invoked only on non-authority clients + /// INetworkprefabInstanceHandler.Instantiate implementation /// Called when Netcode for GameObjects need an instance to be spawned /// public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation) { - m_PrefabInstance.SetActive(true); - m_PrefabInstance.transform.position = transform.position; - m_PrefabInstance.transform.rotation = transform.rotation; + m_prefabInstance.SetActive(true); + m_prefabInstance.transform.position = transform.position; + m_prefabInstance.transform.rotation = transform.rotation; return m_SpawnedNetworkObject; } /// - /// Client and Server side - /// INetworkPrefabInstanceHandler.Destroy implementation + /// Called on all game clients + /// INetworkprefabInstanceHandler.Destroy implementation /// public void Destroy(NetworkObject networkObject) { - m_PrefabInstance.SetActive(false); + m_prefabInstance.SetActive(false); } public void SpawnInstance() { - if (!IsServer) + if (!HasAuthority) { return; } - if (m_PrefabInstance != null && m_SpawnedNetworkObject != null && !m_SpawnedNetworkObject.IsSpawned) + if (m_prefabInstance != null && m_SpawnedNetworkObject != null && !m_SpawnedNetworkObject.IsSpawned) { - m_PrefabInstance.SetActive(true); + m_prefabInstance.SetActive(true); m_SpawnedNetworkObject.Spawn(); StartCoroutine(DespawnTimer()); } @@ -257,16 +264,16 @@ public class SinglePooledDynamicSpawner : NetworkBehaviour, INetworkPrefabInstan public override void OnNetworkSpawn() { - // We register our network Prefab and this NetworkBehaviour that implements the - // INetworkPrefabInstanceHandler interface with the Prefab handler - NetworkManager.PrefabHandler.AddHandler(PrefabToSpawn, this); + // We register our network prefab and this NetworkBehaviour that implements the + // INetworkprefabInstanceHandler interface with the prefab handler + NetworkManager.prefabHandler.AddHandler(prefabToSpawn, this); - if (!IsServer || !SpawnPrefabAutomatically) + if (!HasAuthority || !SpawnprefabAutomatically) { return; } - if (SpawnPrefabAutomatically) + if (SpawnprefabAutomatically) { SpawnInstance(); } @@ -284,30 +291,30 @@ public class SinglePooledDynamicSpawner : NetworkBehaviour, INetworkPrefabInstan public override void OnDestroy() { // This example destroys the - if (m_PrefabInstance != null) + if (m_prefabInstance != null) { // Always deregister the prefab - NetworkManager.Singleton.PrefabHandler.RemoveHandler(PrefabToSpawn); - Destroy(m_PrefabInstance); + NetworkManager.Singleton.prefabHandler.RemoveHandler(prefabToSpawn); + Destroy(m_prefabInstance); } base.OnDestroy(); } } ``` -You might run across a situation where you still want other components on the root `GameObject` of your network Prefab instance to remain active. Primarily, you want to be able to easily disable the components that would normally be active when the netcode object is considered spawned. +You might run across a situation where you still want other components on the root `GameObject` of your network prefab instance to remain active. Primarily, you want to be able to easily disable the components that would normally be active when the netcode object is considered spawned. -Below is an example of what a non-pooled friendly Prefab might look like: +Below is an example of what a non-pooled friendly prefab might look like: ![image](../images/non-pooled-friendly-prefab.png) -The issues you might run into with the above Prefab hierarchy is that everything is on a single `GameObject`, and as such if you wanted to disable the `MeshRenderer` and the `NetworkObjectLabel`, [one of our classes in the Netcode for GameObjects test project](https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/blob/f0631414e5a5358a5ac7811d43273b1a82a60ca9/testproject/Assets/Scripts/NetworkObjectLabel.cs#L4), you would need to get those component types before disabling them (that is, during `Start` or `OnNetworkSpawn` or get them when `OnNetworkDespawn` is invoked). +The issues you might run into with the above prefab hierarchy is that everything is on a single `GameObject`, and as such if you wanted to disable the `MeshRenderer` and the `NetworkObjectLabel`, [one of our classes in the Netcode for GameObjects test project](https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/blob/f0631414e5a5358a5ac7811d43273b1a82a60ca9/testproject/Assets/Scripts/NetworkObjectLabel.cs#L4), you would need to get those component types before disabling them (that is, during `Start` or `OnNetworkSpawn` or get them when `OnNetworkDespawn` is invoked). -To reduce this level of complexity, a more "pooled dynamic spawning" friendly Prefab heirarchy might look like this: +To reduce this level of complexity, a more "pooled dynamic spawning" friendly prefab heirarchy might look like this: ![image](../images/pooled-friendly-prefab.png) -The `NetworkObject` sits at the root `GameObject` of the network prefab. The child `GameObject`, SpawnedComponents, then has everything that you might want to have disabled when the network Prefab instance isn't spawned: +The `NetworkObject` sits at the root `GameObject` of the network prefab. The child `GameObject`, SpawnedComponents, then has everything that you might want to have disabled when the network prefab instance isn't spawned: ![image](../images/pooled-friendly-prefab-child.png) @@ -316,23 +323,23 @@ This reduces the complexity down to setting the SpawnedComponents `GameObject` t > [!NOTE] > Using this type of a hierarchical separation is useful in many ways (especially when you have a much more complex prefab). For more complex prefabs, you can further expand this pattern into specific categories (that is, visuals, physics, sound, etc) which will provide you with a more macrocosmic way to control enabling or disabling many different components without having to have references to all of them. -## In-Scene Placed `NetworkObject` +## In-scene placed `NetworkObject` -Any objects in the scene with active and spawned `NetworkObject` components will get automatically replicated by Netcode. There is no need to manually spawn them when scene management is enabled in the `NetworkManager`. In-scene placed `NetworkObjects` should typically be used like a "static" netcode object, where the netcode object is typically spawned upon the scene being loaded on the server-side and synchronized with clients once they finish loading the same scene. +Any objects in the scene with active and spawned `NetworkObject` components will get automatically replicated by Netcode. There is no need to manually spawn them when scene management is enabled in the `NetworkManager`. In-scene placed `NetworkObjects` should typically be used like a "static" netcode object, where the netcode object is typically spawned upon the scene being loaded on the authority-side and synchronized with other clients once they finish loading the same scene. [Learn more about In-Scene Placed `NetworkObjects`](scenemanagement/inscene-placed-networkobjects.md) Generally, there are **two** modes that define how an in-scene placed `NetworkObject` is synchronized. - Soft Synchronization (Scene Management enabled) -- Prefab Synchronization (Scene Management disabled) +- prefab Synchronization (Scene Management disabled) -### Soft Synchronization +### Soft synchronization `SoftSync` or "Soft Synchronization" is a term you might run across if you run into any issue with in-scene placed `NetworkObjects`. Soft synchronization only occurs if scene management is enabled in the `NetworkManager` properties. If you receive a "soft synchronization error", then this typically means that a client can't locate the same in-scene placed `NetworkObject` after loading a scene. -### Prefab Synchronization +### Prefab synchronization -`PrefabSync` or "Prefab Synchronization" is used if scene management is disabled in the `NetworkManager`. With Prefab synchronization, every in-scene placed `NetworkObject` has to be a network Prefab and must be registered with `NetworkPrefabs` list. When a client starts, Netcode will destroy all existing in-scene placed `NetworkObject`s and spawn its corresponding Prefab from the `NetworkPrefabs` list instead. This also means that you will have to implement your own scene manager and handle how you synchronize clients when they join a network session. +`prefabSync` or "prefab Synchronization" is used if scene management is disabled in the `NetworkManager`. With prefab synchronization, every in-scene placed `NetworkObject` has to be a network prefab and must be registered with `Networkprefabs` list. When a client starts, Netcode will destroy all existing in-scene placed `NetworkObject`s and spawn its corresponding prefab from the `Networkprefabs` list instead. This also means that you will have to implement your own scene manager and handle how you synchronize clients when they join a network session. -**PrefabSync is ONLY recommended for advanced development and/or multi project setups**. +**prefabSync is ONLY recommended for advanced development and/or multi project setups**. diff --git a/com.unity.netcode.gameobjects/Documentation~/spawn-despawn.md b/com.unity.netcode.gameobjects/Documentation~/spawn-despawn.md index fda545daff..506d7d5614 100644 --- a/com.unity.netcode.gameobjects/Documentation~/spawn-despawn.md +++ b/com.unity.netcode.gameobjects/Documentation~/spawn-despawn.md @@ -5,6 +5,7 @@ Spawn and despawn objects in your project. | **Topic** | **Description** | | :------------------------------ | :------------------------------- | | **[Object spawning](basics/object-spawning.md)** | Spawning in Netcode for GameObjects means to instantiate and/or spawn the object that is synchronized between all clients by the server. | +| **[Network prefab handler](advanced-topics/network-prefab-handler.md)** | The network prefab handler system provides advanced control over how network prefabs are instantiated and destroyed during runtime. | | **[Object pooling](advanced-topics/object-pooling.md)** | Netcode for GameObjects provides built-in support for Object Pooling, which allows you to override the default Netcode destroy and spawn handlers with your own logic. | | **[Object visibility](basics/object-visibility.md)** | Object (NetworkObject) visibility is a Netcode for GameObjects term used to describe whether a `NetworkObject` is visible to one or more clients as it pertains to a netcode/network perspective. | | **[Spawning synchronization](basics/spawning-synchronization.md)** | Ensuring that objects spawn in a synchronized manner across clients can be difficult, although the challenges differ depending on which [network topology](terms-concepts/network-topologies.md) you're using. | \ No newline at end of file