Skip to content

Commit cb7ec98

Browse files
fix: player prefab spawning not honoring when spawnwithobservers is disabled (#3077)
* fix When SpawnWithObservers is disabled on player prefab, the instantiated instance should only spawn on the authority side. For client-server this will always be the server/host. For distributed authority this will always be the owner. * test Validation test for the SpawnWithObservers regression bug. * update updating changelog entry
1 parent 390c332 commit cb7ec98

File tree

5 files changed

+103
-8
lines changed

5 files changed

+103
-8
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
1313
### Fixed
1414

1515
- Fixed issue where applying the position and/or rotation to the `NetworkManager.ConnectionApprovalResponse` when connection approval and auto-spawn player prefab were enabled would not apply the position and/or rotation when the player prefab was instantiated. (#3078)
16+
- Fixed issue where `NetworkObject.SpawnWithObservers` was not being honored when spawning the player prefab. (#3077)
1617
- Fixed issue with the client count not being correct on the host or server side when a client disconnects itself from a session. (#3075)
1718

1819
### Changed

com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1613,7 +1613,12 @@ internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool pla
16131613
}
16141614
else if (NetworkManager.DistributedAuthorityMode && !NetworkManager.DAHost)
16151615
{
1616-
NetworkManager.SpawnManager.SendSpawnCallForObject(NetworkManager.ServerClientId, this);
1616+
// If spawning with observers or if not spawning with observers but the observer count is greater than 1 (i.e. owner/authority creating),
1617+
// then we want to send a spawn notification.
1618+
if (SpawnWithObservers || !SpawnWithObservers && Observers.Count > 1)
1619+
{
1620+
NetworkManager.SpawnManager.SendSpawnCallForObject(NetworkManager.ServerClientId, this);
1621+
}
16171622
}
16181623
else
16191624
{
@@ -3053,10 +3058,15 @@ internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBuf
30533058
}
30543059
}
30553060

3056-
// Add all known players to the observers list if they don't already exist
3057-
foreach (var player in networkManager.SpawnManager.PlayerObjects)
3061+
// Only add all other players as observers if we are spawning with observers,
3062+
// otherwise user controls via NetworkShow.
3063+
if (networkObject.SpawnWithObservers)
30583064
{
3059-
networkObject.Observers.Add(player.OwnerClientId);
3065+
// Add all known players to the observers list if they don't already exist
3066+
foreach (var player in networkManager.SpawnManager.PlayerObjects)
3067+
{
3068+
networkObject.Observers.Add(player.OwnerClientId);
3069+
}
30603070
}
30613071
}
30623072
}

com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,22 @@ private void AddPlayerObject(NetworkObject playerObject)
7272
return;
7373
}
7474
}
75+
7576
foreach (var player in m_PlayerObjects)
7677
{
77-
player.Observers.Add(playerObject.OwnerClientId);
78-
playerObject.Observers.Add(player.OwnerClientId);
78+
// If the player's SpawnWithObservers is not set then do not add the new player object's owner as an observer.
79+
if (player.SpawnWithObservers)
80+
{
81+
player.Observers.Add(playerObject.OwnerClientId);
82+
}
83+
84+
// If the new player object's SpawnWithObservers is not set then do not add this player as an observer to the new player object.
85+
if (playerObject.SpawnWithObservers)
86+
{
87+
playerObject.Observers.Add(player.OwnerClientId);
88+
}
7989
}
90+
8091
m_PlayerObjects.Add(playerObject);
8192
if (!m_PlayerObjectsTable.ContainsKey(playerObject.OwnerClientId))
8293
{

com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -545,9 +545,14 @@ protected IEnumerator CreateAndStartNewClient()
545545
private bool AllPlayerObjectClonesSpawned(NetworkManager joinedClient)
546546
{
547547
m_InternalErrorLog.Clear();
548+
// If we are not checking for spawned players then exit early with a success
549+
if (!ShouldCheckForSpawnedPlayers())
550+
{
551+
return true;
552+
}
553+
548554
// Continue to populate the PlayerObjects list until all player object (local and clone) are found
549555
ClientNetworkManagerPostStart(joinedClient);
550-
551556
var playerObjectRelative = m_ServerNetworkManager.SpawnManager.PlayerObjects.Where((c) => c.OwnerClientId == joinedClient.LocalClientId).FirstOrDefault();
552557
if (playerObjectRelative == null)
553558
{

com.unity.netcode.gameobjects/Tests/Runtime/PlayerObjectTests.cs

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace Unity.Netcode.RuntimeTests
1111
[TestFixture(HostOrServer.Server)]
1212
internal class PlayerObjectTests : NetcodeIntegrationTest
1313
{
14-
protected override int NumberOfClients => 1;
14+
protected override int NumberOfClients => 2;
1515

1616
protected GameObject m_NewPlayerToSpawn;
1717

@@ -52,4 +52,72 @@ public IEnumerator SpawnAndReplaceExistingPlayerObject()
5252
Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for client-side player object to change!");
5353
}
5454
}
55+
56+
/// <summary>
57+
/// Validate that when auto-player spawning but SpawnWithObservers is disabled,
58+
/// the player instantiated is only spawned on the authority side.
59+
/// </summary>
60+
[TestFixture(HostOrServer.DAHost)]
61+
[TestFixture(HostOrServer.Host)]
62+
[TestFixture(HostOrServer.Server)]
63+
internal class PlayerSpawnNoObserversTest : NetcodeIntegrationTest
64+
{
65+
protected override int NumberOfClients => 2;
66+
67+
public PlayerSpawnNoObserversTest(HostOrServer hostOrServer) : base(hostOrServer) { }
68+
69+
protected override bool ShouldCheckForSpawnedPlayers()
70+
{
71+
return false;
72+
}
73+
74+
protected override void OnCreatePlayerPrefab()
75+
{
76+
var playerNetworkObject = m_PlayerPrefab.GetComponent<NetworkObject>();
77+
playerNetworkObject.SpawnWithObservers = false;
78+
base.OnCreatePlayerPrefab();
79+
}
80+
81+
[UnityTest]
82+
public IEnumerator SpawnWithNoObservers()
83+
{
84+
yield return s_DefaultWaitForTick;
85+
86+
if (!m_DistributedAuthority)
87+
{
88+
// Make sure clients did not spawn their player object on any of the clients including the owner.
89+
foreach (var client in m_ClientNetworkManagers)
90+
{
91+
foreach (var playerObject in m_ServerNetworkManager.SpawnManager.PlayerObjects)
92+
{
93+
Assert.IsFalse(client.SpawnManager.SpawnedObjects.ContainsKey(playerObject.NetworkObjectId), $"Client-{client.LocalClientId} spawned player object for Client-{playerObject.NetworkObjectId}!");
94+
}
95+
}
96+
}
97+
else
98+
{
99+
// For distributed authority, we want to make sure the player object is only spawned on the authority side and all non-authority instances did not spawn it.
100+
var playerObjectId = m_ServerNetworkManager.LocalClient.PlayerObject.NetworkObjectId;
101+
foreach (var client in m_ClientNetworkManagers)
102+
{
103+
Assert.IsFalse(client.SpawnManager.SpawnedObjects.ContainsKey(playerObjectId), $"Client-{client.LocalClientId} spawned player object for Client-{m_ServerNetworkManager.LocalClientId}!");
104+
}
105+
106+
foreach (var clientPlayer in m_ClientNetworkManagers)
107+
{
108+
playerObjectId = clientPlayer.LocalClient.PlayerObject.NetworkObjectId;
109+
Assert.IsFalse(m_ServerNetworkManager.SpawnManager.SpawnedObjects.ContainsKey(playerObjectId), $"Client-{m_ServerNetworkManager.LocalClientId} spawned player object for Client-{clientPlayer.LocalClientId}!");
110+
foreach (var client in m_ClientNetworkManagers)
111+
{
112+
if (clientPlayer == client)
113+
{
114+
continue;
115+
}
116+
Assert.IsFalse(client.SpawnManager.SpawnedObjects.ContainsKey(playerObjectId), $"Client-{client.LocalClientId} spawned player object for Client-{clientPlayer.LocalClientId}!");
117+
}
118+
}
119+
120+
}
121+
}
122+
}
55123
}

0 commit comments

Comments
 (0)