Skip to content

Commit 3ff9326

Browse files
committed
feat: Network Object Instantiation Payload
1 parent 10ef2eb commit 3ff9326

7 files changed

Lines changed: 143 additions & 33 deletions

File tree

com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -797,8 +797,9 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne
797797
// Server-side spawning (only if there is a prefab hash or player prefab provided)
798798
if (!NetworkManager.DistributedAuthorityMode && response.CreatePlayerObject && (response.PlayerPrefabHash.HasValue || NetworkManager.NetworkConfig.PlayerPrefab != null))
799799
{
800-
var playerObject = response.PlayerPrefabHash.HasValue ? NetworkManager.SpawnManager.GetNetworkObjectToSpawn(response.PlayerPrefabHash.Value, ownerClientId, response.Position ?? null, response.Rotation ?? null)
801-
: NetworkManager.SpawnManager.GetNetworkObjectToSpawn(NetworkManager.NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash, ownerClientId, response.Position ?? null, response.Rotation ?? null);
800+
var instantiationPayloadWriter = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(new FastBufferWriter(0,Allocator.Temp)));
801+
var playerObject = response.PlayerPrefabHash.HasValue ? NetworkManager.SpawnManager.GetNetworkObjectToSpawn(response.PlayerPrefabHash.Value, ownerClientId, ref instantiationPayloadWriter, response.Position ?? null, response.Rotation ?? null)
802+
: NetworkManager.SpawnManager.GetNetworkObjectToSpawn(NetworkManager.NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash, ownerClientId, ref instantiationPayloadWriter, response.Position ?? null, response.Rotation ?? null);
802803

803804
// Spawn the player NetworkObject locally
804805
NetworkManager.SpawnManager.SpawnNetworkObjectLocally(
@@ -950,7 +951,8 @@ internal void CreateAndSpawnPlayer(ulong ownerId)
950951
if (playerPrefab != null)
951952
{
952953
var globalObjectIdHash = playerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash;
953-
var networkObject = NetworkManager.SpawnManager.GetNetworkObjectToSpawn(globalObjectIdHash, ownerId, playerPrefab.transform.position, playerPrefab.transform.rotation);
954+
var instantiationPayloadWriter = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(new FastBufferWriter(0, Allocator.Temp)));
955+
var networkObject = NetworkManager.SpawnManager.GetNetworkObjectToSpawn(globalObjectIdHash, ownerId, ref instantiationPayloadWriter, playerPrefab.transform.position, playerPrefab.transform.rotation);
954956
networkObject.IsSceneObject = false;
955957
networkObject.SpawnAsPlayerObject(ownerId, networkObject.DestroyWithScene);
956958
}

com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Runtime.CompilerServices;
5+
using Unity.Collections;
56
using Unity.Netcode.Components;
67
#if UNITY_EDITOR
78
using UnityEditor;
@@ -45,6 +46,13 @@ public sealed class NetworkObject : MonoBehaviour
4546
[SerializeField]
4647
internal uint InScenePlacedSourceGlobalObjectIdHash;
4748

49+
/// <summary>
50+
/// Metadata sent during the instantiation process.
51+
/// Retrieved in INetworkCustomSpawnDataSynchronizer before instantiation,
52+
/// and available to INetworkPrefabInstanceHandler.Instantiate() for custom handling by user code.
53+
/// </summary>
54+
internal FastBufferReader InstantiationPayload;
55+
4856
/// <summary>
4957
/// Gets the Prefab Hash Id of this object if the object is registerd as a prefab otherwise it returns 0
5058
/// </summary>
@@ -2812,6 +2820,7 @@ internal struct SceneObject
28122820
public ulong NetworkObjectId;
28132821
public ulong OwnerClientId;
28142822
public ushort OwnershipFlags;
2823+
public FastBufferReader InstantiationPayload;
28152824

28162825
public bool IsPlayerObject
28172826
{
@@ -2882,6 +2891,12 @@ public bool SpawnWithObservers
28822891
set => ByteUtility.SetBit(ref m_BitField, 10, value);
28832892
}
28842893

2894+
public bool HasInstantiationPayload
2895+
{
2896+
get => ByteUtility.GetBit(m_BitField, 11);
2897+
set => ByteUtility.SetBit(ref m_BitField, 11, value);
2898+
}
2899+
28852900
// When handling the initial synchronization of NetworkObjects,
28862901
// this will be populated with the known observers.
28872902
public ulong[] Observers;
@@ -2948,12 +2963,26 @@ public void Serialize(FastBufferWriter writer)
29482963
var writeSize = 0;
29492964
writeSize += HasTransform ? FastBufferWriter.GetWriteSize<TransformData>() : 0;
29502965
writeSize += FastBufferWriter.GetWriteSize<int>();
2966+
if (HasInstantiationPayload)
2967+
{
2968+
writeSize += FastBufferWriter.GetWriteSize<int>();
2969+
writeSize += InstantiationPayload.Length;
2970+
}
29512971

29522972
if (!writer.TryBeginWrite(writeSize))
29532973
{
29542974
throw new OverflowException("Could not serialize SceneObject: Out of buffer space.");
29552975
}
29562976

2977+
if (HasInstantiationPayload)
2978+
{
2979+
writer.WriteValueSafe(InstantiationPayload.Length);
2980+
unsafe
2981+
{
2982+
writer.WriteBytes(InstantiationPayload.GetUnsafePtr(), InstantiationPayload.Length);
2983+
}
2984+
}
2985+
29572986
if (HasTransform)
29582987
{
29592988
writer.WriteValue(Transform);
@@ -3014,12 +3043,34 @@ public void Deserialize(FastBufferReader reader)
30143043
readSize += HasTransform ? FastBufferWriter.GetWriteSize<TransformData>() : 0;
30153044
readSize += FastBufferWriter.GetWriteSize<int>();
30163045

3046+
int preInstanceDataSize = 0;
3047+
if (HasInstantiationPayload)
3048+
{
3049+
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize<int>()))
3050+
{
3051+
throw new OverflowException($"Could not deserialize SceneObject: Reading past the end of the buffer ({nameof(InstantiationPayload)} size)");
3052+
}
3053+
3054+
reader.ReadValueSafe(out preInstanceDataSize);
3055+
readSize += FastBufferWriter.GetWriteSize<int>();
3056+
readSize += preInstanceDataSize;
3057+
}
3058+
30173059
// Try to begin reading the remaining bytes
30183060
if (!reader.TryBeginRead(readSize))
30193061
{
30203062
throw new OverflowException("Could not deserialize SceneObject: Reading past the end of the buffer");
30213063
}
30223064

3065+
if (HasInstantiationPayload)
3066+
{
3067+
unsafe
3068+
{
3069+
InstantiationPayload = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.Persistent, preInstanceDataSize);
3070+
reader.Seek(reader.Position + preInstanceDataSize);
3071+
}
3072+
}
3073+
30233074
if (HasTransform)
30243075
{
30253076
reader.ReadValue(out Transform);
@@ -3148,7 +3199,9 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId = NetworkManager
31483199
NetworkSceneHandle = NetworkSceneHandle,
31493200
Hash = CheckForGlobalObjectIdHashOverride(),
31503201
OwnerObject = this,
3151-
TargetClientId = targetClientId
3202+
TargetClientId = targetClientId,
3203+
HasInstantiationPayload = InstantiationPayload.IsInitialized,
3204+
InstantiationPayload = InstantiationPayload
31523205
};
31533206

31543207
// Handle Parenting
@@ -3243,6 +3296,11 @@ internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBuf
32433296
// in order to be able to determine which NetworkVariables the client will be allowed to read.
32443297
networkObject.OwnerClientId = sceneObject.OwnerClientId;
32453298

3299+
// Even though the Instantiation Payload is typically consumed during the spawn message handling phase,
3300+
// we still assign it here to preserve the original spawn metadata for potential inspection, diagnostics,
3301+
// or in case future systems want to access it directly without relying on synchronization messages.
3302+
networkObject.InstantiationPayload = sceneObject.InstantiationPayload;
3303+
32463304
// Special Case: Invoke NetworkBehaviour.OnPreSpawn methods here before SynchronizeNetworkBehaviours
32473305
networkObject.InvokeBehaviourNetworkPreSpawn();
32483306

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace Unity.Netcode
2+
{
3+
/// <summary>
4+
/// Interface for synchronizing custom instantiation payloads during NetworkObject spawning.
5+
/// Used alongside <see cref="INetworkPrefabInstanceHandler"/> to extend instantiation behavior.
6+
/// </summary>
7+
public interface INetworkInstantiationPayloadSynchronizer
8+
{
9+
/// <summary>
10+
/// Provides a method for synchronizing instantiation payload data during the spawn process.
11+
/// Extends <see cref="INetworkPrefabInstanceHandler"/> to allow passing additional data prior to instantiation
12+
/// to help identify or configure the local object instance that should be linked to the spawned NetworkObject.
13+
///
14+
/// This method is invoked immediately before <see cref="INetworkPrefabInstanceHandler.Instantiate"/> is called,
15+
/// allowing you to cache or prepare information needed during instantiation.
16+
/// </summary>
17+
/// <param name="serializer">The buffer serializer used to read or write custom instantiation data.</param>
18+
void OnSynchronize<T>(ref BufferSerializer<T> serializer) where T : IReaderWriter;
19+
}
20+
}

com.unity.netcode.gameobjects/Runtime/Spawning/INetworkInstantiationPayloadSynchronizer.cs.meta

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,12 +252,35 @@ internal uint GetSourceGlobalObjectIdHash(uint networkPrefabHash)
252252
/// <param name="position"></param>
253253
/// <param name="rotation"></param>
254254
/// <returns></returns>
255-
internal NetworkObject HandleNetworkPrefabSpawn(uint networkPrefabAssetHash, ulong ownerClientId, Vector3 position, Quaternion rotation)
255+
internal NetworkObject HandleNetworkPrefabSpawn<T>(uint networkPrefabAssetHash, ulong ownerClientId, ref BufferSerializer<T> preInstanceDataSerializer, Vector3 position, Quaternion rotation) where T : IReaderWriter
256256
{
257257
if (m_PrefabAssetToPrefabHandler.TryGetValue(networkPrefabAssetHash, out var prefabInstanceHandler))
258258
{
259-
var networkObjectInstance = prefabInstanceHandler.Instantiate(ownerClientId, position, rotation);
259+
if (prefabInstanceHandler is INetworkInstantiationPayloadSynchronizer synchronizer)
260+
{
261+
synchronizer.OnSynchronize(ref preInstanceDataSerializer);
262+
}
260263

264+
var networkObjectInstance = prefabInstanceHandler.Instantiate(ownerClientId, position, rotation);
265+
266+
if (networkObjectInstance != null)
267+
{
268+
if (preInstanceDataSerializer.IsReader)
269+
{
270+
networkObjectInstance.InstantiationPayload = preInstanceDataSerializer.GetFastBufferReader();
271+
}
272+
else
273+
{
274+
var writer = preInstanceDataSerializer.GetFastBufferWriter();
275+
if (writer.Length > 0)
276+
{
277+
unsafe
278+
{
279+
networkObjectInstance.InstantiationPayload = new FastBufferReader(writer.GetUnsafePtr(), Collections.Allocator.Persistent, writer.Length);
280+
}
281+
}
282+
}
283+
}
261284
//Now we must make sure this alternate PrefabAsset spawned in place of the prefab asset with the networkPrefabAssetHash (GlobalObjectIdHash)
262285
//is registered and linked to the networkPrefabAssetHash so during the HandleNetworkPrefabDestroy process we can identify the alternate prefab asset.
263286
if (networkObjectInstance != null && !m_PrefabInstanceToPrefabAsset.ContainsKey(networkObjectInstance.GlobalObjectIdHash))

0 commit comments

Comments
 (0)