상태 동기화
게임 서버는 모든 클라이언트 간에 게임 상태를 실시간으로 동기화합니다. 효율적인 델타 압축을 통해 변경된 부분만 전송합니다.
상태 구조
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)
})