【Unity 6 × ML-Agents】Vol.2 スクリプト&脳

前回の記事で、転んでも壊れない強靭な「物理ボディ」を作り上げました。 今回は、そのボディに命を吹き込む「AIの脳みそ(スクリプト)」を作成します。

「機械学習のコード」と聞くと難しそうですが、やることはシンプルに3つだけです。

  1. 観察(Observation): 「今、体がどうなってる?」をAIに教える。
  2. 行動(Action): AIの指令を「関節のパワー」に変換する。
  3. 報酬(Reward): 「前に進んだら褒める」ルールを作る。

これらをまとめた WalkerAgent.cs を作っていきましょう!


第6章:Agentスクリプトの作成と準備

まずはスクリプトファイルを作ります。

  1. Projectウィンドウで右クリック > Create > C# Script
  2. 名前を WalkerAgent にします(※名前を変えると後で面倒なので統一しましょう)。
  3. スクリプトを開き、以下のように Agent クラスを継承させます。

C#

using UnityEngine;
using Unity.MLAgents;
using Unity.MLAgents.Sensors;
using Unity.MLAgents.Actuators;

// MonoBehaviour ではなく Agent を継承するのがポイント!
public class WalkerAgent : Agent 
{
    // ここに体のパーツを登録するための変数を書きます
    public Transform target; // 目標地点(ゴール)
    public Transform hips;   // 腰(体の中心)
    
    // 各関節のコンポーネントを操作するためにリスト化します
    ConfigurableJoint[] joints;
    Rigidbody[] rbs;

    // 初期位置を覚えるための変数
    Vector3 startingPos;

    // ゲーム開始時に1回だけ呼ばれる設定
    public override void Initialize()
    {
        // 体についている全てのJointとRigidbodyを取得
        joints = GetComponentsInChildren<ConfigurableJoint>();
        rbs = GetComponentsInChildren<Rigidbody>();
        
        // スタート地点を記憶
        startingPos = hips.position;
    }

    // エピソード(学習の1回分)が始まるたびに呼ばれるリセット処理
    public override void OnEpisodeBegin()
    {
        // 全身の速度をゼロにして、回転をリセット
        foreach (var rb in rbs)
        {
            rb.linearVelocity = Vector3.zero; // Unity 6では velocity ではなく linearVelocity 推奨
            rb.angularVelocity = Vector3.zero;
        }

        // スタート位置に戻す(少しランダムにずらすと汎用性が上がる)
        hips.position = startingPos + new Vector3(Random.Range(-0.5f, 0.5f), 0, 0);
        hips.rotation = Quaternion.identity;
    }
}

【解説】
OnEpisodeBegin は、転んでやり直しになるたびに呼ばれます。 毎回同じ場所からスタートするとAIが「場所ごとのカンニング」をしてしまうので、
Random.Range で少し左右にずらすのが「賢いAI」を育てるコツです。


第7章:AIの「目」を作る(CollectObservations)

AIは画面を見ていません。数字で世界を認識します。 「ターゲットまでの距離」や「体の傾き」を数字として渡してあげましょう。

C#

    public override void CollectObservations(VectorSensor sensor)
    {
        // 1. ターゲット(ゴール)との距離と方向
        // 今の腰の位置からターゲットへのベクトルを渡します
        sensor.AddObservation(target.position - hips.position);

        // 2. 腰(Hips)の向き
        // 自分がどっちを向いているか、傾いているかを知るため
        sensor.AddObservation(hips.forward);
        sensor.AddObservation(hips.up);

        // 3. 各関節の状態
        foreach (var joint in joints)
        {
            // 関節が今どれくらい曲がっているか?
            sensor.AddObservation(joint.transform.localRotation);
            
            // その関節がどれくらいの速さで動いているか?
            // (これを入れないと、振動したりカクカクしやすくなります)
               sensor.AddObservation(joint.transform.GetComponent<Rigidbody>().angularVelocity);
        }
    }

【なぜこれが必要?】
目隠しでスイカ割りをすることを想像してください。
「右に3メートル」と言われないと動けませんよね?
AIも同じです。
また、「関節の速度(angularVelocity)」 を渡すのが重要で、
これがないとAIは力の加減が分からず、
常にフルパワーで動いてしまいます。


第8章:AIの「筋肉」を動かす(OnActionReceived)

AIは状況を見て、例えば「右足を30度曲げろ!」といった命令(数値)を出します。 これを受け取って、実際の関節(Configurable Joint)を動かす処理です。

C#

    public override void OnActionReceived(ActionBuffers actionBuffers)
    {
        // AIから送られてきた数値リスト(-1.0 〜 1.0 の範囲)
        var continuousActions = actionBuffers.ContinuousActions;

        // すべての関節に対してループ処理
        for (int i = 0; i < joints.Length; i++)
        {
            // Configurable Joint の「目標角度(TargetRotation)」を操作します
            
            // AIの数値を、実際の角度(例:-30度〜30度)に変換
            float xTarget = continuousActions[i * 3 + 0] * 30f;
            float yTarget = continuousActions[i * 3 + 1] * 30f;
            float zTarget = continuousActions[i * 3 + 2] * 30f;

            // Jointに「この角度になれ!」と命令する
            joints[i].targetRotation = Quaternion.Euler(xTarget, yTarget, zTarget);
        }

        // --- ここでついでに「報酬」も計算します ---
        
        // 【重要】頭(腰)の位置をチェック
        // 地面に落ちたら(高さが0.5以下になったら)失敗!
        if (hips.position.y < 0.5f)
        {
            SetReward(-1.0f); // 罰を与える
            EndEpisode();     // やり直し
        }
        else
        {
            // 生きていれば、ターゲットに向かう速度に応じて報酬を与える
            // hips.linearVelocity.z は「前方への速度」です
            float moveSpeed = hips.GetComponent<Rigidbody>().linearVelocity.z;
            AddReward(moveSpeed * 0.1f); 
        }
    }

【解説:Actionの仕組み】
前回設定した Spring(バネ)は、「targetRotation(目標角度)に戻ろうとする力」でしたね。
AIはこの targetRotation を毎フレーム操作することで、筋肉のように手足を動かします。

  • i * 3 しているのは、
    1つの関節につきX, Y, Zの3軸分の命令を受け取るためです。

第9章:人間がテスト操作できるようにする(Heuristic)

学習を始める前に、「そもそも関節が正しく動くか?」をキーボードでテストできるようにします。 これを書いておかないと、バグったときに原因が「AIの頭が悪い」のか「プログラムがミスってる」のか分かりません。

C#

    public override void Heuristic(in ActionBuffers actionBuffersOut)
    {
        var continuousActionsOut = actionBuffersOut.ContinuousActions;

        // スペースキーを押したら「全ての関節を曲げる」テスト
        float input = Input.GetKey(KeyCode.Space) ? 1.0f : 0.0f;

        for (int i = 0; i < joints.Length; i++)
        {
            // 全ての関節のX軸に 1.0 を入力してみる
            continuousActionsOut[i * 3 + 0] = input;
        }
    }

第10章:Unityエディタでのコンポーネント設定

スクリプトが書けたので、Unity画面に戻って設定を行います。

10-1. スクリプトのアタッチ

Hierarchyにある ロボットの親オブジェクト(Walker) に、今作った WalkerAgent.cs をドラッグ&ドロップします。

10-2. 変数の割り当て

Inspectorに TargetHips の枠が表示されているはずです。

  • Target: ゴール地点にするオブジェクト(Cubeなどを作って配置)を入れます。
  • Hips: ロボットの mixamorig:Hips をドラッグして入れます。

10-3. Behavior Parameters の設定

Agentスクリプトをアタッチすると、自動的に Behavior Parameters というコンポーネントも追加されます。ここがAIの設定画面です。

  • Behavior Name: WalkerBrain (好きな名前)
  • Vector Observation > Space Size:「自動計算」されません!
    • ここは手動で計算する必要がありますが、今はとりあえず 100 くらいの大きな数字を入れておけばエラーになりません(実行時にコンソールに「実際は〇〇個でした」と出るので、後でその数字に直せばOKです)。
  • Actions > Continuous Actions: ここが重要です。
    • 関節の数 × 3軸 です。
    • 例えば関節が10個あるなら、30 にします。
    • (ここが足りないと IndexOutOfRangeException エラーが出ます)

10-4. Decision Requester の追加

最後に、Decision Requester というコンポーネントを追加(Add Component)してください。

  • Decision Period:5
    • 「5フレームに1回、AIに判断させる」という意味です。
    • 毎フレーム判断させると動きがプルプル震えてしまうので、5回に1回くらいが人間らしい動きになります。

第11章:動作テスト

いきなり学習させる前に、キーボードで動くか確認します。

  1. Behavior ParametersBehavior TypeHeuristic Only に変更します。
  2. Unityの再生ボタンを押します。
  3. スペースキーを押してみてください。
    • ロボットがビクン!と反応して関節が動けば成功です。
    • もし動かなければ、関節の Configurable Joint の設定(Springなど)か、Actionの数が見直しましょう。

テストがOKなら、Behavior TypeDefault に戻しておいてください。


次回予告

これで「体」と「脳」がつながりました! しかしまだAIの脳は「空っぽ」なので、再生してもその場で崩れ落ちるだけです。

次回、いよいよ完結編 【Vol.3:学習実行とパラメータ調整編】 です。

  • 「設定ファイル(YAML)」の書き方
  • コマンド一発で学習を開始する方法
  • 学習ログの見方(ちゃんと賢くなってる?)
  • 賢くなった脳みそ(.onnx)をゲームに組み込む方法

ここまで来れば、オリジナル歩行AIの完成は目の前です!


[今回のチェックリスト]

  • [ ] Agentスクリプトを作成し、親オブジェクトにアタッチした。
  • [ ] HipsとTargetを割り当てた。
  • [ ] Behavior Parameters の Action数を(関節数×3)に設定した。
  • [ ] Decision Requester を追加した。
  • [ ] Heuristicモードでスペースキーを押して体が動いた。