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-sdkLe 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).nullhors d'une session vérifiée.ctx.locale- les chaînes de langue résolues (ctx.locale._langplus tes clés), ounull.ctx.skin- les couleurs et URL de ressources résolues (ctx.skin._theme, toujourslightoudark, plus tes clés), ounull.ctx.config- la configuration de gameplay résolue, ounull.
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 :
- Dérive chaque choix aléatoire de
ctx.seed. N'appelle jamaisMath.random()ni ne lis l'horloge murale pour quoi que ce soit qui affecte le résultat. - 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'URLBloben ligne ;- les récupérations de CDN externe à l'exécution -
connect-srcles 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
- Le contrat de rejeu : transforme ton jeu enregistré en l'artefact
runqui rend le jeu rejouable. - Le manifeste caputchin.json : décris le jeu, ses préréglages et son bundle.
- Publier au marketplace : tague le dépôt et passe en ligne.
Voir aussi
- Référence du SDK de jeu : chaque export, en entier.
- L'engine-kit : la façon optionnelle d'obtenir le déterminisme gratuitement.