자체 호스팅 게임 빌드하기
이 튜토리얼이 끝나면 당신은 진짜 Caputchin 게임을 갖게 됩니다: 위젯의 샌드박스된 iframe에서 돌고, 게임 SDK를 통해 호스트와 이야기하고, 서버가 다시 실행할 수 있는 트레이스를 보고하는 JavaScript 번들. 수동 모드와 달리, 자체 호스팅 게임은 그 라운드가 재생 가능하므로 사이트 키를 게이트할 수 있습니다. 이 튜토리얼은 게임을 빌드하고 호스팅하는 것을 다룹니다; 재생과 게이팅이 실제로 게이트를 켜는 산출물을 다룹니다.
이것이 어디에 맞는지는 당신 자신의 게임 돌리기를 읽으세요. 정적 JavaScript 파일을 호스팅할 곳(어떤 CDN이나 정적 호스트)과 페이지의 위젯이 필요합니다.
자체 호스팅 게임의 모양
자체 호스팅 게임은 다음을 하는 하나의 자족적 JavaScript 번들입니다:
@caputchin/game-sdk의register함수를 임포트하고 단일 게임 팩토리를 등록합니다.- 위젯이 팩토리에 주는 컨테이너에, 그것과 함께 넘겨진 해소된 언어, 스킨, 구성을 써서 렌더링합니다.
- 라운드별 시드 아래에서 결정론적으로 플레이되며, 플레이를 트레이스로 기록합니다.
- 승리 시, 그 기록으로
bridge.pass({ trace })를 호출합니다.
위젯은 당신의 번들을 그 iframe에 로드하고 당신의 팩토리를 호출합니다; 팩토리가 호출되는 것이 곧 시작 신호입니다(기다릴 별도의 시작 이벤트가 없습니다).
1. 게임 팩토리 등록하기
SDK를 설치하고 당신의 팩토리로 register를 한 번 호출하세요. 당신은 매니페스트를 넘기지 않습니다; 서버가 프리셋을 해소해 팩토리에 건넵니다.
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;
});npm install @caputchin/game-sdk2. 라운드별 컨텍스트 읽기
팩토리의 세 번째 인자 ctx는 당신의 게임이 이 방문자에게 필요한 모든 것을 지닙니다:
ctx.seed— 라운드별 리플레이 시드. 모든 무작위성을 이것에서 도출하세요. 게임이 검증된 세션 밖에서 돌 때 null.ctx.locale— 해소된 언어 객체:ctx.locale._lang에 당신의 번역된 문자열 키.ctx.skin— 해소된 스킨 객체:ctx.skin._theme(늘light또는dark)에 당신의 색상과 자산 키.ctx.config— 해소된 게임플레이 구성(또는 null).
이것들은 Caputchin이 당신의 대시보드에서 정의한 스키마와 프리셋에서 낸, 이 방문자를 위한 해소된 프리셋입니다. 당신은 그것들에서 당신 자신의 키를 읽습니다; 당신이 직접 무엇도 해소하지 않습니다.
당신의 레이아웃에 명시적 크기가 필요하면, 첫 페인트 뒤에 호스트에 한 번 알리세요:
bridge.setSize(360, 480);3. 플레이를 결정론적으로 만들기
이것이 게임을 게이트 가능하게 만드는 규칙입니다: 게임은 시드와 플레이어의 입력이 주어지면 결정론적이어야 합니다. 결과에 영향을 주는 어떤 것에도 Math.random()을 호출하거나 벽시계를 읽지 마세요; 모든 무작위 선택을 ctx.seed에서 도출하세요. 서버는 같은 시드와 같은 기록된 트레이스 아래에서 당신의 로직을 다시 실행하며 같은 판정에 닿아야 합니다.
// 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 문자열을 둔 객체를 받습니다:
function onWin(traceString) {
bridge.pass({ trace: traceString });
}트레이스는 플랫폼에 불투명합니다: 그것은 당신의 게임만이 정의하는, 당신의 로직이 시드와 결합해 결과를 재현하게 하는 어떤 문자열이든입니다. 게임은 여기서 점수를 보고하지 않습니다; 채점은, 있다면, 당신 자신의 iframe 안 관심사입니다.
브리지 사실 두 가지 더:
- 첫
pass가 라운드를 사용합니다; 실패하거나 버려진 라운드는 그저pass를 호출하지 않음으로 신호됩니다(이 브리지에는fail메서드가 없습니다). bridge.error({ code, message })는 플레이어 패배가 아니라 게임 내부 실패(자산 로드 실패, 예외)를 위한 것입니다. 그것은 호스트에error이벤트를 드러냅니다.
호스트가 pass를 사용한 뒤, 그것이 위젯 토큰을 해제하고, 당신의 백엔드가 평소처럼 그 토큰을 검증합니다.
5. 샌드박스를 위해 번들하기
위젯은 정확히 하나의 스크립트 URL을 로드하니, 모든 것이 그 단일 파일에 있어야 합니다. 당신의 번들러(esbuild, rollup, vite, webpack; 무엇이든)를 하나의 자족적 출력으로 구성하세요:
- 자산(스프라이트, 사운드, 폰트)을 데이터 URL로 인라인;
- 코드 분할 비활성화;
- WASM을 쓰면, base64로 임베드하고 바이트에서 인스턴스화.
iframe이 불투명 오리진이고 엄격한 CSP를 두므로 샌드박스 안에서 동작하지 않는 패턴:
- 경로 상대
fetch('./sprite.png')— 가져올 경로가 없음; - 동적
import('./chunk.js')— 두 번째 URL이 차단됨; new Worker('./worker.js')— 대신 인라인BlobURL에서 워커를 띄우세요;- 런타임의 외부 CDN 가져오기 —
connect-src가 그것들을 차단함.
6. 번들을 호스팅하고 위젯을 그것으로 향하게 하기
빌드된 파일을 당신 자신의 정적 호스트에서 https로 제공한 다음(로컬 개발에는 루프백 http가 허용됨), 위젯을 그것으로 향하게 하세요:
<caputchin-game
sitekey="cpt_pub_..."
game-src="https://cdn.example.com/my-game/game.js"
></caputchin-game>위젯은 당신의 번들을 그 샌드박스된 iframe에 로드하고, 컨텍스트로 당신의 팩토리를 호출하고, 당신의 pass를 기다립니다. 이 시점에 게임은 돌고 검증하지만, 아직 키를 게이트하도록 허용되지 않습니다.
7. 게이트 가능하게 만들기
자체 호스팅 게임을 검증 게이트로 쓰려면, 두 가지가 더 필요하며, 둘 다 다음에 다룹니다:
- 대시보드에서 커스텀 게임으로 등록하고(당신이 고르는 id) 그 필드 스키마와 프리셋을 정의해 컨텍스트가 진짜 locale/skin/config를 지니게 하기.
- 재생 산출물 업로드하기: 서버가 시드와 트레이스에서 판정을 다시 도출하려고 돌리는, 당신 게임 로직의 헤드리스 빌드. 재생과 게이팅을 보세요.
재생 산출물이 업로드되어 그 자체 확인을 통과하기 전까지, 커스텀 게임은 재생 불가로 보이고 게이트할 수 없습니다.
함께 보기
- 재생과 게이팅: 게이트를 켜는 산출물.
- 대시보드 스키마 레퍼런스: 컨텍스트가 해소하는 필드 정의하기.
- 수동 모드: 더 가벼운, 게이트하지 않는 대안.
- 백엔드에서 검증하기: pass가 내는 토큰 확인하기.