Communitygithub.com

takumifukasawa/agent-skills

実行中のゲームからGoogle Playストアの掲載用アセット(フィーチャーグラフィック、スクリーンショット、掲載テキスト)を生成します。

agent-skills とは?

このスキルは、実際に動作しているゲームから忠実なストア画像を生成するためのツールです。モックアップではなく、ヘッドレスChromium上で実際のゲームをキャプチャします。主な成果物は3つあります。1つ目は、ストアサイズに合わせたスクリーンショット(`scripts/screenshots.cjs`)、2つ目は1024x500のフィーチャーグラフィック(`scripts/feature_graphic.py`)、3つ目は掲載テキスト(短い説明/詳しい説明)です。ワークフローは「多くの候補を撮影 → ユーザーが選択」という流れで、選択を容易にするためのピッカーページ(Artifact)も提供されます。このスキルは、Phaser/PixiJS/Three.js/生のCanvasで動作するローカルで提供されるゲームに対応しており、Claude Codeや類似のエージェントで使用することを想定しています。また、`game-icon-from-screenshot`スキルの兄弟スキルであり、アプリアイコンの候補も同じピッカーで選べます。

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

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

実行中のゲームからストアアセットを生成する

実際に動作しているゲームをキャプチャして、忠実なストア画像を生成します(モックアップではありません)。再利用可能なハーネスは scripts/ にあり、ゲーム固有の小さな設定(URL、準備完了確認、ゲームのポーズ/非表示/操作、タイトルテキスト)を指定します。

3つの成果物:

  1. スクリーンショット (scripts/screenshots.cjs) — 正確なストアサイズで一括出力します。
  2. フィーチャーグラフィック — 1024x500のプロモーションバナー。ランドスケープの target でゲームフレームを全面に表示するか、フレーム+タイトル構成(scripts/feature_graphic.py)かを選びます。デフォルトは気分重視のゲームでは全面表示です。
  3. 掲載テキスト — 短い説明+詳しい説明: references/listing-text.md(制限、トーン、主張してはいけないこと、「AI生成アセット」フォームの質問)を参照してください。

ストアサイズの要件とプリセットについては references/play-store-sizes.md を参照してください。

ワークフロー: 多くの候補 → ユーザーが選択

ユーザーが選択します。あなたはバリエーションを提供し、選択を容易にします。

  1. 各ステージ/レベル/テーマごとに多くのフレームをキャプチャします(例: 5ステージ×8〜10枚)。ゲームの進行度も利用してバリエーションを付けます(初期/中期/後期のアップグレード段階)。初期のフレームは空っぽに見え、後期のフレームは被写体が埋もれる可能性があります。
  2. バリエーションを持たせつつ約15枚に絞り込みます(各ステージ、成功/失敗の瞬間、豊かな背景と落ち着いた背景)。最初に scripts/contact_sheet.py(番号付き)で自分で確認します。次にユーザーに選択させます — ピッカーページ(後述)が利用可能な場合はそれを使用します(ユーザーは一度見るとそれを要求します)。そうでない場合は番号付きシート画像を送り、番号を受け取ります。
  3. セットを推奨し、その理由を説明します(色/ステージのバラエティがセット全体にわたっていること、同じ瞬間を繰り返さないこと)。ユーザーのコメントに応じて再撮影し(「被写体をもう少し中央に」)、そのリテイクを同じピックキーを持つ別のグループとしてピッカーに追加します。
  4. 最終セットをアップロード順に1つのフォルダにコピーし(1-…png、2-…)、決定事項(ファイル、選択されたテキストの原文、却下されたオプションとその理由、キャプチャ方法)をプロジェクトのドキュメントに記録します。生成された画像は、プロジェクトが既にストアアセットを追跡している場合を除き、git には含めないでください。

python3 ${CLAUDE_SKILL_DIR}/scripts/contact_sheet.py sheet.png a.png b.png … --cols 5 --thumb 300 (基本的なPlaywright/Canvas技術は game-icon-from-screenshot と共有されています。)

ピッカーページ(Artifact): ユーザーがセットを選択

python3 ${CLAUDE_SKILL_DIR}/scripts/build_picker.py picker.json picker.html を実行し、picker.html をArtifactツールで公開します。capabilities: {"db": {}} を指定します(Artifactツールが必要なため、最初に artifact-design / artifact-capabilities スキルをロードしてください)。仕様形式は scripts/build_picker.py のヘッダーにあります。

このページが行うこと(すべて仕様通りで、テンプレートにはゲーム固有のものはありません):

  • 画像グループ — mode: "multi"(スクリーンショット: チェック順 = アップロード順、← → で並べ替え、max あり)または "single"(フィーチャーグラフィック、アプリアイコン)。複数のグループが1つの pick キーを共有できます(最初の候補+リテイク = 1つの選択肢)。rec からの「おすすめ」チップ、⤢ ズーム、上部に選択セットを表示するトレイ、「おすすめで埋める」ボタン。
  • テキストグループ — 提出テキスト、その下に翻訳、limit に対する文字数カウント、各オプションの右上にコピーボタン(提出テキストのみコピー)を備えたラジオオプション。
  • コメントを指示として — グループごとのコメントボックス(noteKey)と全体への「全体への指示」。ユーザーはそこに指示を書き(「2 案目の最後の行はいらない」、編集および翻訳する日本語ドラフト)、「保存しました」と言います。
  • 選択肢 — 小さなはい/いいえのラジオボタン(フィーチャーグラフィックにタイトルテキストを入れるかどうか)。
  • すべては1つのdbドキュメント(doc、デフォルト picks/current)に保存されます。ArtifactData(action: "get", collection: "picks", doc_id: "current" がデフォルト)で読み取ります。ピック/選択肢/ノートキーはそのドキュメントを共有するため、ビルダーは、ユーザーが保存したと言うたびに、キーの衝突や重複したアイテムIDを拒否します。空でないすべてのノートに基づいて行動し、何が変更されたかを伝えます。ページを変更するには、specを編集して再ビルドし、同じファイルパス(同じURL)で公開します。保存された選択はIDが同じままなので保持されます。最初の公開後、picks を1回読み取ってストアの回答を確認します。

アプリアイコンも同じページを使用します。アイコンのみのピッカー(1つの "single" グループ、aspect: "1/1"、thumbWidth: 512)またはストア画像の隣のもう1つのグループとしてアイコンを表示します。候補は game-icon-from-screenshot で作成します。

前提条件

  • ローカルURLで実行中のゲーム(npm run dev → 例: http://localhost:5173)。
  • Playwright + Chromium(プロジェクトの node_modules、npxキャッシュ、またはグローバルから自動解決。それ以外の場合は npx playwright install chromium)。
  • Pillow(python3 -m pip install Pillow) — リサイズ/合成用。
  • フィーチャーグラフィックのタイトル用のバンドルフォントファイル(.ttf。可変フォントはウェイト軸で対応)。

アンチエイリアシング: スーパーサンプリング、アップスケールはしない

正確な出力サイズでキャプチャするとギザギザになります。高密度でキャプチャし、ダウンスケールします。superSample(deviceScaleFactor)を2に設定し、ハーネスがターゲットサイズにダウンスケールします。重要: 密度は superSample で行い、はるかに大きなビューポートを使用しないでください。非常に大きなキャンバスはヘッドレスレンダリング/物理ループを停止させる可能性があります(ゲームがスポーン時にフリーズします)。ゲームが実際に動作するサイズ(例: 1080x1920)でビューポートを維持し、代わりに superSample を上げてください。

スクリーンショット

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

  • 静的ポーズショット (shots): それぞれ新しいページを開き、pose 関数を実行し(スコア設定、被写体の位置調整、結果オーバーレイの表示など)、オプションでHUDを非表示にし、キャプチャします。準備画面や結果画面に適しています。
  • ライブゲームプレイ (live): プレイを開始し、ポーリングごとに keepAlive 関数を実行してゲームをアクティブ/操作し続け(「オートパイロット」)、pick 関数がフレームが良いと判断したときにキャプチャします。ライブフレームは実際の動きや軌跡を示します — これが「実際にプレイしている」ショットを取得する方法であり、静的なものではありません。多くキャプチャし、ユーザーに選択させます。

fn フィールドはページ内で実行されます。.cjs 設定では実際の関数を記述します(.toString() でシリアライズされるため、自己完結型に保ちます — Nodeクロージャは使用しないでください)。.json 設定ではアロー関数のソース文字列を記述します。

  • タイマー (timed): 自分でプレイできるゲーム(?autoplay= クエリ、デモ/アトラクトモードなど)向け。各URLを順に開き、intervalMs ごとに撮影します — ステージごとに多くの実際のフレームを取得する最も簡単な方法です。

設定(スクリーンショット)

url、ready、target {width,height}(出力サイズ+ビューポート)、viewport(異なる場合のキャプチャサイズ。フレームはターゲットアスペクトにクロップされ、cropY がバンドをアンカーします)、superSample(2)、launchArgs、executablePath、css(注入される。DOM HUDの最も簡単な非表示方法)、routes [{match, replace: [[from,to]…]}](キャプチャのみの提供ソースのパッチ)、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。完全なフィールドドキュメントは scripts/screenshots.cjs のヘッダーにあります。

Playwrightは解決可能でなければなりません。node_modules にそれが含まれるディレクトリから実行するか、NODE_PATH=<dir>/node_modules node ${CLAUDE_SKILL_DIR}/scripts/screenshots.cjs …(CommonJS)を実行します。インストールされたブラウザがパッケージより古い場合、スクリプトは ms-playwright キャッシュ内の最新のChromiumにフォールバックします。

例(WebGL / three.js、自己再生、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
  }
}

ポートレートのみのゲーム(ワイドビューポートをレターボックス化し、ポートレートビューを超えるとワールドが終了する)の全面1024x500バナー: キャプチャ時のみアスペクト制限を解除し、2倍でより正方形のビューをキャプチャし、バンドをクロップします。

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

例(Phaser、ポートレート。devが window.__game を公開)

ある特定のゲーム(玉をリングに通すゲーム)の実際の作業例です。自分のシーン/オブジェクト名、ポーズロジック、ドライブ/ピック条件に置き換えてください。

// phone.shots.cjs
const G = () => window.__game.scene.scenes[0]; // ページ内ヘルパー(以下にインライン化)
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,            // 信頼できるタップで開始(Phaserは合成イベントを無視)
    hideHud: false,            // ゲームプレイショットではスコアを表示
    start: () => { window.__game.scene.scenes[0].goals = 6; }, // 追加セットアップ(例: バリエーションを有効にする)
    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; },
  },
};

タブレットセット: 同じ設定で target: { width: 1440, height: 2560 }。(選択した電話ショットを1440x2560にアップスケールして、選択したフレームを正確に維持することもできます。)

ポーズアニメーション (scripts/pose-capture.cjs) — postFX/ブルームゲーム向け

live モードは、ゲームの物理がヘッドレスで実行されることを前提としています。しかし、多くの場合そうではありません。 WebGLのブルーム/postFXパイプラインはヘッドレスレンダリング+物理ループを停止させる可能性があり、被写体はスポーン時にフリーズします(速度は設定されますが、位置は決して統合されません)。すべての「ライブ」フレームが同一になり、被写体はスポーン時に固まってしまいます。これは実際のプロジェクトで深刻な問題でした。最初に確認すべき症状: 2回のポーリングで被写体の y をプローブし、それが決して変わらない場合、物理は停止しています。

その場合は、戦わないでください — 被写体を手動で操作します:

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

各ショットはワールドを一時停止し、あなたが定義したパスに沿って被写体をNフレームかけてアニメーションさせます(任意のトレイル/パーティクルエミッターを実行)。これにより、物理に関係なく実際のモーショントレイルが構築されます。ショットごとの設定: {name, setup, poseAt(t), steps, stepMs} — setup は1回実行され、シーンをショットの状態に設定します(例: 玉とゲートのゲームでは、ゲートをスポーン、state="playing"、world.pause()、trail.start()、準備ヒントを非表示、スコアを設定)。poseAt(t) は [0..1] の t に対して被写体を配置します。

ポーズされたトレイルを実際の動きとして認識させるには(例のゲームの被写体は重力下で落下します。自分のゲームの動きに合わせて調整してください):

  • ベジェパス、直線的なlerpではない。 直線パスは硬く、定規で引いたようなトレイルになります。制御点を横にオフセットした2次ベジェは、ゲームが実際に生成する自然な曲線アークを与えます。ショットごとに曲線を変えてください。
  • イーズイン t*t(加速)は重力として読み取れます。
  • ショットのみの適度なエミッターブースト(より高い frequency + より長い lifespan)。手動アニメーションは実際の高速落下よりも遅く、デフォルトのエミッターはパーティクルが少なすぎます。適度にブーストしてください — 過剰にジュースを注入したトレイルはゲームとはまったく似ていません。

フィーチャーグラフィック

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

構成: 暗い背景 → 実際のゲームスクリーンショット(左、ネオンフレーム) → タイトル + タグライン(右、バンドルフォント) → アクセントライン。superSample倍でレンダリングされ、ダウンスケールされます。

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

以下の例の値はある特定のゲームのものです。自分のタイトル、タグライン、色、フォントに置き換えてください。

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

教訓(これらを実行すること)

  • ヘッドレスでのWebGLにはSwiftShaderが必要です。 --use-angle=swiftshader --enable-unsafe-swiftshader がないと、three.jsのキャンバスは真っ黒になります。速度は遅いです。一度に1ページだけ開いてください(5つの並列WebGLページはスクリーンショット呼び出しをタイムアウトさせました)。
  • CSSでのHUD非表示: キャンバスの親を非表示にしないでください。 包括的な div { visibility: hidden } はキャンバスも非表示にします(すべてのフレームが同一、約8KB)。DOMを一度プローブし、IDでキャンバスラッパーを除外してください。同一の小さなファイル = 何もレンダリングされていません。
  • ポートレートのみのゲームとランドスケープバナー。 最大アスペクト制限はワイドビューポートをレターボックス化します(中央の狭い列)。routes を使用してキャプチャごとに解除します。ゲームファイルは…

[以下、元のSKILL.mdの続きが翻訳されますが、これは完全な翻訳ではありません。原文が切り詰められているため、この翻訳もその時点で終了しています。]

Individual skills in this repo

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

関連スキル