Skip to content

Commit d84313f

Browse files
EmandMExtrysNoelStephensUnity
authored
feat: Add ability to send instantiation data to a prefab handler (#3497)
<!-- Replace this block with what this PR does and why. Describe what you'd like reviewers to know, how you applied the engineering principles, and any interesting tradeoffs made. Delete bullet points below that don't apply, and update the changelog section as appropriate. --> <!-- Add short version of the JIRA ticket to the PR title (e.g. "feat: new shiny feature [MTT-123]") --> Continues the work from @Extrys in #3430. ## Changelog - Added: A PrefabHandler that gives the ability to send data along with object instantiation. - Changed: Moved `INetworkPrefabInstanceHandler` to its own file. ## Testing and Documentation - Includes unit tests. <!-- Uncomment and mark items off with a * if this PR deprecates any API: ### Deprecated API - [ ] An `[Obsolete]` attribute was added along with a `(RemovedAfter yyyy-mm-dd)` entry. - [ ] An [api updater] was added. - [ ] Deprecation of the API is explained in the CHANGELOG. - [ ] The users can understand why this API was removed and what they should use instead. --> ## Backport <!-- If this is a backport: - Add the following to the PR title: "\[Backport\] ..." . - Link to the original PR. If this needs a backport - state this here If a backport is not needed please provide the reason why. If the "Backports" section is not present it will lead to a CI test failure. --> This is a new feature so no backport is needed --------- Co-authored-by: Extrys <[email protected]> Co-authored-by: Noel Stephens <[email protected]>
1 parent 5f89b96 commit d84313f

11 files changed

+461
-108
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
3939

4040
- Added `SinglePlayerTransport` that provides the ability to start as a host for a single player network session. (#3473)
4141
- When using UnityTransport >=2.4 and Unity >= 6000.1.0a1, SetConnectionData will accept a fully qualified hostname instead of an IP as a connect address on the client side. (#3441)
42+
- Added `NetworkPrefabInstanceHandlerWithData<T>`, a variant of `INetworkPrefabInstanceHandler` that provides access to custom instantiation data directly within the `Instantiate()` method. (#3430)
4243

4344
### Fixed
4445

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1593,8 +1593,9 @@ internal void ShutdownInternal()
15931593
// Completely reset the NetworkClient
15941594
ConnectionManager.LocalClient = new NetworkClient();
15951595

1596-
// This cleans up the internal prefabs list
1596+
// Clean up the internal prefabs data
15971597
NetworkConfig?.Prefabs?.Shutdown();
1598+
PrefabHandler.Shutdown();
15981599

15991600
// Reset the configuration hash for next session in the event
16001601
// that the prefab list changes

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

Lines changed: 85 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ public uint PrefabIdHash
5858
}
5959
}
6060

61+
/// <summary>
62+
/// InstantiationData sent during the instantiation process.
63+
/// Available to read as T parameter to <see cref="NetworkPrefabInstanceHandlerWithData{T}.Instantiate(ulong, Vector3, Quaternion, T)"/> for custom handling by user code.
64+
/// </summary>
65+
internal byte[] InstantiationData;
66+
6167
/// <summary>
6268
/// All <see cref="NetworkTransform"/> component instances associated with a <see cref="NetworkObject"/> component instance.
6369
/// </summary>
@@ -2857,6 +2863,12 @@ public bool SpawnWithObservers
28572863
set => ByteUtility.SetBit(ref m_BitField, 10, value);
28582864
}
28592865

2866+
public bool HasInstantiationData
2867+
{
2868+
get => ByteUtility.GetBit(m_BitField, 11);
2869+
set => ByteUtility.SetBit(ref m_BitField, 11, value);
2870+
}
2871+
28602872
// When handling the initial synchronization of NetworkObjects,
28612873
// this will be populated with the known observers.
28622874
public ulong[] Observers;
@@ -2884,6 +2896,7 @@ public struct TransformData : INetworkSerializeByMemcpy
28842896

28852897
public int NetworkSceneHandle;
28862898

2899+
internal int SynchronizationDataSize;
28872900

28882901
public void Serialize(FastBufferWriter writer)
28892902
{
@@ -2945,9 +2958,29 @@ public void Serialize(FastBufferWriter writer)
29452958
writer.WriteValue(OwnerObject.GetSceneOriginHandle());
29462959
}
29472960

2961+
// write placeholder for serialized data size.
2962+
// Can't be bitpacked because we don't know the value until we calculate it later
2963+
var positionBeforeSynchronizing = writer.Position;
2964+
writer.WriteValueSafe(0);
2965+
var sizeToSkipCalculationPosition = writer.Position;
2966+
2967+
if (HasInstantiationData)
2968+
{
2969+
writer.WriteValueSafe(OwnerObject.InstantiationData);
2970+
}
2971+
29482972
// Synchronize NetworkVariables and NetworkBehaviours
29492973
var bufferSerializer = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(writer));
29502974
OwnerObject.SynchronizeNetworkBehaviours(ref bufferSerializer, TargetClientId);
2975+
2976+
var currentPosition = writer.Position;
2977+
// Write the total number of bytes written for synchronization data.
2978+
writer.Seek(positionBeforeSynchronizing);
2979+
// We want the size of everything after our size to skip calculation position
2980+
var size = currentPosition - sizeToSkipCalculationPosition;
2981+
writer.WriteValueSafe(size);
2982+
// seek back to the head of the writer.
2983+
writer.Seek(currentPosition);
29512984
}
29522985

29532986
public void Deserialize(FastBufferReader reader)
@@ -3003,6 +3036,10 @@ public void Deserialize(FastBufferReader reader)
30033036
// The NetworkSceneHandle is the server-side relative
30043037
// scene handle that the NetworkObject resides in.
30053038
reader.ReadValue(out NetworkSceneHandle);
3039+
3040+
// Read the size of the remaining synchronization data
3041+
// This data will be read in AddSceneObject()
3042+
reader.ReadValueSafe(out SynchronizationDataSize);
30063043
}
30073044
}
30083045

@@ -3017,12 +3054,7 @@ internal void SynchronizeNetworkBehaviours<T>(ref BufferSerializer<T> serializer
30173054
{
30183055
if (serializer.IsWriter)
30193056
{
3020-
// write placeholder int.
3021-
// Can't be bitpacked because we don't know the value until we calculate it later
30223057
var writer = serializer.GetFastBufferWriter();
3023-
var positionBeforeSynchronizing = writer.Position;
3024-
writer.WriteValueSafe(0);
3025-
var sizeToSkipCalculationPosition = writer.Position;
30263058

30273059
// Synchronize NetworkVariables
30283060
foreach (var behavior in ChildNetworkBehaviours)
@@ -3048,12 +3080,6 @@ internal void SynchronizeNetworkBehaviours<T>(ref BufferSerializer<T> serializer
30483080
}
30493081

30503082
var currentPosition = writer.Position;
3051-
// Write the total number of bytes written for NetworkVariable and NetworkBehaviour
3052-
// synchronization.
3053-
writer.Seek(positionBeforeSynchronizing);
3054-
// We want the size of everything after our size to skip calculation position
3055-
var size = currentPosition - sizeToSkipCalculationPosition;
3056-
writer.WriteValueSafe(size);
30573083
// Write the number of NetworkBehaviours synchronized
30583084
writer.Seek(networkBehaviourCountPosition);
30593085
writer.WriteValueSafe(synchronizationCount);
@@ -3063,41 +3089,26 @@ internal void SynchronizeNetworkBehaviours<T>(ref BufferSerializer<T> serializer
30633089
}
30643090
else
30653091
{
3066-
var seekToEndOfSynchData = 0;
30673092
var reader = serializer.GetFastBufferReader();
3068-
try
3069-
{
3070-
reader.ReadValueSafe(out int sizeOfSynchronizationData);
3071-
seekToEndOfSynchData = reader.Position + sizeOfSynchronizationData;
3072-
3073-
// Apply the network variable synchronization data
3074-
foreach (var behaviour in ChildNetworkBehaviours)
3075-
{
3076-
behaviour.InitializeVariables();
3077-
behaviour.SetNetworkVariableData(reader, targetClientId);
3078-
}
30793093

3080-
// Read the number of NetworkBehaviours to synchronize
3081-
reader.ReadValueSafe(out byte numberSynchronized);
3094+
// Apply the network variable synchronization data
3095+
foreach (var behaviour in ChildNetworkBehaviours)
3096+
{
3097+
behaviour.InitializeVariables();
3098+
behaviour.SetNetworkVariableData(reader, targetClientId);
3099+
}
30823100

3083-
// If a NetworkBehaviour writes synchronization data, it will first
3084-
// write its NetworkBehaviourId so when deserializing the client-side
3085-
// can find the right NetworkBehaviour to deserialize the synchronization data.
3086-
for (int i = 0; i < numberSynchronized; i++)
3087-
{
3088-
reader.ReadValueSafe(out ushort networkBehaviourId);
3089-
var networkBehaviour = GetNetworkBehaviourAtOrderIndex(networkBehaviourId);
3090-
networkBehaviour.Synchronize(ref serializer, targetClientId);
3091-
}
3101+
// Read the number of NetworkBehaviours to synchronize
3102+
reader.ReadValueSafe(out byte numberSynchronized);
30923103

3093-
if (seekToEndOfSynchData != reader.Position)
3094-
{
3095-
Debug.LogWarning($"[Size mismatch] Expected: {seekToEndOfSynchData} Currently At: {reader.Position}!");
3096-
}
3097-
}
3098-
catch
3104+
// If a NetworkBehaviour writes synchronization data, it will first
3105+
// write its NetworkBehaviourId so when deserializing the client-side
3106+
// can find the right NetworkBehaviour to deserialize the synchronization data.
3107+
for (int i = 0; i < numberSynchronized; i++)
30993108
{
3100-
reader.Seek(seekToEndOfSynchData);
3109+
reader.ReadValueSafe(out ushort networkBehaviourId);
3110+
var networkBehaviour = GetNetworkBehaviourAtOrderIndex(networkBehaviourId);
3111+
networkBehaviour.Synchronize(ref serializer, targetClientId);
31013112
}
31023113
}
31033114
}
@@ -3121,7 +3132,8 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId = NetworkManager
31213132
NetworkSceneHandle = NetworkSceneHandle,
31223133
Hash = CheckForGlobalObjectIdHashOverride(),
31233134
OwnerObject = this,
3124-
TargetClientId = targetClientId
3135+
TargetClientId = targetClientId,
3136+
HasInstantiationData = InstantiationData != null && InstantiationData.Length > 0
31253137
};
31263138

31273139
// Handle Parenting
@@ -3186,8 +3198,18 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId = NetworkManager
31863198
/// <returns>The deserialized NetworkObject or null if deserialization failed</returns>
31873199
internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBufferReader reader, NetworkManager networkManager, bool invokedByMessage = false)
31883200
{
3189-
//Attempt to create a local NetworkObject
3190-
var networkObject = networkManager.SpawnManager.CreateLocalNetworkObject(sceneObject);
3201+
var endOfSynchronizationData = reader.Position + sceneObject.SynchronizationDataSize;
3202+
3203+
byte[] instantiationData = null;
3204+
if (sceneObject.HasInstantiationData)
3205+
{
3206+
reader.ReadValueSafe(out instantiationData);
3207+
}
3208+
3209+
3210+
// Attempt to create a local NetworkObject
3211+
var networkObject = networkManager.SpawnManager.CreateLocalNetworkObject(sceneObject, instantiationData);
3212+
31913213

31923214
if (networkObject == null)
31933215
{
@@ -3200,8 +3222,7 @@ internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBuf
32003222
try
32013223
{
32023224
// If we failed to load this NetworkObject, then skip past the Network Variable and (if any) synchronization data
3203-
reader.ReadValueSafe(out int networkBehaviourSynchronizationDataLength);
3204-
reader.Seek(reader.Position + networkBehaviourSynchronizationDataLength);
3225+
reader.Seek(endOfSynchronizationData);
32053226
}
32063227
catch (Exception ex)
32073228
{
@@ -3219,9 +3240,24 @@ internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBuf
32193240
// Special Case: Invoke NetworkBehaviour.OnPreSpawn methods here before SynchronizeNetworkBehaviours
32203241
networkObject.InvokeBehaviourNetworkPreSpawn();
32213242

3222-
// Synchronize NetworkBehaviours
3223-
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(reader));
3224-
networkObject.SynchronizeNetworkBehaviours(ref bufferSerializer, networkManager.LocalClientId);
3243+
// Process the remaining synchronization data from the buffer
3244+
try
3245+
{
3246+
// Synchronize NetworkBehaviours
3247+
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(reader));
3248+
networkObject.SynchronizeNetworkBehaviours(ref bufferSerializer, networkManager.LocalClientId);
3249+
3250+
// Ensure that the buffer is completely reset
3251+
if (reader.Position != endOfSynchronizationData)
3252+
{
3253+
Debug.LogWarning($"[Size mismatch] Expected: {endOfSynchronizationData} Currently At: {reader.Position}!");
3254+
reader.Seek(endOfSynchronizationData);
3255+
}
3256+
}
3257+
catch
3258+
{
3259+
reader.Seek(endOfSynchronizationData);
3260+
}
32253261

32263262
// If we are an in-scene placed NetworkObject and we originally had a parent but when synchronized we are
32273263
// being told we do not have a parent, then we want to clear the latest parent so it is not automatically
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System.Collections.Generic;
2+
using UnityEngine;
3+
4+
namespace Unity.Netcode
5+
{
6+
/// <summary>
7+
/// Interface for customizing, overriding, spawning, and destroying Network Prefabs
8+
/// Used by <see cref="NetworkPrefabHandler"/>
9+
/// </summary>
10+
public interface INetworkPrefabInstanceHandler
11+
{
12+
/// <summary>
13+
/// Client Side Only
14+
/// Once an implementation is registered with the <see cref="NetworkPrefabHandler"/>, this method will be called every time
15+
/// a Network Prefab associated <see cref="NetworkObject"/> is spawned on clients
16+
///
17+
/// Note On Hosts: Use the <see cref="NetworkPrefabHandler.RegisterHostGlobalObjectIdHashValues(GameObject, List{T})"/>
18+
/// method to register all targeted NetworkPrefab overrides manually since the host will be acting as both a server and client.
19+
///
20+
/// Note on Pooling: If you are using a NetworkObject pool, don't forget to make the NetworkObject active
21+
/// via the <see cref="GameObject.SetActive(bool)"/> method.
22+
/// </summary>
23+
/// <remarks>
24+
/// If you need to pass custom data at instantiation time (e.g., selecting a variant, setting initialization parameters, or choosing a pre-instantiated object),
25+
/// implement <see cref="NetworkPrefabInstanceHandlerWithData{T}"/> instead.
26+
/// </remarks>
27+
/// <param name="ownerClientId">the owner for the <see cref="NetworkObject"/> to be instantiated</param>
28+
/// <param name="position">the initial/default position for the <see cref="NetworkObject"/> to be instantiated</param>
29+
/// <param name="rotation">the initial/default rotation for the <see cref="NetworkObject"/> to be instantiated</param>
30+
/// <returns>The instantiated NetworkObject instance. Returns null if instantiation fails.</returns>
31+
public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation);
32+
33+
/// <summary>
34+
/// Invoked on Client and Server
35+
/// Once an implementation is registered with the <see cref="NetworkPrefabHandler"/>, this method will be called when
36+
/// a Network Prefab associated <see cref="NetworkObject"/> is:
37+
///
38+
/// Server Side: destroyed or despawned with the destroy parameter equal to true
39+
/// If <see cref="NetworkObject.Despawn(bool)"/> is invoked with the default destroy parameter (i.e. false) then this method will NOT be invoked!
40+
///
41+
/// Client Side: destroyed when the client receives a destroy object message from the server or host.
42+
///
43+
/// Note on Pooling: When this method is invoked, you do not need to destroy the NetworkObject as long as you want your pool to persist.
44+
/// The most common approach is to make the <see cref="NetworkObject"/> inactive by calling <see cref="GameObject.SetActive(bool)"/>.
45+
/// </summary>
46+
/// <param name="networkObject">The <see cref="NetworkObject"/> being destroyed</param>
47+
public void Destroy(NetworkObject networkObject);
48+
}
49+
}

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

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

0 commit comments

Comments
 (0)