빠르게 로딩되는 웹 게임 출시하기 (실전 체크리스트)
플레이어가 몇 초 안에 플레이를 시작할 수 없으면 떠나버립니다. 이 튜토리얼은 거의 모든 웹 게임 스택(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-originCross-Origin-Embedder-Policy: require-corp
COEP 헤더에는 두 가지 선택지가 있습니다. require-corp는 엄격한 옵션이지만, 모든 cross-origin 에셋(CDN 텍스처, 서드파티 스크립트)이 Cross-Origin-Resource-Policy 헤더를 보내야 하며 그렇지 않으면 차단됩니다. Cross-Origin-Embedder-Policy: credentialless도 SharedArrayBuffer와 cross-origin isolation을 활성화하지만, cross-origin 리소스가 CORP로 명시적 동의하도록 요구하는 대신 자격 증명 없이 로드합니다. 직접 관리하지 않는 CDN에서 에셋을 가져온다면 보통 credentialless가 덜 골치 아픈 길입니다.
헤더와 서드파티 임베드가 문제를 일으킬 수 있으니, 실제 호스트에서 일찍 테스트하세요.
10) SDK 없이도 플레이할 수 있게 만드세요
Cinevva의 창작자 워크플로를 목표로 한다면 다음을 지향하세요:
- 안정적으로 부팅되는 하나의 URL
- 데모를 시작하는 데 필요한 로그인 없음
- 예측 가능한 입력 컨트롤
그러면 추가 SDK를 통합하지 않고도 배포할 준비가 됩니다.
관련 글:
- 게임 창작자를 위한 안내
- AI 생성 콘텐츠 정책
- 스트리밍 에셋 로딩 — 대형 게임을 위한 점진적 로딩 패턴
- 게임 캐싱을 위한 Service Worker — 재방문 시 즉시 로딩되도록 에셋 캐싱
- 웹 게임 엔진 비교 — 엔진별 빌드 크기와 로딩 시간
- itch.io에 게임 출시하는 방법 — itch.io 임베드를 위한 브라우저 게임 최적화