セルフホストのゲームを作る
このチュートリアルの終わりまでに、本物の Caputchin のゲームを持っていることでしょう。ウィジェットのサンドボックス化された iframe で走り、ゲーム SDK を通じてホストと話し、サーバーが再実行できる トレース を報告する JavaScript のバンドルです。手動モード と違い、セルフホストのゲームは サイトキーをゲートできます。そのラウンドがリプレイ可能だからです。このチュートリアルはゲームの構築とホスティングを扱います。リプレイとゲート が、実際にゲートをオンにするアーティファクトを扱います。
これがどこに合うかは 自分のゲームを動かす を読んでください。静的な JavaScript ファイルをホストする場所(どの CDN か静的ホスト)と、あなたのページのウィジェットが必要です。
セルフホストのゲームの形
セルフホストのゲームは、次をする 1 つの自己完結した 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. ラウンドごとのコンテキストを読む
ファクトリーの 3 つ目の引数 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 内の関心事です。
ブリッジの事実をあと 2 つ:
- 最初の
passがラウンドを引き換えます。失敗または放棄されたラウンドは、単にpassを 呼ばない ことで知らされます(このブリッジにfailメソッドはありません)。 bridge.error({ code, message })は、ゲーム内部の失敗(アセットの読み込み失敗、例外)のためで、プレイヤーの敗北のためではありません。それはホストにerrorイベントを表に出します。
ホストが pass を引き換えた後、それはウィジェットのトークンを解放し、あなたのバックエンドが そのトークンを検証 します。いつもどおりです。
5. サンドボックスのためにバンドルする
ウィジェットはちょうど 1 つのスクリプト URL を読み込むので、すべてがその単一のファイルに入っていなければなりません。1 つの自己完結した出力のために、バンドラー(esbuild、rollup、vite、webpack。どれでも)を設定してください:
- アセット(スプライト、音、フォント)をデータ URL としてインライン化する。
- コード分割を無効にする。
- WASM を使うなら、base64 として埋め込み、バイトからインスタンス化する。
サンドボックスの中で 動かない パターン。iframe が不透明オリジンで厳格な CSP を持つからです:
- パス相対の
fetch('./sprite.png'):取得する元のパスがありません。 - 動的な
import('./chunk.js'):2 つ目の URL がブロックされます。 new Worker('./worker.js'):代わりにインラインのBlobURL から worker を生成してください。- 実行時の外部 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. ゲート可能にする
セルフホストのゲームを検証ゲートとして使うには、あと 2 つのことが必要で、どちらも次に扱います:
- ダッシュボードで カスタムゲームとして登録 し(あなたが選ぶ id)、コンテキストが本物の locale/skin/config を運ぶよう、そのフィールド スキーマとプリセット を定義します。
- リプレイアーティファクトをアップロード します。サーバーがシードとトレースから判定を再導出するために走らせる、あなたのゲームロジックのヘッドレスビルドです。リプレイとゲート を参照してください。
リプレイアーティファクトがアップロードされてセルフチェックを通るまで、カスタムゲームは リプレイ不可 と表示され、ゲートできません。
あわせて読む
- リプレイとゲート:ゲートをオンにするアーティファクト。
- ダッシュボードスキーマリファレンス:コンテキストが解決するフィールドを定義する。
- 手動モード:より軽い、ゲートしない選択肢。
- バックエンドで検証する:pass が生むトークンを確認する。