面白くないゲームをAIで改良!動画分析と画像生成でゲームのコアを面白くする!【AIを活用したクリッカーゲーム制作#11】

AIゲームクリエイターズ

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

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

はじめに

「ゲームを作りたいけど、何から手をつければいいか分からない…」 「Unityのチュートリアルは終えたけど、その先で挫折してしまった…」

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

このブログは、AIの力を借りて、ゲーム制作のハードルをぐっと下げ、誰でもゲームクリエイターになれる可能性を追求するチャレンジの記録です。

AIにプログラムを書いてもらい、イラストや音楽も作ってもらう。そんな未来のゲーム開発スタイルを、一緒に体験していきましょう!

前回の振り返り

前回(#10)は、AIエージェントを備えた高機能コードエディタ「Cursor」を導入し、ピッケルのアップグレード機能を実装しました。AIにUIのデザインからスクリプトの実装までを任せることで、効率的に開発を進めることができましたね。

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

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

AIと協力して、40代のゲーム作りが趣味の会社員である筆者(よどサン)が、Unityでスマホゲームを開発し、ストアでのリリースを目指します。

この過程で得られた知識やノウハウを共有し、AIを活用したゲーム開発に興味がある方、Unity初心者の方、そして趣味でゲーム制作を楽しみたい社会人の方の助けになることを目指しています。

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

今回は、以下のAIツールを駆使してゲームを改良していきます。

  • Google AI Studio (Gemini 2.5 Pro): ゲームのプレイ動画を分析し、改良案を提案してもらいます。
  • ChatGPT (GPT-4o): ゲーム内で使用する段階的に変化する画像や、効果音の生成を試みます。
  • Cursor: AIエージェントが組み込まれたコードエディタで、仕様変更のスクリプトを書いてもらいます。

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

  • まずは真似して同じように試してみて、慣れてきたら少しアレンジするのが良いと思います
  • 手順などは、動画を見てもらうのが一番分かりやすいと思います
  • AIへの指示文(プロンプト)はこのページに載せていますので、コピペして、ご自身用に修正しながらご使用ください

それでは、AIに助けてもらいながら、ゲーム制作を始めていきましょう!


今回のテーマ:ゲームを面白くするための改良(着手編)

さて、前回までで「鉱石をクリックして集め、その鉱石でピッケルをアップグレードする」という、ゲームの基本的なサイクルが完成しました。

しかし、正直に言って、このゲーム…面白くない!

このままではストアに出せるレベルではないため、ここからは「ストアに出しても恥ずかしくない」面白さを目指して、本格的な改良フェーズに入ります。

幸い、このチャレンジを始めた半年前と比べてAIの性能は格段に向上しています。 パワーアップしたAIの力を借りて、ゲームを面白くしていきましょう!

【AI活用①】ゲーム動画をAIに分析してもらい、改良案を出してもらう

ゲームの現状と課題

まず、現状で特に気になっている問題点は、「スコア」と「プログレスバー」の役割が曖昧なことです。

  • 画面上部のスコア: クリックで増えても、何にも使えない。
  • 画面中央のプログレスバー: メーターが溜まっても、何も起きない。

これらの要素が何のために存在するのか、その意味づけが全く定まっていません。

そこで、この課題をAIに相談してみることにしました。

Unity Recorderでゲームプレイ動画を撮影

これまでは文章でAIに状況を説明していましたが、最近のAIは動画をインプットとして使えるようになりました。

百聞は一見にしかず、ということで早速ゲームのプレイ動画を撮影し、AIに見せてみることにします。

動画の撮影には、Unity公式の「Recorder」パッケージを使用します。

Recorderの導入と撮影手順
  1. Unityエディタの上部メニューから Window > PackageManager を開きます。
  2. 左上のプルダウンが「Unity Registry」になっていることを確認し、検索窓に「Recorder」と入力します。
  3. 表示されたRecorderパッケージをインストールします(未導入の場合)。
  4. 上部メニューから Window > General > Recorder > Recorder Window を選択して、Recorderウィンドウを開きます。
  5. 「Add Recorder」から「Movie」を追加します。
  6. 「Recording Mode」を「Manual」に設定します。
  7. 「START RECORDING」ボタンを押して録画を開始し、ゲームをプレイします。録画を停止すると、プロジェクト内の「Recordings」フォルダに動画ファイルが保存されます。

Google AI Studioで動画を分析

撮影した動画ファイルをインプットできるAIはまだ限られています。主要なチャットAI(ChatGPT, Gemini, Claudeなど)の中で、動画ファイルを直接アップロードできるのは、現時点ではChatGPTだけです。

しかし、開発者向けのプラットフォームであるGoogle AI Studioであれば、動画ファイルをインプットして、より高性能な動画分析が可能です。 個人的に試した限りでは、ChatGPTよりも分析性能が高いと感じたため、今回はGoogle AI Studioの「Gemini 2.5 Pro」モデルを使用します。

Google AI Studio
Google AI Studio is the fastest way to start building with Gemini, our next generation family of multimodal generative AI models.
プロンプト(AIへの指示)

Google AI Studioに先ほど録画した動画をアップロードし、解析が終わったのを確認してから、以下のようにお願いしました。

添付の動画は「鉱石クリッカー」という開発中のスマホゲームの画面を録画したものです。 このゲームには「スコア」(画面最上部)と「進行メーター」(画面中央)がありますが、それぞれの意味づけが不十分だと思っています。 これらの要素の効果的な役割を考えてください。いくつかアイデアを出してください。そのうえでおススメの案を教えてください。

Gemini 2.5 Proの回答例

拝見しました。「鉱石クリッカー」面白そうですね!スコアと進行メーターの役割について、いくつかアイデアを提案させていただきます。

現状の動きから推測すると:

  • スコア: タップや鉱石破壊に応じて増加するポイント。現状では直接的なゲーム内効果は薄い。
  • 進行メーター: 現在の鉱石の耐久度。いっぱいになると鉱石が破壊され、新しい鉱石に切り替わる。

これらの要素をより効果的にするためのアイデアです。


スコアの役割アイデア

  1. フィーバーゲージ:
    • 内容: スコアが一定値に達すると「フィーバーモード」に突入。一定時間、獲得素材量アップ、タップ威力(スコア増加量や進行メーターの進み)アップ、レア素材出現率アップなどのボーナスが発生する。
    • 効果: 単調になりがちなクリッカーゲームにアクセントを加え、スコアを溜める明確な目標と報酬を提供します。スコアが直接的な恩恵に繋がるため、意味が明確になります。
    • 表示: 「Score」の代わりに「Fever」と表示し、ゲージのように見せるのも良いでしょう。
  2. ランキングシステム:
    • 内容: 一定時間内、または特定の条件下(例:特定の鉱石をN個破壊するまで)で獲得したスコアを競うランキングを導入する。
    • 効果: プレイヤー間の競争を促し、リプレイ性を高めます。ハイスコアを目指すモチベーションになります。
  3. 実績・称号システム連動:
    • 内容: 累計スコアや1プレイでのハイスコアに応じて、実績が解除されたり、特別な称号が獲得できるようにする。
    • 効果: 長期的な目標を提供し、コレクション要素としても楽しめます。
  4. 特殊スキル/ブースト発動コスト:
    • 内容: スコアを消費して、一時的な特殊スキル(例:オートタップ、一定時間全素材ドロップなど)を発動できるようにする。ただし、これは素材とは別のリソース管理になるため、ゲームが複雑になる可能性も考慮。
    • 効果: プレイヤーに能動的な選択肢を与え、戦略性を加えます。
  5. 名声ポイント/熟練度:
    • 内容: スコアを「採掘名声」のような位置づけにし、一定の名声に達すると永続的なパッシブ効果(例:基本採掘力微増、レア素材ドロップ率微増)が得られたり、新しい採掘エリアが解放されたりする。
    • 効果: プレイヤーの成長を実感させ、長期的なプレイを促します。

進行メーターの役割アイデア (鉱石の耐久度メーターとして)

  1. 破壊時ボーナス:
    • 内容: メーターがいっぱいになり鉱石を破壊した瞬間に、追加の素材ボーナス(例:ランダムで素材数個追加、レア素材の出現確率アップ、あるいは確定で1つドロップなど)を付与する。
    • 効果: 鉱石を破壊する達成感を高め、次の鉱石への意欲を刺激します。メーターを溜める行為そのものにご褒美感が生まれます。
  2. 連続破壊コンボ:
    • 内容: 鉱石を連続して素早く破壊していく(メーターを効率よく満タンにする)とコンボが発生し、コンボ数に応じてスコア獲得量や素材獲得量にボーナス倍率がかかる。
    • 効果: スピーディーで効率的なプレイを促し、プレイヤーのスキル介入度を高めます。
  3. オーバーチャージ/パーフェクト破壊:
    • 内容: メーターがいっぱいになった瞬間に特定のタイミングでタップする(例:メーターが光る、特定のゾーンを通過する時など)と「パーフェクト破壊」となり、通常より大きなボーナスが得られる。または、メーターがいっぱいになった後も少しだけチャージでき、その量に応じてボーナスが変わるが、溜めすぎるとペナルティがあるなど。
    • 効果: タップのタイミングに少しだけ技術要素を加え、単調さを軽減します。
  4. 鉱石の見た目との連動強化:
    • 内容: 現状でも鉱石から破片が飛び散っていますが、メーターの進捗に応じて鉱石のヒビが大きくなったり、色が変化したり、内部のレア鉱石がチラ見えしたりするなど、視覚的な変化をより顕著にする。
    • 効果: 破壊への期待感を高め、メーターの進捗を直感的に分かりやすくします。
  5. ラッキーゾーン出現:
    • 内容: メーターの特定区間にランダムで「ラッキーゾーン」が出現。このゾーンで鉱石を破壊すると、特別なボーナス(大量の素材、限定アイテムなど)が得られる。
    • 効果: プレイに偶発的な楽しみを加え、メーターを常に意識させます。

おすすめの案とその理由

スコア:

  • おすすめ: 1. フィーバーゲージ
  • 理由:
    • 明確な目標と報酬: スコアを溜めることで直接的なゲームプレイ上のメリット(フィーバータイム)が得られるため、スコアの意味がプレイヤーにとって非常に分かりやすくなります。
    • ゲームプレイの活性化: 定期的なフィーバータイムは、単調になりがちなゲームプレイにメリハリと爽快感を与えます。
    • 既存システムとの親和性: タップでスコアが増えるというシンプルな仕組みは、現在のゲームデザインを大きく変えることなく導入しやすいです。

進行メーター:

  • おすすめ: 1. 破壊時ボーナス4. 鉱石の見た目との連動強化 の組み合わせ
  • 理由:
    • 達成感の向上: 鉱石を破壊した際に明確なボーナスがあることで、メーターを溜め切る行為の満足度が高まります。
    • 視覚的フィードバックの強化: 鉱石の見た目が進行メーターと連動してダイナミックに変化することで、破壊のプロセスがより楽しく、直感的に理解できるようになります。
    • シンプルで効果的: これらは既存の進行メーターの役割(鉱石の耐久度)を補強する形で機能し、プレイヤーにとっても理解しやすい改善です。

組み合わせによる相乗効果:
もし「スコア=フィーバーゲージ」と「進行メーター=破壊時ボーナス付き鉱石耐久度」を採用する場合、
「フィーバー中に鉱石を破壊すると、破壊時ボーナスがさらに豪華になる!」
といった連動も考えられ、プレイヤーはフィーバーの発動タイミングと鉱石の破壊タイミングを戦略的に考えるようになり、ゲームの奥深さが増すでしょう。


これらのアイデアが、ゲームをより魅力的にするための一助となれば幸いです!

ゲーム改良の方向性を決定

AIの提案は、私の考えていた方向性と見事に一致していました! 特に、おすすめ案は素晴らしいですね。これをベースに、今後の改良方針を以下のように整理しました。

AIの提案を元に仕様を整理

  • 進行メーター → 「鉱床破壊ゲージ」
    1. 画面中央の大きな岩を「鉱床」と呼ぶことにします。(AIの回答で使われていた「鉱床」という言葉がしっくりきました! )
    2. タップするたびにゲージが溜まり、鉱床にひびが入るなど、見た目が変化していきます。
    3. ゲージがMAXになると鉱床が破壊され、アイテムをゲットできます。
    4. 破壊後、次の新しい鉱床が出現します。 (ランダムでレアな鉱床が出現しても面白いかもしれませんね )
  • スコア → 「名声ポイント(仮)」
    1. 貯めることで、名声や称号が得られる役割を持たせます。
    2. 一定ポイントで新しいエリアが解放されるようにします。
    3. ピッケルのアップグレード時に消費するコストとしても利用するかもしれません。

今回の記事では、まず「鉱床破壊ゲージ」の②と③、つまり「タップでゲージが溜まり鉱床が崩れていき、MAXになると破壊される」という一連の流れを実装していきます。

【AI活用②】ChatGPTで「段階的に変化する」画像素材を生成する

鉱床が壊れていく様子を表現するために、壊れる段階ごとの画像が必要になります。

以前の画像生成AIでは、同じオブジェクトの微妙な差分を作るのは非常に困難でした。 しかし、最近のChatGPTの画像生成機能は、画像の再現性が大幅に向上し、こうした「段階的な変化」の表現が非常に得意になっています。

プロンプト入力と画像生成

商用利用を想定しているため、有料版のChatGPT(GPT-4o)を使用します。 まずはベースとなる岩の画像を生成しましょう。

プロンプト(AIへの指示)

2Dゲーム用の素材画像。大きな岩が1つ中央にある絵。岩の色は添付画像の石の色と合わせる。背景は透明。

(※ゲーム内で既に使っている小さい石の画像を添付しました)

いい感じの岩が生成されたので、ここから段階的にひび割れの指示を追加していきます。

(追加指示1) この岩が少しひび割れた絵を作成してください。

(追加指示2) この岩が、更にひびが強く入った絵を作成してください。

(追加指示3) 更に強くひびが入り、一部には欠けが見られる絵を作成してください。

(追加指示4) 更に強く深くひびが入り、細かな無数のひびもあちこちに見られ、欠けている箇所も増えた絵を作成してください。

(追加指示5) 岩が完全に砕けてしまい、破片がバラバラになった絵を作成してください。

このように、元のデザインを維持したまま差分を追加してくれるのは、本当に便利になりましたね。

破壊エフェクト用画像の生成

最後に、破壊時に飛び散るエフェクト用の破片画像も生成してもらいます。

プロンプト(AIへの指示)

この岩の破片のパーティクル素材用画像。程よく細かい破片画像(大きすぎず、小さすぎず)にしてください。同じくらいの大きさでいいので、色々な形の破片を複数縦横に整列させて書いてください。背景は透明。

これで必要な画像素材が揃いました。ダウンロード後、念のためすべての画像の背景を透過ツールで処理しておきます。

【AI活用③】Cursor(AIコードエディタ)で仕様変更を実装する

画像素材が準備できたので、いよいよスクリプトを修正していきます。AIコードエディタ「Cursor」を使って、先ほど決めた仕様を実装してもらいましょう。

Cursorへの指示(プロンプト)

今回はモデルに「Claude 3.7 sonnet」を選択し、以下のように依頼しました。 スクリプト間の依存関係をまとめたファイルをコンテキストとして与えることで、より的確な修正を期待します。

現状、ProgressBarは鉱床(Ore)をクリックした回数が単純にカウントされてメーターが増えていくだけですが、これをメーターがたまったら鉱床が破壊されるようにしたい。つまり鉱床の耐久力を表すメーターとしたいです。 さらに、メーターがある一定増えるごとに鉱床のスプライトが変わるようにしたいです。変更用の複数の画像をセットできるようにしてください。 そして、鉱床破壊時には、破片が飛び散るようにしたいです。破片用の画像を用意するのでセットできるように飛び散るようにしてください。

スクリプト間の関係を表すテキストは @ScriptDependenciesMermaid.md です。

Cursor(Claude 3.7 sonnet)の回答とスクリプト修正

承知しました。現在の鉱床クリックシステムを耐久力メーターに変更し、鉱床の見た目を進行に応じて変化させ、破壊時に破片エフェクトを追加する機能を実装します。 関連するスクリプト OreController.csGameManager.cs を以下のように修正します。

OreController.cs(修正)
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

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; // 現在の耐久値
    
    [Header("鉱床の見た目変化")]
    public Sprite[] oreStages; // 鉱床の段階的なスプライト(最初の完全な状態から最後の崩れた状態まで)
    private SpriteRenderer oreRenderer; // 鉱床のスプライトレンダラー
    
    [Header("鉱床破壊エフェクト")]
    public GameObject destroyEffectPrefab; // 鉱床破壊時の破片エフェクトのPrefab
    public Sprite[] debrisSprites; // 破片として使用するスプライト
    
    private ClickEffect clickEffect;
    private Camera mainCamera; // カメラの参照
    private AudioSource audioSource; // 効果音再生用のAudioSource

    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;
        
        // 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);
            
            // スプライトを設定
            oreRenderer.sprite = oreStages[spriteIndex];
        }
    }

    private void OnMouseDown()
    {
        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 (Random.value <= acquisitionChance)
        {
            // 鉱石が獲得できた場合:各鉱石の種類の確率に基づいて選ぶ
            OreData selectedOre = GetRandomOre();
            if (selectedOre != null)
            {
                int miningPower = pickaxeManager.GetMiningPower();
                int pointsToAdd = selectedOre.pointValue * miningPower;
                // スコアと鉱石カウントを更新
                gameManager.AddPoints(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)
        {
            DestroyOre();
        }
    }
    
    // 鉱床破壊処理
    private void DestroyOre()
    {
        // 破壊エフェクトを生成
        CreateDestroyEffect();
        
        // 鉱床の耐久値をリセット
        ResetOreDurability();
        
        // スプライトを初期状態に戻す
        UpdateOreSprite();
    }
    
    // 破壊エフェクトの生成
    private void CreateDestroyEffect()
    {
        if (destroyEffectPrefab != null && debrisSprites != null && debrisSprites.Length > 0)
        {
            // 破壊エフェクトを生成
            GameObject destroyEffect = Instantiate(destroyEffectPrefab, transform.position, Quaternion.identity);
            
            // 破片の数を決定(5〜10個程度)
            int debrisCount = Random.Range(5, 11);
            
            // 破片を生成
            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(
                    Random.Range(-0.5f, 0.5f),
                    Random.Range(-0.5f, 0.5f),
                    0
                );
                
                // SpriteRendererを追加
                SpriteRenderer debrisRenderer = debris.AddComponent<SpriteRenderer>();
                debrisRenderer.sprite = debrisSprites[Random.Range(0, debrisSprites.Length)];
                debrisRenderer.sortingOrder = 2; // 鉱床の前に表示
                
                // RigidBody2Dを追加して物理挙動を持たせる
                Rigidbody2D rb = debris.AddComponent<Rigidbody2D>();
                rb.gravityScale = Random.Range(0.5f, 1.5f);
                
                // ランダムな力と回転を加える
                Vector2 force = new Vector2(
                    Random.Range(-5f, 5f),
                    Random.Range(2f, 7f)
                );
                rb.AddForce(force, ForceMode2D.Impulse);
                rb.AddTorque(Random.Range(-10f, 10f), ForceMode2D.Impulse);
                
                // 数秒後に破片を破棄
                Destroy(debris, 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 = 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;
        }
    }
}
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 score = 0;               // 現在のスコア

    private void Start()
    {
        UpdateUI();
        
        // ボタンのリスナーを変更: 直接アップグレードではなくメニューを開くように
        upgradeButton.onClick.RemoveAllListeners();
        upgradeButton.onClick.AddListener(OpenUpgradeMenu);
    }

    public void AddPoints(int points)
    {
        score += 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 = "Score: " + score;

        // 現在のピッケルのインデックスを算出(配列内での位置)
        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で行うため、ここでは更新しない
    }
}

Unity MCPによる自動化の試み(と手動作業)

Cursorは、AIがUnityエディタを直接操作する「Unity MCP」という機能にも対応しています。 今回、AIは破壊エフェクト用のPrefabを自動で作成しようと試みましたが、残念ながら途中でエラーが発生してしまいました。

まだまだ発展途上の技術のようですね。今回はAIの指示に従って、手動でPrefabの作成と設定を行いました。

Unityエディタでの設定

  1. 生成した鉱床の段階ごとの画像をプロジェクトに取り込みます。
  2. OreController スクリプトのインスペクターに、段階ごとのスプライト画像をセットします。
  3. 破壊エフェクト用の破片画像をインポートし、インスペクターで Sprite ModeMultiple に変更します。
  4. Sprite Editor を開き、「Slice」機能で画像を個別の破片に分割します。
  5. 作成した破壊エフェクトのPrefabに、分割した破片画像をセットします。

【AI活用④】ChatGPTで効果音を生成してみる

実装を進める中で、効果音の追加を指示し忘れていたことに気づきました。 鉱床にひびが入る時と、破壊される時に効果音を鳴らすように、Cursorに追加でお願いしました。

さて、その効果音ですが…なんと、最近のChatGPT(GPT-4o)は効果音も生成できるようになったとのこと! これは試してみるしかありません。

ChatGPT(GPT-4o)への指示(プロンプト)

(指示1) 大きな岩にひびが入る効果音を作成してください。「ピシピシっパキパキッ」という感じで。

(指示2) 大きな岩が破壊されて砕けて、「ガシャン、バラバラ」という音が鳴る効果音を作成して。

生成結果と課題

生成された効果音を聞いてみましたが…うーん、残念ながら「岩」という感じの音ではなく、今回のイメージとは合いませんでした。 >> 実際の音は動画で

期待していただけに少し残念ですが、AIによる効果音生成はまだ発展途上なのかもしれません。プロンプト次第では良いものが作れる可能性もあるので、今後に期待ですね!

結局、今回はいつもお世話になっている「効果音ラボ」さんでイメージに合う素材を探し、使用させていただきました。

動作確認

すべての設定を終え、ゲームを実行してみます。

クリックするたびにプログレスバーが溜まり、一定の値に達すると効果音と共に鉱床の見た目がひび割れていきます。そして、ゲージがMAXになると、派手なエフェクトと効果音と共に鉱床が破壊されるようになりました!

無事に、思い描いていた機能が実装できましたね!

まとめ

今回は、面白くなかったゲームを改良する第一歩として、AIの様々な能力を活用しました。

  • Google AI Studioにゲーム動画を分析させ、的確な改良案を得る。
  • ChatGPTの進化した画像生成機能で、段階的に変化する画像素材を簡単に作成する。
  • Cursorに仕様変更を依頼し、スクリプトを効率的に修正する。

特に、ChatGPTの画像生成能力の向上は、ゲーム制作において非常に大きな武器になると感じました。 AIエージェントの力も借りることで、仕様変更のような複雑な作業も、以前よりずっと簡単に行えるようになっています。

次回予告

ゲームの改良はまだ始まったばかりです。次回は、今回の方針整理で出てきたもう一つの要素、「名声ポイント」周りの実装や、破壊した鉱床から次の鉱床が出現する仕組みなどを追加していく予定です。

このブログでは、AIと共にゲームを完成させていくリアルな過程をお届けします。AIやゲーム制作に興味のある方は、ぜひチャンネル登録をして、次回の更新をお待ちいただければと思います。

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

コメント

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