Caputchin
Développement de jeu pour le marketplace

Bâtir un jeu pour le marketplace

À la fin de ce tutoriel, tu auras un jeu Caputchin complet et jouable dans un seul bundle JavaScript, prêt à être rendu rejouable et publié. C'est la version ennuyeuse, fais-chaque-étape ; lis d'abord Livre un jeu au marketplace pour savoir où ça s'insère.

Il te faut Node et un bundler (esbuild, rollup, vite ou webpack, n'importe lequel). Tu n'as pas besoin d'un compte Caputchin pour bâtir, seulement pour publier.

1. Installe le SDK

npm install @caputchin/game-sdk

Le SDK est minuscule : un assistant register plus des types TypeScript. Il n'embarque pas le runtime du widget, donc ton jeu reste petit.

2. Enregistre une fabrique de jeu

Un jeu est un seul appel à register avec une fonction de fabrique. Le widget invoque ta fabrique dans l'iframe en bac à sable ; cet appel est lui-même le signal de départ (il n'y a pas d'événement de départ distinct à attendre).

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

Tu ne passes pas le manifeste à register ; le serveur lit caputchin.json au moment de l'indexation et livre les préréglages résolus à ta fabrique comme ctx. Vois la référence du SDK pour la surface complète.

3. Rends à partir du contexte par manche

Le troisième argument de la fabrique, ctx, porte tout ce qui change par visiteur :

  • ctx.seed - la graine par manche. Dérive tout l'aléatoire de celle-ci (vois l'étape 5). null hors d'une session vérifiée.
  • ctx.locale - les chaînes de langue résolues (ctx.locale._lang plus tes clés), ou null.
  • ctx.skin - les couleurs et URL de ressources résolues (ctx.skin._theme, toujours light ou dark, plus tes clés), ou null.
  • ctx.config - la configuration de gameplay résolue, ou null.

Lis tes propres clés dessus ; tu ne résous jamais les préréglages toi-même. Chacun est null quand ton manifeste ne déclare aucun bloc correspondant, donc retombe toujours sur un défaut intégré :

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...
}

Si ta mise en page a besoin d'une taille explicite, appelle bridge.setSize(width, height) une fois après ton premier rendu.

4. Victoire, défaite, et erreurs

Ton jeu décide quand le joueur gagne et le dit à l'hôte via le pont :

  • À la victoire, appelle bridge.pass({ trace }) avec la trace de la manche (étape 5).
  • À la défaite ou à l'abandon, n'appelle rien ; le silence est le signal d'échec.
  • Si le jeu lui-même casse (une ressource a échoué, une exception), appelle bridge.error({ code, message }). C'est pour une défaillance interne au jeu, pas pour un joueur qui perd.
function onWin(traceString) {
  bridge.pass({ trace: traceString });
}
function onAssetFailure(err) {
  bridge.error({ code: "asset-load-failed", message: String(err) });
}

5. Enregistre une trace

C'est l'étape qui rend le jeu rejouable. À mesure que le joueur agit, enregistre les entrées qui pilotent le résultat (quelle cible il a touchée, dans l'ordre, avec le timing si ça compte) et sérialise cet enregistrement en une chaîne ou un tableau d'octets : c'est ta trace. Combinée à la graine, la trace doit laisser ta logique reproduire le résultat exact, parce que le serveur la réexécute.

Deux règles rendent ça possible :

  1. Dérive chaque choix aléatoire de ctx.seed. N'appelle jamais Math.random() ni ne lis l'horloge murale pour quoi que ce soit qui affecte le résultat.
  2. Enregistre assez pour rejouer. La trace plus la graine est l'entrée complète de ta logique de jeu.
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;
}

La forme de seed, de la trace et du verdict est définie par le contrat de rejeu ; cette page couvre la transformation de ce jeu enregistré en l'artefact run headless que le serveur rejoue. Si écrire à la main une logique déterministe semble laborieux, le kit moteur le fait pour toi, optionnellement.

6. Bundle pour le bac à sable

Le widget charge exactement une URL de script, donc tout doit être dans ce seul fichier. Configure ton bundler pour une seule sortie autonome :

  • intègre les ressources comme URL de données (sprites, sons, polices) ;
  • désactive le découpage de code ;
  • si tu utilises WASM, intègre-le en base64 et instancie à partir des octets.

Les schémas qui ne fonctionnent pas dans l'iframe à origine opaque et à CSP stricte :

  • un fetch('./sprite.png') relatif au chemin - il n'y a pas de chemin d'où récupérer ;
  • un import('./chunk.js') dynamique - la deuxième URL est bloquée ;
  • new Worker('./worker.js') - lance plutôt les workers à partir d'URL Blob en ligne ;
  • les récupérations de CDN externe à l'exécution - connect-src les bloque.

7. Teste-le en local

Il n'y a pas de harnais spécial. Intègre le widget dans une page HTML statique, pointe game-src vers ta sortie de bundle locale, et joue-le dans un navigateur :

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

C'est le même chemin game-src qu'un jeu personnalisé utilise ; pour le marketplace, tu publieras plutôt le dépôt pour que la plateforme épingle le bundle pour toi.

Étapes suivantes

Voir aussi

Sur cette page