未分類

【AIプログラミング検証】3D生態シミュレーション【ChatGPT o3-mini-high】

きたたく

本記事では、ChatGPT o3-mini-highが生成したコードを実際に実行してAIのプログラミング能力を検証します。

プロンプト

あなたはUnityのプロフェッショナルなゲーム開発者です。C#を使用して、シンプルな生態系シミュレーションを作成してください。ゲームは以下の要件を満たす必要があります。

1. 基本要件

  • Unityの3D環境で動作する
  • 生態系のシミュレーションを実装
  • エージェント(動物)*が食料を探し、繁殖し、死ぬ
  • ランダムに配置される環境で、AIが適切な行動をとる
  • このシミュレーションを面白くするユニークな要素を考案・実装

2. 環境

  • フィールド(平面)に、ランダムな位置で食料が生成される
  • 複数の動物(エージェント)がこのフィールド内を移動する

3. AIの行動ルール

  • 移動: ランダムに歩き回るが、食料があるとそちらに向かう
  • 食事: 食料に到達するとエネルギーが回復
  • 繁殖: 一定のエネルギーを持つと、新しい個体を生成
  • 死亡: エネルギーがゼロになると消滅

4. シミュレーションのロジック

  • エージェントは1フレームごとにエネルギーを消費する
  • 食料は時間経過で一定数ランダムに再生成
  • AIの移動はナビゲーションシステムまたはシンプルなランダムウォークを使用
  • 全てのエージェントが死ぬとシミュレーション終了

5. 比較ポイント

このプロジェクトは異なるAIモデルで比較するため、以下の点に注意すること:

  • AIの動きが自然か(エージェントが正しく食料を探せるか)
  • シミュレーションが破綻しないか(すぐに全滅しないか)
  • ゲームが適切に終了するか(エージェントが無限に増え続けないか)
  • コードが明確で分かりやすいか(整理されたオブジェクト指向設計)

6. コード要件

  • C#を使用し、Unityの標準的なコンポーネント(MonoBehaviour、NavMeshなど)を活用する
  • 最低限のUI(エージェント数、食料数のカウンター)を表示
  • パフォーマンスを考慮し、大量のエージェントがいてもFPSが極端に低下しないようにする

7. 出力

完全なUnity C#コードを出力してください。

  • エージェントのスクリプト(Animal.cs)
  • 食料のスクリプト(Food.cs)
  • ゲームマネージャーのスクリプト(GameManager.cs)
  • Unityのセットアップ手順(どのオブジェクトにスクリプトをアタッチするか)

このコードを基に、開発者が簡単にシミュレーションを実行できるようにしてください。

ChatGPT o3-mini-highの出力

以下は、Unity上で動作するシンプルな生態系シミュレーションのサンプルコードです。
以下のコードは、Animal.cs(エージェント=動物)、Food.cs(食料)、GameManager.cs(シミュレーション全体の管理)の3つのスクリプトで構成されています。
また、シミュレーションをより面白くするためのユニークな要素として…

  • エネルギー量に応じた色の変化(低エネルギーは赤、高エネルギーは緑に変化)
  • 一定時間ごとに発生する「豊作シーズン」(一定期間、食料のスポーンレートがアップします)

などを実装しています。

以下に各スクリプトのコードと、Unity上でのセットアップ手順を示します。

using UnityEngine;
using UnityEngine.AI;

public class Animal : MonoBehaviour
{
    [Header("Energy Settings")]
    public float energy = 100f;
    public float energyConsumptionRate = 2f;      // 単位:秒間のエネルギー消費量
    public float energyGainFromFood = 30f;
    public float reproductionThreshold = 150f;    // このエネルギー以上で繁殖

    [Header("Movement Settings")]
    public float detectionRadius = 10f;           // 食料検知範囲
    public float wanderRadius = 20f;              // ランダム移動の範囲

    private NavMeshAgent agent;
    private GameManager gameManager;

    void Start()
    {
        // NavMeshAgent コンポーネントを取得
        agent = GetComponent<NavMeshAgent>();
        // GameManager はシーン内に1つだけ存在する前提
        gameManager = GameObject.FindObjectOfType<GameManager>();

        // 初回はランダムな目的地へ移動
        ChooseRandomDestination();
    }

    void Update()
    {
        // 毎フレーム、エネルギーを消費する(Time.deltaTime により秒単位の消費)
        energy -= energyConsumptionRate * Time.deltaTime;
        if (energy <= 0f)
        {
            Die();
            return;
        }

        // 周囲にある食料を検知
        Food targetFood = FindClosestFood();
        if (targetFood != null)
        {
            // 食料が見つかった場合、その位置へ向かう
            agent.SetDestination(targetFood.transform.position);
        }
        else
        {
            // 目的地に到着していれば、新たなランダムな目的地を選択
            if (!agent.pathPending && agent.remainingDistance < 0.5f)
            {
                ChooseRandomDestination();
            }
        }

        // 繁殖判定:エネルギーが閾値を超えたら繁殖
        if (energy >= reproductionThreshold)
        {
            Reproduce();
        }

        // エネルギー量に応じた色の更新(低エネルギー=赤、高エネルギー=緑)
        UpdateColor();
    }

    /// <summary>
    /// 周囲のコライダーから Food コンポーネントを持つ最も近い食料を探す
    /// </summary>
    /// <returns>見つかった食料(なければ null)</returns>
    Food FindClosestFood()
    {
        Collider[] hits = Physics.OverlapSphere(transform.position, detectionRadius);
        Food closestFood = null;
        float minDist = Mathf.Infinity;
        foreach (Collider hit in hits)
        {
            Food food = hit.GetComponent<Food>();
            if (food != null)
            {
                float dist = Vector3.Distance(transform.position, food.transform.position);
                if (dist < minDist)
                {
                    minDist = dist;
                    closestFood = food;
                }
            }
        }
        return closestFood;
    }

    /// <summary>
    /// ランダムな目的地を選び、NavMeshAgent にセットする
    /// </summary>
    void ChooseRandomDestination()
    {
        Vector3 randomDirection = Random.insideUnitSphere * wanderRadius;
        randomDirection += transform.position;
        NavMeshHit hit;
        if (NavMesh.SamplePosition(randomDirection, out hit, wanderRadius, NavMesh.AllAreas))
        {
            agent.SetDestination(hit.position);
        }
    }

    /// <summary>
    /// 繁殖処理:GameManager を通じて新たな個体をスポーンし、エネルギーを半分に分割する
    /// </summary>
    void Reproduce()
    {
        if (gameManager != null)
        {
            // 親の周囲に少し離れた位置へ新しい個体を生成
            Vector3 spawnPos = transform.position + Random.insideUnitSphere * 2f;
            spawnPos.y = transform.position.y;
            gameManager.SpawnAnimal(spawnPos);
            energy /= 2f;
        }
    }

    /// <summary>
    /// エネルギーが0以下になった場合の死亡処理
    /// </summary>
    void Die()
    {
        Destroy(gameObject);
    }

    /// <summary>
    /// エネルギー量に応じてマテリアルの色を変化させる
    /// </summary>
    void UpdateColor()
    {
        Renderer rend = GetComponent<Renderer>();
        if (rend != null)
        {
            // energy が 0 なら赤、繁殖閾値なら緑とする補間(0~reproductionThreshold)
            float t = Mathf.InverseLerp(0, reproductionThreshold, energy);
            rend.material.color = Color.Lerp(Color.red, Color.green, t);
        }
    }

    /// <summary>
    /// 衝突判定:食料に接触した場合、食べる
    /// ※ 食料オブジェクトには Collider(Is Triggerにチェック)を設定してください
    /// </summary>
    /// <param name="other">接触したコライダー</param>
    private void OnTriggerEnter(Collider other)
    {
        Food food = other.GetComponent<Food>();
        if (food != null)
        {
            energy += energyGainFromFood;
            food.Consume();
        }
    }
}
using UnityEngine;

public class Food : MonoBehaviour
{
    [Header("Floating Animation (Optional)")]
    public float floatSpeed = 0.5f;
    public float floatAmplitude = 0.5f;
    
    private Vector3 startPosition;

    void Start()
    {
        startPosition = transform.position;
    }

    void Update()
    {
        // 食料がふわふわ浮くようなアニメーション(上下運動)
        transform.position = startPosition + new Vector3(0, Mathf.Sin(Time.time * floatSpeed) * floatAmplitude, 0);
    }

    /// <summary>
    /// エージェントに食べられた時に呼ばれる処理
    /// </summary>
    public void Consume()
    {
        Destroy(gameObject);
    }
}
using System.Collections;
using UnityEngine;
using UnityEngine.UI;

public class GameManager : MonoBehaviour
{
    [Header("Prefab Settings")]
    public GameObject animalPrefab;
    public GameObject foodPrefab;

    [Header("Initial Spawn Counts")]
    public int initialAnimalCount = 10;
    public int initialFoodCount = 20;

    [Header("Field Settings")]
    public Vector2 fieldSize = new Vector2(50, 50); // X-Z平面上のフィールドサイズ

    [Header("Food Spawn Settings")]
    public float foodSpawnInterval = 5f;
    public int foodSpawnCount = 5;

    [Header("UI (任意)")]
    public Text animalCountText;
    public Text foodCountText;
    // 状況表示用(例:豊作シーズン中、シミュレーション終了など)
    public Text simulationStatusText;

    void Start()
    {
        // 初期の動物を生成
        for (int i = 0; i < initialAnimalCount; i++)
        {
            Vector3 pos = GetRandomPosition();
            SpawnAnimal(pos);
        }
        // 初期の食料を生成
        for (int i = 0; i < initialFoodCount; i++)
        {
            SpawnFood(GetRandomPosition());
        }
        // 食料を定期的にスポーンするコルーチンを開始
        StartCoroutine(SpawnFoodRoutine());
        // さらに、一定間隔で「豊作シーズン」が発生するコルーチンを開始(ユニークな要素)
        StartCoroutine(SeasonalEventRoutine());
    }

    void Update()
    {
        // UI のカウンター更新
        UpdateUI();

        // 全エージェントが死亡したらシミュレーション終了
        if (GameObject.FindObjectsOfType<Animal>().Length == 0)
        {
            if (simulationStatusText != null)
            {
                simulationStatusText.text = "シミュレーション終了:すべての動物が死にました。";
            }
            StopAllCoroutines(); // 食料スポーンなどのコルーチンを停止
        }
    }

    /// <summary>
    /// ランダムな位置を返す(フィールド内)
    /// </summary>
    /// <returns>ランダムな Vector3</returns>
    Vector3 GetRandomPosition()
    {
        float x = Random.Range(-fieldSize.x / 2f, fieldSize.x / 2f);
        float z = Random.Range(-fieldSize.y / 2f, fieldSize.y / 2f);
        return new Vector3(x, 0, z);
    }

    /// <summary>
    /// 外部から呼ばれる動物の生成処理
    /// </summary>
    /// <param name="position">生成位置</param>
    public void SpawnAnimal(Vector3 position)
    {
        Instantiate(animalPrefab, position, Quaternion.identity);
    }

    /// <summary>
    /// 外部から呼ばれる食料の生成処理
    /// </summary>
    /// <param name="position">生成位置</param>
    public void SpawnFood(Vector3 position)
    {
        Instantiate(foodPrefab, position, Quaternion.identity);
    }

    /// <summary>
    /// 定期的に食料を生成するコルーチン
    /// </summary>
    IEnumerator SpawnFoodRoutine()
    {
        while (true)
        {
            yield return new WaitForSeconds(foodSpawnInterval);
            for (int i = 0; i < foodSpawnCount; i++)
            {
                SpawnFood(GetRandomPosition());
            }
        }
    }

    /// <summary>
    /// ユニークな要素:一定間隔で「豊作シーズン」を発生させ、一定期間食料のスポーンレートをアップさせる
    /// </summary>
    IEnumerator SeasonalEventRoutine()
    {
        while (true)
        {
            // 30秒ごとに豊作シーズン発生
            yield return new WaitForSeconds(30f);

            // 現在のスポーン設定をバックアップ
            int originalFoodSpawnCount = foodSpawnCount;
            float originalFoodSpawnInterval = foodSpawnInterval;

            // 豊作シーズン:食料生成数を2倍、スポーン間隔を半分に
            foodSpawnCount *= 2;
            foodSpawnInterval /= 2f;
            if (simulationStatusText != null)
            {
                simulationStatusText.text = "★ 豊作シーズン ★";
            }

            // 豊作シーズンは10秒間持続
            yield return new WaitForSeconds(10f);

            // 設定を元に戻す
            foodSpawnCount = originalFoodSpawnCount;
            foodSpawnInterval = originalFoodSpawnInterval;
            if (simulationStatusText != null)
            {
                simulationStatusText.text = "";
            }
        }
    }

    /// <summary>
    /// UI のカウンター(動物数、食料数)の更新
    /// </summary>
    void UpdateUI()
    {
        if (animalCountText != null)
        {
            animalCountText.text = "動物数: " + GameObject.FindObjectsOfType<Animal>().Length;
        }
        if (foodCountText != null)
        {
            foodCountText.text = "食料数: " + GameObject.FindObjectsOfType<Food>().Length;
        }
    }
}

Unity セットアップ手順

  1. 新規プロジェクト作成
    Unity Hub などから新しい 3D プロジェクト を作成します。
  2. シーンにフィールドを配置
    • Plane(平面)をシーンに追加し、フィールドとして使用します。
    • Plane に Collider(自動で追加されています)を確認してください。
    • ※ NavMeshAgent を使用する場合は、Plane に対して NavMesh のベイクを行います(Window > AI > Navigation)。
  3. Animal プレハブの作成
    • GameObject > 3D Object > Capsule などで動物を表現するオブジェクトを作成します。
    • 作成したオブジェクトに Animal.cs をアタッチします。
    • 同じオブジェクトに NavMeshAgent コンポーネントを追加します。
    • Collider(Capsule Collider)もそのままで問題ありません(※ OnTriggerEnter を使用する場合、動物側は通常コライダーで大丈夫です)。
  4. Food プレハブの作成
    • GameObject > 3D Object > Sphere などで食料を表現するオブジェクトを作成します。
    • 作成したオブジェクトに Food.cs をアタッチします。
    • Collider(Sphere Collider)を Is Trigger にチェックしておきます。
  5. GameManager オブジェクトの作成
    • シーン上に Empty GameObject を作成し、名前を「GameManager」に変更します。
    • このオブジェクトに GameManager.cs をアタッチします。
    • インスペクター上で、以下の項目を設定します:
      • Animal Prefab:先ほど作成した Animal プレハブをドラッグ&ドロップ
      • Food Prefab:Food プレハブをドラッグ&ドロップ
      • Initial Animal CountInitial Food Count はお好みで調整
      • Field Size:フィールド(Plane)の大きさに合わせる(例:50×50)
      • UI の Text:Canvas 上に作成した Text オブジェクトをそれぞれ割り当てます(下記参照)。
  6. UI の設定(任意)
    • Canvas を作成し、Text コンポーネント(または TextMeshPro)で以下のカウンターを用意します:
      • AnimalCountText:動物数表示用
      • FoodCountText:食料数表示用
      • SimulationStatusText:シーズン情報やシミュレーション終了時のメッセージ表示用
    • 作成した Text オブジェクトを GameManager の該当フィールドにドラッグ&ドロップして関連付けます。
  7. NavMesh のベイク
    • NavMeshAgent を利用するため、Window > AI > Navigation を開き、Plane を「Navigation Static」に設定し、NavMesh をベイクします。
  8. 再生してシミュレーション開始
    • シーン内に初期の動物と食料が生成され、動物は周囲をランダムに歩き回り、検知した食料に向かって移動します。
    • エネルギーが回復すると動物は繁殖し、エネルギーが尽きると消滅します。
    • 定期的に食料が生成され、また30秒ごとに一時的な「豊作シーズン」が発生して食料生成数が増加します。
    • 全ての動物が死ぬとシミュレーション終了のメッセージが表示され、食料生成も停止します。

このコードとセットアップ手順をもとに、開発者は簡単にシミュレーションを実行でき、AIの動きやエコシステムのバランス、シーズンイベントなどを比較・検証することが可能です。必要に応じてパラメータ(エネルギー消費率、繁殖閾値、スポーンレートなど)を調整して、より細かいシミュレーション挙動を実現してください。

ABOUT ME
きたたく
きたたく
学習工学博士
自由に生きる人を増やしたい大学教員・経営者。
生成AIで経済的自由を達成するための知識を発信中。
元不登校から飛び級で博士号(工学)を取得した連続起業家。
プログラミングを通じて「U-22 プログラミングコンテスト CSAJ 会長賞」「IoT Challenge Award 総務大臣賞」「人工知能学会研究会 優秀賞」など40件を超える賞を受賞。
スマホアプリ会社を創業(売却)し、プログラミングスクールの会社を創業(20店舗経営)

著書:知識ゼロからのプログラミング学習術(秀和システム) 他多数
記事URLをコピーしました