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);