Unityで人間生活ミュレーション – 2

Unityで「自律的に生活するAI」を作るための、C#コードのサンプル

このシステムは、拡張性を考えて以下の2つのスクリプトに分かれています。

  1. SmartObject.cs: 家具につけるスクリプト(「これはベッドで、体力が回復する」という情報を定義)。
  2. HumanLifeAI.cs: 人間につけるスクリプト(欲求を管理し、適切な家具を探して行動する)。

そのままコピペして動作確認ができるように設計しています。


1. 家具用スクリプト (SmartObject.cs)

まず、家具が「何の欲求を満たすか」を定義するスクリプトを作成します。

C#

using UnityEngine;

// 欲求の種類を定義
public enum NeedType
{
    None,
    Hunger, // 空腹
    Energy, // 体力
    Fun     // 娯楽
}

public class SmartObject : MonoBehaviour
{
    [Header("家具の設定")]
    public string objectName = "Furniture"; // 家具の名前
    public NeedType needType;               // 回復する欲求の種類
    public float recoveryAmount = 50f;      // 回復量
    public float interactionTime = 3f;      // 行動にかかる時間(秒)

    // AIが家具を使うための立ち位置(設定なければ家具の位置)
    public Vector3 GetInteractionPosition()
    {
        return transform.position;
    }
}

2. AI用スクリプト (HumanLifeAI.cs)

次に、AIの頭脳となるスクリプトです。NavMeshAgentを使って移動し、欲求に応じて行動を決定します。

C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using System.Linq; // リスト操作用

public class HumanLifeAI : MonoBehaviour
{
    [Header("欲求パラメータ (0-100)")]
    public float hunger = 100f;
    public float energy = 100f;
    public float fun = 100f;

    [Header("減衰スピード")]
    public float hungerDecay = 5f; // 1秒ごとに減る量
    public float energyDecay = 2f;
    public float funDecay = 3f;

    // 内部状態
    private NavMeshAgent agent;
    private bool isBusy = false; // 行動中かどうか

    void Start()
    {
        agent = GetComponent<NavMeshAgent>();
    }

    void Update()
    {
        // 1. 時間経過で欲求を減らす
        DecreaseNeeds();

        // 2. 行動中でなければ、次の行動を決める
        if (!isBusy)
        {
            ThinkAndAct();
        }
    }

    // --- 思考ルーチン ---
    void ThinkAndAct()
    {
        // 優先順位: お腹 -> 眠気 -> 暇つぶし -> うろうろ

        if (hunger < 30)
        {
            FindAndInteract(NeedType.Hunger);
        }
        else if (energy < 20)
        {
            FindAndInteract(NeedType.Energy);
        }
        else if (fun < 40)
        {
            FindAndInteract(NeedType.Fun);
        }
        else
        {
            // 特に不満がなければランダムに歩き回る
            Wander();
        }
    }

    // --- 行動ロジック ---
    void FindAndInteract(NeedType type)
    {
        // シーン内の全てのSmartObjectを探す
        SmartObject[] allObjects = FindObjectsOfType<SmartObject>();

        // 求めている欲求タイプに一致する家具だけを抽出
        var targetObjects = allObjects.Where(obj => obj.needType == type).ToList();

        if (targetObjects.Count > 0)
        {
            // 一番近い家具を探す(簡易版)
            SmartObject nearest = targetObjects[0];
            float minDist = Vector3.Distance(transform.position, nearest.transform.position);

            foreach (var obj in targetObjects)
            {
                float dist = Vector3.Distance(transform.position, obj.transform.position);
                if (dist < minDist)
                {
                    minDist = dist;
                    nearest = obj;
                }
            }

            // 行動コルーチンを開始
            StartCoroutine(ExecuteAction(nearest));
        }
        else
        {
            Debug.LogWarning($"タイプ {type} の家具が見つかりません!");
            Wander(); // 家具がないならとりあえず歩く
        }
    }

    // 家具を使う一連の流れ(移動 -> 待機 -> 回復)
    IEnumerator ExecuteAction(SmartObject target)
    {
        isBusy = true; // 他の思考を停止
        Debug.Log($"{target.objectName} に向かいます...");

        // 移動開始
        agent.SetDestination(target.GetInteractionPosition());

        // 到着を待つ
        while (agent.pathPending || agent.remainingDistance > agent.stoppingDistance)
        {
            yield return null;
        }

        Debug.Log($"{target.objectName} を使用中...");
        
        // ここでアニメーション再生を入れると良い (例: anim.SetTrigger("Eat"))

        // 行動時間分待つ
        yield return new WaitForSeconds(target.interactionTime);

        // 欲求を回復
        RestoreNeed(target.needType, target.recoveryAmount);
        Debug.Log($"{target.objectName} の使用完了!");

        isBusy = false; // 思考再開
    }

    void RestoreNeed(NeedType type, float amount)
    {
        switch (type)
        {
            case NeedType.Hunger: hunger = Mathf.Clamp(hunger + amount, 0, 100); break;
            case NeedType.Energy: energy = Mathf.Clamp(energy + amount, 0, 100); break;
            case NeedType.Fun: fun = Mathf.Clamp(fun + amount, 0, 100); break;
        }
    }

    void Wander()
    {
        // まだ移動中なら何もしない
        if (agent.remainingDistance > agent.stoppingDistance) return;

        // ランダムな地点へ移動
        Vector3 randomDirection = Random.insideUnitSphere * 5f; // 半径5m以内
        randomDirection += transform.position;
        NavMeshHit hit;
        if (NavMesh.SamplePosition(randomDirection, out hit, 5f, 1))
        {
            agent.SetDestination(hit.position);
        }
    }

    void DecreaseNeeds()
    {
        hunger -= hungerDecay * Time.deltaTime;
        energy -= energyDecay * Time.deltaTime;
        fun -= funDecay * Time.deltaTime;
    }
}

3. Unityエディタでのセットアップ手順

このコードを実際に動かすための手順です。

  1. NavMeshの準備:
    • 床(Plane)と壁(Cube)を配置し、Inspector右上の Static にチェックを入れる。
    • Window > AI > Navigation を開き、Bake タブで Bake ボタンを押す(青いメッシュが表示されればOK)。

現在のUnityの標準である「NavMeshSurface」コンポーネントを使った、新しいBakeの方法を解説します。こちらのほうが便利でトラブルも少ないです。


新しいBakeの手順 (AI Navigation パッケージ)

「ウィンドウ」ではなく、シーン上の「部品(コンポーネント)」としてNavMeshを管理します。

1. 必要なパッケージを入れる

まず、新しい機能が有効になっているか確認します。

  1. メニューバーの Window > Package Manager を開く。
  2. 左上のドロップダウンを Packages: Unity Registry にする。
  3. リストから AI Navigation を探して選択。
  4. 右下の Install ボタンを押す(すでにInstalledならそのままでOK)。

2. NavMeshSurface を作る

これからは「Bakeボタン」をInspector上に作ります。

  1. Hierarchyで何もないところを右クリック > Create Empty で空のオブジェクトを作成。
  2. 名前を NavMeshManager などに変更。
  3. Inspectorで Add Component を押し、NavMeshSurface と検索して追加します。

3. 設定して Bake する

追加された NavMeshSurface コンポーネントを見てください。

  1. Agent Type: Humanoid になっているか確認。
  2. Collect Objects:All Game Objects(シーン全体)になっているか確認。
    • ※これで、Staticにチェックを入れていない家具や床も含めて、すべてのColliderを自動で認識してくれます(Static設定が不要になり、非常に楽です)。
  3. 下にある Bake ボタンを押します。

これでシーンビューを見ると、青いメッシュ(NavMesh)が生成されているはずです。

  1. 家具の配置:
    • Cubeをいくつか作り、「冷蔵庫」「ベッド」「テレビ」に見立てる。
    • それぞれのCubeに SmartObject.cs をアタッチする。
    • 設定例:
      • 冷蔵庫: Need Type = Hunger, Recovery = 50
      • ベッド: Need Type = Energy, Recovery = 100, Time = 5
      • テレビ: Need Type = Fun, Recovery = 30
  2. 人間の配置:
    • Capsuleなどのオブジェクトを作成。
    • NavMeshAgent コンポーネントを追加。
    • HumanLifeAI.cs コンポーネントを追加。
  3. 実行:
    • Playボタンを押します。
    • Sceneビューで見ていると、人間(Capsule)がお腹が減ったら冷蔵庫へ、体力が減ったらベッドへ自動で移動し、生活を始めます。
    • Inspectorで HumanLifeAI のパラメータ(Hungerなど)をスライダーでいじってみてください。AIが即座に反応します。