diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md
index 8a7e4befa7..d89c4c79df 100644
--- a/com.unity.netcode.gameobjects/CHANGELOG.md
+++ b/com.unity.netcode.gameobjects/CHANGELOG.md
@@ -10,11 +10,14 @@ Additional documentation and release notes are available at [Multiplayer Documen
### Added
+
### Fixed
-- Fixed a failing UTP test that was failing when you install Unity Transport package 2.0.0 or newer.
+
+- Fixed issue where `NetworkObject.SpawnWithObservers` was not being honored for late joining clients. (#2623)
## Changed
+
## [1.5.1] - 2023-06-07
### Added
diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs
index b59d99ed42..6d9275e7b6 100644
--- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs
@@ -628,6 +628,8 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne
};
if (!NetworkManager.NetworkConfig.EnableSceneManagement)
{
+ // Update the observed spawned NetworkObjects for the newly connected player when scene management is disabled
+ NetworkManager.SpawnManager.UpdateObservedNetworkObjects(ownerClientId);
if (NetworkManager.SpawnManager.SpawnedObjectsList.Count != 0)
{
message.SpawnedObjectsList = NetworkManager.SpawnManager.SpawnedObjectsList;
@@ -651,12 +653,12 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne
SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId);
message.MessageVersions.Dispose();
- // If scene management is enabled, then let NetworkSceneManager handle the initial scene and NetworkObject synchronization
+ // If scene management is disabled, then we are done and notify the local host-server the client is connected
if (!NetworkManager.NetworkConfig.EnableSceneManagement)
{
InvokeOnClientConnectedCallback(ownerClientId);
}
- else
+ else // Otherwise, let NetworkSceneManager handle the initial scene and NetworkObject synchronization
{
NetworkManager.SceneManager.SynchronizeNetworkObjects(ownerClientId);
}
diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs
index a82aa6b90d..8b966a213a 100644
--- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs
@@ -952,27 +952,35 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec
}
///
- /// Updates all spawned for the specified client
+ /// Updates all spawned for the specified newly connected client
/// Note: if the clientId is the server then it is observable to all spawned 's
///
+ ///
+ /// This method is to only to be used for newly connected clients in order to update the observers list for
+ /// each NetworkObject instance.
+ ///
internal void UpdateObservedNetworkObjects(ulong clientId)
{
foreach (var sobj in SpawnedObjectsList)
{
+ // If the NetworkObject has no visibility check then prepare to add this client as an observer
if (sobj.CheckObjectVisibility == null)
{
- if (!sobj.Observers.Contains(clientId))
+ // If the client is not part of the observers and spawn with observers is enabled on this instance or the clientId is the server
+ if (!sobj.Observers.Contains(clientId) && (sobj.SpawnWithObservers || clientId == NetworkManager.ServerClientId))
{
sobj.Observers.Add(clientId);
}
}
else
{
+ // CheckObject visibility overrides SpawnWithObservers under this condition
if (sobj.CheckObjectVisibility(clientId))
{
sobj.Observers.Add(clientId);
}
- else if (sobj.Observers.Contains(clientId))
+ else // Otherwise, if the observers contains the clientId (shouldn't happen) then remove it since CheckObjectVisibility returned false
+ if (sobj.Observers.Contains(clientId))
{
sobj.Observers.Remove(clientId);
}
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs
index 87ec8066e8..ee01794d98 100644
--- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs
@@ -61,8 +61,26 @@ private bool CheckClientsSideObserverTestObj()
return true;
}
+ ///
+ /// Assures the late joining client has all
+ /// NetworkPrefabs required to connect.
+ ///
+ protected override void OnNewClientCreated(NetworkManager networkManager)
+ {
+ foreach (var networkPrefab in m_ServerNetworkManager.NetworkConfig.Prefabs.Prefabs)
+ {
+ if (!networkManager.NetworkConfig.Prefabs.Contains(networkPrefab.Prefab))
+ {
+ networkManager.NetworkConfig.Prefabs.Add(networkPrefab);
+ }
+ }
+ base.OnNewClientCreated(networkManager);
+ }
-
+ ///
+ /// This test validates property
+ ///
+ /// whether to spawn with or without observers
[UnityTest]
public IEnumerator ObserverSpawnTests([Values] ObserverTestTypes observerTestTypes)
{
@@ -92,6 +110,23 @@ public IEnumerator ObserverSpawnTests([Values] ObserverTestTypes observerTestTyp
m_ObserverTestType = ObserverTestTypes.WithObservers;
yield return WaitForConditionOrTimeOut(CheckClientsSideObserverTestObj);
AssertOnTimeout($"{k_WithObserversError} {k_ObserverTestObjName} object!");
+
+ // Validate that a late joining client does not see the NetworkObject when it spawns
+ yield return CreateAndStartNewClient();
+
+ m_ObserverTestType = ObserverTestTypes.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!");
+
+ // Now validate that we can make the NetworkObject visible to the newly joined client
+ m_ObserverTestNetworkObject.NetworkShow(m_ClientNetworkManagers[NumberOfClients].LocalClientId);
+
+ // Validate the NetworkObject is visible to all connected clients (including the recently joined client)
+ m_ObserverTestType = ObserverTestTypes.WithObservers;
+ yield return WaitForConditionOrTimeOut(CheckClientsSideObserverTestObj);
+ AssertOnTimeout($"{k_WithObserversError} {k_ObserverTestObjName} object!");
}
}
///