Caputchin
Marketplace-Spiel-Entwicklung

Ein Marketplace-Spiel bauen

Am Ende dieses Tutorials hast du ein vollständiges, spielbares Caputchin-Spiel in einem JavaScript-Bundle, bereit, abspielbar gemacht und veröffentlicht zu werden. Das ist die langweilige Jeden-Schritt-Version; lies zuerst Ein Spiel an den Marketplace ausliefern dafür, wo es hineinpasst.

Du brauchst Node und einen Bundler (esbuild, rollup, vite oder webpack, einer von ihnen). Du brauchst kein Caputchin-Konto zum Bauen, nur zum Veröffentlichen.

1. Das SDK installieren

npm install @caputchin/game-sdk

Das SDK ist winzig: ein register-Helfer plus TypeScript-Typen. Es bündelt die Widget-Runtime nicht, also bleibt dein Spiel klein.

2. Eine Spiel-Factory registrieren

Ein Spiel ist ein einzelner Aufruf von register mit einer Factory-Funktion. Das Widget ruft deine Factory im gesandboxten Iframe auf; dieser Aufruf ist selbst das Startsignal (es gibt kein separates Start-Event, auf das man warten müsste).

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;
});

Du übergibst das Manifest nicht an register; der Server liest caputchin.json zur Index-Zeit und liefert die aufgelösten Presets als ctx an deine Factory hinunter. Sieh dir die SDK-Referenz für die vollständige Fläche an.

3. Aus dem Pro-Runde-Kontext rendern

Das dritte Argument der Factory, ctx, trägt alles, was sich pro Besucher ändert:

  • ctx.seed: der Pro-Runde-Seed. Leite jede Zufälligkeit hieraus ab (siehe Schritt 5). null außerhalb einer verifizierten Session.
  • ctx.locale: aufgelöste Sprach-Strings (ctx.locale._lang plus deine Schlüssel), oder null.
  • ctx.skin: aufgelöste Farben und Asset-URLs (ctx.skin._theme, immer light oder dark, plus deine Schlüssel), oder null.
  • ctx.config: aufgelöste Gameplay-Konfiguration, oder null.

Lies deine eigenen Schlüssel von diesen ab; du löst Presets nie selbst auf. Jedes ist null, wenn dein Manifest keinen passenden Block deklariert, also fall immer auf einen eingebauten Standard zurück:

function startGame(container, bridge, ctx) {
  const title = ctx.locale?.title ?? "Tap the targets";
  const accent = ctx.skin?.accent_color ?? "#2da44e";
  const targetCount = ctx.config?.target_count ?? 5;
  // ...render with these...
}

Braucht dein Layout eine explizite Größe, ruf bridge.setSize(width, height) einmal nach deinem ersten Paint.

4. Gewinnen, verlieren und Fehler

Dein Spiel entscheidet, wann der Spieler gewinnt, und sagt es dem Host über die Brücke:

  • Bei einem Sieg ruf bridge.pass({ trace }) mit dem Trace der Runde (Schritt 5).
  • Bei einem Verlust oder Abbruch ruf nichts; Stille ist das Fehler-Signal.
  • Wenn das Spiel selbst kaputtgeht (ein Asset schlug fehl, eine Exception), ruf bridge.error({ code, message }). Das ist für ein spiel-internes Versagen, keinen verlierenden Spieler.
function onWin(traceString) {
  bridge.pass({ trace: traceString });
}
function onAssetFailure(err) {
  bridge.error({ code: "asset-load-failed", message: String(err) });
}

5. Einen Trace aufzeichnen

Das ist der Schritt, der das Spiel abspielbar macht. Während der Spieler agiert, zeichne die Eingaben auf, die das Ergebnis treiben (welches Ziel er traf, in Reihenfolge, mit Timing, wenn es zählt), und serialisier diesen Datensatz zu einem String oder Byte-Array: das ist dein Trace. Kombiniert mit dem Seed muss der Trace deine Logik das exakte Ergebnis reproduzieren lassen, weil der Server ihn erneut laufen lässt.

Zwei Regeln machen das möglich:

  1. Leite jede Zufallswahl aus ctx.seed ab. Ruf nie Math.random() auf und lies nie die Wall-Clock für irgendetwas, das das Ergebnis beeinflusst.
  2. Zeichne genug zum Abspielen auf. Der Trace plus der Seed ist die vollständige Eingabe für deine Spiel-Logik.
function makeRng(seed) {
  // seed is ctx.seed; feed it so the server reproduces the same sequence.
  let s = hashSeed(seed);
  return () => (s = (s * 1103515245 + 12345) & 0x7fffffff) / 0x7fffffff;
}

Die Form von seed, dem Trace und dem Urteil sind durch den Replay-Vertrag definiert; diese Seite behandelt, wie man dieses aufgezeichnete Spiel in das Headless-run-Artefakt verwandelt, das der Server abspielt. Klingt deterministische Logik von Hand schreiben fummelig, das Engine-Kit tut es für dich, optional.

6. Für die Sandbox bündeln

Das Widget lädt genau eine Skript-URL, also muss alles in dieser einen Datei sein. Konfigurier deinen Bundler für eine in sich geschlossene Ausgabe:

  • Assets inline als Data-URLs (Sprites, Sounds, Schriften);
  • Code-Splitting deaktivieren;
  • nutzt du WASM, bette es als base64 ein und instanziiere aus Bytes.

Muster, die im Opaque-Origin-, Strict-CSP-Iframe nicht funktionieren:

  • pfad-relatives fetch('./sprite.png'), es gibt keinen Pfad, von dem abgerufen werden kann;
  • dynamisches import('./chunk.js'), die zweite URL ist blockiert;
  • new Worker('./worker.js'), starte Worker stattdessen aus Inline-Blob-URLs;
  • externe CDN-Abrufe zur Laufzeit, connect-src blockiert sie.

7. Es lokal testen

Es gibt keine spezielle Harness. Bette das Widget in eine statische HTML-Seite ein, richte game-src auf deine lokale Bundle-Ausgabe und spiel es in einem Browser:

<caputchin-game sitekey="cpt_pub_..." game-src="http://localhost:8080/game.js"></caputchin-game>

Das ist derselbe game-src-Weg, den ein Custom-Spiel nutzt; für den Marketplace veröffentlichst du stattdessen das Repo, sodass die Plattform das Bundle für dich pinnt.

Nächste Schritte

Siehe auch

Auf dieser Seite