TIL

TIL_2025-01-10

explosion149 2025. 1. 10.

 

에러를 해결했다

원인은 !!!

 

LocationUpdate와 LocationUpdatePayload를 햇갈려서 주고 받을때 문제가 발생한 것이였다.

 

아래 코드는 오늘 업데이트 된 코드 들이다!!!

// 위치 정보 업데이트 payload
message LocationUpdatePayload {
    UserLocation user = 1;  // 단일 사용자 위치 정보를 포함하는 필드

    // 사용자 위치 정보를 정의하는 메시지
    message UserLocation {
        string id = 1; // 사용자 ID
        float x = 2;   // 사용자 X 좌표
        float y = 3;   // 사용자 Y 좌표
        string status = 4; // 사용자 상태
    }
}

 

더보기
// src/Codes/Spawner.cs

/**
 * 사용자 오브젝트를 생성하고 관리하는 클래스입니다.
 * 현재 활성화된 사용자 목록을 유지하며, 위치 업데이트를 처리합니다.
 * 비활성화된 사용자를 오브젝트 풀에서 제거합니다.
 */

using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class Spawner : MonoBehaviour
{
    public static Spawner instance; // Singleton 패턴을 위한 인스턴스

    void Awake() // MonoBehaviour의 Awake 메소드, 객체가 생성될 때 호출
    {
        instance = this; // 현재 인스턴스를 싱글톤 인스턴스로 설정
    }

    private Dictionary<string, PlayerPrefab> activeUsers = new Dictionary<string, PlayerPrefab>(); // 활성화된 사용자 목록
    private Dictionary<string, float> lastUpdateTime = new Dictionary<string, float>(); // 각 사용자의 마지막 업데이트 시간
    private const float USER_TIMEOUT = 5f; // 5초 동안 업데이트가 없으면 제거

    // 위치 업데이트 데이터를 기반으로 사용자 오브젝트를 생성하거나 업데이트하는 메소드
    public void Spawn(LocationUpdate data)
    {
        if (!GameManager.instance.isLive) // 게임이 활성화되어 있지 않으면 처리 중지
            return;

        float currentTime = Time.time; // 현재 시간 저장

        // 새로운 위치 업데이트 처리
        foreach (LocationUpdate.UserLocation user in data.users) // 위치 업데이트 데이터의 각 사용자에 대해 반복
        {
            if (user.id == GameManager.instance.deviceId) // 현재 디바이스의 사용자 ID는 무시
                continue;

            lastUpdateTime[user.id] = currentTime; // 해당 사용자의 마지막 업데이트 시간 갱신

            // 활성 사용자 목록에서 기존 플레이어를 찾음
            if (activeUsers.TryGetValue(user.id, out PlayerPrefab existingPlayer))
            {
                existingPlayer.UpdatePosition(user.x, user.y); // 기존 플레이어의 위치 업데이트
            }
            else // 새로운 플레이어인 경우
            {
                GameObject newPlayer = GameManager.instance.pool.Get(user); // 오브젝트 풀에서 사용자 오브젝트 요청
                if (newPlayer != null) // 요청한 오브젝트가 유효한 경우
                {
                    PlayerPrefab playerScript = newPlayer.GetComponent<PlayerPrefab>(); // PlayerPrefab 스크립트 가져오기
                    if (playerScript != null) // 스크립트가 유효한 경우
                    {
                        playerScript.UpdatePosition(user.x, user.y); // 새로운 플레이어의 위치 설정
                        activeUsers[user.id] = playerScript; // 활성 사용자 목록에 추가
                    }
                }
            }
        }

        // 일정 시간 동안 업데이트가 없는 유저만 제거
        List<string> usersToRemove = activeUsers.Keys // 활성 사용자 목록의 키를 가져옴
            .Where(id => currentTime - lastUpdateTime.GetValueOrDefault(id, 0) > USER_TIMEOUT) // 타임아웃 기준에 따라 필터링
            .ToList(); // 리스트로 변환

        // 타임아웃이 발생한 사용자 제거
        foreach (string userId in usersToRemove)
        {
            Debug.Log($"Removing user {userId} due to timeout"); // 제거되는 사용자 로그 출력
            if (activeUsers.TryGetValue(userId, out PlayerPrefab player)) // 사용자 목록에서 플레이어 찾기
            {
                GameManager.instance.pool.Remove(userId); // 오브젝트 풀에서 해당 사용자 제거
                activeUsers.Remove(userId); // 활성 사용자 목록에서 제거
                lastUpdateTime.Remove(userId); // 마지막 업데이트 시간 목록에서 제거
            }
        }
    }
}

 

 

 

더보기
// src/Codes/PlayerPrefab.cs

/**
 * 플레이어 프리팹의 애니메이션과 위치 업데이트를 관리하는 클래스입니다.
 * 서버로부터 위치 정보를 수신하고, 애니메이션의 속성을 업데이트합니다.
 * 디바이스 ID를 텍스트로 표시합니다.
 */

using TMPro; // TextMeshPro를 사용하기 위한 네임스페이스
using UnityEngine; // Unity 관련 기능을 사용하기 위한 네임스페이스

public class PlayerPrefab : MonoBehaviour
{
    public RuntimeAnimatorController[] animCon; // 애니메이터 컨트롤러 배열
    private Animator anim; // 애니메이터 컴포넌트
    private SpriteRenderer spriter; // 스프라이트 렌더러
    private Vector3 lastPosition; // 이전 위치
    private Vector3 currentPosition; // 현재 위치
    private Vector3 targetPosition; // 목표 위치
    private Vector3 currentVelocity; // 현재 속도
    private uint playerId; // 플레이어 ID
    TextMeshPro myText; // 텍스트 메쉬 프로

    [SerializeField] private float smoothTime = 0.1f; // 부드러운 이동을 위한 시간
    private bool hasTarget = false; // 목표 위치가 설정되었는지 여부
    private float lastUpdateTime; // 마지막 업데이트 시간
    private const float UPDATE_THRESHOLD = 0.01f; // 업데이트 간격 기준
    private const float MIN_MOVE_THRESHOLD = 0.001f; // 최소 이동 기준

    // Awake 메소드, 객체가 생성될 때 호출
    void Awake()
    {
        anim = GetComponent<Animator>(); // 애니메이터 컴포넌트 가져오기
        spriter = GetComponent<SpriteRenderer>(); // 스프라이트 렌더러 가져오기
        myText = GetComponentInChildren<TextMeshPro>(); // 자식에서 텍스트 메쉬 프로 가져오기
    }

    // 플레이어 초기화 메소드
    public void Init(uint playerId, string id)
    {
        anim.runtimeAnimatorController = animCon[playerId]; // 애니메이터 컨트롤러 설정
        lastPosition = transform.position; // 현재 위치를 이전 위치로 설정
        currentPosition = transform.position; // 현재 위치 초기화
        targetPosition = transform.position; // 목표 위치 초기화
        this.playerId = playerId; // 플레이어 ID 설정
        lastUpdateTime = Time.time; // 마지막 업데이트 시간 초기화

        // 디바이스 ID를 텍스트로 설정
        if (id.Length > 5)
        {
            myText.text = id[..5]; // ID가 5자 이상이면 잘라서 표시
        }
        else
        {
            myText.text = id; // ID가 5자 이하이면 그대로 표시
        }

        // 스프라이트의 색상 초기화
        if (spriter != null)
        {
            spriter.color = new Color(1f, 1f, 1f, 1f); // 완전 불투명
        }

        myText.GetComponent<MeshRenderer>().sortingOrder = 6; // 텍스트의 정렬 순서 설정
    }

    // 활성화될 때 호출되는 메소드
    void OnEnable()
    {
        // 애니메이터 컨트롤러 설정
        if (anim != null && playerId < animCon.Length)
        {
            anim.runtimeAnimatorController = animCon[playerId];
        }

        // 스프라이트 색상 초기화
        if (spriter != null)
        {
            spriter.color = new Color(1f, 1f, 1f, 1f); // 완전 불투명
        }
    }

    // 위치 업데이트 메소드
    public void UpdatePosition(float x, float y)
    {
        Vector3 newTargetPos = new Vector3(x, y); // 새로운 목표 위치 생성

        // 너무 작은 움직임은 무시
        if (hasTarget && Vector3.Distance(targetPosition, newTargetPos) < MIN_MOVE_THRESHOLD)
        {
            return; // 목표 위치가 너무 가까우면 종료
        }

        // 업데이트 간격이 너무 짧으면 스킵
        float currentTime = Time.time; // 현재 시간 저장
        if (currentTime - lastUpdateTime < UPDATE_THRESHOLD)
        {
            return; // 업데이트 간격이 짧으면 종료
        }

        lastPosition = currentPosition; // 이전 위치 업데이트
        targetPosition = newTargetPos; // 목표 위치 업데이트
        lastUpdateTime = currentTime; // 마지막 업데이트 시간 갱신
        hasTarget = true; // 목표 위치가 설정되었음을 표시
    }

    // 매 프레임 호출되는 Update 메소드
    void Update()
    {
        // 게임이 진행 중이지 않거나 목표가 없으면 종료
        if (!GameManager.instance.isLive || !hasTarget)
            return;

        float deltaTime = Time.deltaTime; // 프레임 간 시간 차이 저장

        // 부드러운 이동 처리
        currentPosition = Vector3.SmoothDamp(
            currentPosition, // 현재 위치
            targetPosition, // 목표 위치
            ref currentVelocity, // 현재 속도 참조
            smoothTime, // 부드러운 이동 시간
            Mathf.Infinity, // 최대 속도
            deltaTime // 프레임 간 시간 차이
        );

        // 위치가 충분히 변경되었을 때만 업데이트
        if (Vector3.Distance(transform.position, currentPosition) > MIN_MOVE_THRESHOLD)
        {
            transform.position = currentPosition; // 오브젝트 위치 업데이트
            UpdateAnimation(deltaTime); // 애니메이션 업데이트
        }
    }

    // LateUpdate 메소드, Update 이후 호출
    void LateUpdate()
    {
        // 게임이 진행 중이지 않으면 종료
        if (!GameManager.instance.isLive)
            return;

        // 목표 위치에 충분히 가까워졌을 때
        if (hasTarget && Vector3.Distance(currentPosition, targetPosition) < MIN_MOVE_THRESHOLD)
        {
            currentPosition = targetPosition; // 현재 위치를 목표 위치로 설정
            transform.position = currentPosition; // 오브젝트 위치 업데이트
            currentVelocity = Vector3.zero; // 속도 초기화
            anim.SetFloat("Speed", 0); // 애니메이션 속도 초기화
        }
    }

    // 애니메이션 업데이트 메소드
    private void UpdateAnimation(float deltaTime)
    {
        if (anim == null || spriter == null) return; // 애니메이터나 스프라이트가 없으면 종료

        Vector2 moveDirection = targetPosition - lastPosition; // 이동 방향 계산
        float speed = currentVelocity.magnitude; // 현재 속도 계산

        // 부드러운 애니메이션 전환
        float currentSpeed = anim.GetFloat("Speed"); // 현재 애니메이션 속도 가져오기
        float targetSpeed = speed; // 목표 애니메이션 속도 설정
        float smoothSpeed = Mathf.Lerp(currentSpeed, targetSpeed, deltaTime * 10f); // 부드러운 속도 전환

        anim.SetFloat("Speed", smoothSpeed); // 애니메이션 속도 설정

        // 이동 방향에 따라 스프라이트 반전
        if (Mathf.Abs(moveDirection.x) > MIN_MOVE_THRESHOLD)
        {
            spriter.flipX = moveDirection.x < 0; // 왼쪽으로 이동할 경우 스프라이트 반전
        }
    }

    // 비활성화될 때 호출되는 메소드
    void OnDisable()
    {
        hasTarget = false; // 목표가 없음을 표시
        currentVelocity = Vector3.zero; // 현재 속도 초기화
    }
}

 

 

더보기
// src/Codes/Packets.cs

/**
 * 패킷을 직렬화하고 역직렬화하는 기능을 제공하는 클래스입니다.
 * 다양한 패킷 유형과 페이로드에 대한 정의를 포함하고 있습니다.
 */

using UnityEngine; // Unity 관련 기능을 사용하기 위한 네임스페이스
using ProtoBuf; // Protobuf 직렬화를 사용하기 위한 네임스페이스
using System.IO; // 파일 및 스트림 작업을 위한 네임스페이스
using System.Buffers; // 버퍼 작성을 위한 네임스페이스
using System.Collections.Generic; // 컬렉션을 사용하기 위한 네임스페이스
using System; // 기본 시스템 기능을 위한 네임스페이스

public class Packets : MonoBehaviour
{
    // 패킷 유형을 정의하는 열거형
    public enum PacketType { Ping, Normal, Location = 3 } // Ping, Normal, Location 패킷 유형

    // 핸들러 ID를 정의하는 열거형
    public enum HandlerIds
    {
        Init = 0, // 초기화 핸들러 ID
        LocationUpdate = 2 // 위치 업데이트 핸들러 ID 
    }

    // 직렬화 메서드
    public static void Serialize<T>(IBufferWriter<byte> writer, T data)
    {
        Serializer.Serialize(writer, data); // Protobuf를 사용하여 데이터를 직렬화
    }

    // 역직렬화 메서드
    public static T Deserialize<T>(byte[] data)
    {
        try
        {
            using (var stream = new MemoryStream(data)) // 메모리 스트림 생성
            {
                return ProtoBuf.Serializer.Deserialize<T>(stream); // Protobuf를 사용하여 데이터를 역직렬화
            }
        }
        catch (Exception ex)
        {
            Debug.LogError($"Deserialize: Failed to deserialize data. Exception: {ex}"); // 오류 로그 출력
            throw; // 예외 재던지기
        }
    }
}

// 초기화 페이로드를 정의하는 클래스
[ProtoContract]
public class InitialPayload
{
    [ProtoMember(1, IsRequired = true)]
    public string deviceId { get; set; } // 디바이스 ID

    [ProtoMember(2, IsRequired = true)]
    public uint playerId { get; set; } // 플레이어 ID

    [ProtoMember(3, IsRequired = true)]
    public float latency { get; set; } // 지연 시간
}

// 공통 패킷을 정의하는 클래스
[ProtoContract]
public class CommonPacket
{
    [ProtoMember(1)]
    public uint handlerId { get; set; } // 핸들러 ID

    [ProtoMember(2)]
    public string userId { get; set; } // 사용자 ID

    [ProtoMember(3)]
    public string version { get; set; } // 버전 정보

    [ProtoMember(4)]
    public uint sequence { get; set; }  // 시퀀스 번호

    [ProtoMember(5)]
    public byte[] payload { get; set; } // 패킷 데이터
}

// 위치 업데이트 페이로드를 정의하는 클래스
[ProtoContract]
public class LocationUpdatePayload
{
    [ProtoMember(1)]
    public List<UserLocation> users { get; set; } // 사용자 위치 리스트

    // JSON 직렬화를 위한 기본 생성자
    public LocationUpdatePayload()
    {
        users = new List<UserLocation>(); // 사용자 위치 리스트 초기화
    }

    // 사용자 위치 정보를 정의하는 클래스
    [ProtoContract]
    public class UserLocation
    {
        [ProtoMember(1)]
        public string id { get; set; } // 사용자 ID

        [ProtoMember(2)]
        public float x { get; set; } // X 좌표

        [ProtoMember(3)]
        public float y { get; set; } // Y 좌표

        [ProtoMember(4)]
        public string status { get; set; } // 사용자 상태
    }
}

// 위치 업데이트를 정의하는 클래스
[ProtoContract]
public class LocationUpdate
{
    [ProtoMember(1)]
    public List<UserLocation> users { get; set; } // 사용자 위치 리스트

    // 기본 생성자
    public LocationUpdate()
    {
        users = new List<UserLocation>(); // 사용자 위치 리스트 초기화
    }

    // 사용자 위치 정보를 정의하는 클래스
    [ProtoContract]
    public class UserLocation
    {
        [ProtoMember(1)]
        public string id { get; set; } // 사용자 ID

        [ProtoMember(2)]
        public float x { get; set; } // X 좌표

        [ProtoMember(3)]
        public float y { get; set; } // Y 좌표

        [ProtoMember(4)]
        public string status { get; set; } // 사용자 상태

        [ProtoMember(5)]
        public uint playerId { get; set; } // 플레이어 ID

        [ProtoMember(6)]  // 추가 필드
        public long lastUpdateTime { get; set; } // 마지막 업데이트 시간
    }
}

// 응답 패킷을 정의하는 클래스
[ProtoContract]
public class Response
{
    [ProtoMember(1)]
    public uint handlerId { get; set; } // 핸들러 ID

    [ProtoMember(2)]
    public uint responseCode { get; set; } // 응답 코드

    [ProtoMember(3)]
    public long timestamp { get; set; } // 타임스탬프

    [ProtoMember(4)]
    public byte[] data { get; set; } // 응답 데이터
}

 

 

 

 

더보기
//src/Codes/NetworkManager.cs

/* 네트워크를 통해 클라이언트와 서버 간의 연결을 관리하는 클래스입니다.
IP와 포트를 입력받아 서버에 연결하고, 데이터를 송수신합니다.
패킷의 생성 및 처리, 게임 시작 로직을 포함합니다. */

using System;
using System.Buffers;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;

public class NetworkManager : MonoBehaviour
{
    public static NetworkManager instance; // 네트워크 매니저 인스턴스

    public InputField ipInputField; // IP 주소를 입력받기 위한 입력 필드
    public InputField portInputField; // 포트 번호를 입력받기 위한 입력 필드
    public InputField deviceIdInputField; // 디바이스 ID를 입력받기 위한 입력 필드
    public GameObject uiNotice; // 사용자에게 알림을 표시하기 위한 UI 오브젝트
    private TcpClient tcpClient; // TCP 클라이언트 객체
    private NetworkStream stream; // 네트워크 통신을 위한 스트림

    WaitForSecondsRealtime wait; // 대기 시간을 설정하기 위한 변수

    private byte[] receiveBuffer = new byte[4096]; // 수신 데이터를 저장하기 위한 버퍼
    private List<byte> incompleteData = new List<byte>(); // 불완전한 데이터를 저장하기 위한 리스트

    private bool isReconnecting = false; // 재연결 여부를 나타내는 플래그
    private int maxReconnectAttempts = 3; // 최대 재연결 시도 횟수
    private float reconnectDelay = 5f; // 재연결 지연 시간 (초)
    private int currentReconnectAttempt = 0; // 현재 재연결 시도 횟수

    private float locationUpdateInterval = 1.0f; // 위치 업데이트 간격 (초)
    private float lastUpdateTime = 0f; // 마지막 업데이트 시간

    private uint currentSequence = 0;  // 시퀀스 번호 카운터

    // 시퀀스 번호를 증가시키고 반환하는 메서드
    private uint GetNextSequence()
    {
        return ++currentSequence;  // 시퀀스 값을 증가시키고 반환
    }

    private Queue<PacketData> packetQueue = new Queue<PacketData>(); // 패킷 데이터를 저장하기 위한 큐
    private object queueLock = new object(); // 큐 접근을 위한 잠금 객체

    // 패킷 데이터 구조체
    private struct PacketData
    {
        public Packets.PacketType Type { get; set; } // 패킷 타입
        public byte[] Data { get; set; } // 패킷 데이터
    }

    private Dictionary<string, ClientState> connectedClients = new Dictionary<string, ClientState>(); // 연결된 클라이언트 상태를 저장하는 딕셔너리

    // 각 클라이언트의 상태를 나타내는 클래스
    private class ClientState
    {
        public Vector2 lastPosition; // 마지막 위치
        public long lastUpdateTime; // 마지막 업데이트 시간
        public bool isActive; // 클라이언트의 활성 상태 여부
    }

    // MonoBehaviour의 Awake 메소드, 객체가 생성될 때 호출
    void Awake()
    {
        instance = this; // 네트워크 매니저 인스턴스를 초기화
        wait = new WaitForSecondsRealtime(5); // 5초 대기 시간 설정
    }


    void Update()
    {
        // 게임이 활성화되어 있고 TCP 클라이언트가 연결되어 있는 경우
        if (GameManager.instance.isLive && tcpClient.Connected)
        {
            // 정기적으로 모든 클라이언트의 위치 정보 전송
            if (Time.time - lastUpdateTime >= locationUpdateInterval) // 지정된 업데이트 간격이 지났는지 확인
            {
                // 현재 플레이어의 위치를 서버에 전송
                SendLocationUpdatePacket(GameManager.instance.player.transform.position.x,
                GameManager.instance.player.transform.position.y);
                lastUpdateTime = Time.time; // 마지막 업데이트 시간을 현재 시간으로 갱신
            }
        }

        // 큐에 있는 패킷 처리
        while (true) // 무한 루프를 통해 큐에 있는 모든 패킷을 처리
        {
            PacketData packet; // 패킷 데이터 구조체
            lock (queueLock) // 큐에 대한 동기화 잠금
            {
                // 큐가 비어있다면 루프 종료
                if (packetQueue.Count == 0)
                    break;
                // 큐에서 패킷을 꺼내기
                packet = packetQueue.Dequeue();
            }

            try
            {
                // 패킷 타입에 따라 처리
                switch (packet.Type)
                {
                    case Packets.PacketType.Normal:
                        HandleNormalPacket(packet.Data); // 일반 패킷 처리
                        break;
                    case Packets.PacketType.Location:
                        HandleLocationPacket(packet.Data); // 위치 패킷 처리
                        break;
                    case Packets.PacketType.Ping:
                        HandlePingPacket(packet.Data); // 핑 패킷 처리
                        break;
                    default:
                        // 알 수 없는 패킷 타입 경고 로그 출력
                        Debug.LogWarning($"Unknown packet type: {packet.Type}");
                        break;
                }
            }
            catch (Exception e)
            {
                // 패킷 처리 중 오류가 발생한 경우 오류 로그 출력
                Debug.LogError($"Error processing packet: {e.Message}");
            }
        }
    }

    // 핑 패킷을 처리하는 메소드
    private void HandlePingPacket(byte[] data)
    {
        Debug.Log("Ping packet received."); // 핑 패킷 수신 로그 출력
    }

    // 시작 버튼 클릭 시 호출되는 메소드
    public void OnStartButtonClicked()
    {
        // 입력 필드에서 IP와 포트 정보를 가져옴
        string ip = ipInputField.text;
        string port = portInputField.text;

        // 포트가 유효한지 확인
        if (IsValidPort(port))
        {
            int portNumber = int.Parse(port); // 포트 번호 정수로 변환

            // 디바이스 ID 입력 필드가 비어있지 않은 경우
            if (deviceIdInputField.text != "")
            {
                GameManager.instance.deviceId = deviceIdInputField.text; // 입력된 디바이스 ID 설정
            }
            // 디바이스 ID가 비어있는 경우 고유 ID 생성
            else if (GameManager.instance.deviceId == "")
            {
                GameManager.instance.deviceId = GenerateUniqueID(); // 고유 ID 생성
            }

            // 연결 시도 로그 출력
            Debug.Log($"Connecting with DeviceId: {GameManager.instance.deviceId}");

            // 서버에 연결
            if (ConnectToServer(ip, portNumber))
            {
                StartGame(); // 게임 시작
            }
            else
            {
                // 연결 실패 시 효과음 재생 및 알림 표시
                AudioManager.instance.PlaySfx(AudioManager.Sfx.LevelUp);
                StartCoroutine(NoticeRoutine(1)); // 알림 루틴 시작
            }
        }
        else
        {
            // 포트가 유효하지 않은 경우
            AudioManager.instance.PlaySfx(AudioManager.Sfx.LevelUp);
            StartCoroutine(NoticeRoutine(0)); // 알림 루틴 시작
        }
    }

    // IP 유효성 검사 메소드
    bool IsValidIP(string ip)
    {
        // 간단한 IP 유효성 검사: IP 주소 형식이 올바른지 확인
        return System.Net.IPAddress.TryParse(ip, out _);
    }

    // 포트 유효성 검사 메소드
    bool IsValidPort(string port)
    {
        // 간단한 포트 유효성 검사 (0 - 65535)
        if (int.TryParse(port, out int portNumber)) // 포트 문자열을 정수로 변환
        {
            return portNumber > 0 && portNumber <= 65535; // 유효한 포트 범위인지 확인
        }
        return false; // 변환 실패 시 false 반환
    }

    // 서버에 연결하는 메소드
    bool ConnectToServer(string ip, int port)
    {
        try
        {
            tcpClient = new TcpClient(ip, port); // TCP 클라이언트 생성 및 서버에 연결
            stream = tcpClient.GetStream(); // 네트워크 스트림 가져오기
            Debug.Log($"Connected to {ip}:{port}"); // 연결 성공 메시지 출력

            return true; // 연결 성공
        }
        catch (SocketException e) // 소켓 예외 발생 시
        {
            HandleError("Failed to connect to server", e); // 에러 메시지 처리
            return false; // 연결 실패
        }
    }

    // 고유 ID 생성 메소드
    string GenerateUniqueID()
    {
        return System.Guid.NewGuid().ToString(); // 고유 ID 생성
    }

    // 게임 시작 메소드
    void StartGame()
    {
        // 게임 시작 코드 작성
        Debug.Log("Game Started"); // 게임 시작 메시지 출력
        StartReceiving(); // 데이터 수신 시작
        SendInitialPacket(); // 초기 패킷 전송
        var player = GameManager.instance.player; // 현재 플레이어 객체 가져오기
        SendLocationUpdatePacket(player.transform.position.x, player.transform.position.y); // 플레이어 위치 업데이트 전송
    }

    // 알림 UI를 표시하고 숨기는 루틴
    IEnumerator NoticeRoutine(int index)
    {
        uiNotice.SetActive(true); // 알림 UI 활성화
        uiNotice.transform.GetChild(index).gameObject.SetActive(true); // 특정 알림 표시

        yield return wait; // 대기

        uiNotice.SetActive(false); // 알림 UI 비활성화
        uiNotice.transform.GetChild(index).gameObject.SetActive(false); // 특정 알림 숨김
    }

    // 바이트 배열을 빅 엔디안으로 변환하는 메소드
    public static byte[] ToBigEndian(byte[] bytes)
    {
        if (BitConverter.IsLittleEndian) // 현재 시스템이 리틀 엔디안인 경우
        {
            Array.Reverse(bytes); // 바이트 배열을 역순으로 변환
        }
        return bytes; // 변환된 배열 반환
    }

    // 패킷 헤더 생성 메소드
    byte[] CreatePacketHeader(int dataLength, Packets.PacketType packetType)
    {
        // 헤더 길이(4) + 패킷 타입(1) + 데이터 길이
        int packetLength = 4 + 1 + dataLength; // 전체 패킷 길이 계산
        byte[] header = new byte[5]; // 헤더 배열 생성

        // 패킷 길이를 빅 엔디안으로 변환
        byte[] lengthBytes = BitConverter.GetBytes(packetLength); // 패킷 길이 바이트 배열 생성
        lengthBytes = ToBigEndian(lengthBytes); // 빅 엔디안으로 변환
        Array.Copy(lengthBytes, 0, header, 0, 4); // 길이 바이트 복사

        // 패킷 타입 설정
        header[4] = (byte)packetType; // 패킷 타입 추가

        return header; // 생성된 헤더 반환
    }

    // 패킷 전송 메소드
    async void SendPacket<T>(T payload, uint handlerId)
    {
        var payloadWriter = new ArrayBufferWriter<byte>(); // 바이트 배열 작성기 생성
        Packets.Serialize(payloadWriter, payload); // 페이로드 직렬화
        byte[] payloadData = payloadWriter.WrittenSpan.ToArray(); // 직렬화된 데이터 배열로 변환

        // 공통 패킷 생성
        CommonPacket commonPacket = new CommonPacket
        {
            handlerId = handlerId, // 핸들러 ID 설정
            userId = GameManager.instance.deviceId, // 사용자 ID 설정
            version = GameManager.instance.version, // 버전 설정
            sequence = 0,  // 시퀀스 번호 관리 필요
            payload = payloadData // 페이로드 추가
        };

        var commonPacketWriter = new ArrayBufferWriter<byte>(); // 공통 패킷 작성기 생성
        Packets.Serialize(commonPacketWriter, commonPacket); // 공통 패킷 직렬화
        byte[] data = commonPacketWriter.WrittenSpan.ToArray(); // 직렬화된 데이터 배열로 변환

        // 디버그용 로그
        Debug.Log($"Sending packet - HandlerId: {handlerId}, UserId: {GameManager.instance.deviceId}, PayloadLength: {payloadData.Length}");

        // 패킷 헤더 생성
        byte[] header = CreatePacketHeader(data.Length, Packets.PacketType.Normal); // 패킷 헤더 생성
        byte[] packet = new byte[header.Length + data.Length]; // 전체 패킷 배열 생성
        Array.Copy(header, 0, packet, 0, header.Length); // 헤더 복사
        Array.Copy(data, 0, packet, header.Length, data.Length); // 데이터 복사

        await Task.Delay(GameManager.instance.latency); // 지연 시간 대기
        stream.Write(packet, 0, packet.Length); // 패킷 전송
    }

    // 위치 업데이트 패킷 전송 메소드
    public void SendLocationUpdatePacket(float x, float y)
    {
        if (!tcpClient.Connected) // 클라이언트가 연결되어 있지 않은 경우
        {
            Debug.LogWarning("Cannot send location update: Not connected"); // 경고 메시지 출력
            return; // 메소드 종료
        }
        try
        {
            // 서버 좌표로 변환
            float serverX = ConvertToServerX(x);
            float serverY = ConvertToServerY(y);

            // 위치 업데이트 객체 생성
            LocationUpdate locationUpdate = new LocationUpdate();
            locationUpdate.users.Add(new LocationUpdate.UserLocation
            {
                id = GameManager.instance.deviceId, // 사용자 ID 설정
                playerId = GameManager.instance.playerId,  // playerId 추가
                x = serverX, // 변환된 X 좌표 설정
                y = serverY, // 변환된 Y 좌표 설정
                status = "active", // 사용자 상태 설정
                lastUpdateTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() // 마지막 업데이트 시간 설정
            });

            // 위치 전송 로그 출력
            Debug.Log($"Sending position - Unity: ({x}, {y}), Server: ({serverX}, {serverY})");
            SendPacket(locationUpdate, (uint)Packets.HandlerIds.LocationUpdate); // 위치 업데이트 패킷 전송
        }
        catch (Exception e)
        {
            // 오류 발생 시 로그 출력
            Debug.LogError($"Error sending location update: {e.Message}\n{e.StackTrace}");
        }
    }

    // 좌표 변환 메서드들
    private float ConvertToUnityX(float serverX)
    {
        return serverX * GameManager.instance.gridSize; // 서버 X 좌표를 Unity X 좌표로 변환
    }

    private float ConvertToServerX(float unityX)
    {
        return unityX / GameManager.instance.gridSize; // Unity X 좌표를 서버 X 좌표로 변환
    }

    private float ConvertToUnityY(float serverY)
    {
        return -serverY * GameManager.instance.gridSize;  // 서버 Y 좌표를 Unity Y 좌표로 반전하여 변환
    }

    private float ConvertToServerY(float unityY)
    {
        return -unityY / GameManager.instance.gridSize;  // Unity Y 좌표를 서버 Y 좌표로 반전하여 변환
    }






    // 초기 패킷 전송 메소드
    void SendInitialPacket()
    {
        // 초기 패킷 생성
        InitialPayload initialPayload = new InitialPayload
        {
            deviceId = GameManager.instance.deviceId, // 디바이스 ID 설정
            playerId = GameManager.instance.playerId, // 플레이어 ID 설정
            latency = GameManager.instance.latency // 지연 시간 설정
        };

        // 초기 패킷 전송 로그 출력
        Debug.Log($"Sending initial packet - DeviceId: {initialPayload.deviceId}, PlayerId: {initialPayload.playerId}");
        SendPacket(initialPayload, (uint)Packets.HandlerIds.Init); // 초기 패킷 전송
    }

    // 데이터 수신 시작 메소드
    void StartReceiving()
    {
        _ = ReceivePacketsAsync(); // 비동기 데이터 수신 시작
    }

    // 비동기 패킷 수신 메소드
    async System.Threading.Tasks.Task ReceivePacketsAsync()
    {
        while (tcpClient.Connected) // TCP 클라이언트가 연결된 동안 반복
        {
            try
            {
                // 데이터 읽기
                int bytesRead = await stream.ReadAsync(receiveBuffer, 0, receiveBuffer.Length);
                if (bytesRead > 0) // 읽은 바이트가 있을 경우
                {
                    ProcessReceivedData(receiveBuffer, bytesRead); // 수신 데이터 처리
                }
            }
            catch (Exception e)
            {
                HandleError("Error receiving data", e); // 수신 오류 메시지 처리
                break; // 루프 종료
            }
        }
    }

    // 수신 데이터 처리 메소드
    void ProcessReceivedData(byte[] data, int length)
    {
        try
        {
            // 수신한 데이터 추가
            incompleteData.AddRange(data.AsSpan(0, length).ToArray());

            // 패킷이 완전할 때까지 반복
            while (incompleteData.Count >= 5)
            {
                // 패킷 길이 확인
                byte[] lengthBytes = incompleteData.GetRange(0, 4).ToArray();
                int packetLength = BitConverter.ToInt32(ToBigEndian(lengthBytes), 0); // 패킷 길이 변환

                // 완전한 패킷을 수신할 때까지 대기
                if (incompleteData.Count < packetLength)
                {
                    return; // 패킷이 완전하지 않으면 종료
                }

                // 패킷 타입과 데이터 추출
                Packets.PacketType packetType = (Packets.PacketType)incompleteData[4];
                byte[] packetData = incompleteData.GetRange(5, packetLength - 5).ToArray();

                // 처리된 데이터 제거
                incompleteData.RemoveRange(0, packetLength);

                // 패킷을 큐에 추가
                lock (queueLock) // 큐에 대한 동기화 잠금
                {
                    packetQueue.Enqueue(new PacketData
                    {
                        Type = packetType, // 패킷 타입
                        Data = packetData // 패킷 데이터
                    });
                }
            }
        }
        catch (Exception e)
        {
            Debug.LogError($"Error in ProcessReceivedData: {e.Message}"); // 처리 중 오류 발생 시 로그 출력
        }
    }

    // 일반 패킷 처리 메소드
    void HandleNormalPacket(byte[] packetData)
    {
        try
        {
            // 패킷 데이터 역직렬화
            var response = Packets.Deserialize<Response>(packetData);
            Debug.Log($"Received response - HandlerId: {response.handlerId}, ResponseCode: {response.responseCode}");

            // 응답 코드가 0이 아닌 경우 경고 표시
            if (response.responseCode != 0 && !uiNotice.activeSelf)
            {
                AudioManager.instance.PlaySfx(AudioManager.Sfx.LevelUp); // 효과음 재생
                StartCoroutine(NoticeRoutine(2)); // 알림 루틴 시작
                return; // 메소드 종료
            }

            // 응답 데이터가 있는 경우 처리
            if (response.data != null && response.data.Length > 0)
            {
                if (response.handlerId == 0) // 초기 응답 처리
                {
                    GameManager.instance.GameStart(); // 게임 시작
                }
                else
                {
                    ProcessResponseData(response.data, response.handlerId); // 응답 데이터 처리
                }
            }
        }
        catch (Exception e)
        {
            Debug.LogError($"Error processing normal packet: {e.Message}"); // 오류 발생 시 로그 출력
        }
    }

    // 응답 데이터 처리 메소드
    void ProcessResponseData(byte[] data, uint handlerId)
    {
        try
        {
            // 데이터 문자열로 변환
            string jsonString = Encoding.UTF8.GetString(data);
            Debug.Log($"Processing response data for handlerId: {handlerId}, Data: {jsonString}");

            switch (handlerId)
            {
                case (uint)Packets.HandlerIds.LocationUpdate: // 위치 업데이트 핸들러
                    var locationUpdate = JsonUtility.FromJson<LocationUpdatePayload>(jsonString); // JSON 변환
                    if (locationUpdate != null && locationUpdate.users != null && locationUpdate.users.Count > 0)
                    {
                        var user = locationUpdate.users[0]; // 첫 번째 사용자 정보
                        Debug.Log($"Received location update - X: {user.x}, Y: {user.y}, Status: {user.status}");
                        // 위치 업데이트 처리 로직 추가
                    }
                    break;
                    // 다른 핸들러 케이스 추가
            }
        }
        catch (Exception e)
        {
            Debug.LogError($"Error processing response data: {e.Message}"); // 처리 중 오류 발생 시 로그 출력
        }
    }

    // 위치 패킷 처리 메소드
    void HandleLocationPacket(byte[] data)
    {
        try
        {
            // 위치 업데이트 패킷 역직렬화
            LocationUpdate response = Packets.Deserialize<LocationUpdate>(data);
            if (response.users != null && response.users.Count > 0) // 사용자 정보가 있는 경우
            {
                LocationUpdate convertedResponse = new LocationUpdate
                {
                    users = new List<LocationUpdate.UserLocation>() // 사용자 위치 리스트 초기화
                };

                foreach (var user in response.users) // 각 사용자에 대해 반복
                {
                    // Unity 좌표계로 변환
                    float unityX = ConvertToUnityX(user.x);
                    float unityY = ConvertToUnityY(user.y);

                    // 클라이언트 상태 업데이트
                    if (!connectedClients.ContainsKey(user.id))
                    {
                        connectedClients[user.id] = new ClientState(); // 새로운 클라이언트 상태 추가
                    }

                    var clientState = connectedClients[user.id];
                    clientState.lastPosition = new Vector2(unityX, unityY); // 마지막 위치 업데이트
                    clientState.lastUpdateTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); // 마지막 업데이트 시간
                    clientState.isActive = true; // 클라이언트 활성화

                    // 변환된 사용자 정보 추가
                    var convertedUser = new LocationUpdate.UserLocation
                    {
                        id = user.id,
                        x = unityX,
                        y = unityY,
                        status = user.status,
                        playerId = user.playerId,
                        lastUpdateTime = clientState.lastUpdateTime
                    };

                    convertedResponse.users.Add(convertedUser); // 변환된 사용자 리스트에 추가
                    Debug.Log($"User {user.id} - Server: ({user.x}, {user.y}), Unity: ({unityX}, {unityY}), Status: {user.status}, LastUpdate: {clientState.lastUpdateTime}");
                }

                // 비활성 클라이언트 처리
                long currentTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
                foreach (var client in connectedClients.ToList())
                {
                    if (currentTime - client.Value.lastUpdateTime > 10000) // 10초 이상 업데이트가 없는 경우
                    {
                        client.Value.isActive = false; // 클라이언트 비활성화
                                                       // 비활성 상태의 클라이언트도 위치 정보에 포함
                        convertedResponse.users.Add(new LocationUpdate.UserLocation
                        {
                            id = client.Key,
                            x = client.Value.lastPosition.x,
                            y = client.Value.lastPosition.y,
                            status = "inactive", // 비활성 상태 설정
                            lastUpdateTime = client.Value.lastUpdateTime
                        });
                    }
                }

                Spawner.instance.Spawn(convertedResponse); // 사용자 위치 정보 스폰
            }
        }
        catch (Exception e)
        {
            HandleError("Error processing location packet", e); // 오류 발생 시 처리
        }
    }

    // 오류 처리 메소드
    void HandleError(string message, Exception e = null)
    {
        // 네트워크 오류 메시지 로그 출력
        Debug.LogError($"Network Error: {message}");

        // 예외 정보가 주어진 경우 로그 출력
        if (e != null)
        {
            Debug.LogError($"Exception: {e}");
        }

        // 효과음 재생
        AudioManager.instance.PlaySfx(AudioManager.Sfx.LevelUp);
        // 알림 루틴 시작
        StartCoroutine(NoticeRoutine(2));

        // TCP 클라이언트가 연결되어 있지 않고 재연결 중이 아닌 경우
        if (tcpClient != null && !tcpClient.Connected && !isReconnecting)
        {
            // 재연결 시도 루틴 시작
            StartCoroutine(TryReconnect());
        }
    }

    // 재연결 시도 루틴
    IEnumerator TryReconnect()
    {
        // 이미 재연결 중인 경우 루틴 종료
        if (isReconnecting) yield break;

        isReconnecting = true; // 재연결 중 상태 설정
        currentReconnectAttempt = 0; // 현재 재연결 시도 횟수 초기화

        // TCP 클라이언트가 연결되지 않았고 최대 재연결 시도 횟수에 도달하지 않은 경우
        while (!tcpClient.Connected && currentReconnectAttempt < maxReconnectAttempts)
        {
            try
            {
                // 기존 TCP 클라이언트 닫기
                if (tcpClient != null)
                {
                    tcpClient.Close();
                    tcpClient = null;
                }

                currentReconnectAttempt++; // 재연결 시도 횟수 증가
                Debug.Log($"Attempting to reconnect... Attempt {currentReconnectAttempt}/{maxReconnectAttempts}");

                // 서버에 재연결 시도
                if (ConnectToServer(ipInputField.text, int.Parse(portInputField.text)))
                {
                    stream = tcpClient.GetStream(); // 네트워크 스트림 가져오기
                    StartReceiving(); // 데이터 수신 시작
                    SendInitialPacket(); // 초기 패킷 전송
                    isReconnecting = false; // 재연결 상태 해제
                    Debug.Log("Reconnection successful!"); // 재연결 성공 메시지 출력
                    yield break; // 루틴 종료
                }
            }
            catch (Exception e)
            {
                // 재연결 시도 중 오류 발생 시 로그 출력
                Debug.LogError($"Reconnection attempt failed: {e.Message}");
            }

            // 재연결 지연 시간 대기
            yield return new WaitForSeconds(reconnectDelay);
        }

        // 재연결 시도 후 여전히 연결되지 않은 경우
        if (!tcpClient.Connected)
        {
            Debug.LogError("Failed to reconnect after maximum attempts"); // 재연결 실패 메시지 출력
            StartCoroutine(NoticeRoutine(1)); // 알림 루틴 시작
        }

        isReconnecting = false; // 재연결 상태 해제
    }

    // 애플리케이션 종료 시 호출되는 메소드
    void OnApplicationQuit()
    {
        // TCP 클라이언트가 연결되어 있는 경우 클라이언트 닫기
        if (tcpClient != null && tcpClient.Connected)
        {
            tcpClient.Close(); // 클라이언트 종료
        }
    }

}

 

 

 

더보기
// src/events/onEnd.js


/* 
이 코드는 클라이언트 소켓 연결 종료 시 호출되는 함수를 정의합니다.
연결이 종료되면 로그를 출력하고, 해당 소켓에 대한 사용자 정보를 제거합니다.
사용자 세션 관리를 위한 'removeUser' 함수를 사용합니다.
*/

import { removeUser, getUserBySocket } from '../session/user.session.js';  // 사용자 세션에서 사용자 제거 함수
import { gameSessions } from '../session/sessions.js';
// 클라이언트 소켓 연결 종료 시 호출되는 함수
export const onEnd = (socket) => () => {
  console.log('클라이언트 연결이 종료되었습니다.');

  // 1. 먼저 해당 소켓의 유저 정보를 찾습니다
  const user = getUserBySocket(socket);
  if (user) {
    // 2. 유저가 속한 게임을 찾습니다
    const game = gameSessions.find(g => g.users.some(u => u.id === user.id));
    if (game) {
      // 3. 게임에서 유저를 제거합니다
      game.removeUser(user.id);
      console.log(`User ${user.id} removed from game ${game.id}`);
    }
  }

  // 4. 마지막으로 세션에서 유저를 제거합니다
  removeUser(socket);
};

 

 

 

 

더보기
// src/handlers/user/initial.handler.js

/* 
이 코드는 초기 사용자 핸들러 함수를 정의합니다.
장치 ID를 통해 사용자를 조회하고, 존재하지 않으면 새로 생성하며,
사용자 정보를 응답으로 클라이언트에 전송합니다. 오류 발생 시 적절히 처리합니다.
*/

import { addUser, getUserById, removeUser } from '../../session/user.session.js';  // 사용자 세션에 사용자 추가, 조회 및 제거 함수 임포트
import { userSessions } from '../../session/sessions.js';  // 현재 사용자 세션 정보 임포트
import { HANDLER_IDS, RESPONSE_SUCCESS_CODE } from '../../constants/handlerIds.js';  // 핸들러 ID 및 응답 코드 상수 임포트
import { createResponse } from '../../utils/response/createResponse.js';  // 응답 생성 유틸리티 임포트
import { handleError } from '../../utils/error/errorHandler.js';  // 오류 처리 유틸리티 임포트
import { createUser, findUserByDeviceID, updateUserLogin } from '../../db/user/user.db.js';  // 데이터베이스 사용자 관련 함수 임포트
import { getAllGameSessions } from '../../session/game.session.js';  // 모든 게임 세션 정보를 가져오는 함수 임포트

// 초기 사용자 핸들러 함수 정의
const initialHandler = async ({ socket, userId, payload }) => {
  try {
    // 페이로드에서 deviceId, playerId, latency 추출
    const { deviceId, playerId, latency } = payload;
    console.log(`Initial connection request - DeviceId: ${deviceId}, PlayerId: ${playerId}`);

    // deviceId로 사용자 조회
    let user = await findUserByDeviceID(deviceId);
    console.log('Initial handler - Existing user:', user); // 기존 사용자 정보 로그 출력

    // 사용자가 존재하지 않는 경우 새 사용자 생성
    if (!user) {
      user = await createUser(deviceId);
      console.log('Initial handler - New user created:', user); // 새 사용자 생성 로그 출력
    } else {
      // 사용자가 존재하는 경우 로그인 정보 업데이트
      await updateUserLogin(user.id);
      console.log('Initial handler - User login updated'); // 로그인 업데이트 로그 출력
    }

    // deviceId를 userId로 사용하여 기존 사용자 세션 제거
    const existingUser = getUserById(deviceId);
    if (existingUser) {
      console.log('Initial handler - Removing existing user session'); // 기존 사용자 세션 제거 로그 출력
      removeUser(existingUser.socket); // 기존 사용자 세션 제거
    }

    // 모든 게임 세션 정보 가져오기
    const gameSessions = getAllGameSessions();
    if (gameSessions.length === 0) {
      throw new Error('No active game sessions available'); // 활성 게임 세션이 없으면 오류 발생
    }

    // 첫 번째 게임 세션 사용 (또는 다른 로직으로 게임 세션 선택)
    const gameSession = gameSessions[0];

    // 새로운 세션 추가할 때 deviceId를 userId로 사용
    const newUser = addUser(deviceId, socket); // 사용자 세션 추가
    console.log('Initial handler - User session added:', {
      id: newUser.id,
      sessionsCount: userSessions.length // 현재 사용자 세션 수 로그 출력
    });
    gameSession.addUser(newUser); // 게임 세션에 사용자 추가

    // 초기 응답 생성
    const initialResponse = createResponse(
      HANDLER_IDS.INITIAL, // 핸들러 ID
      RESPONSE_SUCCESS_CODE, // 성공 응답 코드
      {
        userId: deviceId,  // deviceId를 userId로 사용
        playerId: playerId, // 플레이어 ID
        latency: latency,   // 지연 시간
        gameId: gameSession.id  // 게임 세션 ID 포함
      },
      deviceId // 요청을 보낸 사용자 ID
    );

    // 초기 응답 전송
    socket.write(initialResponse);

  } catch (error) {
    // 오류 발생 시 로그 출력 및 오류 처리
    console.error('Error in initialHandler:', error);
    handleError(socket, error); // 오류 처리 유틸리티 호출
  }
};

// 핸들러 함수 내보내기
export default initialHandler;

 

 

 

 

 

더보기
// src/session/user.session.js

/* 
이 코드는 사용자 세션을 관리하는 기능을 제공합니다.
사용자 추가, 제거, ID 또는 소켓을 통한 사용자 조회 및 다음 시퀀스 값을 가져오는 기능을 포함합니다.
각 사용자는 'User' 클래스의 인스턴스로 생성되며, 세션 관리를 위해 사용자 배열에 저장됩니다.
*/

import { userSessions } from './sessions.js';  // 사용자 세션 목록을 임포트
import User from '../classes/models/user.class.js';  // User 클래스 임포트

// 사용자 추가 함수
export const addUser = (id, socket) => {
  console.log(`Adding user to sessions - ID: ${id}`);  // 사용자 추가 로그 출력

  // 이미 존재하는 세션인지 확인
  const existingSession = userSessions.find(user => user.id === id); // ID로 기존 사용자 세션 조회
  if (existingSession) {
    console.log(`Removing existing session for user ${id}`); // 기존 세션 제거 로그 출력
    removeUser(existingSession.socket); // 기존 세션 제거
  }

  // 새로운 사용자 인스턴스 생성
  const user = new User(id, socket);
  userSessions.push(user); // 사용자 배열에 추가

  console.log(`User added successfully. Total sessions: ${userSessions.length}`); // 추가 성공 로그 출력
  console.log('Current sessions:', userSessions.map(u => u.id)); // 현재 세션 로그 출력

  return user; // 추가된 사용자 반환
};

// 사용자 제거 함수
export const removeUser = (socket) => {
  // 소켓을 통해 사용자 세션 조회
  const index = userSessions.findIndex(user => user.socket === socket);
  if (index !== -1) {
    const removedUser = userSessions.splice(index, 1)[0]; // 사용자 배열에서 제거
    console.log(`Removed user ${removedUser.id} from sessions`); // 제거 성공 로그 출력
    return removedUser; // 제거된 사용자 반환
  }
  console.log('No user found to remove'); // 제거할 사용자 미발견 로그 출력
  return null; // 제거할 사용자가 없을 경우 null 반환
};

// ID로 사용자 조회 함수
export const getUserById = (id) => {
  console.log(`Looking for user with ID: ${id}`); // 사용자 조회 로그 출력
  console.log('Current sessions:', userSessions.map(u => u.id)); // 현재 세션 로그 출력

  // ID로 사용자 조회
  const user = userSessions.find(user => user.id === id);

  console.log("저장된 유저 보기", userSessions); // 현재 저장된 사용자 목록 로그 출력

  if (user) {
    console.log('User found'); // 사용자 발견 로그 출력
    return user; // 발견된 사용자 반환
  } else {
    console.log('User not found'); // 사용자 미발견 로그 출력
    return null; // 사용자가 없을 경우 null 반환
  }
};

// 소켓으로 사용자 조회 함수
export const getUserBySocket = (socket) => {
  return userSessions.find((user) => user.socket === socket);  // 소켓을 통해 사용자 조회
};

// 다음 시퀀스 값 가져오기 함수
export const getNextSequence = (id) => {
  const user = getUserById(id);  // ID로 사용자 조회
  if (user) {
    return user.getNextSequence();  // 사용자가 존재할 경우 다음 시퀀스 값 반환
  }
  return null;  // 사용자가 없을 경우 null 반환
};

 

'TIL' 카테고리의 다른 글

TIL_2025-01-15  (0) 2025.01.16
TIL_2025-01-14  (1) 2025.01.14
TIL_2025-01-09  (0) 2025.01.10
TIL_2025-01-08  (0) 2025.01.08
TIL_2025-01-07  (0) 2025.01.07

댓글

💲 추천 글