Skip to content

Commit 87d23e9

Browse files
fix: NetworkManager instantiate & destroy notifications and player spawn prefab offset (#3088)
* update Adding OnInstantiated and OnDestroying events to NetworkManager in order to provide the ability to know when a new NetworkManager instance has been instantiated and is being destroyed. * update Fixing an issue where a player would not spawn with its default network prefab instance's position and rotation settings. * test Adding validation test * update adding changelog entries * style Adding CR between Added and entries. * fix Fixing player spawn position for client-server. * test The validation test to validate the player's spawn position will be the prefab's offset if no other values are specified. * style removing using directive not needed.
1 parent cb7ec98 commit 87d23e9

File tree

6 files changed

+146
-7
lines changed

6 files changed

+146
-7
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ Additional documentation and release notes are available at [Multiplayer Documen
1010

1111
### Added
1212

13+
- Added a static `NetworkManager.OnInstantiated` event notification to be able to track when a new `NetworkManager` instance has been instantiated. (#3088)
14+
- Added a static `NetworkManager.OnDestroying` event notification to be able to track when an existing `NetworkManager` instance is being destroyed. (#3088)
15+
1316
### Fixed
1417

1518
- Fixed issue where applying the position and/or rotation to the `NetworkManager.ConnectionApprovalResponse` when connection approval and auto-spawn player prefab were enabled would not apply the position and/or rotation when the player prefab was instantiated. (#3078)

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -751,8 +751,8 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne
751751
// Server-side spawning (only if there is a prefab hash or player prefab provided)
752752
if (!NetworkManager.DistributedAuthorityMode && response.CreatePlayerObject && (response.PlayerPrefabHash.HasValue || NetworkManager.NetworkConfig.PlayerPrefab != null))
753753
{
754-
var playerObject = response.PlayerPrefabHash.HasValue ? NetworkManager.SpawnManager.GetNetworkObjectToSpawn(response.PlayerPrefabHash.Value, ownerClientId, response.Position.GetValueOrDefault(), response.Rotation.GetValueOrDefault())
755-
: NetworkManager.SpawnManager.GetNetworkObjectToSpawn(NetworkManager.NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash, ownerClientId, response.Position.GetValueOrDefault(), response.Rotation.GetValueOrDefault());
754+
var playerObject = response.PlayerPrefabHash.HasValue ? NetworkManager.SpawnManager.GetNetworkObjectToSpawn(response.PlayerPrefabHash.Value, ownerClientId, response.Position ?? null, response.Rotation ?? null)
755+
: NetworkManager.SpawnManager.GetNetworkObjectToSpawn(NetworkManager.NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash, ownerClientId, response.Position ?? null, response.Rotation ?? null);
756756

757757
// Spawn the player NetworkObject locally
758758
NetworkManager.SpawnManager.SpawnNetworkObjectLocally(
@@ -896,15 +896,15 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne
896896
/// <summary>
897897
/// Client-Side Spawning in distributed authority mode uses this to spawn the player.
898898
/// </summary>
899-
internal void CreateAndSpawnPlayer(ulong ownerId, Vector3 position = default, Quaternion rotation = default)
899+
internal void CreateAndSpawnPlayer(ulong ownerId)
900900
{
901901
if (NetworkManager.DistributedAuthorityMode && NetworkManager.AutoSpawnPlayerPrefabClientSide)
902902
{
903903
var playerPrefab = NetworkManager.FetchLocalPlayerPrefabToSpawn();
904904
if (playerPrefab != null)
905905
{
906906
var globalObjectIdHash = playerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash;
907-
var networkObject = NetworkManager.SpawnManager.GetNetworkObjectToSpawn(globalObjectIdHash, ownerId, position, rotation);
907+
var networkObject = NetworkManager.SpawnManager.GetNetworkObjectToSpawn(globalObjectIdHash, ownerId, playerPrefab.transform.position, playerPrefab.transform.rotation);
908908
networkObject.IsSceneObject = false;
909909
networkObject.SpawnAsPlayerObject(ownerId, networkObject.DestroyWithScene);
910910
}

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,17 @@ namespace Unity.Netcode
1717
[AddComponentMenu("Netcode/Network Manager", -100)]
1818
public class NetworkManager : MonoBehaviour, INetworkUpdateSystem
1919
{
20+
/// <summary>
21+
/// Subscribe to this static event to get notifications when a <see cref="NetworkManager"/> instance has been instantiated.
22+
/// </summary>
23+
public static event Action<NetworkManager> OnInstantiated;
24+
25+
/// <summary>
26+
/// Subscribe to this static event to get notifications when a <see cref="NetworkManager"/> instance is being destroyed.
27+
/// </summary>
28+
public static event Action<NetworkManager> OnDestroying;
29+
30+
2031
#if UNITY_EDITOR
2132
// Inspector view expand/collapse settings for this derived child class
2233
[HideInInspector]
@@ -1030,6 +1041,8 @@ private void Awake()
10301041
#if UNITY_EDITOR
10311042
EditorApplication.playModeStateChanged += ModeChanged;
10321043
#endif
1044+
// Notify we have instantiated a new instance of NetworkManager.
1045+
OnInstantiated?.Invoke(this);
10331046
}
10341047

10351048
private void OnEnable()
@@ -1632,6 +1645,9 @@ private void OnDestroy()
16321645

16331646
UnityEngine.SceneManagement.SceneManager.sceneUnloaded -= OnSceneUnloaded;
16341647

1648+
// Notify we are destroying NetworkManager
1649+
OnDestroying?.Invoke(this);
1650+
16351651
if (Singleton == this)
16361652
{
16371653
Singleton = null;

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -706,14 +706,14 @@ internal NetworkObject InstantiateAndSpawnNoParameterChecks(NetworkObject networ
706706
/// Gets the right NetworkObject prefab instance to spawn. If a handler is registered or there is an override assigned to the
707707
/// passed in globalObjectIdHash value, then that is what will be instantiated, spawned, and returned.
708708
/// </summary>
709-
internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ownerId, Vector3 position = default, Quaternion rotation = default, bool isScenePlaced = false)
709+
internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ownerId, Vector3? position, Quaternion? rotation, bool isScenePlaced = false)
710710
{
711711
NetworkObject networkObject = null;
712712
// If the prefab hash has a registered INetworkPrefabInstanceHandler derived class
713713
if (NetworkManager.PrefabHandler.ContainsHandler(globalObjectIdHash))
714714
{
715715
// Let the handler spawn the NetworkObject
716-
networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, ownerId, position, rotation);
716+
networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, ownerId, position ?? default, rotation ?? default);
717717
networkObject.NetworkManagerOwner = NetworkManager;
718718
}
719719
else
@@ -764,7 +764,9 @@ internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ow
764764
else
765765
{
766766
// Create prefab instance while applying any pre-assigned position and rotation values
767-
networkObject = UnityEngine.Object.Instantiate(networkPrefabReference, position, rotation).GetComponent<NetworkObject>();
767+
networkObject = UnityEngine.Object.Instantiate(networkPrefabReference).GetComponent<NetworkObject>();
768+
networkObject.transform.position = position ?? networkObject.transform.position;
769+
networkObject.transform.rotation = rotation ?? networkObject.transform.rotation;
768770
networkObject.NetworkManagerOwner = NetworkManager;
769771
networkObject.PrefabGlobalObjectIdHash = globalObjectIdHash;
770772
}

com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,60 @@ internal class NetworkManagerEventsTests
1313
private NetworkManager m_ClientManager;
1414
private NetworkManager m_ServerManager;
1515

16+
private NetworkManager m_NetworkManagerInstantiated;
17+
private bool m_Instantiated;
18+
private bool m_Destroyed;
19+
20+
/// <summary>
21+
/// Validates the <see cref="NetworkManager.OnInstantiated"/> and <see cref="NetworkManager.OnDestroying"/> event notifications
22+
/// </summary>
23+
[UnityTest]
24+
public IEnumerator InstantiatedAndDestroyingNotifications()
25+
{
26+
NetworkManager.OnInstantiated += NetworkManager_OnInstantiated;
27+
NetworkManager.OnDestroying += NetworkManager_OnDestroying;
28+
var waitPeriod = new WaitForSeconds(0.01f);
29+
var prefab = new GameObject("InstantiateDestroy");
30+
var networkManagerPrefab = prefab.AddComponent<NetworkManager>();
31+
32+
Assert.IsTrue(m_Instantiated, $"{nameof(NetworkManager)} prefab did not get instantiated event notification!");
33+
Assert.IsTrue(m_NetworkManagerInstantiated == networkManagerPrefab, $"{nameof(NetworkManager)} prefab parameter did not match!");
34+
35+
m_Instantiated = false;
36+
m_NetworkManagerInstantiated = null;
37+
38+
for (int i = 0; i < 3; i++)
39+
{
40+
var instance = Object.Instantiate(prefab);
41+
var networkManager = instance.GetComponent<NetworkManager>();
42+
Assert.IsTrue(m_Instantiated, $"{nameof(NetworkManager)} instance-{i} did not get instantiated event notification!");
43+
Assert.IsTrue(m_NetworkManagerInstantiated == networkManager, $"{nameof(NetworkManager)} instance-{i} parameter did not match!");
44+
Object.DestroyImmediate(instance);
45+
Assert.IsTrue(m_Destroyed, $"{nameof(NetworkManager)} instance-{i} did not get destroying event notification!");
46+
m_Instantiated = false;
47+
m_NetworkManagerInstantiated = null;
48+
m_Destroyed = false;
49+
}
50+
m_NetworkManagerInstantiated = networkManagerPrefab;
51+
Object.Destroy(prefab);
52+
yield return null;
53+
Assert.IsTrue(m_Destroyed, $"{nameof(NetworkManager)} prefab did not get destroying event notification!");
54+
NetworkManager.OnInstantiated -= NetworkManager_OnInstantiated;
55+
NetworkManager.OnDestroying -= NetworkManager_OnDestroying;
56+
}
57+
58+
private void NetworkManager_OnInstantiated(NetworkManager networkManager)
59+
{
60+
m_Instantiated = true;
61+
m_NetworkManagerInstantiated = networkManager;
62+
}
63+
64+
private void NetworkManager_OnDestroying(NetworkManager networkManager)
65+
{
66+
m_Destroyed = true;
67+
Assert.True(m_NetworkManagerInstantiated == networkManager, $"Destroying {nameof(NetworkManager)} and current instance is not a match for the one passed into the event!");
68+
}
69+
1670
[UnityTest]
1771
public IEnumerator OnServerStoppedCalledWhenServerStops()
1872
{

com.unity.netcode.gameobjects/Tests/Runtime/PlayerObjectTests.cs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,68 @@ public IEnumerator SpawnWithNoObservers()
120120
}
121121
}
122122
}
123+
124+
/// <summary>
125+
/// This test validates the player position and rotation is correct
126+
/// relative to the prefab's initial settings if no changes are applied.
127+
/// </summary>
128+
[TestFixture(HostOrServer.DAHost)]
129+
[TestFixture(HostOrServer.Host)]
130+
[TestFixture(HostOrServer.Server)]
131+
internal class PlayerSpawnPositionTests : IntegrationTestWithApproximation
132+
{
133+
protected override int NumberOfClients => 2;
134+
135+
public PlayerSpawnPositionTests(HostOrServer hostOrServer) : base(hostOrServer) { }
136+
137+
private Vector3 m_PlayerPosition;
138+
private Quaternion m_PlayerRotation;
139+
140+
protected override void OnCreatePlayerPrefab()
141+
{
142+
var playerNetworkObject = m_PlayerPrefab.GetComponent<NetworkObject>();
143+
m_PlayerPosition = GetRandomVector3(-10.0f, 10.0f);
144+
m_PlayerRotation = Quaternion.Euler(GetRandomVector3(-180.0f, 180.0f));
145+
playerNetworkObject.transform.position = m_PlayerPosition;
146+
playerNetworkObject.transform.rotation = m_PlayerRotation;
147+
base.OnCreatePlayerPrefab();
148+
}
149+
150+
private void PlayerTransformMatches(NetworkObject player)
151+
{
152+
var position = player.transform.position;
153+
var rotation = player.transform.rotation;
154+
Assert.True(Approximately(m_PlayerPosition, position), $"Client-{player.OwnerClientId} position {position} does not match the prefab position {m_PlayerPosition}!");
155+
Assert.True(Approximately(m_PlayerRotation, rotation), $"Client-{player.OwnerClientId} rotation {rotation.eulerAngles} does not match the prefab rotation {m_PlayerRotation.eulerAngles}!");
156+
}
157+
158+
[UnityTest]
159+
public IEnumerator PlayerSpawnPosition()
160+
{
161+
if (m_ServerNetworkManager.IsHost)
162+
{
163+
PlayerTransformMatches(m_ServerNetworkManager.LocalClient.PlayerObject);
164+
165+
foreach (var client in m_ClientNetworkManagers)
166+
{
167+
yield return WaitForConditionOrTimeOut(() => client.SpawnManager.SpawnedObjects.ContainsKey(m_ServerNetworkManager.LocalClient.PlayerObject.NetworkObjectId));
168+
AssertOnTimeout($"Client-{client.LocalClientId} does not contain a player prefab instance for client-{m_ServerNetworkManager.LocalClientId}!");
169+
PlayerTransformMatches(client.SpawnManager.SpawnedObjects[m_ServerNetworkManager.LocalClient.PlayerObject.NetworkObjectId]);
170+
}
171+
}
172+
173+
foreach (var client in m_ClientNetworkManagers)
174+
{
175+
yield return WaitForConditionOrTimeOut(() => m_ServerNetworkManager.SpawnManager.SpawnedObjects.ContainsKey(client.LocalClient.PlayerObject.NetworkObjectId));
176+
AssertOnTimeout($"Client-{m_ServerNetworkManager.LocalClientId} does not contain a player prefab instance for client-{client.LocalClientId}!");
177+
PlayerTransformMatches(m_ServerNetworkManager.SpawnManager.SpawnedObjects[client.LocalClient.PlayerObject.NetworkObjectId]);
178+
foreach (var subClient in m_ClientNetworkManagers)
179+
{
180+
yield return WaitForConditionOrTimeOut(() => subClient.SpawnManager.SpawnedObjects.ContainsKey(client.LocalClient.PlayerObject.NetworkObjectId));
181+
AssertOnTimeout($"Client-{subClient.LocalClientId} does not contain a player prefab instance for client-{client.LocalClientId}!");
182+
PlayerTransformMatches(subClient.SpawnManager.SpawnedObjects[client.LocalClient.PlayerObject.NetworkObjectId]);
183+
}
184+
}
185+
}
186+
}
123187
}

0 commit comments

Comments
 (0)