diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 0f5ee9b0d9..26853592f8 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,6 +10,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- Added `NetworkObject.SpawnWithObservers` property (default is true) that when set to false will spawn a `NetworkObject` with no observers and will not be spawned on any client until `NetworkObject.NetworkShow` is invoked. (#2568) + ### Fixed - Fixed warning "Runtime Network Prefabs was not empty at initialization time." being erroneously logged when no runtime network prefabs had been added (#2565) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index bbabe2e98a..3643d44a57 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -188,6 +188,12 @@ internal void GenerateGlobalObjectIdHash() /// public Action OnMigratedToNewScene; + /// + /// When set to false, the NetworkObject will be spawned with no observers initially (other than the server) + /// + [Tooltip("When false, the NetworkObject will spawn with no observers initially. (default is true)")] + public bool SpawnWithObservers = true; + /// /// Delegate type for checking visibility /// diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 0cb3c9db9a..a82aa6b90d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -617,8 +617,10 @@ private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong } } - if (NetworkManager.IsServer) + // If we are the server and should spawn with observers + if (NetworkManager.IsServer && networkObject.SpawnWithObservers) { + // Add client observers for (int i = 0; i < NetworkManager.ConnectedClientsList.Count; i++) { if (networkObject.CheckObjectVisibility == null || networkObject.CheckObjectVisibility(NetworkManager.ConnectedClientsList[i].ClientId)) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs index f07b196378..87ec8066e8 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs @@ -14,6 +14,86 @@ public class NetworkObjectOnSpawnTests : NetcodeIntegrationTest protected override int NumberOfClients => 2; + public enum ObserverTestTypes + { + WithObservers, + WithoutObservers + } + private GameObject m_ObserverPrefab; + private NetworkObject m_ObserverTestNetworkObject; + private ObserverTestTypes m_ObserverTestType; + + private const string k_ObserverTestObjName = "ObsObj"; + private const string k_WithObserversError = "Not all clients spawned the"; + private const string k_WithoutObserversError = "A client spawned the"; + + protected override void OnServerAndClientsCreated() + { + m_ObserverPrefab = CreateNetworkObjectPrefab(k_ObserverTestObjName); + base.OnServerAndClientsCreated(); + } + + + private bool CheckClientsSideObserverTestObj() + { + foreach (var client in m_ClientNetworkManagers) + { + if (!s_GlobalNetworkObjects.ContainsKey(client.LocalClientId)) + { + // When no observers there shouldn't be any client spawned NetworkObjects + // (players are held in a different list) + return !(m_ObserverTestType == ObserverTestTypes.WithObservers); + } + var clientObjects = s_GlobalNetworkObjects[client.LocalClientId]; + // Make sure they did spawn the object + if (m_ObserverTestType == ObserverTestTypes.WithObservers) + { + if (!clientObjects.ContainsKey(m_ObserverTestNetworkObject.NetworkObjectId)) + { + return false; + } + if (!clientObjects[m_ObserverTestNetworkObject.NetworkObjectId].IsSpawned) + { + return false; + } + } + } + return true; + } + + + + [UnityTest] + public IEnumerator ObserverSpawnTests([Values] ObserverTestTypes observerTestTypes) + { + m_ObserverTestType = observerTestTypes; + var prefabNetworkObject = m_ObserverPrefab.GetComponent(); + prefabNetworkObject.SpawnWithObservers = observerTestTypes == ObserverTestTypes.WithObservers; + var instance = SpawnObject(m_ObserverPrefab, m_ServerNetworkManager); + m_ObserverTestNetworkObject = instance.GetComponent(); + var withoutObservers = m_ObserverTestType == ObserverTestTypes.WithoutObservers; + if (withoutObservers) + { + // Just give a little time to make sure nothing spawned + yield return s_DefaultWaitForTick; + } + yield return WaitForConditionOrTimeOut(CheckClientsSideObserverTestObj); + AssertOnTimeout($"{(withoutObservers ? k_WithoutObserversError : k_WithObserversError)} {k_ObserverTestObjName} object!"); + // If we spawned without observers + if (withoutObservers) + { + // Make each client an observer + foreach (var client in m_ClientNetworkManagers) + { + m_ObserverTestNetworkObject.NetworkShow(client.LocalClientId); + } + + // Validate the clients spawned the NetworkObject + m_ObserverTestType = ObserverTestTypes.WithObservers; + yield return WaitForConditionOrTimeOut(CheckClientsSideObserverTestObj); + AssertOnTimeout($"{k_WithObserversError} {k_ObserverTestObjName} object!"); + } + } /// /// Tests that instantiating a and destroying without spawning it /// does not run or . @@ -52,6 +132,11 @@ protected override void OnCreatePlayerPrefab() protected override IEnumerator OnTearDown() { + if (m_ObserverPrefab != null) + { + Object.Destroy(m_ObserverPrefab); + } + if (m_TestNetworkObjectPrefab != null) { Object.Destroy(m_TestNetworkObjectPrefab);