Hi 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.