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:
Родитель
4393621268
Коммит
e10c266dfd
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче