Skip to content

Commit cd6ead3

Browse files
feat: add pre and post spawn methods (#2906)
* update adding pre and post spawn methods. clearing the m_ChildNetworkBehaviours on Awake. * update adding change log entries * update Needed to add a NetworkManager ref into the pre spawn method. Needed to account for locally spawning. * test Adding test that validates the pre spawn and post spawn methods are invoked and that order of operations for OnNetworkSpawn invocation is not an issue with OnNetworkPostSpawn invocation. * style updating comments * style updating comments a bit more * test Migrating OnDynamicNetworkPreAndPostSpawn into its own class to avoid interfering with the generic tests. * style removing property no longer used and remove LF/CR * update and test Added NetworkBehaviour.OnNetworkSessionSynchronized and NetworkBehaviour.OnInSceneObjectsSpawned methods. Added test to validate the above methods. Added assets for the test to validate the above methods.
1 parent e3643cf commit cd6ead3

17 files changed

+792
-7
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

+15
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
66

77
Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).
88

9+
## [Unreleased]
10+
11+
### Added
12+
13+
- Added `NetworkBehaviour.OnNetworkPreSpawn` and `NetworkBehaviour.OnNetworkPostSpawn` methods that provide the ability to handle pre and post spawning actions during the `NetworkObject` spawn sequence. (#2906)
14+
- Added a client-side only `NetworkBehaviour.OnNetworkSessionSynchronized` convenience method that is invoked on all `NetworkBehaviour`s after a newly joined client has finished synchronizing with the network session in progress. (#2906)
15+
- Added `NetworkBehaviour.OnInSceneObjectsSpawned` convenience method that is invoked when all in-scene `NetworkObject`s have been spawned after a scene has been loaded or upon a host or server starting. (#2906)
16+
17+
### Fixed
18+
19+
- Fixed issue where a `NetworkObject` component's associated `NetworkBehaviour` components would not be detected if scene loading is disabled in the editor and the currently loaded scene has in-scene placed `NetworkObject`s. (#2906)
20+
21+
### Changed
22+
23+
924
## [1.9.1] - 2024-04-18
1025

1126
### Added

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

+90
Original file line numberDiff line numberDiff line change
@@ -615,16 +615,70 @@ internal void UpdateNetworkProperties()
615615
}
616616
}
617617

618+
/// <summary>
619+
/// Gets called after the <see cref="NetworkObject"/> is spawned. No NetworkBehaviours associated with the NetworkObject will have had <see cref="OnNetworkSpawn"/> invoked yet.
620+
/// A reference to <see cref="NetworkManager"/> is passed in as a parameter to determine the context of execution (IsServer/IsClient)
621+
/// </summary>
622+
/// <remarks>
623+
/// <param name="networkManager">a ref to the <see cref="NetworkManager"/> since this is not yet set on the <see cref="NetworkBehaviour"/></param>
624+
/// The <see cref="NetworkBehaviour"/> will not have anything assigned to it at this point in time.
625+
/// Settings like ownership, NetworkBehaviourId, NetworkManager, and most other spawn related properties will not be set.
626+
/// This can be used to handle things like initializing/instantiating a NetworkVariable or the like.
627+
/// </remarks>
628+
protected virtual void OnNetworkPreSpawn(ref NetworkManager networkManager) { }
629+
618630
/// <summary>
619631
/// Gets called when the <see cref="NetworkObject"/> gets spawned, message handlers are ready to be registered and the network is setup.
620632
/// </summary>
621633
public virtual void OnNetworkSpawn() { }
622634

635+
/// <summary>
636+
/// Gets called after the <see cref="NetworkObject"/> is spawned. All NetworkBehaviours associated with the NetworkObject will have had <see cref="OnNetworkSpawn"/> invoked.
637+
/// </summary>
638+
/// <remarks>
639+
/// Will be invoked on each <see cref="NetworkBehaviour"/> associated with the <see cref="NetworkObject"/> being spawned.
640+
/// All associated <see cref="NetworkBehaviour"/> components will have had <see cref="OnNetworkSpawn"/> invoked on the spawned <see cref="NetworkObject"/>.
641+
/// </remarks>
642+
protected virtual void OnNetworkPostSpawn() { }
643+
644+
/// <summary>
645+
/// [Client-Side Only]
646+
/// When a new client joins it is synchronized with all spawned NetworkObjects and scenes loaded for the session joined. At the end of the synchronization process, when all
647+
/// <see cref="NetworkObject"/>s and scenes (if scene management is enabled) have finished synchronizing, all NetworkBehaviour components associated with spawned <see cref="NetworkObject"/>s
648+
/// will have this method invoked.
649+
/// </summary>
650+
/// <remarks>
651+
/// This can be used to handle post synchronization actions where you might need to access a different NetworkObject and/or NetworkBehaviour not local to the current NetworkObject context.
652+
/// This is only invoked on clients during a client-server network topology session.
653+
/// </remarks>
654+
protected virtual void OnNetworkSessionSynchronized() { }
655+
656+
/// <summary>
657+
/// [Client & Server Side]
658+
/// When a scene is loaded an in-scene placed NetworkObjects are all spawned, this method is invoked on all of the newly spawned in-scene placed NetworkObjects.
659+
/// </summary>
660+
/// <remarks>
661+
/// This can be used to handle post scene loaded actions for in-scene placed NetworkObjcts where you might need to access a different NetworkObject and/or NetworkBehaviour not local to the current NetworkObject context.
662+
/// </remarks>
663+
protected virtual void OnInSceneObjectsSpawned() { }
664+
623665
/// <summary>
624666
/// Gets called when the <see cref="NetworkObject"/> gets despawned. Is called both on the server and clients.
625667
/// </summary>
626668
public virtual void OnNetworkDespawn() { }
627669

670+
internal void NetworkPreSpawn(ref NetworkManager networkManager)
671+
{
672+
try
673+
{
674+
OnNetworkPreSpawn(ref networkManager);
675+
}
676+
catch (Exception e)
677+
{
678+
Debug.LogException(e);
679+
}
680+
}
681+
628682
internal void InternalOnNetworkSpawn()
629683
{
630684
IsSpawned = true;
@@ -653,6 +707,42 @@ internal void VisibleOnNetworkSpawn()
653707
}
654708
}
655709

710+
internal void NetworkPostSpawn()
711+
{
712+
try
713+
{
714+
OnNetworkPostSpawn();
715+
}
716+
catch (Exception e)
717+
{
718+
Debug.LogException(e);
719+
}
720+
}
721+
722+
internal void NetworkSessionSynchronized()
723+
{
724+
try
725+
{
726+
OnNetworkSessionSynchronized();
727+
}
728+
catch (Exception e)
729+
{
730+
Debug.LogException(e);
731+
}
732+
}
733+
734+
internal void InSceneNetworkObjectsSpawned()
735+
{
736+
try
737+
{
738+
OnInSceneObjectsSpawned();
739+
}
740+
catch (Exception e)
741+
{
742+
Debug.LogException(e);
743+
}
744+
}
745+
656746
internal void InternalOnNetworkDespawn()
657747
{
658748
IsSpawned = false;

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

+52
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,7 @@ internal int GetSceneOriginHandle()
512512

513513
private void Awake()
514514
{
515+
m_ChildNetworkBehaviours = null;
515516
SetCachedParent(transform.parent);
516517
SceneOrigin = gameObject.scene;
517518
}
@@ -1360,6 +1361,18 @@ internal static void CheckOrphanChildren()
13601361
}
13611362
}
13621363

1364+
internal void InvokeBehaviourNetworkPreSpawn()
1365+
{
1366+
var networkManager = NetworkManager;
1367+
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
1368+
{
1369+
if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
1370+
{
1371+
ChildNetworkBehaviours[i].NetworkPreSpawn(ref networkManager);
1372+
}
1373+
}
1374+
}
1375+
13631376
internal void InvokeBehaviourNetworkSpawn()
13641377
{
13651378
NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId);
@@ -1384,6 +1397,42 @@ internal void InvokeBehaviourNetworkSpawn()
13841397
}
13851398
}
13861399

1400+
internal void InvokeBehaviourNetworkPostSpawn()
1401+
{
1402+
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
1403+
{
1404+
if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
1405+
{
1406+
ChildNetworkBehaviours[i].NetworkPostSpawn();
1407+
}
1408+
}
1409+
}
1410+
1411+
1412+
internal void InternalNetworkSessionSynchronized()
1413+
{
1414+
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
1415+
{
1416+
if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
1417+
{
1418+
ChildNetworkBehaviours[i].NetworkSessionSynchronized();
1419+
}
1420+
}
1421+
}
1422+
1423+
internal void InternalInSceneNetworkObjectsSpawned()
1424+
{
1425+
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
1426+
{
1427+
if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
1428+
{
1429+
ChildNetworkBehaviours[i].InSceneNetworkObjectsSpawned();
1430+
}
1431+
}
1432+
}
1433+
1434+
1435+
13871436
internal void InvokeBehaviourNetworkDespawn()
13881437
{
13891438
NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId, true);
@@ -1886,6 +1935,9 @@ internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBuf
18861935
// in order to be able to determine which NetworkVariables the client will be allowed to read.
18871936
networkObject.OwnerClientId = sceneObject.OwnerClientId;
18881937

1938+
// Special Case: Invoke NetworkBehaviour.OnPreSpawn methods here before SynchronizeNetworkBehaviours
1939+
networkObject.InvokeBehaviourNetworkPreSpawn();
1940+
18891941
// Synchronize NetworkBehaviours
18901942
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(reader));
18911943
networkObject.SynchronizeNetworkBehaviours(ref bufferSerializer, networkManager.LocalClientId);

com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs

+6
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,12 @@ public void Handle(ref NetworkContext context)
170170
networkManager.IsConnectedClient = true;
171171
// When scene management is disabled we notify after everything is synchronized
172172
networkManager.ConnectionManager.InvokeOnClientConnectedCallback(context.SenderId);
173+
174+
// For convenience, notify all NetworkBehaviours that synchronization is complete.
175+
foreach (var networkObject in networkManager.SpawnManager.SpawnedObjectsList)
176+
{
177+
networkObject.InternalNetworkSessionSynchronized();
178+
}
173179
}
174180

175181
ConnectedClientIds.Dispose();

com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs

+17
Original file line numberDiff line numberDiff line change
@@ -1688,6 +1688,17 @@ private void OnServerLoadedScene(uint sceneEventId, Scene scene)
16881688
}
16891689
}
16901690

1691+
foreach (var keyValuePairByGlobalObjectIdHash in ScenePlacedObjects)
1692+
{
1693+
foreach (var keyValuePairBySceneHandle in keyValuePairByGlobalObjectIdHash.Value)
1694+
{
1695+
if (!keyValuePairBySceneHandle.Value.IsPlayerObject)
1696+
{
1697+
keyValuePairBySceneHandle.Value.InternalInSceneNetworkObjectsSpawned();
1698+
}
1699+
}
1700+
}
1701+
16911702
// Add any despawned when spawned in-scene placed NetworkObjects to the scene event data
16921703
sceneEventData.AddDespawnedInSceneNetworkObjects();
16931704

@@ -2142,6 +2153,12 @@ private void HandleClientSceneEvent(uint sceneEventId)
21422153

21432154
OnSynchronizeComplete?.Invoke(NetworkManager.LocalClientId);
21442155

2156+
// For convenience, notify all NetworkBehaviours that synchronization is complete.
2157+
foreach (var networkObject in NetworkManager.SpawnManager.SpawnedObjectsList)
2158+
{
2159+
networkObject.InternalNetworkSessionSynchronized();
2160+
}
2161+
21452162
EndSceneEvent(sceneEventId);
21462163
}
21472164
break;

com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs

+23-2
Original file line numberDiff line numberDiff line change
@@ -722,7 +722,7 @@ internal void DeserializeScenePlacedObjects()
722722
{
723723
// is not packed!
724724
InternalBuffer.ReadValueSafe(out ushort newObjectsCount);
725-
725+
var sceneObjects = new List<NetworkObject>();
726726
for (ushort i = 0; i < newObjectsCount; i++)
727727
{
728728
var sceneObject = new NetworkObject.SceneObject();
@@ -734,10 +734,22 @@ internal void DeserializeScenePlacedObjects()
734734
m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(sceneObject.NetworkSceneHandle);
735735
}
736736

737-
NetworkObject.AddSceneObject(sceneObject, InternalBuffer, m_NetworkManager);
737+
var networkObject = NetworkObject.AddSceneObject(sceneObject, InternalBuffer, m_NetworkManager);
738+
739+
if (sceneObject.IsSceneObject)
740+
{
741+
sceneObjects.Add(networkObject);
742+
}
738743
}
739744
// Now deserialize the despawned in-scene placed NetworkObjects list (if any)
740745
DeserializeDespawnedInScenePlacedNetworkObjects();
746+
747+
// Notify all newly spawned in-scene placed NetworkObjects that all in-scene placed
748+
// NetworkObjects have been spawned.
749+
foreach (var networkObject in sceneObjects)
750+
{
751+
networkObject.InternalInSceneNetworkObjectsSpawned();
752+
}
741753
}
742754
finally
743755
{
@@ -983,6 +995,15 @@ internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager)
983995
}
984996
}
985997

998+
// Notify that all in-scene placed NetworkObjects have been spawned
999+
foreach (var networkObject in m_NetworkObjectsSync)
1000+
{
1001+
if (networkObject.IsSceneObject.HasValue && networkObject.IsSceneObject.Value)
1002+
{
1003+
networkObject.InternalInSceneNetworkObjectsSpawned();
1004+
}
1005+
}
1006+
9861007
// Now deserialize the despawned in-scene placed NetworkObjects list (if any)
9871008
DeserializeDespawnedInScenePlacedNetworkObjects();
9881009

com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBehaviourReference.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public NetworkBehaviourReference(NetworkBehaviour networkBehaviour)
4545
/// <returns>True if the <see cref="NetworkBehaviour"/> was found; False if the <see cref="NetworkBehaviour"/> was not found. This can happen if the corresponding <see cref="NetworkObject"/> has not been spawned yet. you can try getting the reference at a later point in time.</returns>
4646
public bool TryGet(out NetworkBehaviour networkBehaviour, NetworkManager networkManager = null)
4747
{
48-
networkBehaviour = GetInternal(this, null);
48+
networkBehaviour = GetInternal(this, networkManager);
4949
return networkBehaviour != null;
5050
}
5151

@@ -58,7 +58,7 @@ public bool TryGet(out NetworkBehaviour networkBehaviour, NetworkManager network
5858
/// <returns>True if the <see cref="NetworkBehaviour"/> was found; False if the <see cref="NetworkBehaviour"/> was not found. This can happen if the corresponding <see cref="NetworkObject"/> has not been spawned yet. you can try getting the reference at a later point in time.</returns>
5959
public bool TryGet<T>(out T networkBehaviour, NetworkManager networkManager = null) where T : NetworkBehaviour
6060
{
61-
networkBehaviour = GetInternal(this, null) as T;
61+
networkBehaviour = GetInternal(this, networkManager) as T;
6262
return networkBehaviour != null;
6363
}
6464

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

+27-3
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,12 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO
604604
return networkObject;
605605
}
606606

607-
// Ran on both server and client
607+
/// <summary>
608+
/// Invoked when spawning locally
609+
/// </summary>
610+
/// <remarks>
611+
/// Pre and Post spawn methods *CAN* be invoked prior to invoking <see cref="SpawnNetworkObjectLocallyCommon"/>
612+
/// </remarks>
608613
internal void SpawnNetworkObjectLocally(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong ownerClientId, bool destroyWithScene)
609614
{
610615
if (networkObject == null)
@@ -625,11 +630,21 @@ internal void SpawnNetworkObjectLocally(NetworkObject networkObject, ulong netwo
625630
Debug.LogError("Spawning NetworkObjects with nested NetworkObjects is only supported for scene objects. Child NetworkObjects will not be spawned over the network!");
626631
}
627632
}
633+
// Invoke NetworkBehaviour.OnPreSpawn methods
634+
networkObject.InvokeBehaviourNetworkPreSpawn();
628635

629636
SpawnNetworkObjectLocallyCommon(networkObject, networkId, sceneObject, playerObject, ownerClientId, destroyWithScene);
637+
638+
// Invoke NetworkBehaviour.OnPostSpawn methods
639+
networkObject.InvokeBehaviourNetworkPostSpawn();
630640
}
631641

632-
// Ran on both server and client
642+
/// <summary>
643+
/// Invoked from AddSceneObject
644+
/// </summary>
645+
/// <remarks>
646+
/// IMPORTANT: Pre spawn methods need to be invoked from within <see cref="NetworkObject.AddSceneObject"/>.
647+
/// </remarks>
633648
internal void SpawnNetworkObjectLocally(NetworkObject networkObject, in NetworkObject.SceneObject sceneObject, bool destroyWithScene)
634649
{
635650
if (networkObject == null)
@@ -642,7 +657,11 @@ internal void SpawnNetworkObjectLocally(NetworkObject networkObject, in NetworkO
642657
throw new SpawnStateException("Object is already spawned");
643658
}
644659

660+
// Do not invoke Pre spawn here (SynchronizeNetworkBehaviours needs to be invoked prior to this)
645661
SpawnNetworkObjectLocallyCommon(networkObject, sceneObject.NetworkObjectId, sceneObject.IsSceneObject, sceneObject.IsPlayerObject, sceneObject.OwnerClientId, destroyWithScene);
662+
663+
// It is ok to invoke NetworkBehaviour.OnPostSpawn methods
664+
networkObject.InvokeBehaviourNetworkPostSpawn();
646665
}
647666

648667
private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong ownerClientId, bool destroyWithScene)
@@ -948,11 +967,16 @@ internal void ServerSpawnSceneObjectsOnStartSweep()
948967
}
949968
}
950969

951-
952970
foreach (var networkObject in networkObjectsToSpawn)
953971
{
954972
SpawnNetworkObjectLocally(networkObject, GetNetworkObjectId(), true, false, networkObject.OwnerClientId, true);
955973
}
974+
975+
// Notify all in-scene placed NetworkObjects have been spawned
976+
foreach (var networkObject in networkObjectsToSpawn)
977+
{
978+
networkObject.InternalInSceneNetworkObjectsSpawned();
979+
}
956980
}
957981

958982
internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObject)

0 commit comments

Comments
 (0)