본문으로 건너뛰기

게임 (Game)

실시간 멀티플레이어 게임 API입니다. 두 가지 사용 방식을 제공합니다:

  • cb.game (GameAPI) — REST API: 룸 목록 조회, 매치메이킹 큐, 로비 관리
  • GameRoom 인스턴스 — WebSocket: 실제 게임 진행 중 상태 동기화 / 액션 / 채팅

GameRoom 생성

cb.game.createClient(config) 로 GameRoom 인스턴스를 생성합니다. appIdpublicKey 는 SDK 초기화 시 전달한 값을 사용하므로 별도로 넘기지 않아도 됩니다.

typescript
const room = cb.game.createClient({
  clientId: 'player-1',
  autoReconnect: true,
  maxReconnectAttempts: 5
})

// 이벤트 핸들러 등록 — 메서드 이름이 'on' + 이벤트명 형태
room.on('onConnect', () => console.log('게임 서버 연결됨'))
room.on('onDisconnect', (event) => console.log('연결 해제'))
room.on('onStateUpdate', (state) => console.log('상태 업데이트', state))
room.on('onPlayerJoined', (player) => console.log('입장', player))
room.on('onPlayerLeft', (player) => console.log('퇴장', player))
room.on('onChat', (msg) => console.log('채팅', msg))
room.on('onError', (err) => console.error(err))

룸 생성 / 참가 / 나가기

typescript
// 1) 먼저 WebSocket 연결을 열어야 합니다 — createClient() 는 객체만 만들고
//    실제 연결은 connect() 가 수행합니다. 이 호출이 빠지면
//    createRoom/joinRoom 이 "WebSocket is not connected" 를 던집니다.
await room.connect()

// 2) 새 룸 생성
const initialState = await room.createRoom({
  // roomId 생략 시 서버가 자동 발급
  maxPlayers: 4,
  tickRate: 64,
  metadata: { game_mode: 'deathmatch' }
})
console.log('룸 ID:', room.roomId)

// 또는 connect() 호출 시 roomId 를 같이 넘기면 연결과 동시에 입장됩니다
// await room.connect('room_xxx')

// 기존 룸 참가 (metadata 는 string→string 만 허용)
const joinedState = await room.joinRoom('room_xxx', { nickname: '홍길동' })

// 나가기 / 연결 해제
await room.leaveRoom()
room.disconnect()

게임 상태 / 액션 / 채팅

typescript
// 액션 전송 (서버가 액션을 처리해 모든 플레이어에게 브로드캐스트)
room.sendAction({
  type: 'attack',
  data: { target_id: 'player-2', damage: 50 }
})

// 상태 다시 조회 (보통 onStateUpdate 로 자동 수신됨)
const state = await room.requestState()

// 채팅
room.sendChat('안녕하세요!')

// 핑 (latency 측정)
const latencyMs = await room.ping()

룸 목록 조회 (cb.game REST)

typescript
const rooms = await cb.game.listRooms()
// 또는 특정 앱
const rooms2 = await cb.game.listRooms('app_xxx')

// 단일 룸 정보
const info = await cb.game.getRoom('room_xxx')

매치큐 (Matchqueue Primitive)

📌 2026-04-28 game v3.0 부터 lobby/party/matchmaking 모듈이 통째로 제거되고, matchqueue + leaderboard + scripts 3가지 primitive + 사용자 Lua 조합으로 재설계됐습니다. 매칭 로직 자체(ELO/지역/티어 등)는 사용자가 Lua 스크립트로 작성하고, primitive는 ticket 저장/조회/제거만 제공합니다.

typescript
// 매치 큐에 ticket 등록 — appId/queueKey/ticketId 는 [a-zA-Z0-9_-] 만 허용
const ticket = await cb.game.enqueueMatch(
  appId,
  'ranked',                                  // queueKey (예: 모드별/시즌별 분리)
  'tk_' + crypto.randomUUID(),               // ticketId
  { rating: 1500, region: 'asia', team_size: 5 },  // attributes (Lua 매칭 후보 선별용)
  60                                         // ttlSec (0 이면 만료 없음)
)

// 큐 ticket 목록 조회 — 보통 서버 Lua 가 호출. 클라이언트 직접 호출도 가능
const { tickets } = await cb.game.listMatchqueue(appId, 'ranked')

// 매칭 취소
await cb.game.cancelMatch(appId, 'ranked', ticket.ticket_id)

매칭 성사 후 룸 배정/입장은 사용자 Lua가 cb.game.matchqueue.list → 후보 선별 → 룸 생성/배정 → 알림 발송을 직접 조립합니다. 자세한 패턴은 게임 서버 → Lua 스크립팅 참고.

리더보드 (Leaderboard Primitive)

typescript
// 점수 제출 — mode "set" (기본, overwrite) 또는 "incr" (증감)
await cb.game.submitScore(appId, 'global_elo', 'user_123', 32, 'incr')

// 상위 N명 (기본 10)
const { entries } = await cb.game.getTopScores(appId, 'global_elo', 50)

// 특정 멤버 순위 + 점수
const me = await cb.game.getMemberRank(appId, 'global_elo', 'user_123')

// 멤버 주변 (위 5명 + 본인 + 아래 5명)
const around = await cb.game.getRankAround(appId, 'global_elo', 'user_123', 5, 5)

// 시즌 종료 등 전체 리셋
await cb.game.resetLeaderboard(appId, 'global_elo')

// 계정 삭제 시 특정 멤버만 제거
await cb.game.removeFromLeaderboard(appId, 'global_elo', 'user_123')

💡 시즌 구분은 leaderboardKey suffix로 분리하세요 (예: ranks:2026q2). ELO/티어 공식은 사용자 Lua가 submitScore 호출로 결과를 기록합니다.

Lua 스크립트 관리

createRoomscriptName 으로 활성 버전이 자동 바인딩되고, NATS publish로 모든 게임 서버 인스턴스의 VM pool이 핫 리로드됩니다.

typescript
// 새 버전 업로드 (latest+1, 동일 hash면 신규 버전 미생성 = idempotent)
const v = await cb.game.uploadScript(appId, 'ranked_br', luaSourceCode)

// 스크립트 목록 / 상세 / 버전 목록
const { scripts } = await cb.game.listScripts(appId)
const detail = await cb.game.getScript(appId, 'ranked_br')
const { versions } = await cb.game.listScriptVersions(appId, 'ranked_br')

// 특정 버전 활성화 (생략 시 latest)
await cb.game.activateScript(appId, 'ranked_br', 3)

// 직전 활성 버전으로 롤백
await cb.game.rollbackScript(appId, 'ranked_br')

// 비활성화 (delete 아님, 기록 보존)
await cb.game.deactivateScript(appId, 'ranked_br')

// 완전 삭제
await cb.game.deleteScript(appId, 'ranked_br')

createRoom 호출 시 scriptName 이 미존재이면 즉시 SCRIPT_NOT_FOUND 가 반환됩니다(script_version 도 응답에 echo). 안티치트 모범 사례는 게임 서버 → 안티치트 참고.