본문으로 건너뛰기

상태 동기화

게임 서버는 모든 클라이언트 간에 게임 상태를 실시간으로 동기화합니다. 효율적인 델타 압축을 통해 변경된 부분만 전송합니다.

상태 구조

typescript
interface GameState {
  roomId: string                  // 룸 ID
  state: Record<string, unknown>  // 게임 상태 데이터
  version: number                 // 상태 버전 (증가)
  serverTime: number              // 서버 시간
  tickRate: number                // 틱 레이트
  players: GamePlayer[]           // 현재 플레이어 목록
}

상태 업데이트 수신

typescript
const game = new GameRoom({
  appId: 'YOUR_APP_ID',
  clientId: 'player_123',
  publicKey: 'cb_pk_...'
})

// 전체 상태 업데이트
game.on('onStateUpdate', (state) => {
  console.log('현재 상태:', state.state)
  console.log('버전:', state.version)
  console.log('서버 시간:', state.serverTime)

  // 게임 렌더링 업데이트
  updateGameWorld(state.state)
})

// 델타 업데이트 (변경 사항만)
game.on('onDelta', (delta) => {
  console.log(`버전 ${delta.fromVersion} → ${delta.toVersion}`)
  console.log('변경 사항:', delta.changes)

  // 변경 사항 적용
  for (const change of delta.changes) {
    applyChange(change)
  }
})

델타 구조

typescript
interface GameDelta {
  fromVersion: number      // 이전 버전
  toVersion: number        // 새 버전
  changes: StateChange[]   // 변경 목록
  tick: number            // 서버 틱
}

interface StateChange {
  path: string            // 변경 경로 (예: "players.p1.position.x")
  operation: 'set' | 'delete'
  value?: unknown         // 새 값 (set일 때)
  oldValue?: unknown      // 이전 값 (선택)
}

액션 전송

typescript
// 게임 액션 전송
game.sendAction({
  type: 'move',           // 액션 타입
  data: {                 // 액션 데이터
    x: 100,
    y: 200,
    direction: 'right'
  }
})

// 신뢰성 있는 액션 (중요한 이벤트)
game.sendAction({
  type: 'use_skill',
  data: { skillId: 'fireball', targetId: 'enemy_1' }
}, true)  // reliable = true

클라이언트 예측

네트워크 지연을 숨기기 위해 클라이언트에서 먼저 상태를 예측합니다:

typescript
class GameClient {
  private predictedState: GameState
  private pendingActions: GameAction[] = []

  // 액션 전송 시 로컬 예측
  sendAction(action: GameAction) {
    // 1. 로컬 예측 적용
    this.applyPrediction(action)
    this.pendingActions.push(action)

    // 2. 서버로 전송
    this.game.sendAction(action)
  }

  // 서버 상태 수신 시 재조정
  onServerState(serverState: GameState) {
    // 1. 서버 상태로 롤백
    this.predictedState = { ...serverState }

    // 2. 확인되지 않은 액션들 재적용
    const confirmedVersion = serverState.version
    this.pendingActions = this.pendingActions.filter(
      a => a.sequence! > confirmedVersion
    )

    for (const action of this.pendingActions) {
      this.applyPrediction(action)
    }
  }

  private applyPrediction(action: GameAction) {
    // 로컬 상태 업데이트 (예: 플레이어 이동)
    if (action.type === 'move') {
      const pos = this.predictedState.state.myPosition as { x: number, y: number }
      pos.x += action.data.dx
      pos.y += action.data.dy
    }
  }
}

상태 보간

부드러운 애니메이션을 위해 상태를 보간합니다:

typescript
class StateInterpolator {
  private stateBuffer: { state: GameState; time: number }[] = []
  private renderDelay = 100  // 100ms 지연

  // 새 상태 수신
  addState(state: GameState) {
    this.stateBuffer.push({
      state,
      time: Date.now()
    })

    // 오래된 상태 제거
    const cutoff = Date.now() - 1000
    this.stateBuffer = this.stateBuffer.filter(s => s.time > cutoff)
  }

  // 현재 보간된 상태
  getInterpolatedState(): GameState | null {
    const renderTime = Date.now() - this.renderDelay

    // 렌더 시간 전후의 상태 찾기
    let before: typeof this.stateBuffer[0] | null = null
    let after: typeof this.stateBuffer[0] | null = null

    for (const entry of this.stateBuffer) {
      if (entry.time <= renderTime) {
        before = entry
      } else {
        after = entry
        break
      }
    }

    if (!before || !after) return before?.state ?? null

    // 선형 보간
    const t = (renderTime - before.time) / (after.time - before.time)
    return this.interpolate(before.state, after.state, t)
  }

  private interpolate(a: GameState, b: GameState, t: number): GameState {
    // 위치 등 숫자 값 보간
    const result = { ...b }
    // ... 보간 로직
    return result
  }
}

스냅샷 압축

대역폭 절약을 위한 상태 압축:

typescript
// 전체 상태 대신 델타만 전송
game.on('onDelta', (delta) => {
  // 변경된 부분만 수신
  for (const change of delta.changes) {
    applyDelta(change)
  }
})

// 주기적으로 전체 상태 동기화 (리싱크)
game.on('onStateUpdate', (state) => {
  // 전체 상태로 교정
  fullStateSync(state)
})