Client players not owning their objects, the objects not being local, and not owned by the server

Hi, I’ve been running into this issue where two players are spawned in from a lobby (One is hosting the other is a client who joins). The Host works perfectly fine and has the correct network object bools.
as shown here

But my main issue is that the Client player does not have these bools working
also as shown here

This has caused client players to obviously not work properly as they should, like how the host is.
I’ve tried looking through the code albiet not with a lot of luck I had Co-Pilot help me create this script so bear that in mind I’m terrible with networking

If anyone could give me any help or feedback I would really appriciate it!

Here is the code for the Menu, Lobby and player spawning.

Menu script

  using UnityEngine;
  using UnityEngine.UI;
  using TMPro;
  using Unity.Netcode;
  using System.Collections;

public class MainMenu : NetworkBehaviour
{
[Header("UI References")]
[SerializeField] private TMP_InputField playerNameInputField;
[SerializeField] private TMP_InputField lobbyIdInputField;
[SerializeField] private Button hostButton;
[SerializeField] private Button joinButton;
[SerializeField] private GameObject mainMenuPanel;
[SerializeField] private GameObject lobbyPanel;
[SerializeField] private GameObject lobbyManager; // Drag your Lobby Manager here in the Inspector

public static class PlayerData
{
    public static string PlayerName = "Player";
}

private void Start()
{
    if (!ValidateUIReferences()) return;

    hostButton.onClick.AddListener(OnHostButtonClicked);
    joinButton.onClick.AddListener(OnJoinButtonClicked);
    mainMenuPanel.SetActive(true);
    lobbyPanel.SetActive(false);

    if (playerNameInputField != null)
    {
        playerNameInputField.text = GameManager.Instance != null ? GameManager.Instance.PlayerName : PlayerData.PlayerName;
        SavePlayerName(playerNameInputField.text);
        playerNameInputField.onEndEdit.AddListener(SavePlayerName);
    }
}

private void SavePlayerName(string name)
{
    if (!string.IsNullOrWhiteSpace(name))
    {
        if (GameManager.Instance != null)
        {
            GameManager.Instance.PlayerName = name.Trim();
            Debug.Log($"Player name saved: {GameManager.Instance.PlayerName}");
        }
        else
        {
            Debug.LogError("GameManager instance is missing. Cannot save player name.");
        }
    }
    else
    {
        Debug.LogWarning("Player name is empty or invalid. Please enter a valid name.");
    }
}

private bool ValidateUIReferences()
{
    bool isValid = true;
    if (!playerNameInputField)
    {
        Debug.LogError("Player Name Input Field is not assigned! Please assign it in the Inspector.");
        isValid = false;
    }
    if (!lobbyIdInputField)
    {
        Debug.LogError("Lobby ID Input Field is not assigned! Please assign it in the Inspector.");
        isValid = false;
    }
    if (!hostButton)
    {
        Debug.LogError("Host Button is not assigned! Please assign it in the Inspector.");
        isValid = false;
    }
    if (!joinButton)
    {
        Debug.LogError("Join Button is not assigned! Please assign it in the Inspector.");
        isValid = false;
    }
    if (!isValid)
    {
        Debug.LogError("UI references are not assigned to MainMenu! Check the Inspector.");
    }
    return isValid;
}

public void OnHostButtonClicked()
{
    if (NetworkManager.Singleton.IsHost)
    {
        ShowLobbyPanel();
        Debug.LogWarning("Host is already running. No need to start it again.");
        return;
    }

    Debug.Log("Attempting to start host...");
    if (NetworkManager.Singleton.StartHost())
    {
        ShowLobbyPanel();
        Debug.Log("Host started successfully.");
        
    }
    else
    {
        StartCoroutine(RestartHost());
        Debug.LogError("Failed to start host.");
    }
}

private IEnumerator RestartHost()
{
    var nm = NetworkManager.Singleton;
    nm.Shutdown();
    yield return new WaitWhile(() => nm.IsServer || nm.IsClient);
    Debug.Log("Host shutdown complete. Restarting host...");
}

public void OnJoinButtonClicked()
{
    string lobbyId = lobbyIdInputField.text.Trim();

    if (string.IsNullOrEmpty(lobbyId))
    {
        Debug.LogError("Please enter a lobby ID.");
        return;
    }

    Debug.Log($"Attempting to join lobby with ID: {lobbyId}...");

    if (NetworkManager.Singleton.IsServer || NetworkManager.Singleton.IsHost)
    {
        Debug.LogWarning("NetworkManager is already running as a host or server. Shutting it down before starting client...");
        NetworkManager.Singleton.Shutdown();
    }

    StartCoroutine(WaitForShutdownAndStartClient());
}

private IEnumerator WaitForShutdownAndStartClient()
{
    yield return new WaitWhile(() => NetworkManager.Singleton.IsServer || NetworkManager.Singleton.IsClient);

    if (NetworkManager.Singleton.StartClient())
    {
        Debug.Log("Client started successfully. Joining lobby...");
        ShowLobbyPanel();
    }
}

private void ShowLobbyPanel()
{
    Debug.Log("Switching to Lobby Panel...");
    mainMenuPanel.SetActive(false);
    lobbyPanel.SetActive(true);

    // Enable Lobby Manager directly
    if (lobbyManager != null)
    {
        lobbyManager.SetActive(true);
        Debug.Log("Lobby Manager activated.");
    }

    StartCoroutine(WaitForHostAndSetupLobby());
}

private IEnumerator WaitForHostAndSetupLobby()
{
    // Wait until the host is fully started
    while (!NetworkManager.Singleton.IsHost)
    {
        yield return null;
    }

    // Use the serialized reference, not FindFirstObjectByType
    if (lobbyManager != null)
    {
        var lobbyMgrScript = lobbyManager.GetComponent<LobbyManager>();
        if (lobbyMgrScript != null)
        {
            lobbyMgrScript.HostSetup();
        }
        else
        {
            Debug.LogError("LobbyManager script not found on lobbyManager GameObject!");
        }
    }
    else
    {
        Debug.LogError("lobbyManager GameObject reference is not set in MainMenu!");
    }
 }
}

Lobby script

  using UnityEngine;
  using UnityEngine.UI;
  using TMPro;
  using Unity.Netcode;
  using UnityEngine.SceneManagement;
  using System.Collections;
  using System.Collections.Generic;

public class LobbyManager : MonoBehaviour
{
[Header("UI References")]
[SerializeField] private TMP_Text playerListText;
[SerializeField] private Button startGameButton;
[SerializeField] private TMP_Text lobbyIdText;
[SerializeField] private Button readyButton;

private string lobbyId;
public static readonly Dictionary<string, List<ulong>> activeLobbies = new Dictionary<string, List<ulong>>();

// Ready system
private Dictionary<ulong, bool> playerReadyStates = new Dictionary<ulong, bool>();

private void Start()
{
    StartCoroutine(WaitForNetworkManagerAndInitialize());
}

private IEnumerator WaitForNetworkManagerAndInitialize()
{
    while (NetworkManager.Singleton == null)
    {
        yield return null;
    }

    if (!ValidateUIReferences()) yield break;

    Cursor.lockState = CursorLockMode.None;
    Cursor.visible = true;

    startGameButton.gameObject.SetActive(false); // Always hide here
    readyButton.gameObject.SetActive(true);
    readyButton.onClick.AddListener(OnReadyClicked);

    UpdatePlayerList();

    if (NetworkManager.Singleton.SceneManager != null)
    {
        NetworkManager.Singleton.SceneManager.OnSceneEvent += OnSceneEvent;
    }

    startGameButton.onClick.AddListener(StartGame);
}

private bool ValidateUIReferences()
{
    if (playerListText && startGameButton && lobbyIdText && readyButton)
        return true;

    Debug.LogError("UI references are not assigned to LobbyManager!");
    return false;
}

private void GenerateLobbyId()
{
    if (!NetworkManager.Singleton.IsHost) return;

    // Generate a unique lobby ID
    lobbyId = System.Guid.NewGuid().ToString("N").Substring(0, 6);
    LobbyManager.activeLobbies[lobbyId] = new List<ulong>();
    lobbyIdText.text = $"Lobby ID: {lobbyId}";

    Debug.Log($"Lobby created with ID: {lobbyId}");
}

private void AddPlayerToLobby(ulong playerId)
{
    if (!NetworkManager.Singleton.IsHost || string.IsNullOrEmpty(lobbyId)) return;

    // Ensure the lobby ID exists in the dictionary
    if (!activeLobbies.ContainsKey(lobbyId))
    {
        Debug.LogWarning($"Lobby ID '{lobbyId}' does not exist in the dictionary. Creating a new entry.");
        activeLobbies[lobbyId] = new List<ulong>();
    }

    if (!activeLobbies[lobbyId].Contains(playerId))
    {
        activeLobbies[lobbyId].Add(playerId);
        playerReadyStates[playerId] = false; // Not ready by default
        UpdatePlayerList();
    }
}

private void UpdatePlayerList()
{
    if (playerListText == null || string.IsNullOrEmpty(lobbyId)) return;

    // Ensure the lobby ID exists in the dictionary
    if (!activeLobbies.ContainsKey(lobbyId))
    {
        Debug.LogWarning($"Lobby ID '{lobbyId}' does not exist in the dictionary. Cannot update player list.");
        return;
    }

    var playerList = activeLobbies[lobbyId];
    playerListText.text = "Players in Lobby:\n";

    foreach (var playerId in playerList)
    {
        string playerName = $"Player {playerId}";
        string readyStatus = playerReadyStates.ContainsKey(playerId) && playerReadyStates[playerId] ? " (Ready)" : " (Not Ready)";
        playerListText.text += $"{playerName}{readyStatus}\n";
    }
}

public void UpdatePlayerListText(string playerList)
{
    if (playerListText != null)
    {
        playerListText.text = playerList;
    }
}

// Called when local player clicks ready
private void OnReadyClicked()
{
    SetReadyServerRpc(NetworkManager.Singleton.LocalClientId);
    readyButton.interactable = false; // Prevent spamming
}

[Rpc(SendTo.Server)]
private void SetReadyServerRpc(ulong playerId)
{
    if (!playerReadyStates.ContainsKey(playerId))
        playerReadyStates[playerId] = false;

    playerReadyStates[playerId] = true;
    UpdatePlayerListClientRpc();

    // Only the host checks and starts the game
    if (NetworkManager.Singleton.IsHost && AllPlayersReady())
    {
        StartGame();
    }
}

[Rpc(SendTo.ClientsAndHost)]
private void UpdatePlayerListClientRpc()
{
    UpdatePlayerList();
}

private bool AllPlayersReady()
{
    if (string.IsNullOrEmpty(lobbyId) || !activeLobbies.ContainsKey(lobbyId))
        return false;

    var playerList = activeLobbies[lobbyId];
    foreach (var playerId in playerList)
    {
        if (!playerReadyStates.ContainsKey(playerId) || !playerReadyStates[playerId])
            return false;
    }
    return true;
}

public void StartGame()
{
    if (!NetworkManager.Singleton.IsHost)
    {
        Debug.LogError("Only the host can start the game.");
        return;
    }

    Debug.Log("All players ready! Loading Store Game scene...");
    NetworkManager.Singleton.SceneManager.LoadScene("Store Game", LoadSceneMode.Single);
}

private void OnSceneEvent(SceneEvent sceneEvent)
{
if (sceneEvent.SceneEventType != SceneEventType.LoadComplete)
{
    Debug.Log($"Scene loaded: {sceneEvent.SceneName}");
}
 }

private void OnDestroy()
{
if (NetworkManager.Singleton != null && NetworkManager.Singleton.SceneManager != null)
{
    NetworkManager.Singleton.SceneManager.OnSceneEvent -= OnSceneEvent;
}

if (NetworkManager.Singleton.IsHost && !string.IsNullOrEmpty(lobbyId))
{
    activeLobbies.Remove(lobbyId);
  }
}

public void HostSetup()
{
if (NetworkManager.Singleton.IsHost)
{
    GenerateLobbyId();
    AddPlayerToLobby(NetworkManager.Singleton.LocalClientId);
    startGameButton.gameObject.SetActive(true); // Enable Start button for host
    Debug.Log("HostSetup: Start button enabled for host.");
  }
 }
}

Spawning script

using UnityEngine;
using Unity.Netcode;
using UnityEngine.SceneManagement;

public class StoreGamePlayerSpawner : NetworkBehaviour
{
public GameObject playerPrefab;

private void OnEnable()
{
    if (NetworkManager.Singleton.IsServer)
    {
        Debug.Log("PlayerSpawner: Subscribing to events");
        NetworkManager.Singleton.OnClientConnectedCallback += OnClientConnected;
        NetworkManager.Singleton.SceneManager.OnSceneEvent += OnSceneEvent;
    }
}

private void OnDisable()
{
    if (NetworkManager.Singleton.IsServer)
    {
        NetworkManager.Singleton.OnClientConnectedCallback -= OnClientConnected;
        NetworkManager.Singleton.SceneManager.OnSceneEvent -= OnSceneEvent;
    }
}

private void OnSceneEvent(SceneEvent sceneEvent)
{
    Debug.Log($"PlayerSpawner: SceneEvent {sceneEvent.SceneEventType} for {sceneEvent.SceneName}, client {sceneEvent.ClientId}");
    // Only spawn after the client has loaded the "Store Game" scene
    if (sceneEvent.SceneEventType == SceneEventType.LoadComplete &&
        sceneEvent.SceneName == "Store Game")
    {
        ulong clientId = sceneEvent.ClientId;
        Debug.Log($"PlayerSpawner: Scene loaded for client {clientId}, trying to spawn player");
        TrySpawnPlayer(clientId);
    }
}

private void OnClientConnected(ulong clientId)
{
    Debug.Log($"PlayerSpawner: Client connected {clientId}, current scene: {SceneManager.GetActiveScene().name}");
    // If the client is already in the Store Game scene, spawn immediately
    if (SceneManager.GetActiveScene().name == "Store Game")
    {
        Debug.Log($"PlayerSpawner: Store Game scene already loaded, trying to spawn player for {clientId}");
        TrySpawnPlayer(clientId);
    }
}

private void TrySpawnPlayer(ulong clientId)
{
    Debug.Log($"PlayerSpawner: TrySpawnPlayer called for client {clientId}");
    if (!NetworkManager.Singleton.ConnectedClients.ContainsKey(clientId))
    {
        Debug.LogWarning($"PlayerSpawner: Client {clientId} not in ConnectedClients!");
        return;
    }
    var client = NetworkManager.Singleton.ConnectedClients[clientId];
    if (client.PlayerObject != null)
    {
        Debug.LogWarning($"PlayerSpawner: Client {clientId} already has a PlayerObject!");
        return;
    }
    var player = Instantiate(playerPrefab, GetSpawnPosition(), Quaternion.identity);
    var networkObject = player.GetComponent<NetworkObject>();
    if (networkObject == null)
    {
        Debug.LogError("PlayerSpawner: Player prefab does not have a NetworkObject component!");
        return;
    }
    // Only use SpawnAsPlayerObject for player objects
    networkObject.SpawnAsPlayerObject(clientId, true);
    Debug.Log($"PlayerSpawner: Spawned player for client {clientId} at position {networkObject.transform.position}");
}

private Vector3 GetSpawnPosition()
{
    return new Vector3(Random.Range(-5, 5), 0, Random.Range(-5, 5));
}
}

And for anyone who might want or need it. Here is the debug log
Player 1 (Host)

Player 2 (Client)

You must not use Network properties or methods or RPCs until after the object has spawned. The IsServer flag may not be true at this point unless you load a scene after calling StartHost/Server.

Any init code should be in OnNetworkSpawn or later. But since you’re hooking up NetworkManager events, it could be too late at this point and may miss one or more clients connecting. You must assign these NetworkManager events BEFORE you call StartHost/Client to make sure you aren’t missing any events.

Spawning objects in the OnSceneEvent is … sketchy. I wouldn’t do it. This isn’t the place to spawn objects. OnClientConnected is the right place. Even if you change scenes afterwards, by default the player object will be moved over to the new scene.

You’re just making it harder on yourself by manually spawning players. Use the built-in player prefab system.

This player prefab won’t be the actual player but a player manager object. It can be empty barring some scripts that control the client’s networking matters. You can use that everywhere, Lobby and Game.

The player prefab also controls the actual player which I would either make a child (non-networked with all networking routed through the parent) or spawn as a separate network object and the player prefab instance keeps a reference.

This also allows you to kill and destroy the player without losing any of the local player state - a common problem devs face when they create a shoot and kill game.