Skip to content

chore: suggested changes for the instantiate with data documentation updates #3579

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<T> : INetworkPrefabInstanceHandlerWithData
Expand All @@ -51,7 +51,7 @@ public abstract class NetworkPrefabInstanceHandlerWithData<T> : 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
Expand All @@ -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/[email protected]/api/Unity.Netcode.NetworkPrefabHandler.html#Unity_Netcode_NetworkPrefabHandler_RemoveHandler_System_UInt32_) (_There are several override versions of this method_).

## Object spawning with prefab handlers

Expand All @@ -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
/// <summary>
/// The instantiation data that is serialzied and sent with the
/// spawn object message and provided prior to instantiating.
/// </summary>
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<T>(BufferSerializer<T> 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
/// <summary>
/// Add to an in-scene placed <see cref="NetworkObject"/>.
/// </summary>
public class SpawnPrefabWithColor : NetworkBehaviour
{
[SerializeField] private GameObject prefabToSpawn;
public string currentName
/// <summary>
/// The network prefab used to register the <see cref="SpawnWithDataSystem"/> handler.
/// </summary>
public GameObject NetworkPrefab;

public void SpawnObject(int objectTypeToSpawn)
{
var instance = Instantiate(prefabToSpawn);
/// <summary>
/// The <see cref="SpawnWithDataSystem"/> handler instance.
/// </summary>
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();
/// <summary>
/// Invoked by some other component or additional script logic controls
/// when an object is spawned.
/// </summary>
/// <param name="color">The color to apply (pseudo example purposes)</param>
/// <returns>The spawned <see cref="NetworkObject"/> instance</returns>
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<SpawnData>
/// <summary>
/// The prefan instance handler that uses instantiation data to handle updating
/// the instance's <see cref="MeshRenderer"/>s material's color. (example purposes only)
/// </summary>
public class SpawnWithColorHandler : NetworkPrefabInstanceHandlerWithData<InstantiateData>
{
public override NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation, SpawnData data)
private GameObject m_RegisteredPrefabToSpawn;
private NetworkManager m_NetworkManager;

/// <summary>
/// Constructor
/// </summary>
/// <param name="registeredPrefab">The prefab used to register this handler instance.</param>
/// <param name="networkPrefabGroup">The prefab group this handler will be able to spawn.</param>
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);
}

/// <summary>
/// Used by the server or a client when using a distributed authority network topology,
/// instantiate the prefab, set the instantiation data, and then spawn.
/// </summary>
public NetworkObject InstantiateSetDataAndSpawn(Vector3 position, Quaternion rotation, InstantiateData instantiateData)
{
var instance = GetPrefabInstance(position, rotation, instantiateData);

var obj = instance.GetComponent<CountingObject>();
obj.currentName = data.name;
// Set the spawndata before spawning
m_NetworkManager.PrefabHandler.SetInstantiationData(instance, instantiateData);

instance.GetComponent<NetworkObject>().Spawn();
return instance;
}

/// <summary>
/// Returns an instance of the registered prefab (no instantiation data set yet)
/// </summary>
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<MeshRenderer>();
foreach (var renderer in meshRenderers)
{
// Assign the color to each MeshRenderer (just a psuedo example)
renderer.material.color = instantiateData.Color;
}

return instance.GetComponent<NetworkObject>();
}

public override void Destroy(NetworkObject networkObject)
/// <inheritdoc/>
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)
/// <inheritdoc/>
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

Expand Down