Unity 기반 멀티플레이 RPG 게임을 1인 개발·운영하며 Node.js와 Socket.IO를 이용한 실시간 통신 서버를 설계·구축했다.
처음에는 채팅과 플레이어 위치 공유로 시작했지만, 게임이 확장되면서 레이드와 파티 퀘스트의 전투 상태, 여러 오브젝트의 위치, 네트워크 단절 후 상태 복구까지 처리해야 했다.
이 글에서는 Socket.IO를 단순 메시지 전달 수단이 아닌, 여러 사용자가 동일한 게임 상태를 공유하는 핵심 통신 계층으로 활용한 과정을 정리한다.
1. 시스템 구성
프로젝트는 REST API와 Socket.IO 서버의 역할을 구분했다.
- REST API
- 사용자·아이템·퀘스트·랭킹 조회 및 저장
- PostgreSQL에 보존해야 하는 영속 데이터 처리
- Socket.IO
- 플레이어 위치·HP·버프 상태 동기화
- 스킬 시전과 전투 피해 전달
- 레이드·파티 퀘스트 진행 상태 공유
- 접속·퇴장·재접속 상태 관리
주요 기술은 다음과 같다.
- 클라이언트: Unity, C#
- 백엔드: Node.js, TypeScript, Express, Socket.IO
- 데이터베이스: PostgreSQL, Sequelize
- 운영 환경: AWS EC2, Linux, systemd, Git
시스템 통신 구조

2. Room 기반 멀티플레이 구조
모든 접속자에게 동일한 데이터를 전송하면 불필요한 트래픽이 발생하고, 서로 다른 콘텐츠의 상태가 섞일 수 있다.
Socket.IO의 Room 기능을 이용해 사용자를 콘텐츠별로 분리했다.
- 마을: 플레이어 위치·방향·버프·채팅 공유
- 던전: 입장 인원과 참가 상태 관리
- 레이드 로비: 레이드별 대기 인원과 입장 상태 공유
- 레이드: 위치·HP·스킬·피해·보스 상태 동기화
- 파티 퀘스트: 파티원·스테이지·오브젝트·보스 상태 동기화
서버에는 1 Socket = 1 Room 정책을 적용했다.
사용자가 다른 콘텐츠로 이동하면 기존 Room에서 퇴장시키고 관련 상태를 제거한 후 새로운 Room에 입장시켰다. 퇴장 이벤트도 기존 Room에 전달하여 다른 클라이언트가 해당 플레이어 오브젝트를 제거하도록 구성했다.
또한 클라이언트가 전송한 userId와 roomId를 그대로 사용하지 않았다. 서버가 관리하는 Socket–사용자–Room 매핑을 기준으로 실제 사용자와 전송 범위를 다시 확인했다.
이를 통해 다른 사용자의 이벤트를 위조하거나 자신이 속하지 않은 Room에 데이터를 전달하는 문제를 방지했다.
3. 위치 데이터 전송 최적화
초기에는 플레이어가 움직이는 매 프레임마다 위치 데이터를 서버로 전송했다.
60FPS 환경이라면 사용자 한 명이 초당 약 60개의 위치 패킷을 보낼 수 있다. 접속자가 늘어나면 서버의 상태 갱신과 브로드캐스트 비용도 함께 증가하는 구조였다.
모바일 2D 게임에서는 모든 렌더링 프레임의 좌표를 전송할 필요가 없다고 판단해, 이동 중 위치 전송을 최대 15Hz로 제한했다.
private const float MOVE_POS_INTERVAL = 1f / 15f;
15Hz는 약 66.7ms마다 한 번, 1초에 최대 15회 전송한다는 의미다. 60FPS 기준 매 프레임 전송과 비교하면 위치 업링크 이벤트를 이론상 최대 75% 줄일 수 있다.
다만 전송 빈도를 제한하면 사용자가 조이스틱에서 손을 뗀 순간의 최종 위치가 누락될 수 있다. 이를 방지하기 위해 입력 종료 시에는 주기와 관계없이 최종 위치를 즉시 한 번 전송했다.
서버도 위치 데이터를 받을 때마다 바로 전체 사용자에게 전달하지 않는다.
수신한 상태를 메모리에 갱신하고 Room의 변경 여부를 기록한 뒤, 공통 실시간 동기화 루프에서 100ms마다 변경된 Room만 브로드캐스트한다.
클라이언트 이동
→ 최대 15Hz로 위치 전송
→ 서버 메모리 상태 갱신
→ Room 변경 여부 기록
→ 100ms마다 변경 상태 브로드캐스트
스킬 사용, HP 변화, 버프 전환, 텔레포트처럼 즉시 반영해야 하는 이벤트는 위치 전송 주기를 거치지 않고 바로 전달했다.
4. 이벤트 특성에 따른 통신 방식 분리
실시간 데이터라고 해서 모두 같은 방식으로 처리하지 않았다.
상태 스냅샷
중간 과정 전체보다 최신 상태가 중요한 데이터다.
- 플레이어 위치와 방향
- HP와 버프
- 보스·광부 등 오브젝트 위치
서버가 최신 상태를 보관하고 100ms 주기로 Room에 전달했다.
즉시 이벤트
발생 순서와 즉시성이 중요한 데이터다.
- 스킬 시전
- 피격 확정
- Room 입장·퇴장
- 스테이지 시작·종료
- 채팅
이벤트가 발생하면 별도의 버퍼 없이 현재 Room 참가자에게 즉시 전달했다.
배치 이벤트
짧은 시간 동안 반복적으로 발생하는 데이터다.
- 레이드 피해
- 파티 퀘스트의 바위·보스 피해
- 다수 오브젝트 위치 변경
고빈도 피해를 건별로 브로드캐스트하면 서버와 클라이언트가 짧은 시간에 많은 이벤트를 처리해야 한다.
이를 해결하기 위해 피해 데이터를 서버 버퍼에 저장한 뒤, 100ms 동기화 틱에서 배열 형태의 Batch로 묶어 전달했다.
다수의 피해 이벤트 수신
→ 서버 상태에 피해 반영
→ 전송 버퍼에 저장
→ 100ms 틱에서 하나의 Batch로 브로드캐스트
이 구조를 통해 전투 결과의 실시간성은 유지하면서 Socket emit과 클라이언트 수신 처리 횟수를 줄였다.

5. 모바일 네트워크 단절과 상태 복구
모바일 환경에서는 앱의 백그라운드 전환이나 일시적인 네트워크 순단으로 Socket 연결이 끊길 수 있다.
연결이 끊긴 사용자를 즉시 삭제하는 방식에서는 다음 문제가 발생했다.
- 다른 사용자 화면에서 캐릭터가 사라짐
- 재접속 후 기존 전투 참가자로 인정되지 않음
- 연결이 끊긴 동안의 스테이지 전환 이벤트 유실
- 서버와 클라이언트의 진행 상태 불일치
클라이언트에서는 포그라운드 복귀 시 현재 씬에 맞는 Room으로 다시 접속하도록 구현했다. 재연결은 2초 → 4초 → 8초 형태의 백오프를 적용하고, 연결 후 현재 위치와 HP를 다시 전송했다.
레이드 서버는 연결 종료 후 25초 동안 참가자 상태를 보존한다. 해당 시간 안에 동일 사용자가 다시 접속하면 새로운 Socket ID를 기존 참가자에게 연결하고, 보스 HP와 참가자 상태를 다시 전송한다.
파티 퀘스트에서는 사용자를 바로 삭제하지 않고 연결 상태만 변경했다. 재접속하면 현재 스테이지, 남은 시간, 오브젝트 상태, 참가자 HP를 해당 사용자에게 다시 전달했다.
정상적인 disconnect 처리에서 누락된 상태를 제거하기 위한 안전장치도 추가했다. 서버가 30초마다 Room 참가자의 Socket ID가 실제 연결 목록에 존재하는지 확인하고, 연결이 없는 잔존 세션을 정리한다.
6. 나의 역할과 주요 성과
기획부터 클라이언트, 서버, 데이터베이스, 배포·운영까지 전 과정을 단독으로 담당했다.
- Unity 멀티플레이 클라이언트 구현
- Socket.IO 이벤트 프로토콜과 Room 구조 설계
- Node.js·TypeScript 실시간 서버 및 REST API 개발
- 위치·전투·스킬·스테이지 동기화 구현
- Sequelize·PostgreSQL 데이터 모델 설계
- AWS EC2 Linux 배포 및 서비스 운영
- 실제 멀티 디바이스 환경의 동기화 오류 분석·개선
프로젝트를 통해 다음 구조를 구현했다.
- REST API와 Socket.IO의 책임을 분리한 백엔드 구성
- 콘텐츠별 Room 기반 사용자·상태 관리
- 위치 전송 15Hz 제한과 서버 100ms 동기화 틱
- 고빈도 전투 이벤트의 합산·배치 처리
- Socket–사용자–Room 매핑 기반 이벤트 검증
- 네트워크 단절 후 Room·참가자·스테이지 상태 복구
- 비정상 종료 후 잔존 세션을 정리하는 운영 안전장치
정리하며
이 프로젝트에서 Socket.IO는 단순 채팅이나 알림 기능이 아니라, 여러 사용자가 동일한 상태를 공유하기 위한 핵심 통신 계층이었다.
실시간 서버를 구현하면서 데이터를 빠르게 전달하는 것만큼 다음 기준이 중요하다는 것을 경험했다.
- 최신 상태만 전달해도 되는 데이터인지
- 발생 즉시 전달해야 하는 이벤트인지
- 짧은 시간 동안 모아서 전달할 수 있는 데이터인지
- 연결이 끊겼을 때 어떤 상태를 보존하고 복구해야 하는지
- 클라이언트가 전송한 값을 서버가 어디까지 신뢰할 것인지
이에 따라 위치 데이터는 주기적인 상태 동기화로, 스킬은 즉시 이벤트로, 고빈도 피해는 배치 이벤트로 구분해 처리했다. 또한 Room 기반 세션 관리와 재접속 후 상태 복구, 잔존 세션 정리까지 구현하면서 실시간 서비스의 전체 연결 생명주기를 다룰 수 있었다.
Node.js·REST API·Socket.IO·PostgreSQL·Linux 운영을 하나의 서비스 안에서 직접 설계하고 개선하며, 실시간성과 성능뿐 아니라 데이터 정합성 및 장애 상황까지 고려하는 백엔드 개발 경험을 쌓았다.
'Bumday > About Me' 카테고리의 다른 글
| [포트폴리오] 반도체 소재 제조 현장의 공정·품질 데이터 시스템 구축기 (0) | 2026.08.04 |
|---|---|
| (대학교 졸업과제) 생활관 스마트 출입 시스템 개발 (0) | 2021.12.20 |
| 프로젝트 이력 (0) | 2021.12.19 |
| 범데이(개발자 프로필) (0) | 2020.10.26 |