Skip to content

빠르게 로딩되는 웹 게임 출시하기 (실전 체크리스트)

플레이어가 몇 초 안에 플레이를 시작할 수 없으면 떠나버립니다. 이 튜토리얼은 거의 모든 웹 게임 스택(Canvas/WebGL/WebGPU, Pixi/Three/PlayCanvas, 자체 엔진 등)에 적용할 수 있는 실용적인 체크리스트입니다.

1) 예산을 먼저 정하세요 (안 그러면 최적화할 수 없습니다)

목표 기기에 맞는 예산을 정하세요. "즉시 플레이" 경험을 위한 견실한 기본값은 이렇습니다.

  • 배포되는 JS+CSS: gzip 기준 약 300~500 KB 미만 (작을수록 좋습니다)
  • 첫 입력 전의 핵심 에셋: 압축 후 약 2~5 MB 미만
  • 첫 입력까지의 시간: 중급 모바일 기기에서 약 3~5초 미만

이 값들을 적어두고 API 계약처럼 다루세요.

2) 올바른 것을 측정하세요

Chrome DevTools에서:

  • Performance로 세션을 캡처하고 long task를 확인하세요.
  • Network로 다음을 확인하세요:
    • 전송 크기 vs 디코딩된 크기
    • 캐싱 헤더
    • 에셋이 점진적으로 스트리밍되는지, 아니면 하나의 거대한 blob으로 도착하는지

게임 관점에서는 다음을 추적하세요:

  • "플레이어가 움직일 수 있는가" 시간
  • "첫 의미 있는 프레임" 시간
  • "첫 입력 지연" (입력 → 프레임)

여러분의 "첫 입력 지연"에 대한 직감은 이제 표준 지표와 연결됩니다. **Interaction to Next Paint (INP)**가 2024년 3월에 First Input Delay를 대체하며 Core Web Vital이 되었고, 첫 상호작용뿐 아니라 모든 상호작용에 걸친 입력부터 다음 페인트까지의 전체 시간을 측정합니다. 75 백분위에서 INP 200ms 미만을 목표로 하세요. 여러분이 이미 추적하는 반응성 수치에 대한 유용한 외부 벤치마크입니다.

3) 압축할 수 있는 건 다 압축하세요

호스팅이 다음을 제공하는지 확인하세요:

  • 텍스트 에셋(JS/CSS/HTML/WASM)에는 가능하면 Brotli(br)
  • 대체 수단으로 gzip

호스트와 CDN이 지원한다면 이제 Zstandard(zstd)가 Content-Encoding의 세 번째 선택지입니다. Chrome과 Edge 123 이상, Firefox 126 이상, Safari 26.3 이상에서 제공되며(사용자의 약 80%), 더 낮은 서버 CPU 비용으로 Brotli급 압축률에 도달할 수 있어 큰 WASM과 JSON 페이로드에 도움이 됩니다. 브라우저는 HTTPS에서만 Accept-Encoding: zstd를 알리므로, 나머지 모두를 위해 Brotli와 gzip을 대체 수단으로 계속 구성해 두세요.

WebAssembly 빌드에서는 압축이 "즉시 로딩"과 "절대 로딩 안 됨"을 가르는 차이가 되는 경우가 많습니다.

4) 처음부터 세상 전체를 다운로드하지 마세요

게임을 이렇게 나누세요:

  • Boot: 최소한의 로더 + 입력 + 첫 장면
  • Core: 핵심 게임플레이 시스템
  • Optional: 추가 레벨, 코스메틱, 고해상도 텍스처, 보너스 오디오 등

"Optional"은 다음 이후에만 로드하세요:

  • 플레이어가 이미 플레이할 수 있게 되었거나
  • 그 콘텐츠를 원한다는 것을 확인했을 때 (예: 메뉴/레벨에 도달)

5) 올바른 에셋 포맷을 선택하세요

대부분의 웹 게임에서:

  • 이미지: UI/배경에는 WebP(인코딩이 느려도 괜찮다면 AVIF)를 우선 사용하세요.
  • 오디오: 일반 용도에는 Ogg Vorbis를 우선 사용하고, Apple 생태계에는 AAC를 테스트하세요.
  • 비디오: 호환성을 위해 MP4/H.264를 우선 사용하고, 지원되며 더 작을 때는 WebM을 사용하세요.

디코딩 비용에 주의하세요. "다운로드가 더 작다"가 여전히 "디코딩이 더 느리다"일 수 있습니다.

6) 첫 장면은 작고 결정적으로 유지하세요

첫 인터랙티브 장면에서 다음을 피하세요:

  • shader 컴파일 폭주
  • 거대한 텍스처 업로드
  • 메인 스레드를 막는 절차적 생성
  • 메가바이트 단위 blob의 동기 JSON 파싱

특히 텍스처는 PNG나 JPEG 대신 Basis Universal 슈퍼컴프레션을 사용한 KTX2로 배포하세요. 업로드 후에도 GPU 압축 상태를 유지하며(로드 시 데스크톱에서 BC7, 모바일에서 ASTC로 트랜스코딩), VRAM을 4~8배 줄이고, 전체 크기 PNG를 디코딩해 원본 픽셀을 GPU로 밀어 넣는 것보다 업로드가 훨씬 저렴해집니다.

무거운 작업을 꼭 해야 한다면 이렇게 하세요:

  • 여러 프레임에 걸쳐 점진적으로, 또는
  • (가능하면) Worker 안에서

7) 적극적으로 캐싱하세요 (단, 올바르게)

콘텐츠 주소 지정 에셋에는 장기 캐싱을 사용하세요:

  • Cache-Control: public, max-age=31536000, immutable

HTML 진입점은 업데이트를 안전하게 배포할 수 있도록 캐싱을 짧게 유지하세요.

8) "실패"를 빠르고 읽기 쉽게 만드세요

문제가 생기면 다음을 보여주세요:

  • 명확한 오류 메시지
  • 재시도 버튼
  • 문제를 신고할 수 있는 링크

조용한 실패는 신뢰를 무너뜨립니다.

9) Wasm 스레드를 쓴다면 COOP/COEP를 이해하세요

빌드가 SharedArrayBuffer에 의존한다면(일부 Wasm 스레딩 구성에서 흔합니다) cross-origin isolation 헤더가 필요할 가능성이 큽니다:

  • Cross-Origin-Opener-Policy: same-origin
  • Cross-Origin-Embedder-Policy: require-corp

COEP 헤더에는 두 가지 선택지가 있습니다. require-corp는 엄격한 옵션이지만, 모든 cross-origin 에셋(CDN 텍스처, 서드파티 스크립트)이 Cross-Origin-Resource-Policy 헤더를 보내야 하며 그렇지 않으면 차단됩니다. Cross-Origin-Embedder-Policy: credentiallessSharedArrayBuffer와 cross-origin isolation을 활성화하지만, cross-origin 리소스가 CORP로 명시적 동의하도록 요구하는 대신 자격 증명 없이 로드합니다. 직접 관리하지 않는 CDN에서 에셋을 가져온다면 보통 credentialless가 덜 골치 아픈 길입니다.

헤더와 서드파티 임베드가 문제를 일으킬 수 있으니, 실제 호스트에서 일찍 테스트하세요.

10) SDK 없이도 플레이할 수 있게 만드세요

Cinevva의 창작자 워크플로를 목표로 한다면 다음을 지향하세요:

  • 안정적으로 부팅되는 하나의 URL
  • 데모를 시작하는 데 필요한 로그인 없음
  • 예측 가능한 입력 컨트롤

그러면 추가 SDK를 통합하지 않고도 배포할 준비가 됩니다.

관련 글: