fix: in-scene placed NetworkObject will not spawn client-side when disabled upon being despawned [MTT-4832] (#2239)

* fix
This resolves the issue with in-scene placed NetworkObjects that are disabled when despawned not being able to re-spawn again and handles synchronizing despawned in-scene placed NetworkObjects during a scene switch (LoadSceneMode.Single).

* test
Added integration test that validates disabling NetworkObjects when despawned works with currently connected clients, late joining clients, and when scene switching (LoadSceneMode.Single) while also having the server despawn the in-scene placed NetworkObject upon its first spawn (i.e. so it starts off not visible/active to the clients when they finish the scene switch).
This commit is contained in:
Noel Stephens 2022-10-07 15:54:20 -05:00 коммит произвёл GitHub
Родитель 4393621268
Коммит e10c266dfd
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
8 изменённых файлов: 352 добавлений и 107 удалений

Просмотреть файл

@ -28,6 +28,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
### Fixed
- Fixed issue where an in-scene placed NetworkObject would not invoke NetworkBehaviour.OnNetworkSpawn if the GameObject was disabled when it was despawned. (#2239)
- Fixed issue where clients were not rebuilding the `NetworkConfig` hash value for each unique connection request. (#2226)
- Fixed the issue where player objects were not taking the `DontDestroyWithOwner` property into consideration when a client disconnected. (#2225)
- Fixed issue where `SceneEventProgress` would not complete if a client late joins while it is still in progress. (#2222)

Просмотреть файл

@ -1441,6 +1441,9 @@ namespace Unity.Netcode
}
}
// Add any despawned when spawned in-scene placed NetworkObjects to the scene event data
sceneEventData.AddDespawnedInSceneNetworkObjects();
// Set the server's scene's handle so the client can build a look up table
sceneEventData.SceneHandle = scene.handle;

Просмотреть файл

@ -268,7 +268,8 @@ namespace Unity.Netcode
internal void AddDespawnedInSceneNetworkObjects()
{
m_DespawnedInSceneObjectsSync.Clear();
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>().Where((c) => c.NetworkManager == m_NetworkManager);
// Find all active and non-active in-scene placed NetworkObjects
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>(includeInactive: true).Where((c) => c.NetworkManager == m_NetworkManager);
foreach (var sobj in inSceneNetworkObjects)
{
if (sobj.IsSceneObject.HasValue && sobj.IsSceneObject.Value && !sobj.IsSpawned)
@ -461,7 +462,6 @@ namespace Unity.Netcode
for (var i = 0; i < m_DespawnedInSceneObjectsSync.Count; ++i)
{
var noStart = writer.Position;
var sceneObject = m_DespawnedInSceneObjectsSync[i].GetMessageSceneObject(TargetClientId);
BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle());
BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash);
var noStop = writer.Position;
@ -507,6 +507,15 @@ namespace Unity.Netcode
}
}
// Write the number of despawned in-scene placed NetworkObjects
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync.Count);
// Write the scene handle and GlobalObjectIdHash value
for (var i = 0; i < m_DespawnedInSceneObjectsSync.Count; ++i)
{
BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle());
BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash);
}
var tailPosition = writer.Position;
// Reposition to our count position to the head before we wrote our object count
writer.Seek(headPosition);
@ -624,6 +633,8 @@ namespace Unity.Netcode
sceneObject.Deserialize(InternalBuffer);
NetworkObject.AddSceneObject(sceneObject, InternalBuffer, m_NetworkManager);
}
// Now deserialize the despawned in-scene placed NetworkObjects list (if any)
DeserializeDespawnedInScenePlacedNetworkObjects();
}
finally
{
@ -746,6 +757,84 @@ namespace Unity.Netcode
}
}
/// <summary>
/// For synchronizing any despawned in-scene placed NetworkObjects that were
/// despawned by the server during synchronization or scene loading
/// </summary>
private void DeserializeDespawnedInScenePlacedNetworkObjects()
{
// Process all de-spawned in-scene NetworkObjects for this network session
m_DespawnedInSceneObjects.Clear();
InternalBuffer.ReadValueSafe(out int despawnedObjectsCount);
var sceneCache = new Dictionary<int, Dictionary<uint, NetworkObject>>();
for (int i = 0; i < despawnedObjectsCount; i++)
{
// We just need to get the scene
ByteUnpacker.ReadValuePacked(InternalBuffer, out int networkSceneHandle);
ByteUnpacker.ReadValuePacked(InternalBuffer, out uint globalObjectIdHash);
var sceneRelativeNetworkObjects = new Dictionary<uint, NetworkObject>();
if (!sceneCache.ContainsKey(networkSceneHandle))
{
if (m_NetworkManager.SceneManager.ServerSceneHandleToClientSceneHandle.ContainsKey(networkSceneHandle))
{
var localSceneHandle = m_NetworkManager.SceneManager.ServerSceneHandleToClientSceneHandle[networkSceneHandle];
if (m_NetworkManager.SceneManager.ScenesLoaded.ContainsKey(localSceneHandle))
{
var objectRelativeScene = m_NetworkManager.SceneManager.ScenesLoaded[localSceneHandle];
// Find all active and non-active in-scene placed NetworkObjects
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>(includeInactive: true).Where((c) =>
c.GetSceneOriginHandle() == localSceneHandle && (c.IsSceneObject != false)).ToList();
foreach (var inSceneObject in inSceneNetworkObjects)
{
if (!sceneRelativeNetworkObjects.ContainsKey(inSceneObject.GlobalObjectIdHash))
{
sceneRelativeNetworkObjects.Add(inSceneObject.GlobalObjectIdHash, inSceneObject);
}
}
// Add this to a cache so we don't have to run this potentially multiple times (nothing will spawn or despawn during this time
sceneCache.Add(networkSceneHandle, sceneRelativeNetworkObjects);
}
else
{
UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) cannot find its relative local scene handle {localSceneHandle}!");
}
}
else
{
UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) cannot find its relative NetworkSceneHandle {networkSceneHandle}!");
}
}
else // Use the cached NetworkObjects if they exist
{
sceneRelativeNetworkObjects = sceneCache[networkSceneHandle];
}
// Now find the in-scene NetworkObject with the current GlobalObjectIdHash we are looking for
if (sceneRelativeNetworkObjects.ContainsKey(globalObjectIdHash))
{
// Since this is a NetworkObject that was never spawned, we just need to send a notification
// out that it was despawned so users can make adjustments
sceneRelativeNetworkObjects[globalObjectIdHash].InvokeBehaviourNetworkDespawn();
if (!m_NetworkManager.SceneManager.ScenePlacedObjects.ContainsKey(globalObjectIdHash))
{
m_NetworkManager.SceneManager.ScenePlacedObjects.Add(globalObjectIdHash, new Dictionary<int, NetworkObject>());
}
if (!m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].ContainsKey(sceneRelativeNetworkObjects[globalObjectIdHash].GetSceneOriginHandle()))
{
m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].Add(sceneRelativeNetworkObjects[globalObjectIdHash].GetSceneOriginHandle(), sceneRelativeNetworkObjects[globalObjectIdHash]);
}
}
else
{
UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) could not be found!");
}
}
}
/// <summary>
/// Client Side:
/// During the processing of a server sent Event_Sync, this method will be called for each scene once
@ -779,72 +868,9 @@ namespace Unity.Netcode
}
}
// Process all de-spawned in-scene NetworkObjects for this network session
m_DespawnedInSceneObjects.Clear();
InternalBuffer.ReadValueSafe(out int despawnedObjectsCount);
var sceneCache = new Dictionary<int, Dictionary<uint, NetworkObject>>();
// Now deserialize the despawned in-scene placed NetworkObjects list (if any)
DeserializeDespawnedInScenePlacedNetworkObjects();
for (int i = 0; i < despawnedObjectsCount; i++)
{
// We just need to get the scene
ByteUnpacker.ReadValuePacked(InternalBuffer, out int networkSceneHandle);
ByteUnpacker.ReadValuePacked(InternalBuffer, out uint globalObjectIdHash);
var sceneRelativeNetworkObjects = new Dictionary<uint, NetworkObject>();
if (!sceneCache.ContainsKey(networkSceneHandle))
{
if (m_NetworkManager.SceneManager.ServerSceneHandleToClientSceneHandle.ContainsKey(networkSceneHandle))
{
var localSceneHandle = m_NetworkManager.SceneManager.ServerSceneHandleToClientSceneHandle[networkSceneHandle];
if (m_NetworkManager.SceneManager.ScenesLoaded.ContainsKey(localSceneHandle))
{
var objectRelativeScene = m_NetworkManager.SceneManager.ScenesLoaded[localSceneHandle];
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>().Where((c) =>
c.GetSceneOriginHandle() == localSceneHandle && (c.IsSceneObject != false)).ToList();
foreach (var inSceneObject in inSceneNetworkObjects)
{
sceneRelativeNetworkObjects.Add(inSceneObject.GlobalObjectIdHash, inSceneObject);
}
// Add this to a cache so we don't have to run this potentially multiple times (nothing will spawn or despawn during this time
sceneCache.Add(networkSceneHandle, sceneRelativeNetworkObjects);
}
else
{
UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) cannot find its relative local scene handle {localSceneHandle}!");
}
}
else
{
UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) cannot find its relative NetworkSceneHandle {networkSceneHandle}!");
}
}
else // Use the cached NetworkObjects if they exist
{
sceneRelativeNetworkObjects = sceneCache[networkSceneHandle];
}
// Now find the in-scene NetworkObject with the current GlobalObjectIdHash we are looking for
if (sceneRelativeNetworkObjects.ContainsKey(globalObjectIdHash))
{
// Since this is a NetworkObject that was never spawned, we just need to send a notification
// out that it was despawned so users can make adjustments
sceneRelativeNetworkObjects[globalObjectIdHash].InvokeBehaviourNetworkDespawn();
if (!m_NetworkManager.SceneManager.ScenePlacedObjects.ContainsKey(globalObjectIdHash))
{
m_NetworkManager.SceneManager.ScenePlacedObjects.Add(globalObjectIdHash, new Dictionary<int, NetworkObject>());
}
if (!m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].ContainsKey(sceneRelativeNetworkObjects[globalObjectIdHash].GetSceneOriginHandle()))
{
m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].Add(sceneRelativeNetworkObjects[globalObjectIdHash].GetSceneOriginHandle(), sceneRelativeNetworkObjects[globalObjectIdHash]);
}
}
else
{
UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) could not be found!");
}
}
}
finally
{

Просмотреть файл

@ -388,6 +388,13 @@ namespace Unity.Netcode
NetworkLog.LogError($"{nameof(NetworkPrefab)} hash was not found! In-Scene placed {nameof(NetworkObject)} soft synchronization failure for Hash: {globalObjectIdHash}!");
}
}
// Since this NetworkObject is an in-scene placed NetworkObject, if it is disabled then enable it so
// NetworkBehaviours will have their OnNetworkSpawn method invoked
if (networkObject != null && !networkObject.gameObject.activeInHierarchy)
{
networkObject.gameObject.SetActive(true);
}
}
if (networkObject != null)

Просмотреть файл

@ -8,79 +8,82 @@ namespace TestProject.ManualTests
/// <summary>
/// Used for manually testing spawning and despawning in-scene
/// placed NetworkObjects
///
/// Note: We do not destroy in-scene placed NetworkObjects, but
/// users must handle visibility (rendering wise) when the in-scene
/// NetworkObject is spawned and despawned. This class just enables
/// or disabled the mesh renderer.
/// </summary>
public class DespawnInSceneNetworkObject : NetworkBehaviour
{
private Coroutine m_ScanInputHandle;
private MeshRenderer m_MeshRenderer;
[Tooltip("When set, the server will despawn the NetworkObject upon its first spawn.")]
public bool StartDespawned;
private void Start()
{
if (!IsSpawned)
{
m_MeshRenderer = GetComponent<MeshRenderer>();
if (m_MeshRenderer != null)
{
m_MeshRenderer.enabled = false;
}
}
}
private Coroutine m_ScanInputHandle;
// Used to prevent the server from despawning
// the in-scene placed NetworkObject after the
// first spawn (only if StartDespawned is true)
private bool m_ServerDespawnedOnFirstSpawn;
private NetworkManager m_CachedNetworkManager;
public override void OnNetworkSpawn()
{
Debug.Log($"{name} spawned!");
m_MeshRenderer = GetComponent<MeshRenderer>();
if (m_MeshRenderer != null)
{
m_MeshRenderer.enabled = true;
}
if (!IsServer)
{
return;
}
m_CachedNetworkManager = NetworkManager;
if (m_ScanInputHandle == null)
{
m_ScanInputHandle = StartCoroutine(ScanInput());
// Using the NetworkManager to create the coroutine so it is not deactivated
// when the GameObject this NetworkBehaviour is attached to is disabled.
m_ScanInputHandle = NetworkManager.StartCoroutine(ScanInput(NetworkObject));
}
// m_ServerDespawnedOnFirstSpawn prevents the server from always
// despawning on the server-side after the first spawn.
if (StartDespawned && !m_ServerDespawnedOnFirstSpawn)
{
m_ServerDespawnedOnFirstSpawn = true;
NetworkObject.Despawn(false);
}
}
public override void OnNetworkDespawn()
{
if (m_MeshRenderer != null)
{
m_MeshRenderer.enabled = false;
}
// It is OK to disable in-scene placed NetworkObjects upon
// despawning. When re-spawned the client-side will re-activate
// the GameObject, while the server-side must set the GameObject
// active itself.
gameObject.SetActive(false);
Debug.Log($"{name} despawned!");
base.OnNetworkDespawn();
}
public override void OnDestroy()
{
if (m_ScanInputHandle != null)
if (m_ScanInputHandle != null && m_CachedNetworkManager != null)
{
StopCoroutine(m_ScanInputHandle);
m_CachedNetworkManager.StopCoroutine(m_ScanInputHandle);
}
m_ScanInputHandle = null;
base.OnDestroy();
}
private IEnumerator ScanInput()
private IEnumerator ScanInput(NetworkObject networkObject)
{
while (true)
{
try
{
if (IsSpawned)
if (networkObject.IsSpawned)
{
if (Input.GetKeyDown(KeyCode.Backspace))
{
Debug.Log($"{name} should despawn.");
NetworkObject.Despawn(false);
networkObject.Despawn(false);
}
}
else if (NetworkManager.Singleton && NetworkManager.Singleton.IsListening)
@ -88,7 +91,8 @@ namespace TestProject.ManualTests
if (Input.GetKeyDown(KeyCode.Backspace))
{
Debug.Log($"{name} should spawn.");
NetworkObject.Spawn();
networkObject.gameObject.SetActive(true);
networkObject.Spawn();
}
}
}
@ -99,7 +103,6 @@ namespace TestProject.ManualTests
yield return null;
}
}
}
}

Просмотреть файл

@ -2250,6 +2250,11 @@ PrefabInstance:
propertyPath: m_Name
value: InSceneObjectToDespawn
objectReference: {fileID: 0}
- target: {fileID: 4518755925279129999, guid: 3a854a190ab5b1b4fb00bec725fdda9e,
type: 3}
propertyPath: StartDespawned
value: 1
objectReference: {fileID: 0}
m_RemovedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: 3a854a190ab5b1b4fb00bec725fdda9e, type: 3}
--- !u!1 &1008611498
@ -2466,7 +2471,6 @@ MonoBehaviour:
m_ProtocolType: 0
m_MaxPacketQueueSize: 128
m_MaxPayloadSize: 512000
m_MaxSendQueueSize: 4096000
m_HeartbeatTimeoutMS: 500
m_ConnectTimeoutMS: 1000
m_MaxConnectAttempts: 60

Просмотреть файл

@ -1,4 +1,5 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using NUnit.Framework;
@ -21,8 +22,7 @@ namespace TestProject.RuntimeTests
protected override IEnumerator OnSetup()
{
NetworkObjectTestComponent.ServerNetworkObjectInstance = null;
NetworkObjectTestComponent.SpawnedInstances.Clear();
NetworkObjectTestComponent.Reset();
m_CanStartServerAndClients = false;
return base.OnSetup();
}
@ -181,7 +181,6 @@ namespace TestProject.RuntimeTests
AssertOnTimeout($"Timed out waiting for the client-side id ({m_ClientNetworkManagers[0].LocalClientId}) server player transform to be set on the client-side in-scene object!");
}
private void OnSceneEvent(SceneEvent sceneEvent)
{
if (sceneEvent.SceneEventType == SceneEventType.LoadComplete && sceneEvent.SceneName == k_SceneToLoad && sceneEvent.ClientId == m_ClientNetworkManagers[0].LocalClientId)
@ -228,6 +227,175 @@ namespace TestProject.RuntimeTests
}
}
private bool m_AllClientsLoadedScene;
private bool m_AllClientsUnloadedScene;
private int m_NumberOfInstancesCheck;
private Scene m_SceneLoaded;
private bool HaveAllClientsDespawnedInSceneObject()
{
// Make sure we despawned all instances
if (NetworkObjectTestComponent.DespawnedInstances.Count < m_NumberOfInstancesCheck)
{
return false;
}
foreach (var despawnedInstance in NetworkObjectTestComponent.DespawnedInstances)
{
if (despawnedInstance.gameObject.activeInHierarchy)
{
return false;
}
}
return true;
}
private bool HaveAllClientsSpawnedInSceneObject()
{
// Make sure we despawned all instances
if (NetworkObjectTestComponent.SpawnedInstances.Count < m_NumberOfInstancesCheck)
{
return false;
}
foreach (var despawnedInstance in NetworkObjectTestComponent.SpawnedInstances)
{
if (!despawnedInstance.gameObject.activeInHierarchy)
{
return false;
}
}
return true;
}
/// <summary>
/// This validates that users can despawn in-scene placed NetworkObjects and disable the
/// associated GameObject when OnNetworkDespawn is invoked while still being able to
/// re-spawn the same in-scene placed NetworkObject.
/// This test validates this for:
/// - Currently connected clients
/// - Late joining client
/// - Scene switching and having the server despawn the NetworkObject the first time it is spawned.
/// </summary>
[UnityTest]
public IEnumerator EnableDisableInSceneObjectTests()
{
NetworkObjectTestComponent.ServerNetworkObjectInstance = null;
// Enabled disabling the NetworkObject when it is despawned
NetworkObjectTestComponent.DisableOnDespawn = true;
// Set the number of instances to expect
m_NumberOfInstancesCheck = NumberOfClients + (m_UseHost ? 1 : 0);
// Start the host and clients and load the in-scene object scene additively
m_CanStartServerAndClients = true;
yield return StartServerAndClients();
m_ServerNetworkManager.SceneManager.OnLoadEventCompleted += SceneManager_OnLoadEventCompleted;
m_ServerNetworkManager.SceneManager.LoadScene(k_SceneToLoad, LoadSceneMode.Additive);
yield return WaitForConditionOrTimeOut(() => m_AllClientsLoadedScene);
AssertOnTimeout($"Timed out waiting for {k_SceneToLoad} scene to be loaded on all clients!");
m_ServerNetworkManager.SceneManager.OnLoadEventCompleted -= SceneManager_OnLoadEventCompleted;
// Verify all connected clients spawned the in-scene placed NetworkObject
yield return WaitForConditionOrTimeOut(HaveAllClientsSpawnedInSceneObject);
AssertOnTimeout($"Timed out waiting for all instances to be spawned and enabled!");
var serverInSceneObjectInstance = NetworkObjectTestComponent.ServerNetworkObjectInstance;
Assert.IsNotNull(serverInSceneObjectInstance, $"Could not get the server-side registration of {nameof(NetworkObjectTestComponent)}!");
// Test #1: Despawn the in-scene placed NetworkObject and verify it is despawned and disabled on the clients
serverInSceneObjectInstance.Despawn(false);
yield return WaitForConditionOrTimeOut(HaveAllClientsDespawnedInSceneObject);
AssertOnTimeout($"[Test #1] Timed out waiting for all instances to be despawned and disabled!");
// Test #2: Late-join a client and re-verify that all in-scene placed object instances are still disabled
yield return CreateAndStartNewClient();
var newlyJoinedClient = m_ClientNetworkManagers[NumberOfClients];
m_NumberOfInstancesCheck++;
yield return WaitForConditionOrTimeOut(HaveAllClientsDespawnedInSceneObject);
AssertOnTimeout($"[Test #2] Timed out waiting for all instances to be despawned and disabled!");
// Test #3: Now spawn the same in-scene placed NetworkObject
serverInSceneObjectInstance.gameObject.SetActive(true);
serverInSceneObjectInstance.Spawn();
yield return WaitForConditionOrTimeOut(HaveAllClientsSpawnedInSceneObject);
AssertOnTimeout($"[Test #2] Timed out waiting for all instances to be enabled and spawned!");
// Test #4: Now unload the in-scene object's scene and scene switch to the same scene while
// also having the server-side disable the in-scene placed NetworkObject and verify all
// connected clients completed the scene switch and that all in-scene placed NetworkObjects
// are despawned and disabled.
m_AllClientsLoadedScene = false;
m_AllClientsUnloadedScene = false;
NetworkObjectTestComponent.ServerNetworkObjectInstance = null;
NetworkObjectTestComponent.DisableOnSpawn = true;
m_ServerNetworkManager.SceneManager.OnUnloadEventCompleted += SceneManager_OnUnloadEventCompleted;
m_ServerNetworkManager.SceneManager.UnloadScene(m_SceneLoaded);
yield return WaitForConditionOrTimeOut(() => m_AllClientsUnloadedScene);
AssertOnTimeout($"Timed out waiting for {k_SceneToLoad} scene to be unloaded on all clients!");
m_ServerNetworkManager.SceneManager.OnUnloadEventCompleted -= SceneManager_OnUnloadEventCompleted;
// Verify the spawned instances list is empty
Assert.True(NetworkObjectTestComponent.SpawnedInstances.Count == 0, $"There are {NetworkObjectTestComponent.SpawnedInstances.Count} that did not despawn when the scene was unloaded!");
// Go ahead and clear out the despawned instances list
NetworkObjectTestComponent.DespawnedInstances.Clear();
// Now scene switch (LoadSceneMode.Single) into the scene with the in-scene placed NetworkObject we have been testing
m_ServerNetworkManager.SceneManager.OnLoadEventCompleted += SceneManager_OnLoadEventCompleted;
m_ServerNetworkManager.SceneManager.LoadScene(k_SceneToLoad, LoadSceneMode.Single);
yield return WaitForConditionOrTimeOut(() => m_AllClientsLoadedScene);
AssertOnTimeout($"Timed out waiting for {k_SceneToLoad} scene to be loaded on all clients!");
m_ServerNetworkManager.SceneManager.OnLoadEventCompleted -= SceneManager_OnLoadEventCompleted;
// Verify all client instances are disabled and despawned when done scene switching
yield return WaitForConditionOrTimeOut(HaveAllClientsDespawnedInSceneObject);
AssertOnTimeout($"[Test #4] Timed out waiting for all instances to be despawned and disabled!");
serverInSceneObjectInstance = NetworkObjectTestComponent.ServerNetworkObjectInstance;
Assert.IsNotNull(serverInSceneObjectInstance, $"[Test #4] Could not get the server-side registration of {nameof(NetworkObjectTestComponent)}!");
// Test #5: Now spawn the in-scene placed NetworkObject
serverInSceneObjectInstance.gameObject.SetActive(true);
serverInSceneObjectInstance.Spawn();
// Verify all clients spawned their in-scene NetworkObject relative instance
yield return WaitForConditionOrTimeOut(HaveAllClientsSpawnedInSceneObject);
AssertOnTimeout($"[Test #2] Timed out waiting for all instances to be enabled and spawned!");
yield return StopOneClient(newlyJoinedClient, true);
// Tests complete!
}
private void SceneManager_OnUnloadEventCompleted(string sceneName, LoadSceneMode loadSceneMode, List<ulong> clientsCompleted, List<ulong> clientsTimedOut)
{
foreach (var clientId in clientsCompleted)
{
Assert.True(m_ServerNetworkManager.ConnectedClientsIds.Contains(clientId));
}
m_AllClientsUnloadedScene = true;
}
private void SceneManager_OnLoadEventCompleted(string sceneName, LoadSceneMode loadSceneMode, List<ulong> clientsCompleted, List<ulong> clientsTimedOut)
{
foreach (var clientId in clientsCompleted)
{
Assert.True(m_ServerNetworkManager.ConnectedClientsIds.Contains(clientId));
}
m_AllClientsLoadedScene = true;
m_SceneLoaded = SceneManager.GetSceneByName(sceneName);
}
/// <summary>
/// Very important to always have a backup "unloading" catch
/// in the event your test fails it could not potentially unload

Просмотреть файл

@ -12,16 +12,44 @@ namespace TestProject.RuntimeTests
/// </summary>
public class NetworkObjectTestComponent : NetworkBehaviour
{
public static bool DisableOnDespawn;
public static bool DisableOnSpawn;
public static NetworkObject ServerNetworkObjectInstance;
public static List<NetworkObjectTestComponent> SpawnedInstances = new List<NetworkObjectTestComponent>();
public static List<NetworkObjectTestComponent> DespawnedInstances = new List<NetworkObjectTestComponent>();
public static void Reset()
{
DisableOnDespawn = false;
DisableOnSpawn = false;
ServerNetworkObjectInstance = null;
SpawnedInstances.Clear();
DespawnedInstances.Clear();
}
// When disabling on spawning we only want this to happen on the initial spawn.
// This is used to track this so the server only does it once upon spawning.
public bool ObjectWasDisabledUponSpawn;
public override void OnNetworkSpawn()
{
SpawnedInstances.Add(this);
if (DisableOnDespawn)
{
if (DespawnedInstances.Contains(this))
{
DespawnedInstances.Remove(this);
}
}
if (IsServer)
{
ServerNetworkObjectInstance = NetworkObject;
if (DisableOnSpawn && !ObjectWasDisabledUponSpawn)
{
NetworkObject.Despawn(false);
ObjectWasDisabledUponSpawn = true;
}
}
SpawnedInstances.Add(this);
base.OnNetworkSpawn();
}
@ -33,6 +61,11 @@ namespace TestProject.RuntimeTests
m_HasNotifiedSpawned = false;
Debug.Log($"{NetworkManager.name} de-spawned {gameObject.name}.");
SpawnedInstances.Remove(this);
if (DisableOnDespawn)
{
DespawnedInstances.Add(this);
gameObject.SetActive(false);
}
base.OnNetworkDespawn();
}