diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 22bef68281..ca910b8ed6 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -14,6 +14,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where spawning a player using `NetworkObject.InstantiateAndSpawn` or `NetworkSpawnManager.InstantiateAndSpawn` would not update the `NetworkSpawnManager.PlayerObjects` or assign the newly spawned player to the `NetworkClient.PlayerObject`. (#3122) - Fixed issue where queued UnitTransport (NetworkTransport) message batches were being sent on the next frame. They are now sent at the end of the frame during `PostLateUpdate`. (#3113) - Fixed issue where `NotOwnerRpcTarget` or `OwnerRpcTarget` were not using their replacements `NotAuthorityRpcTarget` and `AuthorityRpcTarget` which would invoke a warning. (#3111) - Fixed issue where client is removed as an observer from spawned objects when their player instance is despawned. (#3110) diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 61442eb08a..910b1b55e1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -751,7 +751,15 @@ internal NetworkObject InstantiateAndSpawnNoParameterChecks(NetworkObject networ networkObject.IsPlayerObject = isPlayerObject; networkObject.transform.position = position; networkObject.transform.rotation = rotation; - networkObject.SpawnWithOwnership(ownerClientId, destroyWithScene); + // If spawning as a player, then invoke SpawnAsPlayerObject + if (isPlayerObject) + { + networkObject.SpawnAsPlayerObject(ownerClientId, destroyWithScene); + } + else // Otherwise just spawn with ownership + { + networkObject.SpawnWithOwnership(ownerClientId, destroyWithScene); + } return networkObject; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs index d7890c202f..699b82d636 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs @@ -216,6 +216,21 @@ private bool ValidatePlayerObjectOnClients(NetworkManager clientToValidate) m_ErrorLogLevel2.Append($"[Client-{client.LocalClientId} Prefab Mismatch][Expected GlobalObjectIdHash: {expectedGlobalObjectIdHash} but was {remoteNetworkClient.PlayerObject.GlobalObjectIdHash}]"); success = false; } + + var foundPlayer = false; + foreach (var playerObject in client.SpawnManager.PlayerObjects) + { + if (playerObject.NetworkObjectId == clientToValidate.LocalClient.PlayerObject.NetworkObjectId) + { + foundPlayer = true; + break; + } + } + if (!foundPlayer) + { + m_ErrorLogLevel1.AppendLine($"[Client-{client.LocalClientId}] Local {nameof(NetworkSpawnManager.PlayerObjects)} does not contain {clientToValidate.LocalClient.PlayerObject.name}!"); + success = false; + } } return success; } @@ -231,6 +246,12 @@ private bool ValidateAllPlayerObjects() m_ErrorLogLevel1.AppendLine($"[Client-{m_ServerNetworkManager.LocalClientId}]{m_ErrorLogLevel2}"); success = false; } + + if (m_ServerNetworkManager.LocalClient.PlayerObject == null) + { + m_ErrorLogLevel1.AppendLine($"[Client-{m_ServerNetworkManager.LocalClientId}] Local {nameof(NetworkClient.PlayerObject)} is null!"); + success = false; + } } foreach (var client in m_ClientNetworkManagers) @@ -240,6 +261,17 @@ private bool ValidateAllPlayerObjects() m_ErrorLogLevel1.AppendLine($"[Client-{client.LocalClientId}]{m_ErrorLogLevel2}"); success = false; } + if (client.LocalClient.PlayerObject == null) + { + m_ErrorLogLevel1.AppendLine($"[Client-{client.LocalClientId}] Local {nameof(NetworkClient.PlayerObject)} is null!"); + success = false; + } + else + if (!client.SpawnManager.PlayerObjects.Contains(client.LocalClient.PlayerObject)) + { + m_ErrorLogLevel1.AppendLine($"[Client-{client.LocalClientId}] Local {nameof(NetworkSpawnManager.PlayerObjects)} does not contain {client.LocalClient.PlayerObject.name}!"); + success = false; + } } return success; @@ -250,12 +282,19 @@ private NetworkObject GetRandomPlayerPrefab() return m_PlayerPrefabs[Random.Range(0, m_PlayerPrefabs.Count() - 1)].GetComponent(); } + public enum PlayerSpawnTypes + { + Normal, + NetworkObject, + SpawnManager + } + /// /// Validates that when a client changes their player object that all connected client instances mirror the /// client's new player object. /// [UnityTest] - public IEnumerator ValidatePlayerObjects() + public IEnumerator ValidatePlayerObjects([Values] PlayerSpawnTypes playerSpawnType) { // Just do a quick validation for all connected client's NetworkClients yield return WaitForConditionOrTimeOut(AllNetworkClientsValidated); @@ -268,14 +307,51 @@ public IEnumerator ValidatePlayerObjects() if (m_UseHost) { playerPrefabToSpawn = GetRandomPlayerPrefab(); - playerInstance = SpawnPlayerObject(playerPrefabToSpawn.gameObject, m_ServerNetworkManager); + switch (playerSpawnType) + { + case PlayerSpawnTypes.Normal: + { + playerInstance = SpawnPlayerObject(playerPrefabToSpawn.gameObject, m_ServerNetworkManager); + break; + } + case PlayerSpawnTypes.NetworkObject: + { + playerInstance = NetworkObject.InstantiateAndSpawn(playerPrefabToSpawn.gameObject, m_ServerNetworkManager, isPlayerObject: true).gameObject; + break; + } + case PlayerSpawnTypes.SpawnManager: + { + playerInstance = m_ServerNetworkManager.SpawnManager.InstantiateAndSpawn(playerPrefabToSpawn, isPlayerObject: true).gameObject; + break; + } + } + m_ChangedPlayerPrefabs.Add(m_ServerNetworkManager.LocalClientId, playerPrefabToSpawn.GlobalObjectIdHash); } foreach (var client in m_ClientNetworkManagers) { playerPrefabToSpawn = GetRandomPlayerPrefab(); - playerInstance = SpawnPlayerObject(playerPrefabToSpawn.gameObject, client); + var networkManager = m_DistributedAuthority ? client : m_ServerNetworkManager; + + switch (playerSpawnType) + { + case PlayerSpawnTypes.Normal: + { + playerInstance = SpawnPlayerObject(playerPrefabToSpawn.gameObject, client); + break; + } + case PlayerSpawnTypes.NetworkObject: + { + playerInstance = NetworkObject.InstantiateAndSpawn(playerPrefabToSpawn.gameObject, networkManager, client.LocalClientId, isPlayerObject: true).gameObject; + break; + } + case PlayerSpawnTypes.SpawnManager: + { + playerInstance = networkManager.SpawnManager.InstantiateAndSpawn(playerPrefabToSpawn, client.LocalClientId, isPlayerObject: true).gameObject; + break; + } + } m_ChangedPlayerPrefabs.Add(client.LocalClientId, playerPrefabToSpawn.GlobalObjectIdHash); }