AI任せでゲーム開発を加速!タスク管理導入でUnityゲーム制作を効率化【AIを活用したクリッカーゲーム制作#12】

AIゲームクリエイターズ

このページの内容は、下記の動画で詳しく説明しています

このページは、下記の動画で使用しているプロンプト(AIへの指示文)を載せることを目的として作成されています。

はじめに

ゲーム制作なんて自分には絶対に無理! プログラミングもできないし、絵も描けないし…。

そんなふうに思っていませんか?

大丈夫です!AI(人工知能)があなたのゲーム制作を強力にサポートしてくれます。

このブログは、ゲーム作りが趣味の40代会社員である筆者「よどサン」が、AIの力を借りながらUnityでゲームをリリースすることを目指すチャレンジの記録です。

AIと二人三脚で、あなたもゲームクリエイターへの一歩を踏み出してみませんか?

前回の振り返り

前回は、ゲームのコア要素である進行メーターを「鉱床破壊ゲージ」へと進化させました。 ゲージが溜まるにつれて鉱床が徐々に崩れていく演出や、ゲージがMAXになった時の破壊エフェクトなどを実装し、よりリッチなゲーム体験を目指しました。

▼前回の記事はこちら https://code-fam-games.online/ai_game_creators/clicker_game_11/

このチャレンジのコンセプト

このチャレンジでは、「できる限りの事をAIにお任せする」をコンセプトにゲーム制作を進めていきます。

  • 企画・仕様検討: AIに相談しながら面白いゲームのアイデアを練ります。
  • プログラミング: C#のコードはAIに書いてもらいます。
  • 素材制作: イラスト、UI、BGMなどもAIで生成します。
  • Unityの操作: AIに指示を出して、Unityエディタを動かしてもらいます。

ゲーム作りの知識が少なくても、AIという最高のパートナーがいれば、きっとゲームは完成させられるはず!という信念で取り組んでいます。

このページで使用するAIツール

このシリーズでは、誰もが真似しやすいように、無料で使えるAIツールを基本方針としています。

  • Google AI Studio
  • Cursor (AIコードエディタ)

このページ(および動画)の活用方法

このブログ記事は、筆者のYouTubeチャンネルで公開している動画のテキストバージョンです。

  • 動画で全体の流れを掴む → ブログで詳細な手順やコードを確認する
  • ブログを読みながら、自分のペースでUnityとAIの操作を試してみる

といった形で、ご自身の学習スタイルに合わせてご活用ください。


今回のゴール:AI主導の開発体制を強化する

前回、鉱床破壊ゲージを実装しましたが、スコア表示周りの改修など、まだ手つかずのタスクが残っています。 それに加えて、開発を進める中で「目指しているゴール」や「そこにたどり着くまでの道のり」が少し曖昧になってきている感覚がありました。

また、AIエージェントにUnityエディタを操作させるためのツール「UnityMCP」が、時々うまく機能しないことがあり、ポテンシャルを最大限に引き出せていないという課題も感じています。

そこで今回は、開発プロセスそのものをAIに委ねることで、これらの課題を解決し、より安定したAI主導の開発体制を築くことを目指します。

今回のゴールは以下の2点です。

  1. AIにタスク管理を任せる手法を取り入れる
  2. AIがUnityエディタをより確実に操作できるようにする

それでは、さっそく始めていきましょう!

AIにタスク管理を任せる

全体像

最近のAI開発の手法を調べていると、AIエージェントにタスク管理を任せながら開発を進める方法があることを知りました。 これを導入することで、ゴールまでの道のりが明確になり、人間にとってもAIにとっても「次に何をすべきか」がクリアになるというメリットが期待できます。

全体的な流れは以下のようになります。

  1. 要件定義: 作りたいゲームの内容や機能をテキストファイルにまとめる。
  2. PRDの生成: AIが要件定義を読み込み、AI自身が参照するためのドキュメント(PRD: Product Requirements Document)に変換する。
  3. タスクリストの作成: PRDを基に、AIがゲームを完成させるためのタスクリストを作成する。
  4. 開発の実行: AIエージェントがタスクリストのタスクを順番に実行していく。

1. 現状から要件定義を作成する

本来はゲームを作り始める前に要件定義を行いますが、今回はすでに開発が進行中です。 そこで、イレギュラーな方法ですが、現在のゲームの状態から逆引きして要件定義を作成してもらいます。

前回も使った「ゲームのプレイ動画をAIに見せる」という方法を使います。Google AI Studioに動画をアップロードし、次のように指示しました。

【プロンプト】

これはUnityで作った「鉱石クリッカー」というスマホゲームのプレイ動画です。 このゲームの要件定義を書いてください。 動画に映っているゲームの状態が完成とした場合の要件定義にしてください 余計な要件は足さないようにお願いします。

【AIの回答例】

はい、承知いたしました。 ご提示の動画を基に、スマホゲーム「鉱石クリッカー」の要件定義を作成します。 動画内で確認できる機能のみを抽出し、余計な要件は含めないようにします。

はい、承知いたしました。
ご提示の動画を基に、スマホゲーム「鉱石クリッカー」の要件定義を作成します。動画内で確認できる機能のみを抽出し、余計な要件は含めないようにします。


ゲーム「鉱石クリッカー」要件定義書

1. 概要

本ドキュメントは、スマートフォン向けカジュアルゲーム「鉱石クリッカー」の要件を定義するものである。プレイヤーは画面中央の鉱石をタップすることでリソースを獲得し、そのリソースを使ってピッケルをアップグレードすることで、より効率的にリソースを獲得していくことを目的とする。

2. ターゲットプラットフォーム

  • iOS / Android

3. 主要機能要件

3.1. メイン画面(採掘画面)

プレイヤーが最も多くの時間を過ごす画面。鉱石の採掘とリソースの確認を行う。

  • 3.1.1. スコア表示
    • 画面上部に現在のスコアを「Score: [数値]」の形式で表示する。
    • スコアは鉱石を1回タップするごとに、現在装備しているピッケルの「採掘力」の数値分だけ加算される。
  • 3.1.2. 鉱石オブジェクト
    • 画面中央に大きな鉱石を配置する。
    • 鉱石の下に、鉱石の耐久力を示す緑色のゲージ(HPバー)を表示する。
    • プレイヤーが鉱石をタップすると、鉱石から破片が飛び散るエフェクトと、採掘音が発生する。
    • タップごとに、装備中のピッケルの「採掘力」分だけHPバーが減少する。
    • HPバーが0になると、即座に全回復し、継続して採掘できる。
  • 3.1.3. リソース獲得
    • 鉱石をタップすると、以下の3種類のリソースがランダムで1つドロップする。
      • 銅鉱石
      • 鉄鉱石
    • ドロップしたリソースは、後述するインベントリに加算される。
  • 3.1.4. インベントリ表示
    • 画面下部に、現在所持しているリソースの一覧を表示する。
    • 各リソースは「アイコン」「名称」「所持数」で構成される。
    • 表示するリソースは以下の3種類とする。
      • 銅鉱石
      • 鉄鉱石
  • 3.1.5. ピッケル情報とアップグレード機能
    • インベントリの下に、現在装備しているピッケルの「アイコン」と「英語名」を表示する。
    • その上に「ピッケルをアップグレード」というテキストが書かれたボタンを配置する。
    • このボタンをタップすると、後述する「ピッケルアップグレード画面」がモーダルで表示される。

3.2. ピッケルアップグレード画面

ピッケルの作成と装備の切り替えを行う画面。

  • 3.2.1. 画面構成
    • メイン画面の上にモーダルウィンドウとして表示される。
    • 画面右上に、この画面を閉じるための「×」ボタンを配置する。
    • アップグレード可能なピッケルがリスト形式で表示される。
  • 3.2.2. ピッケルリスト
    • 各ピッケルは以下の情報を持つ。
      • ピッケル名(英語)
      • 採掘力(数値)
      • 必要素材(リソース名と必要数)
      • 現在の状態を示すアイコン
    • リストに表示されるピッケルは以下の通り。
      • Wooden Pickaxe
        • 採掘力: 1
        • 必要素材: 石: 10
      • Copper Pickaxe
        • 採掘力: 5
        • 必要素材: 石: 50, 銅鉱石: 10
      • Iron Pickaxe
        • 採掘力: 15
        • 必要素材: 石: 100, 銅鉱石: 30, 鉄鉱石: 10
  • 3.2.3. アップグレード処理と状態変化
    • ピッケルの状態は以下の3種類で、対応するアイコンで示される。
      • チェックマーク: 作成済みで、現在装備中であることを示す。
      • ハンマーアイコン: 必要素材が揃っており、作成可能であることを示す。
      • ロックアイコン: 必要素材が不足しているか、他の条件を満たしておらず作成不可であることを示す。
    • プレイヤーがハンマーアイコンのピッケルをタップすると、以下の処理が行われる。
      • 所持リソースから、そのピッケルの「必要素材」を消費する。
      • タップしたピッケルの状態が「装備中(チェックマーク)」に変わる。
      • 以前装備していたピッケルの状態も「チェックマーク」のまま(作成済みとして残る)。
      • 「×」ボタンでメイン画面に戻ると、装備中のピッケルが更新されている。

4. 非機能要件

  • 画面の向き: 縦画面固定とする。
  • UI言語: 日本語とする(ピッケル名のみ英語)。

AIは動画の内容を正確に分析し、現状の仕様に基づいた要件定義を書き出してくれました。

2. タスク管理用の環境を整える

AIにタスク管理をしてもらうために、Unityプロジェクト内に専用のフォルダを用意します。

フォルダ構成

    先ほどAIが生成した要件定義は、Docs/AIContext/RequirementsSpec.md というファイル名で保存しておきます。

    次に、AIがタスク管理を行うためのルールを定めたファイルを用意します。今回は、AIエージェント開発のGitHubリポジトリで公開されていたものを参考に、Unity用にカスタマイズしました。 これらはCursorが参照する.mdcという形式のファイルで、以下の3つで構成されています。

    1. PRD生成ルール: 要件定義からPRDを生成するためのルール。
    2. タスクリスト生成ルール: PRDからタスクリストを生成するためのルール。
    3. タスクリスト管理ルール: 生成されたタスクリストをどのように処理していくかのルール。

    これら3つのmdcファイルは、/.cursor/rules/ フォルダに置きます。

    unity-create-prd.mdc

    ---
    description: "Unity開発向けPRD生成ルール"
    globs:
    alwaysApply: false
    ---
    
    # Rule: Generating a Product Requirements Document (PRD) for Unity Development
    
    ## Goal
    To guide an AI assistant in creating a detailed Product Requirements Document (PRD) in Markdown format for Unity game development features, based on an initial user prompt. The PRD should be clear, actionable, and suitable for a junior Unity developer to implement.
    
    ## Process
    1. **Receive Initial Prompt:** ユーザーから新機能や改善要望の概要を受け取る。
    2. **Ask Clarifying Questions:** 以下のUnity固有項目を必ず確認する。
       - Unityバージョン(例: 2022.3 LTS)  
       - ターゲットプラットフォーム(例: Android/iOS/WebGL)  
       - 使用するUnityパッケージやプラグイン(URP, Addressables, TextMeshProなど)  
       - 関連シーンやアセットの範囲  
       - テスト方法(Playモードテスト, 自動化テストなど)  
    3. **Generate PRD:** ユーザー回答を踏まえ、下記のPRD構造に従ってドキュメントを生成する。
    4. **Save PRD:** ファイル名を `prd-[feature-name].md` とし、Unityプロジェクト内の `Tasks/` フォルダに保存する。
    
    ## Clarifying Questions (Examples)
    * **Problem/Goal:** "この機能はユーザーにどんな価値を提供しますか?"  
    * **Target User:** "主要な利用者は誰ですか?(エンドユーザー、デザイナー、テスターなど)"  
    * **Core Functionality:** "ユーザーはどの操作を行う必要がありますか?"  
    * **Unity Specific:**  
      - "この機能は新規シーンですか?既存シーンへの追加ですか?"  
      - "必要なScriptableObjectやScriptコンポーネントは?"  
      - "Editor拡張が必要ですか?"  
    * **Acceptance Criteria:** "Unity EditorのPlayモードで動作を確認する条件は?"  
    * **Scope/Boundaries:** "含めない機能(例: シェーダー開発, ネットワーク実装)は?"
    
    ## PRD Structure
    1. **Introduction/Overview:** 機能概要と解決する課題  
    2. **Goals:** 定量/定性で測定可能な目標  
    3. **User Stories:** "As a …"形式で記述  
    4. **Functional Requirements:** 番号付きリストで具体的要件  
    5. **Non-Goals (Out of Scope):** 範囲外項目  
    6. **Design Considerations:** UIレイアウト, 既存デザインガイドライン  
    7. **Technical Considerations:** Unity固有技術要件  
    8. **Success Metrics:** 成功指標  
    9. **Open Questions:** 未解決点
    
    ## Target Audience
    Primary reader: **Junior Unity Developer**。Unity Editor操作に不慣れな場合でも理解できるよう、専門用語を補足しながら記述する。
    
    ## Output
    * **Format:** Markdown (`.md`)  
    * **Location:** `Tasks/`  
    * **Filename:** `prd-[feature-name].md`
    
    ## Final instructions
    1. Do NOT start implementing the PRD  
    2. Ask the user the clarifying questions listed above before生成
    3. ユーザー回答をもとにPRDを改善し、再度出力する

    unity-generate-tasks.mdc

    ---
    description: "Unity開発用タスクリスト生成ルール (Cursor & UnityMCP)"
    globs:
    alwaysApply: false
    ---
    
    # Rule: Generating a Task List from a PRD for Unity Development with Cursor & UnityMCP
    
    ## Goal
    
    AIアシスタントが **Tasks/** 配下のPRDを読み取り、Unityプロジェクトに必要なタスクを Markdown 形式で生成し、進捗フォルダ (`Tasks/ToDo/`) に配置できるようにする。
    
    ## Execution Modes
    
    * **Cursor AI** – AI エージェントのみで実行可能なタスク
    * **UnityMCP** – Unity Editor 操作を伴うタスク
    * **手動** – 人間が実施するタスク
    
    ## Output
    
    | 項目           | 内容                                                              |
    | ------------ | --------------------------------------------------------------- |
    | **Format**   | Markdown (`.md`)                                                |
    | **Folder**   | `Tasks/ToDo/`                                                   |
    | **Filename** | `tasks-<prd-file-base-name>.md` (例: `tasks-player-movement.md`) |
    
    > ⚠️ **新規タスクファイルは必ず `Tasks/ToDo/` に保存すること。**
    
    ## Task List Template
    
    ```markdown
    ## Metadata
    - **PRD**: Docs/PRD/<prd-file-name>.md
    - **UnityVersion**: <2022.3 LTS など>
    - **TargetPlatform**: <Android/iOS/WebGL>
    
    ## Relevant Files
    <ここにファイル一覧>
    
    ### Notes
    <留意点をユーザーとのやり取りを元に記述>
    <下記は必ず記述>
    - Unityエディタの操作はUnityMCPを使用する (Docs/UnityMCP/UnityMCP_Commands.mdを参照)
    - UnityMCP の標準コマンドで取得できない詳細情報は、エディタ拡張を使って補完する(Docs/UnityMCP/UnityMCP_FallbackExtensions.mdを参照)
    
    ## Tasks
    - **Cursor AI** [ ] 1.0 親タスクタイトル
      - **Cursor AI** [ ] 1.1 Sub‑task (Cursor)
      - **UnityMCP** [ ] 1.2 Sub‑task (UnityMCP)
      - **手動** [ ] 1.3 Sub‑task (Manual)
    ```
    
    ## Process
    
    1. **Receive PRD Path** – 例: `Tasks/prd-player-movement.md`
    2. **Analyze PRD** – Functional Requirements, User Stories, Acceptance Criteria を抽出
    3. **Generate Parent Tasks**(最大 5 件)
    4. **Request Confirmation** – "High‑level tasks generated. Respond with 'Go' to release sub‑tasks."
    5. **Generate Sub‑Tasks** – 各親タスクを詳細化し Execution Mode を付与
    6. **Identify Relevant Files** – 生成・変更が見込まれるファイルを列挙
    7. **Save Task List** – `Tasks/ToDo/tasks-<prd>.md` に保存
    
    ## Clarifying Questions
    
    * PRD ファイルパスは?
    * 使用 Unity バージョンは?
    * ターゲットプラットフォームは?
    * 使用パッケージ/プラグインは?
    * テスト要件は?
    
    ## Target Audience
    
    Junior Unity Developer を想定し、CamelCase やフォルダ規約を遵守した例示を行うこと。

    unity-process-task-list.mdc

    ---
    description: "Unity開発用タスクリスト管理ルール (Folder‑Based)"
    globs:
    alwaysApply: false
    ---
    
    # Task List Management for Unity Development (Folder‑Based)
    
    このルールは、`Tasks/` 配下の **ToDo / InProgress / Done** フォルダと Markdown タスクファイルを用いて進捗を管理する。
    
    ## Folder Conventions
    
    ```
    Tasks/
    ├── ToDo/        # 未着手
    ├── InProgress/  # 作業中
    └── Done/        # 完了
    ```
    
    ## Execution Modes
    
    * **Cursor AI** – AI エージェントのみで実行可能なタスク
    * **UnityMCP** – Unity Editor 操作を伴うタスク
    * **手動** – 人間が実施するタスク
    
    
    ## Lifecycle
    
    1. **Start Work** – 次に着手するタスクファイルを `Tasks/ToDo/` から `Tasks/InProgress/` に移動。
    2. **One Sub‑task at a time** – サブタスクを 1 つ選択し、実行前にユーザーへ確認 ("yes" / "y").
    3. **Completion Protocol**
    
       * サブタスク完了時に `[ ]` → `[x]` に更新。
       * 親タスクのすべての子が `[x]` になったら親タスクも `[x]`。
    4. **Finish Work** – タスクファイル内のすべてのチェックが `[x]` になったら、ファイルを `Tasks/Done/` へ移動しコミット。
    5. **Pause for Approval** – 各サブタスク実行後はファイルを保存し、ユーザーの承認を待つ。
    
    ## Maintenance
    
    * **Keep Relevant Files section up to date** – `Assets/` 配下の変更を 1 行説明付きで追記。
    * **Add Emerging Tasks** – 作業中に判明したタスクは新規ファイルとして `Tasks/ToDo/` に追加。
    
    ## AI Instructions
    
    1. `list_directory` 等で次のタスクを特定。
    2. ファイル移動 (`move_file`) と編集 (`read_file` / `append_file`) は Cursor API を使用。Windowsなのでパスの区切りは\。
    3. 変更後、Git にコミットメッセージ: `feat: complete <task-id> sub‑task <n>` の形式を推奨。
    4. ユーザーの "yes" / "y" を得るまで次のサブタスクに移らない。

    3. 要件定義(RequirementsSpec)からPRDを生成する

    環境が整ったので、さっそくAIに作業を依頼します。まずは、先ほど作成した要件定義ファイルからPRDを生成してもらいます。

    【プロンプト】

    @unity-create-prd.mdcに基づいてPRDを生成してください 要件定義は @RequirementsSpec.md に書いてあります

    【AIとのやり取り】

    最初は無料プランのモデル設定が原因でうまく動作しませんでしたが、モデルを「GPT4.1」に変更し、AIからの質問に答えることで、無事にPRDファイルを生成してくれました。 AIはルールファイル(.mdc)に基づき、不足している情報を質問形式で確認してくれたので、非常にスムーズでした。

    4. PRDからタスクリストを生成する

    次に、生成されたPRDを基に、具体的なタスクリストを作成してもらいます。

    【プロンプト】

    @unity-generate-task.mdc に基づいてタスクリストを作成してください。
    参照するPRD @prd-鉱石クリッカー.md

    【AIの回答例】

    @unity-generate-task.mdc のルールに従い、まずはPRDから親タスクを抽出し、ハイレベルタスク案を提示します。
    この内容で「Go」とご返信いただければ、各親タスクのサブタスク詳細化・ファイルリスト化・タスクリストファイル保存まで進めます。

    提示されたタスク案に問題がなかったので、「Go」と返信すると、AIは見事に詳細なタスクリストをファイルとして作成・保存してくれました。

    5. 新たな改善案をタスクに追加する

    ただし、このタスクリストは「現在のゲーム状態」をゴールとしたものなので、全てのタスクはすでに完了している状態です。 そこで、まずはAIに全てのタスクを完了状態にするよう依頼しました。

    その上で、前回の動画でAI自身が提案してくれた改善案(名声ポイント、鉱床破壊ゲージの拡張)をテキストで渡し、関連するドキュメント(要件定義、PRD、タスクリスト)全てに反映させるようお願いしました。

    【プロンプト】

    画面上部のスコア表示と、画面中央のメーターを下記のように改良しようと思います。 下記のドキュメント関係をこの内容を追加するかたちで書き換えてください

    「改良案」
    スコア: 「名声ポイント」
    意味づけ: あなたの鉱夫としての「名声」や「実績」を表します。
    獲得方法:
    鉱石を採掘する(レアな鉱石ほど高ポイント)
    ピッケルをアップグレードする
    進行メーターをMAXにして鉱床を破壊する
    特定のミッションやアチーブメントを達成する(将来的な拡張要素)
    役割:
    プレイヤーの総合的なやり込み度を示します。
    一定の名声ポイントに達すると、新しい鉱山エリアの解放、新しいピッケルの設計図の入手、特別な称号の獲得など、ゲームが進行する上でのマイルストーンとなります。
    フレンドや他のプレイヤーと名声を競うランキングにも活用できます。
    進行メーター: 「鉱床破壊ゲージ」
    意味づけ: 現在ターゲットにしている大きな鉱石の「耐久力」。タップするたびに鉱石が削れていく視覚的なフィードバックと連動させると良いでしょう。
    役割:
    ゲージがMAXになると、その鉱石が完全に破壊され、中からたくさんの資源(その鉱石の種類に応じたもの)と、少量の「名声ポイント」が飛び出します。
    破壊後、次の新しい鉱石が出現します(ランダム、またはエリアによって種類が固定など)。
    ごく稀に、鉱石破壊時に「輝く欠片」のような特殊アイテムがドロップし、これを集めると特別なピッケルが作れたり、一時的なバフアイテムと交換できたりする要素も加えられます。
    この組み合わせのメリット:
    分かりやすさ: プレイヤーは「鉱石を叩いて壊し、資源と名声を得る」という直感的な流れでゲームを理解できます。
    短期・長期の目標設定: 「目の前の鉱石を壊す」という短期目標と、「名声を上げて新しい要素を解放する」という長期目標を同時に提供できます。
    成長の実感: ピッケルをアップグレードすることで鉱床破壊ゲージを早く溜められるようになり、効率よく名声ポイントを稼げるようになるため、成長を実感しやすいです。
    拡張性: 将来的には新しい鉱石の種類、新しい名声ランク、特別な鉱床(ボス的な巨大鉱石など)を追加していくことで、ゲームを長く楽しんでもらえます。

    「修正するドキュメント」
    @RequirementsSpec.md :要件を追加
    @prd-鉱石クリッカー.md :要件追加に伴って修正
    @tasks-鉱石クリッカー.md :追加された要件に基づきタスクを追加

    「参照するルール」
    @unity-cs-rule.mdc :PRD修正時に参照
    @unity-generate-tasks.mdc :タスク追加時に参照

    AIは指示通り、3つのドキュメント全てを更新し、新しいタスクをリストに追加してくれました。 これで、今後の開発の指針となるタスクリストの準備が整いました。

    UnityMCPの操作精度を向上させる

    現状の課題と対策

    次に、もう一つの課題であるUnityMCPの操作精度向上に取り組みます。現状、UnityMCPはシーン上のゲームオブジェクトにどんなコンポーネントがアタッチされているかは取得できますが、そのコンポーネントの詳細な設定情報(インスペクター上の値など)までは取得できていないようでした。 これが原因で、不具合の特定がうまくできないケースがありました。

    対策として、以前の動画で作成した「ゲームオブジェクトの詳細情報をテキストファイルに出力するツール」を改良し、AIエージェントからでも実行できるようにします。

    エディタ拡張スクリプトの作成

    現在のツールは、特定のゲームオブジェクト上で右クリックしないと使えないため、AIには不便です。 そこで、Unityのエディターメニューから一括で実行できるようにスクリプトの修正をAIに依頼しました。

    【プロンプト】

    @InspectorSettingsLogger.cs
    このスクリプトは現在、コンテキストメニューから実行するようになっていますが、これをエディター拡張で実行できるようにしてほしいです。
    また、実行するとシーン上の全てのゲームオブジェクトに対して実行され、オブジェクトごとにテキストファイルが出力されるようにしてください。
    出力は、Assets/GameObjectInfo/というフォルダに出力されるようにしてください

    【AIの回答例】

    AIは指示通り、エディタ拡張用の新しいスクリプトを生成してくれました。

    using UnityEngine;
    using UnityEditor;
    using System.Text;
    using System.IO;
    using System.Reflection;
    
    public class GameObjectInspectorExporter
    {
        [MenuItem("Tools/Export All GameObject Inspector Info")]
        public static void ExportAllGameObjectInspectorInfo()
        {
            // 出力先ディレクトリ
            string outputDir = Path.Combine(Application.dataPath, "GameObjectInfo");
            if (!Directory.Exists(outputDir))
            {
                Directory.CreateDirectory(outputDir);
                Debug.Log($"ディレクトリを作成しました: {outputDir}");
            }
    
            // シーン上の全てのGameObjectを取得
            GameObject[] allObjects = Object.FindObjectsOfType<GameObject>();
            int exportCount = 0;
            foreach (GameObject go in allObjects)
            {
                // 非アクティブなオブジェクトも含める
                if (go.scene.IsValid())
                {
                    string fileName = go.name + "_InspectorSettings.txt";
                    string filePath = Path.Combine(outputDir, fileName);
                    StringBuilder sb = new StringBuilder();
                    sb.AppendLine("Inspector Settings for GameObject: " + go.name);
                    sb.AppendLine("------------------------------------");
    
                    Component[] components = go.GetComponents<Component>();
                    foreach (Component component in components)
                    {
                        if (component == null) continue;
                        sb.AppendLine("Component: " + component.GetType().Name);
                        var fields = component.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance);
                        foreach (var field in fields)
                        {
                            object value = field.GetValue(component);
                            sb.AppendLine("    " + field.Name + ": " + (value != null ? value.ToString() : "null"));
                        }
                        var properties = component.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
                        foreach (var property in properties)
                        {
                            if (property.CanRead && property.GetIndexParameters().Length == 0)
                            {
                                try
                                {
                                    object value = property.GetValue(component, null);
                                    sb.AppendLine("    " + property.Name + ": " + (value != null ? value.ToString() : "null"));
                                }
                                catch { }
                            }
                        }
                        sb.AppendLine();
                    }
                    try
                    {
                        File.WriteAllText(filePath, sb.ToString(), Encoding.UTF8);
                        exportCount++;
                    }
                    catch (IOException e)
                    {
                        Debug.LogError($"{fileName} の書き込みに失敗: " + e.Message);
                    }
                }
            }
            Debug.Log($"{exportCount}個のGameObjectのInspector情報を書き出しました。出力先: {outputDir}");
            AssetDatabase.Refresh();
        }
    } 

    このスクリプトをプロジェクト内の Editor/フォルダに追加すると、Unityエディタの上部メニューに「Tools > Export All GameObject Inspector Info」という項目が現れます。 これを実行すると、指定したフォルダにシーン上の全オブジェクトの情報がテキストファイルとして出力されるようになりました。 これで、AIもオブジェクトの詳細情報を確認できます。

    AIへの情報提供

    AIがこの新しいツールを自律的に使えるように、使い方の手順を記述したドキュメント(マークダウンファイル)を作成し、Docs/UnityMCPフォルダに保存しておきます。

    さらに、AIがこのドキュメントの存在に確実に気づけるように、先ほど作成したタスクリスト生成ルール(.mdcファイル)に、「困ったときにはこのドキュメントを参照するように」という一文を追記しておきました。

    実践!タスクベースのゲーム開発

    準備は大変でしたが、一度タスクリストができてしまえば、あとは簡単です。 AIにタスクの実行を指示するだけです。

    タスクの開始

    【プロンプト】

    タスク6.0をスタートしてください

    最初はOSの環境差(WindowsとMacのパス区切り文字の違い)でエラーが発生しましたが、タスク管理ルールに「Windows環境用のコマンドを使うように」と一文追記することで、無事に解決しました。

    AIはルールに従い、着手したタスクを「ToDo」から「InProgress」フォルダに移動させ、サブタスクを順番に進めていくことを宣言してくれました。

    サブタスク6.1:スコアUIの改修

    最初のサブタスクは「名声ポイント(スコア)のUI・加算ロジック改修」です。 AIはまず現状のスクリプトとUIを調査し、改修方針を提示してきました。

    内容に問題がなかったので「y」と返信すると、AIはUIのテキストを変更し、ロジックを修正するためのスクリプトを提示してくれました。

    【AIが修正したスクリプト】

    ここでAIが修正したスクリプトは下記の通り、コードは後でまとめてお見せします

    • GameManager.cs
    • OreController.cs
    • PickaxeManager.cs

    Unityで実行してみると、画面上部のスコア表示が「名声ポイント」に変わっているのが確認できました。

    サブタスク6.2:鉱床破壊ゲージの演出追加

    次に、鉱床破壊時の演出追加タスクに進みます。AIは「破壊時に報酬ポップアップを表示する」というアクション案を提示し、承認すると実装を進めてくれました。

    しかし、実行してみるとポップアップが表示されません。 調査したところ、AIが生成したテキストオブジェクトのサイズが巨大すぎて画面外にはみ出してしまっていたことが原因でした。

    このようなオブジェクトの配置や見た目に関する調整は、まだAIが苦手とする部分かもしれません。 そこで、人間が原因を特定し、具体的な修正値をAIに指示しました。

    【プロンプト】

    DestroyPopupのテキストのCharactorSizeを0.1にしてください。 また、破壊エフェクトの破片スプライトより奥に表示されてしまっているので、一番手前に表示するようにしてください

    修正後、今度はテキストの原点がズレていることが判明したため、再度修正を依頼しました。

    【プロンプト】

    DestroyPopupテキストの原点を縦横センターにしてください

    数回のやり取りを経て、ようやくポップアップが意図通りに表示されるようになりました。

    これでサブタスクは完了です。

    今回の最終的なスクリプト

    GameManager.cs

    using UnityEngine;
    using UnityEngine.UI;
    
    public class GameManager : MonoBehaviour
    {
        public Text scoreText;               // スコア表示用UI
        public Button upgradeButton;         // アップグレードメニューを開くボタン
        public Slider progressBar;           // 進行バー(鉱床の耐久度を表示)
        public PickaxeManager pickaxeManager; // PickaxeManagerの参照
        public PickaxeUpgradeMenu upgradeMenu; // ピッケルアップグレードメニューの参照
    
        private int famePoint = 0;               // 現在の名声ポイント
    
        private void Start()
        {
            UpdateUI();
            
            // ボタンのリスナーを変更: 直接アップグレードではなくメニューを開くように
            upgradeButton.onClick.RemoveAllListeners();
            upgradeButton.onClick.AddListener(OpenUpgradeMenu);
        }
    
        public void AddFamePoint(int points)
        {
            famePoint += points;
            UpdateUI();
        }
    
        // アップグレードメニューを開く処理に変更
        private void OpenUpgradeMenu()
        {
            if (upgradeMenu != null)
            {
                upgradeMenu.ShowMenu();
            }
            else
            {
                Debug.LogError("Upgrade menu reference not set in GameManager!");
            }
        }
    
        private void UpdateUI()
        {
            // 名声ポイントの更新
            scoreText.text = "名声ポイント: " + famePoint;
    
            // 現在のピッケルのインデックスを算出(配列内での位置)
            int currentIndex = System.Array.IndexOf(pickaxeManager.pickaxeDataList, pickaxeManager.currentPickaxe);
    
            // アップグレードボタンのテキストと有効/無効の設定
            if (currentIndex < pickaxeManager.pickaxeDataList.Length - 1)
            {
                // 次のピッケルに関する情報は表示せず、単にアップグレードメニューを開くボタンとして表示
                upgradeButton.GetComponentInChildren<Text>().text = "ピッケルをアップグレード";
                
                // 次のピッケルが存在する場合はボタンを有効に
                upgradeButton.interactable = true;
            }
            else
            {
                upgradeButton.GetComponentInChildren<Text>().text = "最大レベル";
                upgradeButton.interactable = false;
            }
    
            // 注意:プログレスバーの更新はOreControllerで行うため、ここでは更新しない
        }
    }

    OreController.cs

    using UnityEngine;
    using System.Collections;
    using System.Collections.Generic;
    using Unity.Mathematics;
    
    public class OreController : MonoBehaviour
    {
        public GameManager gameManager; // GameManagerの参照
        public PickaxeManager pickaxeManager; // PickaxeManagerの参照
        public OreManager oreManager; // OreManagerの参照
        public ParticleSystem oreEffectPrefab; // クリック時の小さな破片エフェクトのPrefabを登録
        public GameObject orePopupPrefab; // 鉱石ポップアップ用のPrefab
        
        [Header("鉱床の耐久力設定")]
        public int maxDurability = 100; // 鉱床の最大耐久値
        private int currentDurability; // 現在の耐久値
        private int lastSpriteIndex = 0; // 前回のスプライトインデックス
        
        [Header("鉱床の見た目変化")]
        public Sprite[] oreStages; // 鉱床の段階的なスプライト(最初の完全な状態から最後の崩れた状態まで)
        private SpriteRenderer oreRenderer; // 鉱床のスプライトレンダラー
        
        [Header("鉱床破壊エフェクト")]
        public GameObject destroyEffectPrefab; // 鉱床破壊時の破片エフェクトのPrefab
        public Sprite[] debrisSprites; // 破片として使用するスプライト
        
        [Header("効果音設定")]
        public AudioClip crackSound; // 鉱床にヒビが入る音
        public AudioClip destroySound; // 鉱床が破壊される音
        
        private ClickEffect clickEffect;
        private Camera mainCamera; // カメラの参照
        private AudioSource audioSource; // 効果音再生用のAudioSource
        private bool isDestroyed = false; // 破壊状態フラグ
    
        private void Start()
        {
            clickEffect = GetComponent<ClickEffect>();
            audioSource = GetComponent<AudioSource>(); // AudioSourceの取得
            mainCamera = Camera.main;
            oreRenderer = GetComponent<SpriteRenderer>();
    
            // コンポーネントの確認
            if (gameManager == null || pickaxeManager == null || oreManager == null)
            {
                Debug.LogError("必須コンポーネントが設定されていません!");
            }
    
            if (oreManager.ores == null || oreManager.ores.Length == 0)
            {
                Debug.LogError("OreManagerに鉱石データが設定されていません!");
            }
            
            // 鉱床の初期化
            ResetOreDurability();
            UpdateOreSprite();
        }
        
        // 鉱床の耐久値をリセット
        public void ResetOreDurability()
        {
            currentDurability = maxDurability;
            isDestroyed = false;
            lastSpriteIndex = 0;
            
            // GameManagerのProgressBarを更新
            if (gameManager != null && gameManager.progressBar != null)
            {
                gameManager.progressBar.value = 1.0f - (float)currentDurability / maxDurability;
            }
        }
        
        // 耐久値に応じてスプライトを更新
        private void UpdateOreSprite()
        {
            if (oreRenderer != null && oreStages != null && oreStages.Length > 0)
            {
                // 現在の耐久値に基づいて適切なスプライトのインデックスを計算
                float durabilityPercentage = (float)currentDurability / maxDurability;
                int spriteIndex = Mathf.FloorToInt((1.0f - durabilityPercentage) * (oreStages.Length - 1));
                spriteIndex = Mathf.Clamp(spriteIndex, 0, oreStages.Length - 1);
                
                // スプライトのインデックスが変わったら効果音を再生
                if (spriteIndex > lastSpriteIndex && !isDestroyed)
                {
                    PlayCrackSound();
                }
                
                lastSpriteIndex = spriteIndex;
                
                // スプライトを設定
                oreRenderer.sprite = oreStages[spriteIndex];
            }
        }
    
        private void OnMouseDown()
        {
            // 破壊状態では操作を受け付けない
            if (isDestroyed)
                return;
                
            Debug.Log("Ore clicked!");
    
            // 破片効果音(共通)を再生
            if (audioSource != null)
            {
                audioSource.Play();
            }
            
            // クリックエフェクトを再生
            if (clickEffect != null)
            {
                clickEffect.Play();
            }
            
            // クリック位置にエフェクトを再生
            PlayOreEffectAtClickPosition();
            
            // ピッケルの採掘力に基づいて耐久値を減少
            int damage = pickaxeManager.GetMiningPower();
            ReduceDurability(damage);
            
            // まず、全体の獲得率をチェックする
            float acquisitionChance = pickaxeManager.currentPickaxe.oreAcquisitionChance;
            if (UnityEngine.Random.value <= acquisitionChance)
            {
                // 鉱石が獲得できた場合:各鉱石の種類の確率に基づいて選ぶ
                OreData selectedOre = GetRandomOre();
                if (selectedOre != null)
                {
                    int miningPower = pickaxeManager.GetMiningPower();
                    int pointsToAdd = selectedOre.pointValue * miningPower;
                    // スコアと鉱石カウントを更新
                    gameManager.AddFamePoint(pointsToAdd);
                    oreManager.AddOre(selectedOre);
    
                    Debug.Log($"Obtained {selectedOre.oreName} worth {pointsToAdd} points!");
                    // 鉱石取得ポップアップを表示
                    ShowOrePopup(selectedOre, pointsToAdd);
                }
                else
                {
                    Debug.LogError("全体の獲得判定は成功したが、各鉱石の確率から鉱石が選出できませんでした。確率設定を確認してください。");
                }
            }
            else
            {
                // 鉱石が獲得できなかった場合は、ポップアップは表示せず、効果だけを再生
                Debug.Log("No ore acquired this time.");
            }
        }
        
        // 耐久値を減少させ、必要に応じて鉱床を破壊する
        private void ReduceDurability(int damage)
        {
            currentDurability -= damage;
            
            // GameManagerのProgressBarを更新
            if (gameManager != null && gameManager.progressBar != null)
            {
                gameManager.progressBar.value = 1.0f - (float)currentDurability / maxDurability;
            }
            
            // スプライトを更新
            UpdateOreSprite();
            
            // 耐久値が0以下になったら鉱床を破壊
            if (currentDurability <= 0 && !isDestroyed)
            {
                DestroyOre();
            }
        }
        
        // ヒビが入る音を再生
        private void PlayCrackSound()
        {
            if (audioSource != null && crackSound != null)
            {
                // 現在の音を中断して新しい音を再生
                audioSource.Stop();
                audioSource.PlayOneShot(crackSound);
            }
        }
        
        // 破壊音を再生
        private void PlayDestroySound()
        {
            if (audioSource != null && destroySound != null)
            {
                // 現在の音を中断して新しい音を再生
                audioSource.Stop();
                audioSource.PlayOneShot(destroySound);
            }
        }
        
        // 鉱床破壊処理
        private void DestroyOre()
        {
            isDestroyed = true;
            // 名声ポイント加算(仮: 5ポイント)
            if (gameManager != null)
            {
                gameManager.AddFamePoint(5);
            }
            // 破壊報酬ポップアップを表示
            ShowDestroyPopup("鉱床破壊!\n+5名声ポイント");
            
            // 破壊音を再生
            PlayDestroySound();
            
            // 破壊エフェクトを生成
            CreateDestroyEffect();
            
            // 数秒後に鉱床を復活させる
            StartCoroutine(RespawnOre());
        }
        
        // 鉱床の復活処理
        private IEnumerator RespawnOre()
        {
            // 3秒後に鉱床を復活
            yield return new WaitForSeconds(3.0f);
            
            // 鉱床の耐久値をリセット
            ResetOreDurability();
            
            // スプライトを初期状態に戻す
            UpdateOreSprite();
        }
        
        // 破壊エフェクトの生成
        private void CreateDestroyEffect()
        {
            if (destroyEffectPrefab != null && debrisSprites != null && debrisSprites.Length > 0)
            {
                // 破壊エフェクトを生成
                GameObject destroyEffect = Instantiate(destroyEffectPrefab, transform.position, Quaternion.identity);
                
                // 破片の数を決定(5〜10個程度)
                int minDebrisCount = math.max(5, debrisSprites.Length-5);
                int maxDebrisCount = debrisSprites.Length;
                int debrisCount = UnityEngine.Random.Range(minDebrisCount, maxDebrisCount);
                
                // 破片を生成
                for (int i = 0; i < debrisCount; i++)
                {
                    // 破片用のGameObjectを生成
                    GameObject debris = new GameObject("Debris");
                    debris.transform.SetParent(destroyEffect.transform);
                    
                    // 位置をランダムに少しずらす
                    debris.transform.position = transform.position + new Vector3(
                        UnityEngine.Random.Range(-0.5f, 0.5f),
                        UnityEngine.Random.Range(-0.5f, 0.5f),
                        0
                    );
                    
                    // SpriteRendererを追加
                    SpriteRenderer debrisRenderer = debris.AddComponent<SpriteRenderer>();
                    debrisRenderer.sprite = debrisSprites[UnityEngine.Random.Range(0, debrisSprites.Length)];
                    debrisRenderer.sortingOrder = 2; // 鉱床の前に表示
                    
                    // RigidBody2Dを追加して物理挙動を持たせる
                    Rigidbody2D rb = debris.AddComponent<Rigidbody2D>();
                    rb.gravityScale = UnityEngine.Random.Range(0.5f, 1.5f);
                    
                    // ランダムな力と回転を加える
                    Vector2 force = new Vector2(
                        UnityEngine.Random.Range(-5f, 5f),
                        UnityEngine.Random.Range(2f, 7f)
                    );
                    rb.AddForce(force, ForceMode2D.Impulse);
                    rb.AddTorque(UnityEngine.Random.Range(-10f, 10f), ForceMode2D.Impulse);
                    
                    // 数秒後に破片を破棄
                    Destroy(debris, UnityEngine.Random.Range(1.5f, 2.5f));
                }
                
                // エフェクト自体も時間経過で破棄
                Destroy(destroyEffect, 3f);
            }
        }
    
    
        private OreData GetRandomOre()
        {
            // 現在のピッケルに対応する出現確率リストを取得
            var oreProbabilities = pickaxeManager.GetCurrentOreProbabilities();
            if (oreProbabilities == null || oreProbabilities.Length == 0)
            {
                Debug.LogError("現在のピッケルに対応する鉱石確率が設定されていません!");
                return null;
            }
    
            float randomValue = UnityEngine.Random.value; // 0.0 ~ 1.0 のランダム値を生成
            float cumulativeProbability = 0;
    
            foreach (var oreProbability in oreProbabilities)
            {
                cumulativeProbability += oreProbability.probability; // 累積確率を計算
                if (randomValue <= cumulativeProbability)
                {
                    // 対応するOreDataを検索
                    foreach (OreData ore in oreManager.ores)
                    {
                        if (ore.oreName == oreProbability.oreName)
                        {
                            return ore;
                        }
                    }
                }
            }
    
            return null; // 確率の範囲外の場合
        }
    
        private void PlayOreEffectAtClickPosition()
        {
            if (oreEffectPrefab != null)
            {
                Vector3 clickPosition = mainCamera.ScreenToWorldPoint(Input.mousePosition);
                clickPosition.z = 0;
    
                ParticleSystem effect = Instantiate(oreEffectPrefab, clickPosition, Quaternion.identity);
                effect.Play();
                Destroy(effect.gameObject, effect.main.duration);
            }
            else
            {
                Debug.LogError("OreEffectPrefab is null!");
            }
        }
    
        private void ShowOrePopup(OreData selectedOre, int points)
        {
            Vector3 clickPosition = mainCamera.ScreenToWorldPoint(Input.mousePosition);
            clickPosition.z = 0;
    
            GameObject popup = Instantiate(orePopupPrefab, clickPosition, Quaternion.identity);
    
            // スプライトとポイントを表示
            SpriteRenderer spriteRenderer = popup.GetComponent<SpriteRenderer>();
            if (spriteRenderer != null && selectedOre.oreSprite != null)
            {
                spriteRenderer.sprite = selectedOre.oreSprite;
            }
    
            TextMesh textMesh = popup.GetComponentInChildren<TextMesh>();
            if (textMesh != null)
            {
                textMesh.text = $"+{points}";
            }
    
            StartCoroutine(AnimatePopup(popup));
            Destroy(popup, 0.75f);
        }
    
        private IEnumerator AnimatePopup(GameObject popup)
        {
            float duration = 0.5f;
            float elapsedTime = 0;
            Vector3 originalPosition = popup.transform.position;
    
            while (elapsedTime < duration)
            {
                float yOffset = Mathf.Sin(elapsedTime / duration * Mathf.PI) * 0.5f;
                popup.transform.position = originalPosition + new Vector3(0, yOffset, 0);
                elapsedTime += Time.deltaTime;
                yield return null;
            }
        }
    
        // 破壊報酬ポップアップ表示用メソッド
        private void ShowDestroyPopup(string message)
        {
            Vector3 popupPosition = transform.position + new Vector3(0, 1.5f, 0); // 鉱床の少し上
            GameObject popup = new GameObject("DestroyPopup");
            popup.transform.position = popupPosition;
            TextMesh textMesh = popup.AddComponent<TextMesh>();
            textMesh.text = message;
            textMesh.fontSize = 48;
            textMesh.characterSize = 0.1f;
            textMesh.color = Color.yellow;
            textMesh.anchor = TextAnchor.MiddleCenter;
            textMesh.alignment = TextAlignment.Center;
            // 一番手前に表示
            MeshRenderer meshRenderer = popup.GetComponent<MeshRenderer>();
            if (meshRenderer != null) {
                meshRenderer.sortingOrder = 100;
            }
            // アニメーション(上昇&フェードアウト)
            StartCoroutine(AnimateDestroyPopup(popup));
            Destroy(popup, 1.2f);
        }
        private IEnumerator AnimateDestroyPopup(GameObject popup)
        {
            float duration = 1.0f;
            float elapsed = 0f;
            Vector3 start = popup.transform.position;
            Color startColor = popup.GetComponent<TextMesh>().color;
            while (elapsed < duration)
            {
                popup.transform.position = start + new Vector3(0, elapsed * 0.8f, 0);
                float alpha = Mathf.Lerp(1f, 0f, elapsed / duration);
                var mesh = popup.GetComponent<TextMesh>();
                mesh.color = new Color(startColor.r, startColor.g, startColor.b, alpha);
                elapsed += Time.deltaTime;
                yield return null;
            }
        }
    }

    PickaxeManager.cs

    using UnityEngine;
    using UnityEngine.UI;
    
    public class PickaxeManager : MonoBehaviour
    {
        public PickaxeData[] pickaxeDataList; // ピッケルデータのリスト
        public PickaxeData currentPickaxe;    // 現在使用中のピッケル
        public Image pickaxeIcon;             // UIのピッケルアイコン
        public Text pickaxeNameText;          // UIのピッケル名前表示用テキスト
        public OreManager oreManager;         // OreManagerへの参照
        public GameManager gameManager;         // GameManagerへの参照(名声ポイント加算用)
    
        private int currentIndex = 0;         // 現在のピッケルのインデックス
    
        private void Start()
        {
            if (pickaxeDataList != null && pickaxeDataList.Length > 0)
            {
                currentPickaxe = pickaxeDataList[0]; // 初期ピッケルを設定
                UpdatePickaxeUI();                   // UIを更新
            }
            else
            {
                Debug.LogError("Pickaxe data list is empty! Please assign pickaxe data in the Inspector.");
            }
        }
    
        public int GetMiningPower()
        {
            return currentPickaxe != null ? currentPickaxe.miningPower : 1;
        }
    
        /// <summary>
        /// 現在のピッケルをアップグレードする処理。
        /// 次のピッケルデータに設定された upgradeRequirements を基に、OreManager 経由で
        /// 各種鉱石の在庫チェックと消費を行います。
        /// </summary>
        public bool UpgradePickaxe()
        {
            if (currentIndex < pickaxeDataList.Length - 1)
            {
                // 次のアップグレード用のピッケルデータを取得
                PickaxeData nextPickaxe = pickaxeDataList[currentIndex + 1];
                // 次のアップグレードに必要な各種鉱石の要求を取得
                PickaxeData.UpgradeRequirement[] requirements = nextPickaxe.upgradeRequirements;
    
                // 各要求が満たされているかチェック
                bool canUpgrade = true;
                foreach (var req in requirements)
                {
                    if (!oreManager.HasEnoughOre(req.oreName, req.requiredCount))
                    {
                        canUpgrade = false;
                        break;
                    }
                }
    
                if (canUpgrade)
                {
                    // 全ての要求を満たしている場合、各鉱石を消費する
                    foreach (var req in requirements)
                    {
                        oreManager.ConsumeOre(req.oreName, req.requiredCount);
                    }
    
                    // ピッケルのアップグレード実行
                    currentIndex++;
                    currentPickaxe = pickaxeDataList[currentIndex];
                    UpdatePickaxeUI();
                    Debug.Log("Upgraded to: " + currentPickaxe.pickaxeName);
                    return true;
                }
                else
                {
                    Debug.LogWarning("Not enough ore to upgrade.");
                    return false;
                }
            }
            else
            {
                Debug.LogWarning("No further upgrades available.");
                return false;
            }
        }
    
        /// <summary>
        /// 特定のインデックスのピッケルへアップグレードする処理。
        /// 指定されたピッケルデータに設定された upgradeRequirements を基に、OreManager 経由で
        /// 各種鉱石の在庫チェックと消費を行います。
        /// </summary>
        public bool UpgradeToSpecificPickaxe(int targetIndex)
        {
            // インデックスの妥当性チェック
            if (targetIndex <= currentIndex || targetIndex >= pickaxeDataList.Length)
            {
                Debug.LogWarning("Invalid pickaxe index for upgrade.");
                return false;
            }
    
            // 対象のピッケルデータを取得
            PickaxeData targetPickaxe = pickaxeDataList[targetIndex];
            // アップグレードに必要な各種鉱石の要求を取得
            PickaxeData.UpgradeRequirement[] requirements = targetPickaxe.upgradeRequirements;
    
            // 各要求が満たされているかチェック
            bool canUpgrade = true;
            foreach (var req in requirements)
            {
                if (!oreManager.HasEnoughOre(req.oreName, req.requiredCount))
                {
                    canUpgrade = false;
                    break;
                }
            }
    
            if (canUpgrade)
            {
                // 全ての要求を満たしている場合、各鉱石を消費する
                foreach (var req in requirements)
                {
                    oreManager.ConsumeOre(req.oreName, req.requiredCount);
                }
    
                // ピッケルのアップグレード実行
                currentIndex = targetIndex;
                currentPickaxe = pickaxeDataList[currentIndex];
                UpdatePickaxeUI();
                Debug.Log("Upgraded to: " + currentPickaxe.pickaxeName);
    
                // 名声ポイント加算(仮: 10ポイント)
                if (gameManager != null)
                {
                    gameManager.AddFamePoint(10);
                }
                return true;
            }
            else
            {
                Debug.LogWarning("Not enough ore to upgrade to " + targetPickaxe.pickaxeName);
                return false;
            }
        }
    
        /// <summary>
        /// 次のアップグレードに必要なスコア(旧システム用)を返します。
        /// 現在は主に参考情報として利用されます。
        /// </summary>
        public int GetNextUpgradeCost()
        {
            if (currentIndex < pickaxeDataList.Length - 1)
            {
                return pickaxeDataList[currentIndex + 1].upgradeCost;
            }
            return 0; // 最大レベルに達している場合
        }
    
        /// <summary>
        /// UI上のピッケル表示を更新します。
        /// </summary>
        private void UpdatePickaxeUI()
        {
            if (pickaxeIcon != null && currentPickaxe != null)
            {
                pickaxeIcon.sprite = currentPickaxe.pickaxeSprite;
            }
    
            if (pickaxeNameText != null && currentPickaxe != null)
            {
                pickaxeNameText.text = currentPickaxe.pickaxeName;
            }
        }
    
        /// <summary>
        /// 現在のピッケルに設定されている鉱石出現確率リストを返します。
        /// </summary>
        public PickaxeData.OreProbability[] GetCurrentOreProbabilities()
        {
            if (currentPickaxe != null)
            {
                return currentPickaxe.oreProbabilities;
            }
    
            Debug.LogWarning("Current pickaxe is null. Returning empty probabilities.");
            return new PickaxeData.OreProbability[0];
        }
    
        /// <summary>
        /// 次のアップグレードが可能かどうかを判定します。
        /// 次のピッケルデータに設定されている upgradeRequirements をすべて満たしている場合に true を返します。
        /// </summary>
        public bool CanUpgrade()
        {
            if (currentIndex < pickaxeDataList.Length - 1)
            {
                PickaxeData nextPickaxe = pickaxeDataList[currentIndex + 1];
                foreach (var req in nextPickaxe.upgradeRequirements)
                {
                    if (!oreManager.HasEnoughOre(req.oreName, req.requiredCount))
                    {
                        return false;
                    }
                }
                return true;
            }
            return false;
        }
    }

    まとめ

    今回は、AIエージェントにタスク管理を任せながらゲーム開発を進める、という新しい手法を導入しました。

    準備段階は少し複雑でしたが、一度タスクリストができてしまえば、開発の進行状況が非常に把握しやすくなりました。 小さなタスクのまとまりごとに進めていくので、AIも人間も目の前の作業に集中でき、結果としてミスが起きにくくなるのではないかと感じています。

    今回は途中から無理やり導入したため、まだ手探りの部分もありますが、AI主導でゲーム制作を進める上では非常に有効で、開発の安定性を向上させる手法だと実感できました。

    UnityMCPの精度向上策については、今回は出番がありませんでしたが、次回以降の複雑なタスクでその効果を検証していきたいと思います。

    このブログで紹介した方法は、皆さんのプロジェクトでも導入可能ですので、ぜひ試してみてください。 もし、もっと良い方法をご存知でしたら、ぜひコメントで教えていただけると嬉しいです。


    このチャンネルでは、AIを活用したゲーム制作を通じて、個人でゲームを作ってみたいと考えている方のお役に立てる情報をお届けしていきます。

    AIやゲーム制作に興味のある方は、ぜひチャンネル登録をして、次回の更新をお待ちいただければと思います。

    それでは、また次回お会いしましょう。

    コメント

    タイトルとURLをコピーしました