[SOLVED] Player prefab not spawning

Hi :slight_smile: I have an issue where the players don’t spawn when the game runs and I can’t figure out why.

In my ServerNetPortal I have an approval check and in the script for the player (Tank), I have a random position method for where it should be spawned and my money is on that that’s where the problem lies - I just don’t know exactly where…

using System;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.SceneManagement;

/// <summary>
/// Handle server logic.
/// </summary>
public class ServerGameNetPortal : MonoBehaviour
{
    [Header("Settings")]
    public int maxPlayers = 4;

    private static ServerGameNetPortal instance;
    public static ServerGameNetPortal Instance => instance;

    private Dictionary<string, PlayerData> clientData;
    private Dictionary<ulong, string> clientIDToGuid;
    private Dictionary<ulong, int> clientSceneMap;
    private bool gameIsInProgress;

    private const int MaxConnectionPayload = 1024;

    private GameNetPortal gameNetPortal;

    private void Awake()
    {
        if(instance != null && instance != this)
        {
            Destroy(gameObject);
            return;
        }
        
        instance = this;
        DontDestroyOnLoad(gameObject);
    }

    private void Start()
    {
        gameNetPortal = GetComponent<GameNetPortal>();
        gameNetPortal.OnNetworkReadied += HandleNetworkReadied;

        NetworkManager.Singleton.ConnectionApprovalCallback += ApprovalCheck;

        NetworkManager.Singleton.OnServerStarted += HandleServerStarted;

        clientData = new Dictionary<string, PlayerData>();
        clientIDToGuid = new Dictionary<ulong, string>();
        clientSceneMap = new Dictionary<ulong, int>();
    }

    private void OnDestroy()
    {
        if (gameNetPortal == null)
            return;

        gameNetPortal.OnNetworkReadied -= HandleNetworkReadied;

        if (NetworkManager.Singleton == null)
            return;

        NetworkManager.Singleton.ConnectionApprovalCallback -= ApprovalCheck;

        NetworkManager.Singleton.OnServerStarted -= HandleServerStarted;
    }

    public PlayerData? GetPlayerData(ulong clientID)
    {
        if (clientIDToGuid.TryGetValue(clientID, out string clientGuid))
        {
            if (clientData.TryGetValue(clientGuid, out PlayerData playerData))
                return playerData;
            else
                Debug.Log("No player data found for client ID: {clientID}");
        }
        else
            Debug.Log("No client guid found for client ID: {clientID}");

        return null;
    }

    public void StartGame()
    {
        gameIsInProgress = true;

        NetworkManager.Singleton.SceneManager.LoadScene("GameScene", LoadSceneMode.Single);
    }

    public void EndGame()
    {
        gameIsInProgress = false;

        NetworkManager.Singleton.SceneManager.LoadScene("LobbyScene", LoadSceneMode.Single);
    }

    private void HandleNetworkReadied()
    {
        if (!NetworkManager.Singleton.IsServer)
            return;

        gameNetPortal.OnUserDisconnectRequested += HandleUserDisconnectRequested;
        NetworkManager.Singleton.OnClientDisconnectCallback += HandleClientDisconnect;
        gameNetPortal.OnClientSceneChanged += HandleClientSceneChanged;

        NetworkManager.Singleton.SceneManager.LoadScene("LobbyScene", LoadSceneMode.Single);

        if (NetworkManager.Singleton.IsHost)
            clientSceneMap[NetworkManager.Singleton.LocalClientId] = SceneManager.GetActiveScene().buildIndex;
    }
    private void HandleClientDisconnect(ulong clientID)
    {
        clientSceneMap.Remove(clientID);

        if (clientIDToGuid.TryGetValue(clientID, out string guid))
        {
            clientIDToGuid.Remove(clientID);

            if (clientData[guid].ClientID == clientID)
                clientData.Remove(guid);
        }

        if(clientID == NetworkManager.Singleton.LocalClientId)
        {
            gameNetPortal.OnUserDisconnectRequested -= HandleUserDisconnectRequested;
            NetworkManager.Singleton.OnClientDisconnectCallback -= HandleClientDisconnect;
            gameNetPortal.OnClientSceneChanged -= HandleClientSceneChanged;
        }
    }

    private void HandleClientSceneChanged(ulong clientID, int sceneIndex)
    {
        clientSceneMap[clientID] = sceneIndex;
    }

    private void HandleUserDisconnectRequested()
    {
        HandleClientDisconnect(NetworkManager.Singleton.LocalClientId);

        NetworkManager.Singleton.Shutdown();

        ClearData();

        SceneManager.LoadScene("MenuScene");
    }

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

        string clientGuid = Guid.NewGuid().ToString();
        string playerName = PlayerPrefs.GetString("PlayerName", "Missing Name");

        clientData.Add(clientGuid, new PlayerData(playerName, NetworkManager.Singleton.LocalClientId));
        clientIDToGuid.Add(NetworkManager.Singleton.LocalClientId, clientGuid);
    }

    private void ClearData()
    {
        clientData.Clear();
        clientIDToGuid.Clear();
        clientSceneMap.Clear();

        gameIsInProgress = false;
    }

    private void ApprovalCheck(byte[] connectionData, ulong clientID, NetworkManager.ConnectionApprovedDelegate callback)
    {
        if(connectionData.Length > MaxConnectionPayload)
        {
            callback(false, 0, false, null, null);
            return;
        }

        if(clientID == NetworkManager.Singleton.LocalClientId)
        {
            callback(false, null, true, null, null);
            return;
        }

        string payload = Encoding.UTF8.GetString(connectionData);
        var connectionPayload = JsonUtility.FromJson<ConnectionPayload>(payload);

        ConnectStatus gameReturnStatus = ConnectStatus.Success;

        // Checks if an instance is already running.
        if (clientData.ContainsKey(connectionPayload.clientGuid))
        {
            ulong oldClientID = clientData[connectionPayload.clientGuid].ClientID;
            StartCoroutine(WaitToDisconnectClient(oldClientID, ConnectStatus.LoggedInAgain));
        }

        if (gameIsInProgress)
            gameReturnStatus = ConnectStatus.GameInProgress;

        else if (clientData.Count >= maxPlayers)
            gameReturnStatus = ConnectStatus.ServerIsFull;

        if(gameReturnStatus == ConnectStatus.Success)
        {
            clientSceneMap[clientID] = connectionPayload.clientScene;
            clientIDToGuid[clientID] = connectionPayload.clientGuid;
            clientData[connectionPayload.clientGuid] = new PlayerData(connectionPayload.playerName, clientID);
        }

        callback(false, 0, true, null, null);

        gameNetPortal.ServerToClientSetDisconnectReason(clientID, gameReturnStatus);

        if (gameReturnStatus != ConnectStatus.Success)
            StartCoroutine(WaitToDisconnectClient(clientID, gameReturnStatus));
    }

    private IEnumerator WaitToDisconnectClient(ulong clientID, ConnectStatus reason)
    {
        gameNetPortal.ServerToClientSetDisconnectReason(clientID, reason);

        yield return new WaitForSeconds(0);

        DisconnectThisClient(clientID);
    }

    private void DisconnectThisClient(ulong clientID)
    {
        NetworkObject networkObject = NetworkManager.Singleton.SpawnManager.GetPlayerNetworkObject(clientID);
        
        if (networkObject != null)
            networkObject.Despawn(true);

        NetworkManager.Singleton.DisconnectClient(clientID);
    }
}

and:

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

public class Tank : NetworkBehaviour
{
    public GameObject bullet;
    public Material enemyMaterial;

    private HealthBar localHealthBar;
    private ShieldBar shieldBar;
    private int localHealth = 5;
    private TMP_Text healthTxt;
    private int localShield = 0;
    private Rigidbody rb;

    public NetworkVariable<int> Health = new NetworkVariable<int>(5);

    void Start()
    {
        if (IsLocalPlayer)
        {
            // Set random start position for local player.
            transform.position = GetRandomPosition();

            // Get components of local player.
            healthTxt = GameObject.Find("healthTxt").GetComponent<TMP_Text>();
            localHealthBar = GameObject.Find("healthBar").GetComponent<HealthBar>();
            rb = GetComponent<Rigidbody>();

            // Set text to local health.
            healthTxt.text = localHealth.ToString();

            localHealthBar.SetMaxHealth(localHealth);
            localHealthBar.SetHealth(localHealth);
        }
        else
        {
            // Changes the colour of opponents' tanks.
            GetComponent<MeshRenderer>().material = enemyMaterial;
        }
    }

    void Update()
    {
        if (IsLocalPlayer)
            UpdateClient();
    }

    private void OnCollisionEnter(Collision collision)
    {
        if (collision.transform.CompareTag("Bullet"))
        {
            if(IsClient)
            {
                // Update local health.
                localHealth--;
                healthTxt.text = localHealth.ToString();
                localHealthBar.SetHealth(localHealth);

                // Sync global health.
                SetHealthServerRpc();

                // If dead.
                if(localHealth <= 0)
                {
                    GetComponent<BoxCollider>().enabled = false;
                    Invoke("Respawn", 5);
                }
            }
            // Removes bullet on hit.
            Destroy(collision.gameObject);
        }
    }

    [ServerRpc]
    void SetHealthServerRpc()
    {
        Health.Value = localHealth;
    }

    private void Respawn()
    {
        localHealth = 5;
        healthTxt.text = localHealth.ToString();
        localHealthBar.SetHealth(localHealth);

        SetHealthServerRpc();

        rb.velocity = Vector3.zero;
        transform.position = GetRandomPosition();
        GetComponent<BoxCollider>().enabled = true;
    }

    public void UpdateClient()
    {
        // Moves local player.
        Vector3 movement = Vector3.ClampMagnitude(new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")), 1);
        transform.position += movement * Time.deltaTime * 5;

        // Rotates local player.
        Quaternion newRotation = Quaternion.LookRotation(movement, Vector3.up);
        if(movement.magnitude > 0)
            transform.rotation = Quaternion.Lerp(transform.rotation, newRotation, Time.deltaTime * 5);

        // Allows local player to shoot.
        if (Input.GetButtonDown("Shoot"))
            SpawnBulletServerRpc();
    }

    public Vector3 GetRandomPosition()
    {
        return new Vector3(Random.Range(1, 5), 2, Random.Range(-20, -25));
    }

    // Spawns bullet on server.
    [ServerRpc]
    private void SpawnBulletServerRpc()
    {
        GameObject localBullet = Instantiate(bullet, transform.position + transform.forward * 3, transform.rotation);
        localBullet.GetComponent<NetworkObject>().Spawn();
    }
}

Let me know if further context is needed, but I hope it’s enough.

Hello unity_VNomJxuy2kYlZg,

There are two ways to spawn a player: 1) Open a new scene where there is a player object already in the scene, or 2) Instantiate a player object. Perhaps you are doing this in another script? Without A or B, you will not spawn a new player. To further help you, when exactly are you trying to spawn in a player?

Thank you, @WolverineLight I realize now that my issue probably is that cx - so I decided to make a separate PlayerSpawner script.

public class PlayerSpawner : NetworkBehaviour
{
    [Header("References")]
    public GameObject playerPrefab;

    public override void OnNetworkSpawn()
    {
        SpawnPlayerServerRpc(NetworkManager.Singleton.LocalClientId);
    }

    [ServerRpc(RequireOwnership = false)]
    public void SpawnPlayerServerRpc(ulong clientID)
    {
        GameObject newPlayer;
        newPlayer = (GameObject)Instantiate(playerPrefab);

        NetworkObject networkObject = newPlayer.GetComponent<NetworkObject>();
        newPlayer.SetActive(true);

        networkObject.SpawnAsPlayerObject(clientID, true);
    }
}

This script is located in the Main scene and so, if I’m not wrong, OnNetworkSpawn() will execute on the scene change because that’s when it’s created? Anyways, I was wondering in order to make it work (because it currently doesn’t) should I use a foreach loop in order to spawn a player prefab for every connected client? That seems logical to me, but I didn’t get it to work so some nudges in the correct direction would be nice :slight_smile: