-
스파르타) The Last Rollback (D-18, Node.js 게임 서버 최종 프로젝트)TIL-sparta 2024. 8. 9. 23:35
> 클라이언트에서 패킷 처리 부분에 약간의 오류가 있어 중간 발표 시연 전에 빠르게 수정하게 되었다.학습 키워드: Unity, C#, SceneManager
트러블 슈팅
1) 게임 세션 종료 후 마을 복귀 시 일부 클라이언트에서 다른 유저가 보이지 않는 문제:
마을 귀환을 처리하는 부분의 코드 동작 순서로 인해 일부 유저의 클라이언트에서 몇몇 다른 유저가 보이지 않는 현상이 발생했다. 서버 쪽 로그에서는 모든 유저가 세션에 있는 것을 확인했으며, 모습을 못 보는 클라이언트의 캐릭터가 움직이면 다른 클라이언트에서는 해당 유저의 위치 정보 동기화가 잘 이루어졌기 때문에 클라이언트 코드 문제인 것을 알 수 있었다.
// 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:> 프로젝트 repo
728x90'TIL-sparta' 카테고리의 다른 글
[Docker] Fluent Bit 세팅해보기 (짧) (0) 2024.08.11 [Docker] Docker Desktop 사용해보기 (0) 2024.08.11 스파르타) The Last Rollback (D-19, Node.js 게임 서버 최종 프로젝트) (0) 2024.08.09 스파르타) The Last Rollback (D-20, Node.js 게임 서버 최종 프로젝트) (0) 2024.08.07 스파르타) The Last Rollback (D-21, Node.js 게임 서버 최종 프로젝트) - latency, timer (0) 2024.08.07