Skip to content

feat: Instantiation payload support for INetworkPrefabInstanceHandler #3430

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

Open
wants to merge 6 commits into
base: develop-2.0.0
Choose a base branch
from

Conversation

Extrys
Copy link

@Extrys Extrys commented Apr 27, 2025

Solves Issue #3421

Related to the discussions in Pull Request #3419 and Issue #3421 (follow-up proposal based on new approach).

This PR introduces support for sending custom instantiation payloads, allowing INetworkPrefabInstanceHandler to receive metadata before calling Instantiate().

The feature is fully optional, backward-compatible, and requires no changes to existing user workflows.

Changelog

  • Added: INetworkInstantiationPayloadSynchronizer interface to synchronize custom pre-instantiation data.

Testing and Documentation

  • No new tests added.
  • Inline code documentation provided; INetworkPrefabInstanceHandler.Instantiate() summary updated to mention INetworkInstantiationPayloadSynchronizer
  • Further external documentation is recommended for INetworkInstantiationPayloadSynchronizer.

Deprecated API

  • None.

Backport

  • Not applicable (new feature for development branch only)

Implementation Example

public class MyPrefabInstanceHandler : INetworkPrefabInstanceHandler, INetworkInstantiationPayloadSynchronizer
{
    GameObject[] prefabs;
    public int customSpawnID = 0;

	//NEW
    void INetworkInstantiationPayloadSynchronizer.OnSynchronize<T>(ref BufferSerializer<T> serializer)
        => serializer.SerializeValue(ref customSpawnID);

    public NetworkObject Instantiate(ulong clientId, Vector3 position, Quaternion rotation)
        => Object.Instantiate(prefabs[customSpawnID], position, rotation).GetComponent<NetworkObject>();

    public void Destroy(NetworkObject networkObject)
        => Object.Destroy(networkObject.gameObject);
}

Spawning flow:

MyPrefabInstanceHandler prefabInstanceHandler = new MyPrefabInstanceHandler();
GameObject basePrefab;

void RegisterHandler() => NetworkManager.Singleton.PrefabHandler.AddHandler(basePrefab, prefabInstanceHandler);
void UnregisterHandler() => NetworkManager.Singleton.PrefabHandler.RemoveHandler(basePrefab);

public void Spawn(int id)
{
    prefabInstanceHandler.customSpawnID = id; //Plot twist: simply modify the handler's data
    NetworkManager.Singleton.SpawnManager.InstantiateAndSpawn(basePrefab.GetComponent<NetworkObject>());
}

Important

When spawning, you must update the handler's data before calling Spawn() or InstantiateAndSpawn().
The data set in the handler will be serialized automatically during the prior the instantiation process.

Highlights

  • Optional and non-breaking
  • Intuitive to configure and resilient to errors.
  • Fully aligns with NGO patterns (Serialize/Deserialize symmetry)
  • Late-join and scene object support
  • No public API modifications

@Extrys Extrys requested a review from a team as a code owner April 27, 2025 02:26
@Extrys Extrys changed the title feat: Network Object Instantiation Payload feat: Instantiation payload support for INetworkPrefabInstanceHandler Apr 27, 2025
@Extrys
Copy link
Author

Extrys commented Apr 27, 2025

I just posted a video demonstrating how the system works:

  • * is a symbol set on objects spawned through PrefabHandlers.
  • & is a symbol added to indicate the deterministic ID of an object.

In the video, I spawn A1 and A2, which are instances of the same prefab, displaying the letter A.
Each button spawns the prefab with a different number, and this number is sent via the instantiation payload.
No RPCs or NetworkVariables are needed.

The B object is registered with a regular PrefabHandler (no custom interface implemented here, just for basic testing).

D, E, and F are instantiated directly via Unity's regular Instantiate method.
Each of these sets its deterministic ID manually and registers itself into a local dictionary, indexed by that ID.

2025-04-27.21-45-44.mp4

By implementing a custom INetworkPrefabInstanceHandler together with INetworkInstantiationPayloadSynchronizer,
I simply retrieve the ID from the payload and use it to link the correct instance deterministically.

Here is the core implementation:

public class TestHandlerDeterministicLink : INetworkPrefabInstanceHandler, INetworkInstantiationPayloadSynchronizer
{
	public Dictionary<int, DeterministicIDHolder> deterministicSpawns = new Dictionary<int, DeterministicIDHolder>();

	public int customSpawnID = 0;

	void INetworkInstantiationPayloadSynchronizer.OnSynchronize<T>(ref BufferSerializer<T> serializer) => serializer.SerializeValue(ref customSpawnID);

	public NetworkObject Instantiate(ulong clientId, Vector3 position, Quaternion rotation)
	{
		var obj = deterministicSpawns[customSpawnID];
		TMP_Text text = obj.GetComponent<TMP_Text>();
		text.SetText(text.text + "*");
		return obj.GetComponent<NetworkObject>();
	}

	public void Destroy(NetworkObject networkObject) => GameObject.Destroy(networkObject.gameObject);

	int nextDeterministicId = 0;

	public void InstantiateLocally(GameObject linkablePrefab)
	{
		var spawned = GameObject.Instantiate(linkablePrefab);
		spawned.transform.position = UnityEngine.Random.insideUnitCircle * 0.01f;
		var text = spawned.GetComponent<TMP_Text>();
		text.SetText(nextDeterministicId.ToString() + "&" + text.text);
		var deterministicIdHolder = spawned.GetComponent<DeterministicIDHolder>();
		deterministicSpawns[nextDeterministicId] = deterministicIdHolder;
		deterministicIdHolder.SetID(nextDeterministicId);
		nextDeterministicId++;
	}
}

Warning

While this system enables advanced workflows,
it is important to note that developers are responsible for ensuring that the linked instances are compatible.
This flexibility is intentional to support a variety of custom deterministic linking strategies.

@victorLanga17
Copy link

This would actually save us a lot of trouble.

Right now when after we spawn stuff we have to run like two or three RPCs just to finish setting up objects properly.
If we could send a bit of info during the spawn itself I think we could solve a couple of problems easier.

I wish you luck on get it merged on Unity 6.1, it would be super useful for our current project.

@Extrys
Copy link
Author

Extrys commented Apr 28, 2025

This would actually save us a lot of trouble.

Right now when after we spawn stuff we have to run like two or three RPCs just to finish setting up objects properly. If we could send a bit of info during the spawn itself I think we could solve a couple of problems easier.

I wish you luck on get it merged on Unity 6.1, it would be super useful for our current project.

Sure! I'm just waiting for reviewers to be assigned to this PR.
In the worst case, you can always use my fork, which I will keep updated for my use cases only, so sometimes it might not be fully up to date.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants