Communitygithub.com

takumifukasawa/agent-skills

Genera los activos para la ficha de Google Play de un juego: la FEATURE GRAPHIC promocional (1024x500) y las CAPTURAS DE PANTALLA para teléfono/tableta capturadas del juego real ejecutándose en Chromium headless, además del TEXTO de la ficha (descripción breve y completa).

¿Qué es agent-skills?

Esta habilidad automatiza la creación de todos los activos visuales y textuales necesarios para la ficha de una aplicación en Google Play Console, capturándolos directamente del juego en ejecución (no de maquetas). Permite generar capturas de pantalla en tamaños reales de la tienda, la imagen promocional (feature graphic) de 1024x500 y los textos de la ficha (descripción corta y completa). Está diseñada para juegos desarrollados con Phaser, PixiJS, Three.js o canvas plano que se sirvan localmente. El flujo de trabajo incluye una página selectora (Picker) publicada como Artifact, donde el usuario puede marcar con checkboxes las imágenes y textos que desea incluir, reordenar las capturas, añadir comentarios y copiar los textos finales. También admite la selección de iconos de aplicación generados con la habilidad hermana `game-icon-from-screenshot`. La herramienta maneja la captura densa con supersample para evitar el aliasing, y ofrece modos para capturas estáticas, en vivo (con piloto automático) y animaciones posadas para juegos con postFX que puedan congelarse en modo headless.

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

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Activos de ficha de tienda desde el juego en ejecución

Produce imágenes fieles a la tienda capturando el juego real en ejecución (sin maquetas). El andamiaje reutilizable está en scripts/; tú proporcionas una configuración pequeña y específica del juego (URL, comprobación de listo, cómo posar/ocultar/conducir el juego, texto del título).

Tres entregables:

  1. Capturas de pantalla (scripts/screenshots.cjs) — lotes en los tamaños exactos de la tienda.
  2. Feature graphic — banner promocional de 1024x500: ya sea un fotograma del juego a sangre y fondo limpio (screenshots.cjs con un target apaisado) o una composición de fotograma + título (scripts/feature_graphic.py). Pregunta cuál; por defecto usa el de sangre y fondo para juegos ambientales.
  3. Texto de la ficha — descripción breve + completa: lee references/listing-text.md (límites, tono, qué no afirmar, la pregunta del formulario "Activos generados por IA").

Para los requisitos de tamaño de la tienda y ajustes predefinidos, lee references/play-store-sizes.md.

Flujo de trabajo: muchos candidatos → el usuario elige

El usuario elige; tú ofreces variedad y facilitas la elección.

  1. Captura muchos fotogramas por fase/nivel/temática (p. ej., 5 fases × 8–10). Usa también la progresión del juego para variarlos (etapas de mejora temprana / intermedia / tardía): los fotogramas tempranos se ven vacíos, los tardíos pueden ocultar el sujeto.
  2. Preselecciona ~15 con variedad (cada fase; momentos de pase/ráfaga; fondos ricos vs. tranquilos). Míralos tú mismo primero con scripts/contact_sheet.py (numerados). Luego deja que el usuario elija — en la página de selección (abajo) cuando los Artefactos estén disponibles, que es lo que los usuarios pidieron una vez que la vieron; de lo contrario, envía la imagen de la hoja numerada y toma los números.
  3. Recomienda un conjunto, con una razón (variedad de color / fase entre el conjunto, sin repetir el mismo momento). Vuelve a capturar lo que pidan los comentarios del usuario ("被写体をもう少し中央に") y añade las retomas al selector como otro grupo que comparte la misma clave de selección.
  4. Copia el conjunto final en una carpeta en orden de subida (1-…png, 2-…), y registra la decisión (archivos, texto elegido textualmente, opciones rechazadas y por qué, cómo se capturaron) en la documentación del proyecto. Mantén las imágenes generadas fuera de git a menos que el proyecto ya rastree los activos de la tienda.

python3 ${CLAUDE_SKILL_DIR}/scripts/contact_sheet.py sheet.png a.png b.png … --cols 5 --thumb 300 (La técnica base de Playwright/canvas se comparte con game-icon-from-screenshot).

Página de selección (Artefacto): el usuario marca el conjunto

python3 ${CLAUDE_SKILL_DIR}/scripts/build_picker.py picker.json picker.html, luego publica picker.html con la herramienta Artifact y capabilities: {"db": {}} (carga primero las habilidades artifact-design / artifact-capabilities, ya que la herramienta Artifact lo requiere). El formato de la especificación está en el encabezado de scripts/build_picker.py.

Qué hace la página (todo desde la especificación, nada específico del juego en la plantilla):

  • Grupos de imágenes — mode: "multi" (capturas de pantalla: orden de marca = orden de subida, ← → para reordenar, max) o "single" (feature graphic, icono de aplicación). Varios grupos pueden compartir una clave pick (primeros candidatos + retomas = una elección). Chips "おすすめ" de rec, ⤢ zoom, una bandeja en la parte superior mostrando el conjunto elegido, y un botón "おすすめで埋める".
  • Grupos de texto — opciones de radio con el texto de envío, una traducción debajo, un contador de caracteres contra limit, y un botón de copiar en la esquina superior derecha de cada opción (copia solo el texto de envío).
  • Comentarios como instrucciones — un cuadro de comentarios por grupo (noteKey) más uno general "全体への指示". Los usuarios escriben instrucciones allí ("2 案目の最後の行はいらない", un borrador en japonés para editar y traducir) y dicen "保存しました".
  • choices — radios pequeños de sí/no (texto del título en la feature graphic o no).
  • Todo se guarda en un documento de db (doc, por defecto picks/current); léelo con ArtifactData (action: "get", collection: "picks", doc_id: "current" para el valor por defecto). Las claves de selección / elección / nota comparten ese documento, por lo que el constructor rechaza claves en conflicto e identificadores de ítems duplicados cada vez que el usuario dice que guardó. Actúa sobre cada nota no vacía, luego diles qué cambió. Para cambiar la página, edita la especificación y reconstruye, luego publica la misma ruta de archivo (misma URL; las selecciones guardadas sobreviven porque los ids siguen siendo los mismos). Después de la primera publicación, haz una lectura de picks para confirmar las respuestas de la tienda.

Los iconos de aplicación usan la misma página: un selector solo de iconos (un grupo "single", aspect: "1/1", thumbWidth: 512) o iconos como un grupo más junto a las imágenes de la tienda. Crea los candidatos con game-icon-from-screenshot.

Prerrequisitos

  • Juego ejecutándose en una URL local (npm run dev → p. ej., http://localhost:5173).
  • Playwright + Chromium (resuelto automáticamente desde node_modules del proyecto, el caché de npx o global; si no, npx playwright install chromium).
  • Pillow (python3 -m pip install Pillow) para redimensionar/componer.
  • Un archivo de fuente empaquetado para el título de la feature graphic (un .ttf; las fuentes variables son compatibles mediante ejes de peso).

Anti-aliasing: supersample, no redimensionar

Capturar al tamaño de salida exacto se ve dentado. Captura denso y luego reduce: configura superSample (deviceScaleFactor) = 2 y deja que el andamiaje reduzca al tamaño objetivo. IMPORTANTE: haz la densidad con superSample, no usando un viewport mucho más grande — un canvas muy grande puede bloquear el bucle de renderizado/física headless (el juego se congela al aparecer). Mantén el viewport en un tamaño donde el juego realmente funcione (p. ej., 1080x1920) y aumenta superSample en su lugar.

Capturas de pantalla

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

  • Tomas posadas estáticas (shots): cada una abre una página nueva, ejecuta tu función pose (establece la puntuación, posiciona el sujeto, muestra la superposición de resultados…), opcionalmente oculta el HUD, y captura. Bueno para pantallas de listo/resultado.
  • Juego en vivo (live): inicia el juego, ejecuta tu función keepAlive en cada sondeo para mantener el juego vivo/conduciéndolo (un "piloto automático"), y captura fotogramas cuando tu función pick dice que el fotograma es bueno. Los fotogramas en vivo muestran movimiento/estelas reales — así es como obtienes tomas "realmente jugando", no estáticas. Captura muchos y deja que el usuario elija.

Los campos fn se ejecutan DENTRO DE LA PÁGINA. En una configuración .cjs escribe funciones reales (se serializan con .toString(), así que mantenlas autocontenidas — sin closures de Node). En una configuración .json escribe cadenas fuente de funciones flecha.

  • Temporizado (timed): para juegos que pueden jugarse solos (una consulta ?autoplay=, un modo demo/atracción). Abre cada URL por turno y dispara cada intervalMs — la forma más fácil de obtener muchos fotogramas reales por fase.

Configuración (capturas de pantalla)

url, ready, target {width,height} (tamaño de salida + viewport), viewport (tamaño de captura cuando deba diferir; los fotogramas se recortan a la relación de aspecto objetivo, cropY ancla la banda), superSample (2), launchArgs, executablePath, css (inyectado; la forma más simple de ocultar HUD para HUDs DOM), routes [{match, replace: [[from,to]…]}] (parches solo de captura del código servido), 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. La documentación completa de los campos está en el encabezado de scripts/screenshots.cjs.

Playwright debe ser resoluble: ejecuta desde un directorio que lo tenga en node_modules, o NODE_PATH=<dir>/node_modules node ${CLAUDE_SKILL_DIR}/scripts/screenshots.cjs … (CommonJS). Si el navegador instalado es más antiguo que el paquete, el script recurre al Chromium más reciente en el caché ms-playwright.

Ejemplo (WebGL / three.js, auto-reproducción, HUD DOM)

{
  "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
  }
}

Banner de 1024x500 a sangre y fondo para un juego solo vertical (convierte en buzón los viewports anchos, y su mundo termina más allá de la vista vertical): levanta el límite de relación de aspecto solo para la captura, captura un viewport más cuadrado a 2x, recorta la banda.

{
  "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}
}

Ejemplo (Phaser, vertical; dev expone window.__game)

Un ejemplo resuelto, de un juego específico (un juego de pasar una pelota por un aro). Sustituye tus propios nombres de escena/objeto, lógica de pose y condiciones de conducción/selección.

// phone.shots.cjs
const G = () => window.__game.scene.scenes[0]; // ayuda en página (en línea abajo)
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,            // toque de confianza para comenzar (Phaser ignora eventos sintéticos)
    hideHud: false,            // mantener la puntuación visible en las capturas de juego
    start: () => { window.__game.scene.scenes[0].goals = 6; }, // configuración extra (p. ej., habilitar variedad)
    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; },
  },
};

Conjunto para tableta: la misma configuración con target: { width: 1440, height: 2560 }. (También puedes redimensionar las tomas de teléfono elegidas a 1440x2560 para mantener los fotogramas exactos elegidos.)

Animación posada (scripts/pose-capture.cjs) — para juegos con postFX/bloom

El modo live asume que la física del juego se ejecuta en headless. A menudo no es así. Una canalización WebGL bloom / postFX puede BLOQUEAR el bucle de renderizado+física headless, por lo que el sujeto se congela en su aparición (la velocidad se establece pero la posición nunca se integra) — cada fotograma "en vivo" sale idéntico, el sujeto atascado en su aparición. Esto afectó mucho en un proyecto real. Síntoma a comprobar primero: sondea la y del sujeto en dos sondeos; si nunca cambia, la física está bloqueada.

Cuando eso suceda, no luches contra ello — conduce el sujeto manualmente:

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

Cada toma pausa el mundo, luego anima el sujeto a lo largo de una ruta QUE TÚ DEFINES sobre N fotogramas (con cualquier estela/emisor de partículas en ejecución), de modo que se construya una estela de movimiento real independientemente de la física. Configuración por toma: {name, setup, poseAt(t), steps, stepMs} — setup se ejecuta una vez y pone la escena en el estado de la toma (p. ej., en un juego de pelota y compuerta: genera la compuerta, state="playing", world.pause(), trail.start(), oculta la pista de listo, establece una puntuación), poseAt(t) posiciona el sujeto para t en [0..1].

Qué hace que una estela posada se lea como movimiento real (el sujeto del juego de ejemplo cae bajo gravedad; adáptalo a cómo se mueve el tuyo):

  • Trayectoria Bezier, no una interpolación lineal. Una trayectoria lineal produce una estela rígida, recta como una regla; una bezier cuadrática con un desplazamiento lateral del punto de control da el arco curvo natural que el juego realmente produce. Varía la curva por toma.
  • Facilidad de entrada t*t (acelerando) se lee como gravedad.
  • Aumento moderado del emisor solo para la toma (mayor frequency + mayor lifespan), porque la animación manual es más lenta que una caída rápida real y el emisor por defecto deja muy pocas partículas. Aumenta MODERADAMENTE — una estela demasiado intensa no se parece en nada al juego.

Feature graphic

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

Compone: fondo oscuro → captura de pantalla real del juego (izquierda, marco neón) → título + eslogan (derecha, fuente incluida) → línea de acento. Renderizado a superSamplex y luego reducido.

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

Los valores de ejemplo a continuación son de un juego específico — sustituye tu propio título, eslogan, colores y fuente.

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

Lecciones (haz esto)

  • WebGL en headless necesita SwiftShader. Sin --use-angle=swiftshader --enable-unsafe-swiftshader un canvas de three.js es negro. Es lento: abre una página a la vez (5 páginas WebGL paralelas agotaron el tiempo de espera de la llamada de captura de pantalla).
  • Ocultar el HUD con CSS: no ocultes el padre del canvas. Un div { visibility: hidden } genérico también ocultó el canvas (cada fotograma idéntico, ~8 KB). Sonda el DOM una vez y excluye el contenedor del canvas por ID. Archivos idénticos y diminutos = nada renderizado.
  • Juegos solo verticales y banners apaisados. Un límite de relación de aspecto máxima convierte en buzón un viewport ancho (una columna estrecha en el medio). Levántalo por captura mediante routes; los archivos del juego s...

...y evita el uso de componentes físicos en modo headless si no es necesario.

Individual skills in this repo

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

Skills relacionados