Communitygithub.com

takumifukasawa/agent-skills

Erstellt Google Play Store-Einträge für Spiele: das Werbebild (Feature Graphic, 1024x500), Screenshots für Telefon und Tablet, die aus dem laufenden Spiel in einem headless Chromium aufgenommen werden, sowie den Listing-Text (Kurzbeschreibung und ausführliche Beschreibung).

Was ist agent-skills?

Dieses Skill ermöglicht es KI-Agenten, automatisch vollständige Google Play Store-Einträge für Spiele zu generieren. Es erstellt hochwertige Werbegrafiken (Feature Graphic), Screenshots von Telefon und Tablet, und Listing-Texte direkt aus dem laufenden Spiel – keine Mockups oder manuelle Arbeit nötig. Es unterstützt Spiele-Engines wie Phaser, PixiJS, Three.js sowie einfache Canvas-Spiele, die lokal ausgeführt werden. Der Workflow umfasst drei Hauptlieferungen: 1) Screenshots in exakten Store-Größen, 2) ein 1024x500 Werbebanner (Feature Graphic), entweder als reines Spielbild oder als Compositing mit Titel, und 3) den Listing-Text gemäß den Play Console-Richtlinien. Zusätzlich wird eine interaktive Auswahlseite (Picker Page) als Artifact generiert, auf der der Benutzer per Checkbox und Kommentaren die finale Auswahl treffen kann – inklusive Copy-Buttons für Texte und einer Vorschau der App-Icon-Kandidaten (in Zusammenarbeit mit dem Schwester-Skill `game-icon-from-screenshot`). Das Skill arbeitet mit Playwright/Chromium für die Bildaufnahme und Python (Pillow) für die Bildbearbeitung. Es nutzt Super-Sampling (Faktor 2) für scharfe Kanten, vermeidet Upscaling, und handhabt Probleme wie steckengebliebene Physik in headless WebGL durch spezielle Pose-Capture-Techniken. Es automatisiert den gesamten Prozess von der Aufnahme über die Auswahl bis zur Entscheidungsdokumentation.

Funktioniert mit✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/takumifukasawa/agent-skills/tree/HEAD/gamedev/game-store-assets

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Store-Assets aus dem laufenden Spiel erstellen

Erstelle originalgetreue Store-Bilder durch Aufnahmen des tatsächlich laufenden Spiels (keine Mockups). Das wiederverwendbare Framework befindet sich in scripts/; du lieferst eine kleine SPIELSPEZIFISCHE Konfiguration (URL, Bereitschaftscheck, wie das Spiel posiert/versteckt/gesteuert wird, Titeltext).

Drei Ergebnisse:

  1. Screenshots (scripts/screenshots.cjs) — Stapel in exakten Store-Größen.
  2. Feature Graphic — 1024x500 Werbebanner: entweder ein sauberer, randloser Spielrahmen (screenshots.cjs mit einem Querformat-target) oder ein Rahmen + Titel-Komposition (scripts/feature_graphic.py). Frage nach; Standard ist randlos für stimmungsgetriebene Spiele.
  3. Listing-Text — kurze + ausführliche Beschreibung: lies references/listing-text.md (Grenzen, Tonfall, was nicht behauptet werden darf, die Frage zum Formular „KI-generierte Assets“).

Für Store-Größenanforderungen + Voreinstellungen lies references/play-store-sizes.md.

Arbeitsablauf: viele Kandidaten → der Benutzer wählt aus

Der Benutzer wählt; du bietest Vielfalt und machst die Auswahl einfach.

  1. Nimm viele Bilder pro Level/Abschnitt/Thema auf (z. B. 5 Stufen × 8–10). Nutze den Spielfortschritt, um sie zu variieren (frühe / mittlere / späte Upgrade-Stufen): frühe Bilder wirken leer, späte können das Motiv überladen.
  2. Erstelle eine Vorauswahl von ~15 Bildern mit Abwechslung (jede Stufe; bestandene/krachende Momente; reiche vs. ruhige Hintergründe). Betrachte sie zuerst selbst mit scripts/contact_sheet.py (nummeriert). Dann lass den Benutzer wählen – in der Picker-Seite (unten), wenn Artifacts verfügbar sind (was Benutzer nach dem ersten Einsatz wünschten); ansonsten sende das nummerierte Kontaktblatt und nimm Zahlen entgegen.
  3. Empfehle einen Satz mit Begründung (Farb-/Stufenvielfalt im Satz, nicht denselben Moment wiederholen). Wiederhole Aufnahmen auf Wunsch des Benutzers („被写体をもう少し中央に“) und füge die Nachaufnahmen als weitere Gruppe mit demselben Auswahlschlüssel in den Picker ein.
  4. Kopiere den endgültigen Satz in einen Ordner in Upload-Reihenfolge (1-…png, 2-…) und dokumentiere die Entscheidung (Dateien, ausgewählter Text wörtlich, abgelehnte Optionen und warum, wie sie aufgenommen wurden) in den Projektdokumentationen. Halte die generierten Bilder aus git heraus, es sei denn, das Projekt verfolgt bereits Store-Assets.

python3 ${CLAUDE_SKILL_DIR}/scripts/contact_sheet.py sheet.png a.png b.png … --cols 5 --thumb 300 (Die zugrundeliegende Playwright/Canvas-Technik wird mit game-icon-from-screenshot geteilt.)

Picker-Seite (Artifact): der Benutzer wählt den Satz aus

python3 ${CLAUDE_SKILL_DIR}/scripts/build_picker.py picker.json picker.html, dann veröffentliche picker.html mit dem Artifact-Tool und capabilities: {"db": {}} (lade zuerst die Skills artifact-design / artifact-capabilities, da das Artifact-Tool dies erfordert). Das Spezifikationsformat steht im Header von scripts/build_picker.py.

Was die Seite tut (alles aus der Spezifikation, nichts Spielspezifisches in der Vorlage):

  • Bildgruppen — mode: "multi" (Screenshots: Auswahlreihenfolge = Upload-Reihenfolge, ← → zum Neuanordnen, max) oder "single" (Feature Graphic, App-Icon). Mehrere Gruppen können denselben pick-Schlüssel teilen (erste Kandidaten + Nachaufnahmen = eine Auswahl). „おすすめ“-Chips von rec, ⤢ Zoom, ein Tablett oben mit der ausgewählten Auswahl und ein „おすすめで埋める“-Button.
  • Textgruppen — Radio-Optionen mit dem Einreichungstext, einer Übersetzung darunter, einer Zeichenanzahl gegen limit und einem Copy-Button oben rechts bei jeder Option (kopiert nur den Einreichungstext).
  • Kommentare als Anweisungen — ein Kommentarfeld pro Gruppe (noteKey) plus ein allgemeines „全体への指示“. Benutzer schreiben dort Anweisungen („2 案目の最後の行はいらない“, ein japanischer Entwurf zum Bearbeiten und Übersetzen) und sagen „保存しました“.
  • Auswahlen — kleine Ja/Nein-Radios (Titeltext auf der Feature Graphic oder nicht).
  • Alles wird in einem db-Dokument gespeichert (doc, Standard picks/current); lies es mit ArtifactData (action: "get", collection: "picks", doc_id: "current" für den Standard). Auswahl-/Wahl-/Notiz-Schlüssel teilen sich dieses Dokument, daher verweigert der Builder kollidierende Schlüssel und doppelte Element-IDs, wenn der Benutzer sagt, er habe gespeichert. Reagiere auf jede nicht leere Notiz, dann teile ihnen mit, was sich geändert hat. Um die Seite zu ändern, bearbeite die Spezifikation und baue sie neu, dann veröffentliche denselben Dateipfad (gleiche URL; die gespeicherten Auswahlen bleiben erhalten, weil die IDs gleich bleiben). Nach der ersten Veröffentlichung führe einen Lesevorgang von picks durch, um die Store-Antworten zu bestätigen.

App-Icons verwenden dieselbe Seite: einen Nur-Icon-Picker (eine "single"-Gruppe, aspect: "1/1", thumbWidth: 512) oder Icons als eine weitere Gruppe neben den Store-Bildern. Erstelle die Kandidaten mit game-icon-from-screenshot.

Voraussetzungen

  • Spiel, das unter einer lokalen URL läuft (npm run dev → z. B. http://localhost:5173).
  • Playwright + Chromium (automatisch aufgelöst aus dem Projekt-node_modules, dem npx-Cache oder global; sonst npx playwright install chromium).
  • Pillow (python3 -m pip install Pillow) zum Ändern der Größe / Zusammensetzen.
  • Eine gebündelte Schriftartdatei für den Titel der Feature Graphic (eine .ttf; variable Schriftarten werden über Gewichtsachsen unterstützt).

Anti-Aliasing: Supersample, nicht hochskalieren

Das Aufnehmen in der exakten Ausgabegröße wirkt gezackt. Nimm dicht dann runterskaliert auf: setze superSample (deviceScaleFactor) = 2 und lass das Framework auf die Zielgröße herunterskalieren. WICHTIG: mache die Dichte mit superSample, nicht durch die Verwendung eines viel größeren Viewports – ein sehr großer Canvas kann den headless Render-/Physik-Loop blockieren (das Spiel friert beim Spawn ein). Halte den Viewport in einer Größe, in der das Spiel tatsächlich läuft (z. B. 1080x1920) und erhöhe stattdessen superSample.

Screenshots

node ${CLAUDE_SKILL_DIR}/scripts/screenshots.cjs <config.(cjs|json)> [outDir]

  • Statische posierte Aufnahmen (shots): jede öffnet eine neue Seite, führt deine pose-Fn aus (Punktzahl setzen, Motiv positionieren, Ergebnisüberlagerung anzeigen …), versteckt optional das HUD und nimmt auf. Gut für Bereit-/Ergebnisbildschirme.
  • Live-Gameplay (live): startet das Spiel, führt deine keepAlive-Fn bei jeder Abfrage aus, um das Spiel am Laufen/Steuern zu halten (ein „Autopilot“), und nimmt Bilder auf, wenn deine pick-Fn sagt, dass das Bild gut ist. Live-Bilder zeigen echte Bewegung/Spuren – so bekommst du „tatsächlich spielende“ Aufnahmen, keine statischen. Nimm viele auf und lass den Benutzer wählen.

fn-Felder werden IN DER SEITE ausgeführt. In einer .cjs-Konfiguration schreibe echte Funktionen (sie werden mit .toString() serialisiert, also halte sie in sich geschlossen – keine Node-Closures). In einer .json-Konfiguration schreibe Arrow-Funktions-String-Quellen.

  • Zeitgesteuert (timed): für Spiele, die sich selbst spielen können (ein ?autoplay=-Parameter, ein Demo-/Attract-Modus). Öffnet jede URL der Reihe nach und macht alle intervalMs ein Bild – der einfachste Weg, viele echte Bilder pro Stufe zu bekommen.

Konfiguration (Screenshots)

url, ready, target {width,height} (Ausgabegröße + Viewport), viewport (Aufnahmegröße, wenn sie abweichen soll; Bilder werden auf das Ziel-Seitenverhältnis beschnitten, cropY verankert das Band), superSample (2), launchArgs, executablePath, css (eingefügt; einfachste HUD-Ausblendung für DOM-HUDs), routes [{match, replace: [[from,to]…]}] (nur für die Aufnahme angewandte Patches der ausgelieferten Quelle), waitFontsReady (true), hudHide (fn), shots [{name, pose?, hideHud?, settleMs?}], live {count, start, keepAlive, pick, hideHud, buildMs, pollMs, settleMs, maxTries, prefix}, timed {urls: [{prefix, url}], count, warmupMs, intervalMs}, outDir. Die vollständige Felddokumentation befindet sich im Header von scripts/screenshots.cjs.

Playwright muss auflösbar sein: führe es aus einem Verzeichnis aus, in dem es in node_modules ist, oder NODE_PATH=<dir>/node_modules node ${CLAUDE_SKILL_DIR}/scripts/screenshots.cjs … (CommonJS). Wenn der installierte Browser älter als das Paket ist, fällt das Skript auf das neueste Chromium im ms-playwright-Cache zurück.

Beispiel (WebGL / three.js, selbstspielend, DOM HUD)

{
  "target": {"width": 1080, "height": 1920},
  "viewport": {"width": 540, "height": 960},
  "superSample": 2,
  "launchArgs": ["--use-angle=swiftshader", "--enable-unsafe-swiftshader"],
  "css": "body > div:not(#app) { display: none !important; }",
  "timed": {
    "urls": [{"prefix": "stage1", "url": "http://localhost:5173/?level=1&autoplay=1"},
             {"prefix": "stage2", "url": "http://localhost:5173/?level=2&autoplay=1"}],
    "count": 10, "warmupMs": 3000, "intervalMs": 3500
  }
}

Randloses 1024x500-Banner für ein nur Hochformat-Spiel (es letterboxt breite Viewports, und seine Welt endet jenseits des Hochformat-Views): hebe die Seitenverhältnisbegrenzung nur für die Aufnahme auf, nimm ein quadratischeres View bei 2x auf, beschneide das Band.

{
  "target": {"width": 1024, "height": 500},
  "viewport": {"width": 820, "height": 640},
  "superSample": 2,
  "launchArgs": ["--use-angle=swiftshader", "--enable-unsafe-swiftshader"],
  "css": "body > div:not(#app) { display: none !important; }",
  "routes": [{"match": "GameConfig", "replace": [["maxAspect: 0.6", "maxAspect: 0"]]}],
  "timed": {"urls": [{"prefix": "banner", "url": "http://localhost:5173/?level=1&autoplay=1"}],
            "count": 6, "warmupMs": 3000, "intervalMs": 5000}
}

Beispiel (Phaser, Hochformat; Dev gibt window.__game frei)

Ein durchgearbeitetes Beispiel von einem bestimmten Spiel (ein Ball-durch-einen-Ring-Spiel). Ersetze deine eigenen Szenen-/Objektnamen, Posierlogik und Fahr-/Auswahlbedingungen.

// phone.shots.cjs
const G = () => window.__game.scene.scenes[0]; // In-Seite-Hilfsfunktion (unten eingefügt)
module.exports = {
  url: "http://localhost:5173/",
  target: { width: 1080, height: 1920 },
  superSample: 2,
  ready: () => !!(window.__game?.scene?.scenes?.[0]?.ring),
  hudHide: () => { const s = window.__game.scene.scenes[0]; ["scoreText","bestText","hintText"].forEach(k => s[k]?.setVisible(false)); },
  shots: [
    { name: "ready", hideHud: false },
    { name: "result", hideHud: true, settleMs: 1200, pose: () => {
        const s = window.__game.scene.scenes[0];
        s.matter.world.pause(); s.score = 18; s.highScore = 18; s.lastRunNewRecord = true; s.showResult();
    } },
  ],
  live: {
    count: 24, prefix: "play", pollMs: 45, buildMs: 700,
    startTap: true,            // vertrauenswürdiger Tap zum Starten (Phaser ignoriert synthetische Events)
    hideHud: false,            // Punktzahl in Gameplay-Aufnahmen sichtbar lassen
    start: () => { window.__game.scene.scenes[0].goals = 6; }, // zusätzliche Einrichtung (z. B. Vielfalt aktivieren)
    keepAlive: () => { const s = window.__game.scene.scenes[0]; if (s.state === "playing" && s.ball.y > s.scale.height * 0.5) s.ball.setVelocityY(-13 * s.scaleFactor); },
    pick: () => { const s = window.__game.scene.scenes[0]; return s.state === "playing" && s.ball.body.velocity.y > 4; },
  },
};

Tablet-Set: gleiche Konfiguration mit target: { width: 1440, height: 2560 }. (Du kannst auch ausgewählte Telefon-Aufnahmen auf 1440x2560 hochskalieren, um die exakten ausgewählten Bilder beizubehalten.)

Posierte Animation (scripts/pose-capture.cjs) — für Spiele mit PostFX/Bloom

Der live-Modus geht davon aus, dass die Physik des Spiels in headless läuft. Das tut sie oft nicht. Eine WebGL Bloom / PostFX-Pipeline kann den headless Render+Physik-Loop BLOCKIEREN, sodass das Motiv an seiner Spawn-Position einfriert (Geschwindigkeit wird gesetzt, aber die Position wird nie integriert) – jedes „live“-Bild kommt identisch heraus, das Motiv steckt an seiner Spawn-Position fest. Das hat bei einem echten Projekt sehr gebissen. Symptom, das zuerst geprüft werden sollte: Abfrage des y-Werts des Motivs über zwei Abfragen; wenn es sich nie ändert, ist die Physik blockiert.

Wenn das passiert, kämpfe nicht dagegen an – steuere das Motiv von Hand:

node ${CLAUDE_SKILL_DIR}/scripts/pose-capture.cjs <config.(cjs|json)> [outDir]

Jede Aufnahme pausiert die Welt, animiert dann das Motiv entlang eines von DIR definierten Pfads über N Bilder (wobei ein eventueller Spur-/Partikelemitter läuft), sodass eine echte Bewegungsspur unabhängig von der Physik aufgebaut wird. Konfiguration pro Aufnahme: {name, setup, poseAt(t), steps, stepMs} — setup läuft einmal und versetzt die Szene in den Zustand der Aufnahme (z. B. bei einem Ball-und-Tor-Spiel: das Tor spawnen, state="playing", world.pause(), trail.start(), den Bereitschaftshinweis ausblenden, eine Punktzahl setzen), poseAt(t) positioniert das Motiv für t in [0..1].

Was eine posierte Spur als echte Bewegung lesbar macht (das Beispiel-Spiel fällt unter Schwerkraft; passe an, wie sich deines bewegt):

  • Bezier-Pfad, keine gerade Linie. Ein gerader Pfad erzeugt eine steife, linealgerade Spur; eine quadratische Bezier-Kurve mit einem seitlichen Kontrollpunkt-Offset ergibt den natürlichen gekrümmten Bogen, den das Spiel tatsächlich produziert. Variiere die Kurve pro Aufnahme.
  • Ease-in t*t (beschleunigend) liest sich als Schwerkraft.
  • Moderate Emitter-Verstärkung nur für die Aufnahme (höhere frequency + längere lifespan), weil die Hand-Animation langsamer ist als ein echter schneller Fall und der Standard-Emitter zu wenige Partikel hinterlässt. Verstärke MODERAT – eine überladene Spur sieht dem Spiel überhaupt nicht ähnlich.

Feature Graphic

python3 ${CLAUDE_SKILL_DIR}/scripts/feature_graphic.py <config.json>

Setzt zusammen: dunkler Hintergrund → echter Spiel-Screenshot (links, Neonrahmen) → Titel + Untertitel (rechts, gebündelte Schriftart) → Akzentlinie. Gerendert in superSample×, dann herunterskaliert.

Konfiguration: out, size ([1024,500]), superSample (2), screenshot, font, title, tagline, bg, frameColor, titleColor, titleGlow, tagColor, accentColor, titleWeight, tagWeight.

Die Beispielwerte unten stammen von einem bestimmten Spiel – ersetze sie durch deinen eigenen Titel, Untertitel, Farben und Schriftart.

{
  "out": "store-assets/feature-graphic-1024x500.png",
  "screenshot": "store-assets/feature-shot.png",
  "font": "public/fonts/Orbitron.ttf",
  "title": "MEIN SPIEL", "tagline": "EIN-FINGER-ARKADE",
  "bg": "#0a0a1c", "frameColor": "#27e6ff",
  "titleColor": "#ffffff", "titleGlow": "#27e6ff", "tagColor": "#8fffe0", "accentColor": "#27e6ff",
  "titleWeight": 800, "tagWeight": 600
}

Gelernte Lektionen (diese umsetzen)

  • WebGL in headless benötigt SwiftShader. Ohne --use-angle=swiftshader --enable-unsafe-swiftshader ist ein three.js-Canvas schwarz. Es ist langsam: öffne eine Seite nach der anderen (5 parallele WebGL-Seiten haben den Screenshot-Aufruf zum Timeout geführt).
  • HUD mit CSS ausblenden: blende nicht das Elternelement des Canvas aus. Ein pauschales div { visibility: hidden } hat auch den Canvas ausgeblendet (jedes Bild identisch, ~8 KB). Untersuche das DOM einmal und schließe den Canvas-Wrapper per ID aus. Identische winzige Dateien = nichts gerendert.
  • Nur-Hochformat-Spiele und Querformat-Banner. Eine max-aspect-Begrenzung letterboxt einen breiten Viewport (eine schmale Spalte in der Mitte). Hebe sie pro Aufnahme über routes auf; die Spieldateien s

…

[gekürzt für die Übersetzung]

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

Verwandte Skills