Hey, I’ve been stuck on this problem with spawning in my GameScene with 3 or more clients connected in my multiplayer game in netcode for gameObjects. I have a Character Selection scene which portrays charaterprefabs and their local ready state for everyone, which works fine. They’re all networked and once they are all ready the I call a networked SceneLoader method to load the GameScene method which then on SceneManager_OnLoadEventCompleted
triggered should spawn the players.
When there are only 2 players the clients spawn fine but when there are 3 or more, the later clients joined get the following error:
“[Netcode] [Invalid Destroy][CharacterSelectionPrefab(Clone)][NetworkObjectId:5] Destroy a spawned NetworkObject on a non-host client is not valid. Call Destroy or Despawn on the server/host instead.”
And For each Update() : “MissingReferenceException: The object of type ‘Unity.Netcode.NetworkObject’ has been destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.”
What could be the issue in my logic for my networked scene managers for the characterselectionscene / main game scene manager?
See code references below
private NetworkVariable<State> currentState = new NetworkVariable<State>(State.Start);
private NetworkVariable<float> countdownTimer = new NetworkVariable<float>(3f); // Use float for countdown
private bool isLocalPlayerReady = false;
private Dictionary<ulong, bool> playerReadyDictionary;
private void Awake()
{
Instance = this;
playerReadyDictionary = new Dictionary<ulong, bool>();
if (MultiplayerGameManager.IsServerCustom())
{
NetworkManager.Singleton.SceneManager.OnLoadEventCompleted += SceneManager_OnLoadEventCompleted;
}
Debug.Log("GameManager: Awake");
}
private void Start()
{
if (IsServer)
{
Debug.Log("GameManager: Server is running");
currentState.Value = State.Start;
OnStateChanged?.Invoke(this, EventArgs.Empty);
}
Debug.Log("GameManager: Start");
}
public override void OnNetworkSpawn()
{
if (IsServer)
{
// Ensure an entry in the dictionary for each connected client
foreach (ulong clientId in NetworkManager.Singleton.ConnectedClientsIds)
{
if (!playerReadyDictionary.ContainsKey(clientId))
{
playerReadyDictionary[clientId] = false;
}
}
Debug.Log("GameManager: Initialized playerReadyDictionary for all clients.");
}
currentState.OnValueChanged += State_OnValueChanged;
Debug.Log("GameManager: OnNetworkSpawn");
}
public void SceneManager_OnLoadEventCompleted(string sceneName, LoadSceneMode loadSceneMode, List<ulong> clientsCompleted, List<ulong> clientsTimedOut)
{
Debug.Log("GameManager: Scene loaded successfully on the server. Spawning players...");
// Ensure all clients have completed loading
foreach (ulong clientId in NetworkManager.Singleton.ConnectedClientsIds)
{
if (!clientsCompleted.Contains(clientId))
{
Debug.LogWarning($"GameManager: Client {clientId} has not completed loading.");
return;
}
}
// Spawn players for all clients
foreach (ulong clientId in NetworkManager.Singleton.ConnectedClientsIds)
{
Transform playerTransform = Instantiate(NetworkPlayer);
playerTransform.GetComponent<NetworkObject>().SpawnAsPlayerObject(clientId, true);
Debug.Log($"GameManager: Spawned player for client {clientId}");
}
}
using UnityEngine;
using Unity.Netcode;
using System;
using System.Collections.Generic;
using System.Collections;
public class CharacterSelectionManager : NetworkBehaviour
{
public static CharacterSelectionManager Instance { get; private set; }
[SerializeField] private Transform[] spawnPositions;
[SerializeField] private GameObject defaultCharacterPrefab;
[SerializeField] private GameObject loadingUI;
private Dictionary<ulong, GameObject> spawnedCharacters = new Dictionary<ulong, GameObject>();
private Dictionary<ulong, PlayerData> playerDataDictionary = new Dictionary<ulong, PlayerData>();
public event EventHandler OnReadyChanged;
private void Awake()
{
if (Instance != null)
{
Destroy(gameObject);
return;
}
Instance = this;
loadingUI.SetActive(false); // Ensure loading UI is disabled at start
}
public override void OnNetworkSpawn()
{
if (IsServer)
{
InitializeManager();
}
}
private void InitializeManager()
{
Debug.Log("CharacterSelectionManager: Initializing on Server.");
MultiplayerGameManager.Instance.OnPlayerDataNetworkListChanged += OnPlayerDataNetworkListChanged;
InitializeSpawnPositions();
UpdatePlayerSpawns(); // Call once to initialize current players on start
}
private void OnPlayerDataNetworkListChanged(object sender, EventArgs e)
{
if (IsServer)
{
Debug.Log("CharacterSelectionManager: Player data network list changed.");
UpdatePlayerSpawns();
}
}
private void InitializeSpawnPositions()
{
Debug.Log("CharacterSelectionManager: Initializing spawn positions.");
// Set up the spawn positions if needed
}
private void UpdatePlayerSpawns()
{
var playerDataList = MultiplayerGameManager.Instance.GetPlayerDataList();
Debug.Log($"CharacterSelectionManager: Updating player spawns. Player count: {playerDataList.Count}");
for (int i = 0; i < playerDataList.Count; i++)
{
SpawnCharacterForPlayer(playerDataList[i]);
}
}
private void SpawnCharacterForPlayer(PlayerData playerData)
{
if (spawnedCharacters.ContainsKey(playerData.clientId))
{
Debug.Log($"CharacterSelectionManager: Character already spawned for client {playerData.clientId}.");
return;
}
if (playerData.spawnIndex < spawnPositions.Length)
{
Transform spawnTransform = spawnPositions[playerData.spawnIndex];
GameObject character = Instantiate(defaultCharacterPrefab, spawnTransform.position, spawnTransform.rotation);
Debug.Log($"CharacterSelectionManager: Instantiated character for client {playerData.clientId} at spawn index {playerData.spawnIndex}.");
NetworkObject characterNetworkObject = character.GetComponent<NetworkObject>();
if (characterNetworkObject != null)
{
characterNetworkObject.SpawnAsPlayerObject(playerData.clientId, true);
Debug.Log($"CharacterSelectionManager: Spawned NetworkObject for client {playerData.clientId} with NetworkObjectId {characterNetworkObject.NetworkObjectId}.");
}
spawnedCharacters[playerData.clientId] = character;
}
else
{
Debug.LogWarning($"CharacterSelectionManager: Spawn index {playerData.spawnIndex} out of range for client {playerData.clientId}.");
}
}
public void DespawnCharacter(ulong clientId)
{
if (!IsServer)
{
Debug.LogWarning("DespawnCharacter can only be called on the server.");
return;
}
if (spawnedCharacters.ContainsKey(clientId))
{
GameObject character = spawnedCharacters[clientId];
if (character == null)
{
Debug.LogWarning($"CharacterSelectionManager: Character GameObject for client {clientId} is already null.");
spawnedCharacters.Remove(clientId);
return;
}
NetworkObject networkObject = character.GetComponent<NetworkObject>();
if (networkObject != null)
{
Debug.Log($"CharacterSelectionManager: Despawning NetworkObject for client {clientId} with NetworkObjectId {networkObject.NetworkObjectId}.");
networkObject.Despawn(true); // Ensure the object is despawned and destroyed
}
else
{
Debug.LogWarning($"CharacterSelectionManager: No NetworkObject found for client {clientId}.");
}
spawnedCharacters.Remove(clientId);
DespawnCharacterClientRpc(clientId);
}
else
{
Debug.LogWarning($"CharacterSelectionManager: No character found to despawn for client {clientId}.");
}
}
[ClientRpc]
private void DespawnCharacterClientRpc(ulong clientId)
{
if (spawnedCharacters.ContainsKey(clientId))
{
GameObject character = spawnedCharacters[clientId];
if (character != null)
{
Destroy(character);
}
spawnedCharacters.Remove(clientId);
}
}
private void DespawnAllCharacters()
{
if (!IsServer)
{
Debug.LogWarning("DespawnAllCharacters can only be called on the server.");
return;
}
// Create a list of keys to iterate over
List<ulong> clientIds = new List<ulong>(spawnedCharacters.Keys);
foreach (var clientId in clientIds)
{
DespawnCharacter(clientId);
}
spawnedCharacters.Clear(); // Clear the dictionary after despawning
}
public void SetPlayerReady()
{
SetPlayerReadyServerRpc();
}
[ServerRpc(RequireOwnership = false)]
private void SetPlayerReadyServerRpc(ServerRpcParams serverRpcParams = default)
{
ulong clientId = serverRpcParams.Receive.SenderClientId;
if (playerDataDictionary.ContainsKey(clientId))
{
PlayerData playerData = playerDataDictionary[clientId];
playerData.isReady = true;
playerDataDictionary[clientId] = playerData;
}
else
{
playerDataDictionary[clientId] = new PlayerData
{
clientId = clientId,
isReady = true
};
}
SetPlayerReadyClientRpc(clientId, playerDataDictionary[clientId]);
bool allClientsReady = true;
foreach (ulong id in NetworkManager.Singleton.ConnectedClientsIds)
{
if (!playerDataDictionary.ContainsKey(id) || !playerDataDictionary[id].isReady)
{
allClientsReady = false;
break;
}
}
if (allClientsReady)
{
ShowLoadingUIClientRpc();
StartCoroutine(WaitForDespawnAndSwitchScene());
}
Debug.Log("All players: " + allClientsReady);
}
private IEnumerator WaitForDespawnAndSwitchScene()
{
DespawnAllCharacters();
// Wait until all characters are despawned
while (spawnedCharacters.Count > 0)
{
Debug.Log("Waiting for all characters to despawn...");
yield return null;
}
Debug.Log("All characters despawned. Switching scene.");
SceneLoader.LoadNetwork(SceneLoader.Scene.GameScene);
}
[ClientRpc]
private void SetPlayerReadyClientRpc(ulong clientId, PlayerData playerData)
{
playerDataDictionary[clientId] = playerData;
OnReadyChanged?.Invoke(this, EventArgs.Empty);
}
[ClientRpc]
private void ShowLoadingUIClientRpc()
{
loadingUI.SetActive(true);
}
public bool IsPlayerReady(ulong clientId)
{
return playerDataDictionary.ContainsKey(clientId) && playerDataDictionary[clientId].isReady;
}
}