MVP - Room 단위 게임 서버

반짇고리·2026년 1월 26일

게임 서버 제작기

목록 보기
2/15


1편에서 정한 건 하나였다.

결과는 클라가 통보하고, 서버가 따라가는 구조로 가지 않겠다.

그러면 바로 다음 질문이 남는다.

그래서 뭘 만들 건데?

정말 곤란한 질문이다.

서버에 축을 맞춘다는 건 정했지만, 기준만 잡아서는 아무것도 움직이지 않는다. 기준이 닿을 문제가 있어야 한다.

처음에는 단순한 채팅 서버나 에코 서버도 생각했다. 소켓을 열고, 패킷을 받고, broadcast를 하면 일단 네트워크 서버처럼 보일 수 있으니까.

이걸로 내가 게임 서버를 배웠다고 말할 수 있을까.


먼저

이 글은 가제: Loot of Legends 게임 서버 제작기의 두 번째 기록이다.

첫 번째 글에서 나는 “누가 결과를 확정하는가”를 먼저 잡았다.

그다음에는 더 손에 잡히는 문제가 필요했다.

단순 연결 서버가 아니라, Room, Player, Game State가 있는 문제를 고르자.

왜 하필 Room인가.
왜 단순한 채팅 서버에서 멈추지 않았고, 왜 처음부터 큰 world를 만들지도 않았는가.

너무 작으면 게임 서버답지 않고, 너무 크면 금방 무너진다. 내가 고르려던 지점은 그 사이에 있었다.

채팅 서버: 연결과 전달을 연습한다.
Room 서버: 작은 게임 상태 하나를 끝까지 붙잡는다.

나는 아래쪽 문제를 먼저 붙잡기로 했다.


너무 작거나 너무 크거나

당연하지만 처음부터 큰 게임을 만들 수는 없다.

MMORPG처럼 넓은 월드, 복잡한 길 찾기, 몬스터 AI, 스킬 시스템, 장비, 상점, 매치메이킹 등을 한 번에 넣으면 아마 코드도, 이 시리즈도 대단히 꼬일 것이다.

아직 서버의 기본 경계도 잡는 중인데 모든 걸 한 번에 잡으려는 건 누가 봐도 욕심이다.

하지만 반대로 너무 작은 문제도 위험하다.

예를 들어 에코 서버는 입력을 그대로 돌려준다.

클라 -> 서버: hello
서버 -> 클라: hello

채팅 서버는 조금 더 서버답다. 여러 클라 들어오고, 서버가 메시지를 나눠준다.

클라 A -> 서버: hi
서버 -> 클라 B/C: A said hi

물론 이것도 좋은 학습 주제다. 연결, broadcast, disconnect, message framing을 배울 수 있다.

그런데 내가 만들고 싶었던 건 실시간 멀티플레이 게임 서버였다.

그러면 메시지를 전달하는 것만으로는 부족했다. 방이 있어야 했다.

누가 어떤 방에 들어갈 수 있는지.
누가 준비 상태인지.
언제 게임을 시작할 수 있는지.
어떤 입력이 현재 상태에서 유효한지.
결과를 어떤 순서로 확정해야 하는지.

이런 질문이 있어야 Room이 단순 목록이 아니라 게임 상태의 경계가 된다.


Room

결국 나는 Room을 선택했다.

큰 world를 만들지는 않는다. 대신 Room이라는 작은 경계를 둔다. 그리고 그 안에 Player와 game state를 둔다.

범위는 작다.

하지만 Room 안에서는 충분히 게임 서버다운 질문이 생긴다.

  • 어떤 session이 어떤 room에 속하는가
  • room은 언제 생성되고 사라지는가
  • player는 어떤 순서로 들어오는가
  • 준비 상태는 언제 유효한가
  • room 밖 session의 command는 어떻게 거부되는가
  • battle이 시작되면 room state는 어떻게 바뀌는가
  • 결과 visibility는 누구에게 가야 하는가

이 정도면 일반적인 채팅 서버보다 훨씬 게임 서버다울 것이다.
그리고 왠지 모르게 용기가 생겼다. 그게 중요했다.


Room은 목록이 아니다

나는 Room을 가장 작은 게임 서버 단위로 잡았다. 여기서 Room은 단순히 사람을 묶는 컨테이너가 아니다.

처음엔 나도 조금 그렇게 생각했다.

room = 플레이어 리스트

처음엔 이 정도로 생각했다. 하지만 조금만 생각해도 이건 너무 얕다.

Room은 누가 들어왔는지 담아두는 목록이 아니라, 어떤 입력이 지금 의미 있는지 결정하는 작은 도메인 경계에 가깝다.

Player도 단순한 닉네임이나 접속자 표시가 아니다. 현재 room 안에서 추적해야 하는 런타임 상태의 출발점이다.

Game State는 그 둘 위에 쌓인다.

그냥 “방이 있어야 게임처럼 보이지 않을까?” 정도의 생각에서 쌓아올린 생각인데 지금 다시 보니 꽤 일관적인 것 같다.

실행 흐름, 네트워크 처리, 게임 상태를 분리하고, 게임 상태 쪽에 Room, RoomManager, Player를 두려 했다. 네트워크로 받은 데이터를 곧바로 처리하는 서버가 아니라, Room 안에서 의미를 한 번 더 따지는 구조를 만들겠다는 신호다.

room 생성, 참가, 나가기, room list snapshot, room detail state 같은 문제도 같은 방향으로 잡혔다. 특히 현재 player 수나 member list를 클라의 로컬 캐시가 아니라 server-origin snapshot에서 파생해야 한다는 기준이 중요했다.

room 안의 ready state도 마찬가지다. 한 세션의 ready는 현재 소속된 room에서만 의미가 있고, join/leave/disconnect가 생기면 ready와 battle start 상태가 다시 정리되어야 한다.

이렇게 정리하니 조금 선명해졌다.

내가 만들려던 건 “패킷을 주고받는 서버”가 아니라 “Room 안의 상태 변화를 설명할 수 있는 작은 게임 서버”였다.


흐름

이 선택을 단순화하면 이런 그림이 된다.

(근데 왜 velog에는 mermaid를 지원하지 않나요...?!)

핵심은 Room-owned state다.

클라가 “나 방 들어감”이라고 말한다고 room membership이 생기는 게 아니다. 현재 room 상태를 기준으로 join이 받아들여져야 한다.

클라가 “나 준비했음”이라고 말한다고 바로 다음 상태로 넘어가는 것도 아니다. 그 session이 현재 room에 속해 있는지, ready가 지금 의미 있는 입력인지, 다른 member 상태는 어떤지까지 봐야 한다.

그렇다면 Room은 작은 판정 구역이라고 할 수 있겠다.


기준

그래서 기준을 이렇게 잡았다.

실행 경계와 상태 경계:

  • Core는 server loop, session, dispatch 같은 실행 경계를 담당한다.
  • Net은 패킷, parser, serializer, transport 상태를 담당한다.
  • GameRoom, RoomManager, Player 같은 게임 상태를 담당한다.
  • Room 내부 상태는 Room 도메인 규칙을 거쳐 바뀌는 방향으로 분리된다.

room 가시성과 UI 권한:

  • room 생성, 참가, 나가기, 목록 snapshot은 server-origin 상태를 기준으로 한다.
  • room detail은 영향을 받는 room의 현재 detail full snapshot으로 전달된다.
  • 클라는 로컬 캐시나 응답 하나만으로 룸 내부 UI를 확정하면 안 된다.
  • member order, host 표시, 현재 인원은 server-origin room detail에서 파생되어야 한다.

ready와 battle 전환:

  • Ready는 현재 소속 room 안에서만 의미가 있다.
  • Join, Leave, disconnect는 ready/battle state를 흔든다.
  • battle start는 단순 버튼이 아니라 room 상태 전환이다.

여기까지가 이 글에서 확실히 말할 수 있는 범위다.


맺으며

이번 글의 결론은 단순하다.

나는 채팅 서버보다 조금 더 어렵고, 거대 MMORPG 게임 서버보다 훨씬 작은 문제를 골랐다.

Room 안에서 Player 상태가 변하고, 그 변화가 어떤 규칙으로 받아들여지는지 정하는 문제.

지금 생각해도 이 정도가 딱 첫 번째 게임 서버 문제였던 것 같다.
너무 쉽지도 않고, 그렇다고 너무 어렵지도 않을, 그러면서도 배울 게 많은 크기의 서버다.

profile
Sapere Aude!

0개의 댓글