Newer
Older
Simple-Multiplayer-Unity3D / Assets / Scripts / GameClient.cs
using Multiplayer.Network;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEditor.PackageManager;
using UnityEngine;

public class GameClient : MonoBehaviour
{
    private TcpClient client;
    private NetworkStream stream;
    private bool isConnected;
    private static GameClient instance;
    private ushort localPlayerId;
    private string username;

    //Player isntance
    [SerializeField] private PlayerController playerController;

    //Players
    [SerializeField]private GameObject playerPrefab;
    private Dictionary<ushort, LobbyPlayer> lobbyPlayers = new Dictionary<ushort, LobbyPlayer>();

    private readonly Queue<Action> mainThreadActions = new Queue<Action>();

    public static GameClient Instance => instance;

    public void ConnectToServer(string ip, string playerName)
    {
        client = new TcpClient();
        client.Connect(ip, 7777);
        stream = client.GetStream();
        isConnected = true;

        // Set username and create PlayerData
        username = playerName;

        // Send player join message to server
        SendNetworkMessage(NetworkCommand.PlayerJoin, playerController.playerData);

        // Start listening thread
        new Thread(ListenForMessages).Start();
    }

    private void Awake()
    {
        if (instance != null && instance != this)
        {
            Destroy(gameObject); // Ensures there's only one instance
        }
        else
        {
            instance = this;
        }
    }

    private void Start()
    {
        ConnectToServer("localhost", playerController.name);
    }

    //Listens messages from the game server
    void ListenForMessages()
    {
        byte[] buffer = new byte[1024];
        while (isConnected)
        {
            if (stream.DataAvailable)
            {
                int bytesRead = stream.Read(buffer, 0, buffer.Length);
                byte[] receivedData = new byte[bytesRead];
                Array.Copy(buffer, receivedData, bytesRead);

                NetworkMessage msg = NetworkSerializer.FromBytes<NetworkMessage>(receivedData);

                switch (msg.command)
                {
                    case NetworkCommand.PlayerJoinConfirmed:
                        PlayerData myData = (PlayerData)msg.data;
                        localPlayerId = myData.playerId;
                        Debug.Log("Received PlayerJoinConfirmed. My player ID: " + localPlayerId);
                        break;

                    case NetworkCommand.PlayerJoin:
                        PlayerData joinData = (PlayerData)msg.data;
                        if (joinData.playerId == localPlayerId) break; // Don't spawn yourself
                        mainThreadActions.Enqueue(() => SpawnPlayer(joinData.playerId, joinData));
                        break;

                    case NetworkCommand.PlayerMove:
                        PlayerData moveData = (PlayerData)msg.data;
                        mainThreadActions.Enqueue(() => UpdatePlayerPosition(moveData.playerId, moveData.position, moveData.rotation));
                        break;

                    case NetworkCommand.PlayerLeave:
                        PlayerData leaveData = (PlayerData)msg.data;
                        mainThreadActions.Enqueue(() => DestroyPlayer(leaveData.playerId));
                        break;
                }
            }
        }
    }


    public void SendNetworkMessage(NetworkCommand command, INetworkSerializable data)
    {
        if (isConnected)
        {
            NetworkMessage msg = new NetworkMessage
            {
                command = command,
                data = data
            };
            byte[] serialized = NetworkSerializer.ToBytes(msg);
            SendData(serialized);
        }

        else
        {
            Debug.Log("The client is not connected to a server!");
        }
    }



    //Sends data to the server
    public void SendData(byte[] data)
    {
        if (isConnected)
        {
            stream.Write(data, 0, data.Length);
        }
    }

    //Spawns other player that connected to the server
    void SpawnPlayer(ushort playerId, PlayerData playerData)
    {
        GameObject player = Instantiate(playerPrefab, Vector3.zero, Quaternion.identity);
        player.GetComponent<LobbyPlayer>().SetPlayerData(playerData);
        lobbyPlayers.Add(playerId, player.GetComponent<LobbyPlayer>());
    }

    //Removes the player from the server
    void DestroyPlayer(ushort playerId)
    {
        if (lobbyPlayers.TryGetValue(playerId, out LobbyPlayer player)) //Player search by ID
        {
            Destroy(player); //Destroys the player GameObject
            lobbyPlayers.Remove(playerId); // Removes it from the Dictionary
        }
    }

    //Updates the player position
    void UpdatePlayerPosition(ushort playerId, Vector3 pos, Quaternion rotation)
    {
        if (playerId == playerController.playerData.playerId)
            return; // Don't update our own player!

        if (lobbyPlayers.TryGetValue(playerId, out LobbyPlayer player))
        {
            if (IsPositionValid(pos))
            {
                player.NextPosition = pos;
            }

            player.NextRotation = rotation;
        }
    }


    private bool IsPositionValid(Vector3 pos)
    {
        return !(float.IsNaN(pos.x) || float.IsInfinity(pos.x) ||
                 float.IsNaN(pos.y) || float.IsInfinity(pos.y) ||
                 float.IsNaN(pos.z) || float.IsInfinity(pos.z));
    }

    private void Update()
    {
        while (mainThreadActions.Count > 0)
        {
            mainThreadActions.Dequeue().Invoke();
        }
    }


    //Invoked when the client disconnects
    void OnDestroy()
    {
        isConnected = false;
        client?.Close();
    }

}