Skip to content

Commit 8477fd4

Browse files
fix: NetworkManager instantiate & destroy notifications and player spawn prefab offset (backport) (#3089)
* update Adding NetworkManager.Instantiated and NetworkManager.Destroying event notifications. * test Adding validation test. * update Adding changelog entries. * fix Fixing player instantiation position and rotation issue introduced in an earlier PR (but not yet released). * test test to validate the player spawn position fix. adding the observers test from v2.0.0 (just good to have that in v1.x.x) * style remove whitespaces
1 parent 490fb93 commit 8477fd4

File tree

6 files changed

+190
-5
lines changed

6 files changed

+190
-5
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ 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. (#3089)
14+
- Added a static `NetworkManager.OnDestroying` event notification to be able to track when an existing `NetworkManager` instance is being destroyed. (#3089)
1315
- Added message size validation to named and unnamed message sending functions for better error messages. (#3043)
1416
- Added "Check for NetworkObject Component" property to the Multiplayer->Netcode for GameObjects project settings. When disabled, this will bypass the in-editor `NetworkObject` check on `NetworkBehaviour` components. (#3034)
1517

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -738,9 +738,8 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne
738738

739739
if (response.CreatePlayerObject && (response.PlayerPrefabHash.HasValue || NetworkManager.NetworkConfig.PlayerPrefab != null))
740740
{
741-
var playerObject = response.PlayerPrefabHash.HasValue ? NetworkManager.SpawnManager.GetNetworkObjectToSpawn(response.PlayerPrefabHash.Value, ownerClientId, response.Position.GetValueOrDefault(), response.Rotation.GetValueOrDefault())
742-
: NetworkManager.SpawnManager.GetNetworkObjectToSpawn(NetworkManager.NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash, ownerClientId, response.Position.GetValueOrDefault(), response.Rotation.GetValueOrDefault());
743-
741+
var playerObject = response.PlayerPrefabHash.HasValue ? NetworkManager.SpawnManager.GetNetworkObjectToSpawn(response.PlayerPrefabHash.Value, ownerClientId, response.Position ?? null, response.Rotation ?? null)
742+
: NetworkManager.SpawnManager.GetNetworkObjectToSpawn(NetworkManager.NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash, ownerClientId, response.Position ?? null, response.Rotation ?? null);
744743
// Spawn the player NetworkObject locally
745744
NetworkManager.SpawnManager.SpawnNetworkObjectLocally(
746745
playerObject,

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,16 @@ namespace Unity.Netcode
1616
[AddComponentMenu("Netcode/Network Manager", -100)]
1717
public class NetworkManager : MonoBehaviour, INetworkUpdateSystem
1818
{
19+
/// <summary>
20+
/// Subscribe to this static event to get notifications when a <see cref="NetworkManager"/> instance has been instantiated.
21+
/// </summary>
22+
public static event Action<NetworkManager> OnInstantiated;
23+
24+
/// <summary>
25+
/// Subscribe to this static event to get notifications when a <see cref="NetworkManager"/> instance is being destroyed.
26+
/// </summary>
27+
public static event Action<NetworkManager> OnDestroying;
28+
1929
// TODO: Deprecate...
2030
// The following internal values are not used, but because ILPP makes them public in the assembly, they cannot
2131
// be removed thanks to our semver validation.
@@ -715,6 +725,8 @@ private void Awake()
715725
#if UNITY_EDITOR
716726
EditorApplication.playModeStateChanged += ModeChanged;
717727
#endif
728+
// Notify we have instantiated a new instance of NetworkManager.
729+
OnInstantiated?.Invoke(this);
718730
}
719731

720732
private void OnEnable()
@@ -1274,6 +1286,9 @@ private void OnDestroy()
12741286

12751287
UnityEngine.SceneManagement.SceneManager.sceneUnloaded -= OnSceneUnloaded;
12761288

1289+
// Notify we are destroying NetworkManager
1290+
OnDestroying?.Invoke(this);
1291+
12771292
if (Singleton == this)
12781293
{
12791294
Singleton = null;

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -417,14 +417,14 @@ internal NetworkObject InstantiateAndSpawnNoParameterChecks(NetworkObject networ
417417
/// Gets the right NetworkObject prefab instance to spawn. If a handler is registered or there is an override assigned to the
418418
/// passed in globalObjectIdHash value, then that is what will be instantiated, spawned, and returned.
419419
/// </summary>
420-
internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ownerId, Vector3 position = default, Quaternion rotation = default, bool isScenePlaced = false)
420+
internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ownerId, Vector3? position, Quaternion? rotation, bool isScenePlaced = false)
421421
{
422422
NetworkObject networkObject = null;
423423
// If the prefab hash has a registered INetworkPrefabInstanceHandler derived class
424424
if (NetworkManager.PrefabHandler.ContainsHandler(globalObjectIdHash))
425425
{
426426
// Let the handler spawn the NetworkObject
427-
networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, ownerId, position, rotation);
427+
networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, ownerId, position ?? default, rotation ?? default);
428428
networkObject.NetworkManagerOwner = NetworkManager;
429429
}
430430
else
@@ -476,6 +476,8 @@ internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ow
476476
{
477477
// Create prefab instance
478478
networkObject = UnityEngine.Object.Instantiate(networkPrefabReference).GetComponent<NetworkObject>();
479+
networkObject.transform.position = position ?? networkObject.transform.position;
480+
networkObject.transform.rotation = rotation ?? networkObject.transform.rotation;
479481
networkObject.NetworkManagerOwner = NetworkManager;
480482
networkObject.PrefabGlobalObjectIdHash = globalObjectIdHash;
481483
}

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

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,61 @@ public 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+
70+
1671
[UnityTest]
1772
public IEnumerator OnServerStoppedCalledWhenServerStops()
1873
{
@@ -228,6 +283,9 @@ in NetworkTimeSystem.Sync */
228283
[UnityTearDown]
229284
public virtual IEnumerator Teardown()
230285
{
286+
NetworkManager.OnInstantiated -= NetworkManager_OnInstantiated;
287+
NetworkManager.OnDestroying -= NetworkManager_OnDestroying;
288+
231289
NetcodeIntegrationTestHelpers.Destroy();
232290
if (m_ServerManager != null)
233291
{

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

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Collections;
2+
using System.Linq;
23
using NUnit.Framework;
34
using Unity.Netcode.TestHelpers.Runtime;
45
using UnityEngine;
@@ -48,4 +49,112 @@ public IEnumerator SpawnAndReplaceExistingPlayerObject()
4849
Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for client-side player object to change!");
4950
}
5051
}
52+
53+
/// <summary>
54+
/// Validate that when auto-player spawning but SpawnWithObservers is disabled,
55+
/// the player instantiated is only spawned on the authority side.
56+
/// </summary>
57+
[TestFixture(HostOrServer.Host)]
58+
[TestFixture(HostOrServer.Server)]
59+
internal class PlayerSpawnNoObserversTest : NetcodeIntegrationTest
60+
{
61+
protected override int NumberOfClients => 2;
62+
63+
public PlayerSpawnNoObserversTest(HostOrServer hostOrServer) : base(hostOrServer) { }
64+
65+
protected override bool ShouldCheckForSpawnedPlayers()
66+
{
67+
return false;
68+
}
69+
70+
protected override void OnCreatePlayerPrefab()
71+
{
72+
var playerNetworkObject = m_PlayerPrefab.GetComponent<NetworkObject>();
73+
playerNetworkObject.SpawnWithObservers = false;
74+
base.OnCreatePlayerPrefab();
75+
}
76+
77+
[UnityTest]
78+
public IEnumerator SpawnWithNoObservers()
79+
{
80+
yield return s_DefaultWaitForTick;
81+
82+
var playerObjects = m_ServerNetworkManager.SpawnManager.SpawnedObjectsList.Where((c) => c.IsPlayerObject).ToList();
83+
84+
// Make sure clients did not spawn their player object on any of the clients including the owner.
85+
foreach (var client in m_ClientNetworkManagers)
86+
{
87+
foreach (var playerObject in playerObjects)
88+
{
89+
Assert.IsFalse(client.SpawnManager.SpawnedObjects.ContainsKey(playerObject.NetworkObjectId), $"Client-{client.LocalClientId} spawned player object for Client-{playerObject.NetworkObjectId}!");
90+
}
91+
}
92+
}
93+
}
94+
95+
/// <summary>
96+
/// This test validates the player position and rotation is correct
97+
/// relative to the prefab's initial settings if no changes are applied.
98+
/// </summary>
99+
[TestFixture(HostOrServer.Host)]
100+
[TestFixture(HostOrServer.Server)]
101+
internal class PlayerSpawnPositionTests : IntegrationTestWithApproximation
102+
{
103+
protected override int NumberOfClients => 2;
104+
105+
public PlayerSpawnPositionTests(HostOrServer hostOrServer)
106+
{
107+
m_UseHost = hostOrServer == HostOrServer.Host;
108+
}
109+
110+
private Vector3 m_PlayerPosition;
111+
private Quaternion m_PlayerRotation;
112+
113+
protected override void OnCreatePlayerPrefab()
114+
{
115+
var playerNetworkObject = m_PlayerPrefab.GetComponent<NetworkObject>();
116+
m_PlayerPosition = GetRandomVector3(-10.0f, 10.0f);
117+
m_PlayerRotation = Quaternion.Euler(GetRandomVector3(-180.0f, 180.0f));
118+
playerNetworkObject.transform.position = m_PlayerPosition;
119+
playerNetworkObject.transform.rotation = m_PlayerRotation;
120+
base.OnCreatePlayerPrefab();
121+
}
122+
123+
private void PlayerTransformMatches(NetworkObject player)
124+
{
125+
var position = player.transform.position;
126+
var rotation = player.transform.rotation;
127+
Assert.True(Approximately(m_PlayerPosition, position), $"Client-{player.OwnerClientId} position {position} does not match the prefab position {m_PlayerPosition}!");
128+
Assert.True(Approximately(m_PlayerRotation, rotation), $"Client-{player.OwnerClientId} rotation {rotation.eulerAngles} does not match the prefab rotation {m_PlayerRotation.eulerAngles}!");
129+
}
130+
131+
[UnityTest]
132+
public IEnumerator PlayerSpawnPosition()
133+
{
134+
if (m_ServerNetworkManager.IsHost)
135+
{
136+
PlayerTransformMatches(m_ServerNetworkManager.LocalClient.PlayerObject);
137+
138+
foreach (var client in m_ClientNetworkManagers)
139+
{
140+
yield return WaitForConditionOrTimeOut(() => client.SpawnManager.SpawnedObjects.ContainsKey(m_ServerNetworkManager.LocalClient.PlayerObject.NetworkObjectId));
141+
AssertOnTimeout($"Client-{client.LocalClientId} does not contain a player prefab instance for client-{m_ServerNetworkManager.LocalClientId}!");
142+
PlayerTransformMatches(client.SpawnManager.SpawnedObjects[m_ServerNetworkManager.LocalClient.PlayerObject.NetworkObjectId]);
143+
}
144+
}
145+
146+
foreach (var client in m_ClientNetworkManagers)
147+
{
148+
yield return WaitForConditionOrTimeOut(() => m_ServerNetworkManager.SpawnManager.SpawnedObjects.ContainsKey(client.LocalClient.PlayerObject.NetworkObjectId));
149+
AssertOnTimeout($"Client-{m_ServerNetworkManager.LocalClientId} does not contain a player prefab instance for client-{client.LocalClientId}!");
150+
PlayerTransformMatches(m_ServerNetworkManager.SpawnManager.SpawnedObjects[client.LocalClient.PlayerObject.NetworkObjectId]);
151+
foreach (var subClient in m_ClientNetworkManagers)
152+
{
153+
yield return WaitForConditionOrTimeOut(() => subClient.SpawnManager.SpawnedObjects.ContainsKey(client.LocalClient.PlayerObject.NetworkObjectId));
154+
AssertOnTimeout($"Client-{subClient.LocalClientId} does not contain a player prefab instance for client-{client.LocalClientId}!");
155+
PlayerTransformMatches(subClient.SpawnManager.SpawnedObjects[client.LocalClient.PlayerObject.NetworkObjectId]);
156+
}
157+
}
158+
}
159+
}
51160
}

0 commit comments

Comments
 (0)