---
type: tutorial
title: 자체 호스팅 게임 빌드하기
summary: 게임 SDK로 샌드박스된 게임 번들을 빌드하고, 직접 호스팅하고, game-src로 위젯을 그것으로 향하게 하고, 서버가 리플레이하는 결정론적 트레이스를 내세요.
---

이 튜토리얼이 끝나면 당신은 진짜 Caputchin 게임을 갖게 됩니다: 위젯의 샌드박스된 iframe에서 돌고, 게임 SDK를 통해 호스트와 이야기하고, 서버가 다시 실행할 수 있는 **트레이스**를 보고하는 JavaScript 번들. [수동 모드](/docs/custom-game-development/manual-mode)와 달리, 자체 호스팅 게임은 그 라운드가 재생 가능하므로 [사이트 키를 게이트](/docs/site-keys/games)할 수 있습니다. 이 튜토리얼은 게임을 빌드하고 호스팅하는 것을 다룹니다; [재생과 게이팅](/docs/custom-game-development/replay-and-gating)이 실제로 게이트를 켜는 산출물을 다룹니다.

이것이 어디에 맞는지는 [당신 자신의 게임 돌리기](/docs/custom-game-development/overview)를 읽으세요. 정적 JavaScript 파일을 호스팅할 곳(어떤 CDN이나 정적 호스트)과 페이지의 위젯이 필요합니다.

## 자체 호스팅 게임의 모양

자체 호스팅 게임은 다음을 하는 하나의 자족적 JavaScript 번들입니다:

1. `@caputchin/game-sdk`의 `register` 함수를 임포트하고 단일 **게임 팩토리**를 등록합니다.
2. 위젯이 팩토리에 주는 컨테이너에, 그것과 함께 넘겨진 해소된 언어, 스킨, 구성을 써서 렌더링합니다.
3. 라운드별 시드 아래에서 결정론적으로 플레이되며, 플레이를 **트레이스**로 기록합니다.
4. 승리 시, 그 기록으로 `bridge.pass({ trace })`를 호출합니다.

위젯은 당신의 번들을 그 iframe에 로드하고 당신의 팩토리를 호출합니다; 팩토리가 호출되는 것이 *곧* 시작 신호입니다(기다릴 별도의 시작 이벤트가 없습니다).

## 1. 게임 팩토리 등록하기

SDK를 설치하고 당신의 팩토리로 `register`를 한 번 호출하세요. 당신은 매니페스트를 넘기지 **않습니다**; 서버가 프리셋을 해소해 팩토리에 건넵니다.

```js
import { register } from "@caputchin/game-sdk";

register((container, bridge, ctx) => {
  // container — a DOM element inside the sandboxed iframe; render into it.
  // bridge    — push-only channel to the host (pass / error / setSize).
  // ctx       — the per-round context (seed + resolved locale/skin/config).

  const cleanup = startGame(container, bridge, ctx);

  // Return an optional cleanup function; the widget calls it on unmount.
  return cleanup;
});
```

```bash
npm install @caputchin/game-sdk
```

## 2. 라운드별 컨텍스트 읽기

팩토리의 세 번째 인자 `ctx`는 당신의 게임이 이 방문자에게 필요한 모든 것을 지닙니다:

- `ctx.seed` — 라운드별 리플레이 시드. **모든 무작위성을 이것에서 도출하세요.** 게임이 검증된 세션 밖에서 돌 때 null.
- `ctx.locale` — 해소된 언어 객체: `ctx.locale._lang`에 당신의 번역된 문자열 키.
- `ctx.skin` — 해소된 스킨 객체: `ctx.skin._theme`(늘 `light` 또는 `dark`)에 당신의 색상과 자산 키.
- `ctx.config` — 해소된 게임플레이 구성(또는 null).

이것들은 Caputchin이 당신의 대시보드에서 정의한 [스키마와 프리셋](/docs/custom-game-development/dashboard-schema)에서 낸, 이 방문자를 위한 **해소된 프리셋**입니다. 당신은 그것들에서 당신 자신의 키를 읽습니다; 당신이 직접 무엇도 해소하지 않습니다.

당신의 레이아웃에 명시적 크기가 필요하면, 첫 페인트 뒤에 호스트에 한 번 알리세요:

```js
bridge.setSize(360, 480);
```

## 3. 플레이를 결정론적으로 만들기

이것이 게임을 게이트 가능하게 만드는 규칙입니다: **게임은 시드와 플레이어의 입력이 주어지면 결정론적이어야 합니다.** 결과에 영향을 주는 어떤 것에도 `Math.random()`을 호출하거나 벽시계를 읽지 마세요; 모든 무작위 선택을 `ctx.seed`에서 도출하세요. 서버는 같은 시드와 같은 기록된 트레이스 아래에서 당신의 로직을 다시 실행하며 같은 판정에 닿아야 합니다.

```js
// A tiny seeded PRNG; feed it ctx.seed so the server reproduces the run.
function makeRng(seed) {
  let s = hashToInt(seed);
  return () => (s = (s * 1103515245 + 12345) & 0x7fffffff) / 0x7fffffff;
}
```

플레이하면서 결과를 모는 입력(플레이어가 어떤 표적을 어떤 순서로 맞췄는지, 중요하면 타이밍과 함께)을 기록하세요. 그 기록을 문자열로 직렬화하세요: 그 문자열이 당신의 트레이스입니다.

## 4. 트레이스로 pass하기

방문자가 이기면, 호스트에 트레이스를 건네세요. `pass`는 단일 `trace` 문자열을 둔 객체를 받습니다:

```js
function onWin(traceString) {
  bridge.pass({ trace: traceString });
}
```

트레이스는 **플랫폼에 불투명합니다**: 그것은 당신의 게임만이 정의하는, 당신의 로직이 시드와 결합해 결과를 재현하게 하는 어떤 문자열이든입니다. 게임은 여기서 점수를 보고하지 않습니다; 채점은, 있다면, 당신 자신의 iframe 안 관심사입니다.

브리지 사실 두 가지 더:

- 첫 `pass`가 라운드를 사용합니다; 실패하거나 버려진 라운드는 그저 `pass`를 **호출하지 않음**으로 신호됩니다(이 브리지에는 `fail` 메서드가 없습니다).
- `bridge.error({ code, message })`는 플레이어 패배가 아니라 게임 내부 실패(자산 로드 실패, 예외)를 위한 것입니다. 그것은 호스트에 `error` 이벤트를 드러냅니다.

호스트가 pass를 사용한 뒤, 그것이 위젯 토큰을 해제하고, 당신의 백엔드가 평소처럼 [그 토큰을 검증](/docs/integration-guides/verify-on-your-backend)합니다.

## 5. 샌드박스를 위해 번들하기

위젯은 정확히 하나의 스크립트 URL을 로드하니, 모든 것이 그 단일 파일에 있어야 합니다. 당신의 번들러(esbuild, rollup, vite, webpack; 무엇이든)를 하나의 자족적 출력으로 구성하세요:

- 자산(스프라이트, 사운드, 폰트)을 데이터 URL로 인라인;
- 코드 분할 비활성화;
- WASM을 쓰면, base64로 임베드하고 바이트에서 인스턴스화.

iframe이 불투명 오리진이고 엄격한 CSP를 두므로 샌드박스 안에서 동작하지 **않는** 패턴:

- 경로 상대 `fetch('./sprite.png')` — 가져올 경로가 없음;
- 동적 `import('./chunk.js')` — 두 번째 URL이 차단됨;
- `new Worker('./worker.js')` — 대신 인라인 `Blob` URL에서 워커를 띄우세요;
- 런타임의 외부 CDN 가져오기 — `connect-src`가 그것들을 차단함.

## 6. 번들을 호스팅하고 위젯을 그것으로 향하게 하기

빌드된 파일을 당신 자신의 정적 호스트에서 `https`로 제공한 다음(로컬 개발에는 루프백 `http`가 허용됨), 위젯을 그것으로 향하게 하세요:

```html
<caputchin-game
  sitekey="cpt_pub_..."
  game-src="https://cdn.example.com/my-game/game.js"
></caputchin-game>
```

위젯은 당신의 번들을 그 샌드박스된 iframe에 로드하고, 컨텍스트로 당신의 팩토리를 호출하고, 당신의 `pass`를 기다립니다. 이 시점에 게임은 돌고 검증하지만, 아직 키를 **게이트**하도록 허용되지 않습니다.

## 7. 게이트 가능하게 만들기

자체 호스팅 게임을 검증 게이트로 쓰려면, 두 가지가 더 필요하며, 둘 다 다음에 다룹니다:

1. 대시보드에서 **커스텀 게임으로 등록**하고(당신이 고르는 id) 그 필드 [스키마와 프리셋](/docs/custom-game-development/dashboard-schema)을 정의해 컨텍스트가 진짜 locale/skin/config를 지니게 하기.
2. **재생 산출물 업로드하기**: 서버가 시드와 트레이스에서 판정을 다시 도출하려고 돌리는, 당신 게임 로직의 헤드리스 빌드. [재생과 게이팅](/docs/custom-game-development/replay-and-gating)을 보세요.

재생 산출물이 업로드되어 그 자체 확인을 통과하기 전까지, 커스텀 게임은 **재생 불가**로 보이고 게이트할 수 없습니다.

## 함께 보기

- [재생과 게이팅](/docs/custom-game-development/replay-and-gating): 게이트를 켜는 산출물.
- [대시보드 스키마 레퍼런스](/docs/custom-game-development/dashboard-schema): 컨텍스트가 해소하는 필드 정의하기.
- [수동 모드](/docs/custom-game-development/manual-mode): 더 가벼운, 게이트하지 않는 대안.
- [백엔드에서 검증하기](/docs/integration-guides/verify-on-your-backend): pass가 내는 토큰 확인하기.
