실시간 소셜 게임

Unity 3D + Firebase Realtime Database 기반. 멀티플레이어 실시간 동기화, 커스텀 좌표 압축, Dead Reckoning 구현.

2024.12.27 ~ 2025.01.28 (33일)|팀 프로젝트 (팀장 / 네트워크 담당)
UnityC#FirebaseDead ReckoningCustom Serialization
1 / 3

개요

실시간 소셜 게임

Unity 3D + Firebase Realtime Database 기반 멀티플레이어 소셜 게임. 커스터마이징, 감정표현, 미니게임, 실시간 채팅이 가능한 가상 공간.

  • 개발 형태: 팀 프로젝트 (기획 4 / 프로그래머 4, 팀장)
  • 기간: 2024.12.27 ~ 2025.01.28 (33일)
  • 담당: Firebase Realtime Database 기반 멀티플레이어 실시간 동기화 시스템 전체

동기

기획 4명, 프로그래머 4명이 협업하는 PBL 프로젝트였다. 플레이어가 가상 공간에서 다른 유저와 실시간으로 소통하고 경쟁하는 소셜 게임을 만들어야 했고, 핵심 과제는 여러 클라이언트 간의 데이터 일관성과 실시간성을 동시에 보장하는 것이었다.

Firebase Realtime Database를 백엔드로 선택한 상황에서, 커스터마이징 정보, 감정표현, 아이템, 위치 등 성격이 전혀 다른 데이터를 하나의 동기화 시스템으로 처리해야 했다. 특히 위치 데이터는 빈번한 JSON 직렬화로 인한 트래픽 문제가 예상되었고, 이를 해결할 압축/보간 전략이 필요했다.

접근

동기화 대상을 크게 두 축으로 나눴다.

첫째, 커스터마이징/감정표현/아이템 같은 이벤트성 데이터. 변경 빈도가 낮고 정확한 반영이 중요하다. Firebase의 이벤트 리스너(ValueChanged)를 타입별 하위 노드에 개별 등록하여, 변경된 데이터만 최소 단위로 감지하고 즉시 클라이언트에 반영하는 구조를 설계했다.

둘째, 위치/방향 같은 연속성 데이터. 변경 빈도가 높고 트래픽 효율이 중요하다. Vector3 좌표를 커스텀 알고리즘으로 5글자 문자열에 압축하고, Dead Reckoning과 SmoothDamp 보간을 결합해 동기화 빈도를 줄이면서도 자연스러운 움직임을 유지했다.

게임 플레이
게임 플레이

핵심 구현

영역내용
데이터 동기화Firebase 계층적 노드 설계, 타입별 이벤트 리스너, 비동기 구독/해제 관리
위치 동기화Vector3 커스텀 압축(32byte -> 5byte), Dead Reckoning, SmoothDamp 보간

기술 스택

기술적용 영역
C# / Unity 2022.3게임 로직, 네트워크 매니저, 동기화 시스템
Firebase Realtime Database멀티플레이어 데이터 저장/동기화 백엔드
Dead Reckoning / SmoothDamp클라이언트 위치 예측 및 보간
커스텀 직렬화Vector3 좌표 압축/복원, 방향 양자화

문서 구성

문서내용
01-멀티플레이어-데이터-동기화Firebase 계층 구조 설계, 이벤트 리스너 기반 실시간 반영, 채널 이동 시 데이터 정합성
02-멀티플레이어-위치-동기화Vector3 커스텀 압축 알고리즘, Dead Reckoning 보간, 정밀도 트러블슈팅
2 / 3

멀티플레이어 데이터 실시간 동기화

Firebase Realtime Database 기반 멀티플레이어 데이터 동기화

커스터마이징, 감정표현, 아이템 등 이벤트성 데이터를 계층적 노드 구조와 타입별 이벤트 리스너로 실시간 동기화하는 시스템.


문제

소셜 게임에서 동기화해야 할 데이터 종류가 다양했다. 커스터마이징 정보, 감정표현(이모지), 아이템, 애니메이션 상태 등 성격이 서로 다른 데이터가 하나의 플레이어 노드에 혼재해야 했다. 단순히 플레이어 전체 데이터를 통째로 읽고 쓰면, 이모지 하나 바꿀 때마다 커스터마이징/위치 등 무관한 데이터까지 전부 전송되어 트래픽이 불필요하게 증가한다.

또한 채널 이동이나 친구 초대/추가/삭제 시 데이터가 중복으로 남거나 실시간으로 반영되지 않는 문제가 예상되었다.

접근: 계층적 노드 설계

Firebase Realtime Database의 데이터를 Channels > Channel > Players > Player 형태로 계층화했다. 각 플레이어 노드 하위에 Position, Animation, Emoji, CharacterCustomization 등 타입별 하위 노드를 분리 배치하여, 특정 데이터만 변경되었을 때 해당 노드의 리스너만 트리거되도록 설계했다.

Firebase 데이터베이스 노드 구조
Firebase 데이터베이스 노드 구조

이 구조에서 이모지가 변경되면 Emoji 노드의 리스너만 동작하고, 위치/커스터마이징 데이터는 영향을 받지 않는다. 데이터 타입 간 변경 감지를 완전히 격리한 것이다.


이벤트 리스너 구독 관리

구현

AddHandlers 메서드에서 플레이어별로 Position, Animation, Emoji, CharacterCustomization 각 노드에 ValueChanged 이벤트 리스너를 개별 등록했다. 각 리스너는 전용 콜백(OnPositionChanged, OnEmojiChanged 등)에 연결되어, 변경된 데이터 타입만 정확히 처리한다.

이벤트 리스너 등록 코드
이벤트 리스너 등록 코드

SubscribePlayer 메서드는 플레이어 구독 전체 흐름을 관리한다. 이미 구독 중인 플레이어의 중복 등록을 방지하고, 구독 해제 시 기존 리스너를 정리한 뒤 새로운 리스너를 등록한다. 구독 쿼리와 애니메이션 쿼리를 Dictionary로 관리하여 해제 시 정확한 참조를 보장했다.

플레이어 구독 메서드
플레이어 구독 메서드

트러블슈팅: 채널 이동 시 데이터 중복

채널 이동이나 친구 초대 시 이전 채널의 데이터가 삭제되지 않고 남거나, 새 채널의 데이터가 즉시 반영되지 않는 문제가 발생했다. 원인은 데이터 삭제와 갱신 로직이 명확히 분리되지 않은 것이었다.

해결 방법은 채널 전환 시퀀스를 명확하게 단계화한 것이다. 기존 채널의 리스너를 먼저 해제하고, 데이터를 삭제한 뒤, 새 채널에 리스너를 등록하는 순서를 강제했다. 리스너 해제 없이 데이터만 삭제하면 삭제 이벤트가 다른 클라이언트에 전파되면서 의도치 않은 상태 변경이 발생하기 때문에, 해제 -> 삭제 -> 재등록 순서가 중요했다.

3 / 3

멀티플레이어 위치 실시간 동기화

커스텀 좌표 압축 + Dead Reckoning 기반 위치 동기화

Vector3 좌표를 5글자 문자열로 압축하고, Dead Reckoning과 SmoothDamp 보간을 결합하여 네트워크 전송량을 최소화하면서 자연스러운 움직임을 유지하는 시스템.


문제

Firebase Realtime Database는 JSON 기반이다. 위치 데이터를 빈번하게 직렬화해서 전송하면 트래픽이 급격히 늘어난다. Vector3를 그대로 JSON으로 보내면 {"x":123.456,"y":0,"z":789.012} 형태로 약 32byte가 되고, 프레임 단위로 전송하면 플레이어 수에 비례해서 트래픽이 폭증한다.

동시에, 위치 동기화 빈도를 단순히 줄이면 다른 클라이언트에서 캐릭터가 순간이동하는 것처럼 보인다. 트래픽 절감과 움직임 자연스러움을 동시에 해결해야 했다.

접근: 커스텀 좌표 압축

Vector3의 x, z 좌표를 각각 2글자 ASCII 문자로, 방향과 이동 상태를 1글자로 압축하여 총 5글자 문자열로 변환했다. 32byte에서 5byte로, 전송량을 84% 절감한 구조다.

압축 알고리즘

CompressVector3 메서드는 위치(x, z), 방향, 이동 여부를 하나의 5글자 문자열로 합친다.

Vector3 압축 코드
Vector3 압축 코드

좌표 압축의 핵심은 CompressCoordinate다. 사용 가능한 ASCII 범위(33126, 94개 문자)를 활용해 float 좌표를 정규화한 뒤, 상위/하위 문자 2개로 분할한다. 94 x 94 = 8,836단계의 해상도로, 위치 범위 -500+500 기준 약 0.11 단위의 정밀도를 확보했다.

좌표 압축 코드
좌표 압축 코드

방향 벡터는 8방향으로 양자화한다. Atan2로 각도를 구한 뒤 0~7 인덱스로 변환하고, 이동 여부와 함께 1바이트에 패킹했다.

방향 양자화 코드
방향 양자화 코드

복원 알고리즘

수신 측에서는 5글자 문자열을 역순으로 분해한다. 앞 4글자에서 x, z 좌표를 복원하고, 마지막 1글자에서 방향 인덱스와 이동 상태를 추출한다.

Vector3 복원 코드
Vector3 복원 코드

좌표 복원 코드
좌표 복원 코드


Dead Reckoning + 보간

전송량을 줄이는 것만으로는 부족하다. 동기화 주기 사이에 상대 캐릭터가 멈춰 있다가 순간이동하는 현상을 방지해야 한다.

Dead Reckoning 기법을 적용하여, 마지막으로 수신한 위치와 방향을 기반으로 클라이언트가 상대 캐릭터의 다음 위치를 자체적으로 예측한다. 새로운 위치 데이터가 도착하면 예측 위치와 실제 위치 사이를 SmoothDamp로 부드럽게 보간하여, 갑작스러운 위치 점프 없이 자연스러운 움직임을 만들었다.

임계값 기반 이벤트 트리거도 함께 적용했다. 위치 변화량이 일정 임계값을 넘을 때만 데이터를 전송하여, 정지 상태에서의 불필요한 전송을 차단했다.

클라이언트 간 실시간 위치 동기화
클라이언트 간 실시간 위치 동기화


트러블슈팅: 좌표 압축 정밀도

초기 구현에서 압축/복원 과정의 정밀도가 부족해 위치가 부정확하게 표시되는 문제가 있었다. float -> int 변환 시 반올림 오차가 누적되고, 위치 범위 밖의 좌표가 들어올 때 오버플로가 발생했다.

Mathf.RoundToInt로 변환 정밀도를 확보하고, 정규화 전 범위 클램핑을 추가하여 범위 밖 좌표를 안전하게 처리했다. ASCII 범위도 안전한 출력 가능 문자(33~126)로 한정하여 직렬화 과정에서 특수 문자로 인한 파싱 오류를 방지했다.

실시간 소셜 게임 · 2024.12.27 ~ 2025.01.28 (33일)