ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스파르타) The Last Rollback (D-18, Node.js 게임 서버 최종 프로젝트)
    TIL 2024. 8. 9. 23:35


     > 클라이언트에서 패킷 처리 부분에 약간의 오류가 있어 중간 발표 시연 전에 빠르게 수정하게 되었다.

     

    학습 키워드: Unity, C#, SceneManager

     

    트러블 슈팅

    1) 게임 세션 종료 후 마을 복귀 시 일부 클라이언트에서 다른 유저가 보이지 않는 문제:

    Figure 1. 마을 복귀 후 일부 클라이언트에서 다른 캐릭터가 표시되지 않음
    Figure 2. 상황 발생 당시 client log

     

     마을 귀환을 처리하는 부분의 코드 동작 순서로 인해 일부 유저의 클라이언트에서 몇몇 다른 유저가 보이지 않는 현상이 발생했다. 서버 쪽 로그에서는 모든 유저가 세션에 있는 것을 확인했으며, 모습을 못 보는 클라이언트의 캐릭터가 움직이면 다른 클라이언트에서는 해당 유저의 위치 정보 동기화가 잘 이루어졌기 때문에 클라이언트 코드 문제인 것을 알 수 있었다.

     

    // PacketHandler.cs (클라이언트)
    ...
    public static void S_EnterHandler(PacketSession session, IMessage packet)
    {
        S_Enter enterPacket = packet as S_Enter;
        if (enterPacket == null)
            return;
    
        var player = enterPacket.Player;
    
        Scene scene = SceneManager.GetActiveScene();
        if (scene.name == GameManager.DungeonScene)
        {
            Debug.Log("-----------------DungeonScene에서 TownScene으로 이동-----------------");
            GameManager.Instance.Pkt_ReturnTown = enterPacket;
            SceneManager.LoadScene(GameManager.TownScene);
            TownManager.Instance.GameStart(player); // 씬 이동으로 인해 호출되지 않음
            return;
        }
    
        TownManager.Instance.GameStart(player);
    }
    
    public static void S_SpawnHandler(PacketSession session, IMessage packet)
    {
        S_Spawn spawnPacket = packet as S_Spawn;
        if (spawnPacket == null)
            return;
    
        var playerList = spawnPacket.Players;
        foreach (var playerInfo in playerList) // ???
        {
            var tr = playerInfo.Transform;
    
            var player = TownManager.Instance.CreatePlayer(playerInfo, new Vector3(tr.PosX, tr.PosY, tr.PosZ));
            player.SetIsMine(false);
        }
    }

     

     클라이언트에서 다른 유저 캐릭터를 출력하려면 S_Spawn이라는 패킷을 수신하여 S_SpawnHandler를 호출하게 되는데, 이 패킷이 서버에서 전송되는 타이밍에는 S_Enter 패킷을 같이 보내주고 있다. 그런데 S_EnterHandler가 호출되면 Dungeon Scene에서 탈출하면서 Scene 이동이 이루어지게 되고, 이 때 아직 Scene 이동이 이루어지지 않은 타이밍의 패킷 내용이 유실되면서 일부 유저가 나타나지 않는 것으로 파악했다. 시연까지 시간이 별로 없어서 생각나는대로 빠르게 구현해봤다.

     

    public static void S_SpawnHandler(PacketSession session, IMessage packet)
    {
        S_Spawn spawnPacket = packet as S_Spawn;
        if (spawnPacket == null)
            return;
    
        GameManager.Instance.Pkt_S_SPAWN = spawnPacket;
    
        Scene scene = SceneManager.GetActiveScene();
        if (scene.name == GameManager.DungeonScene)
        {
            SceneManager.LoadScene(GameManager.TownScene);
        }
        else {
            TownManager.Instance.SpawnOthers();
        }
    }
    public void GameStart(PlayerInfo playerInfo)
    {
        if (GameManager.Instance.Pkt_S_SPAWN != null) { // 추가된 조건문
            SpawnOthers();
        }
        
        GameManager.Instance.CurrentScene = GameManager.CurrentMap.Town;
        if (uiStart == null) 
        {
            Spawn(playerInfo);
            return;
        }
        
        GameManager.Instance.UserName = playerInfo.PlayerId;
        
        Spawn(playerInfo);
    }
    
    public void SpawnOthers() {
        var playerList = GameManager.Instance.Pkt_S_SPAWN.Players;
        
        foreach (var otherplayerInfo in playerList)
        {
            var tr = otherplayerInfo.Transform;
    
            var player = TownManager.Instance.CreatePlayer(otherplayerInfo, new Vector3(tr.PosX, tr.PosY, tr.PosZ));
            player.SetIsMine(false);
        }
        GameManager.Instance.Pkt_S_SPAWN = null;
    }

     

     Singleton으로 존재하는 GameManager에 패킷을 저장해두고 씬 이동 후 불러오는 방식으로 변경해놓으니 4개 클라이언트 모두 상대가 정상적으로 출력되었으나, 실제로는 S_Spawn 패킷이 여러개 도착하기 때문에 상당히 아슬아슬한 방법이라고 생각된다. 추후에 문제가 다시 발생한다면 패킷 송수신 구조를 변경해야 할 수도 있겠다.

     

    SceneManager.sceneLoaded += OnSceneLoaded;

     또한, DungeonManager에서 위처럼 SceneManager에 OnSceneLoaded로 씬 로드가 완료될 시 실행되는 메서드를 추가했는데, 이 메서드를 다시 제거해주는 후처리를 하지 않으면 리소스 누수가 일어난다는 글을 읽어서 이 부분 또한 메서드 호출 이후에 '-=' operator를 사용하여 제거해주도록 변경했다.

     

     + 24-08-25 추가: 동일한 문제가 좀 더 낮은 빈도로 발생하는 것이 확인되어 클라이언트 로그를 읽어보던 중 Spawn 패킷이 두 번 수신된다는 사실을 깨닫게됐다. 두 번의 수신 자체는 원래 의도한대로 코드가 잘 실행되었다는 뜻인데, 문제는 위에서 작성한 방식으로는 Scene 이동이 완전히 이루어지기 전에 두개의 패킷을 받았을 때 앞서 받은 패킷이 덮어써진다는 것이다. 실제로 로그를 확인했을 때 먼저 수신한 패킷에 있던 유저만 보이지 않는다는 것을 확인하고 S_Spawn 패킷의 보관 구조를 Queue로 변경하여 여러개를 받더라도 순차적으로 처리할 수 있도록 조정했다.

     

     

    --

     


    REFERENCES:

     

     

    GitHub - eliotjang/the-last-rollback-server: 액션 MORPG

    액션 MORPG. Contribute to eliotjang/the-last-rollback-server development by creating an account on GitHub.

    github.com

     > 프로젝트 repo

     

    728x90
Designed by Tistory.