🍣

Unity Learnチュートリアル「ML-Agents:ハチドリ」で強化学習を始めよう|①Flowerクラスの実装

2024/01/20に公開

ML-Agents:ハチドリ

Unityには、強化学習を構築するためのフレームワークであるML-Agentsがあります。また、Unityの公式なチュートリアル&コースウェアを提供するUnity Learnにおいて、このML-Agentsのチュートリアルを提供する「ML-Agents:ハチドリ」があります。

「ML-Agents:ハチドリ」は、Humming bird (ハチドリ)が蜜を吸うゲームの中で、ハチドリが効率的に蜜を吸うことをゴールとして、強化学習を用いてハチドリをトレーニングするための学習コンテンツです。

この記事は、私が「ML-Agents:ハチドリ」を学習した際に、重要だと考えた事項を備忘用にメモするためのものです。

プロジェクト:①Flower.cs

本プロジェクトでは、個々の花の機能のスクリプトを作成します。ハチドリはこの花と直接やりとりをするので、適切な反応をする必要があります。

1. ML-Agents のインストールとスクリプトの作成

本チュートリアルでは、ML-Agents のパッケージのインストールとスクリプトの設定方法を学びます。

  • Package ManagerからML-Agentsをダウンロード&インストール

  • Scriptsフォルダを新規作成し、その中に以下のC#スクリプトファイルを作成

    • Flower.cs
    • FlowerArea.cs
    • HummingbirdAgent.cs

2. 概要説明

本チュートリアルでは、Flowerクラスの実装を学びます。

  • <summary>タグを使ってクラスの役割などについて記述する。別の場所でこのクラスを記述してそのクラス名にカーソルを合わせると、この<summary>タグで記載した内容がポップアップ表示される。
Flower.cs
/// <summary>
/// Manage a single flower with nector.
/// </summary>	
  • FlowerBud Prefabについて(注:Tutorial動画ではFlower Prefabとなっている)

    • FlowerCollider:花の正面以外の場所(例:花の下や側面)蜜に触れられないようにするためのオブジェクト。Solid Colliderが設定されている。
    • FlowerNectarCollider:beak(クチバシ)が花の蜜に触れているか判定するためのオブジェクト。Mesh ColliderコンポーネントのIs Triggerフラグがオンになっている。Solid Colliderは設定されていない。OnCollisionEnterメソッドを用いてTrigger情報を取得する。
    • 蜜が花の中にある状態においては、花の色を赤としている。Hummingbirdが花の蜜を吸った後は紫系の色にマテリアルを変更する。
  • Flower.cs内のStartメソッドとUpdateメソッドは使用しないので削除する。

3. 変数とアクセス用メソッド

本チュートリアルでは、public 変数を使って Flower クラスを埋め始める方法を学びます。

  • Tooltip Attribute
    • Tooltip AttributeをC#スクリプト内に記載することで、Inspectorウィンドウに表示されるフィールド名にマウスカーソルを合わせると、ヒントメッセージがホバーで表示される。
Flower.cs
  [Tooltip("The color when flower is full")]
   public Color fullFlowerColor = new Color(1f, 0f, .3f); // Pinkish Red
  • nectarCollider変数の宣言 
  • flowerCollider変数の宣言
  • flowerMaterial変数の宣言
  • FlowerUpVectorプロパティの宣言:nectarColliderのY軸方向(=transform属性のupの値)がどこを向いているかを取得するためのプロパティ。
  • FlowerCenterPositionプロパティの宣言:nectarColliderの中心部の座標を取得するためのプロパティ。
  • NectarAmountプロパティの宣言:花の中に残っている蜜の量を取得するためのプロパティ。
  • HasNectarプロパティの宣言:花の中に蜜が残っているか否かを取得するためのプロパティ。

上記を反映したコードは以下の通り。

Flower.cs
public class Flower : MonoBehaviour
{
 
   /*  ~ 省略 ~  */
 
    ///<summary>
    /// The trigger collider representing the nectar.
    ///</summary>
    [HideInInspector]
    public Collider nectarCollider;

    // The solid collider reprensenting the flower petals.
    private Collider flowerCollider;

    // The flower's material.
    private Material flowerMaterial;


    ///<summary>
    /// A vector pointing straight out of the flower.
    ///</summary>
    public Vector3 FlowerUpVector
    {
        get
        {
            return nectarCollider.transform.up;
        }
    }

    /// <summary>
    /// The centor position of the nectar collider.
    /// </summary>
    public Vector3 FlowerCentorPosition
    {
        get
        {
            return nectarCollider.transform.position;
        }
    }

    /// <summary>
    /// The Amount of nectar remaining in the flower.
    /// </summary>
    public float NectarAmount { get; private set;}


    /// <summary>
    /// Wheter the flower has the nectar remaining.
    /// </summary>
    public bool HasNectar
    {
        get
        {
            return NectarAmount > 0f;
        }
    }

}

4. Feed()

本チュートリアルでは、Flower クラスの最初の関数の書き方を学びます。

  • Feedメソッドの宣言:Flowerオブジェクトに残っている蜜の量を減らすメソッド。

    • nectarTaken変数の宣言:花から採取した蜜の量を格納する。 

    • Matsf.Clamp()メソッドでnectarTaken変数の値の範囲を制限。

      • 第1引数:制限したい対象
      • 第2引数:最小値
      • 第3引数:最大値
Flower.cs
public static float Clamp(float value, float min, float max);
    • NectarAmount変数からamount変数の値を控除
      • NectarAmountの値がゼロ以下の場合

        • NectarAmount変数の値をゼロに変更。
        • flowerCollider変数に格納されたオブジェクトを無効化
        • nectarCollider変数に格納されたオブジェクトを無効化
        • flowerMaterial変数に格納されたMaterialオブジェクトの色をemptyFlowerColorに格納された色に変更。
      • nctarTaken変数の値をreturn

上記を反映したコードは以下の通り。

Flower.cs
public class Flower : MonoBehaviour
{
 
   /*  ~ 省略 ~  */
 
    /// <summary>
    /// Attempts to remove nectar from the flower
    /// </summary>
    /// <param name="amount">The amount of nectar to remove</param>
    /// <returns>The actual amount successfully removed</returns>
    public float Feed(float amount)
    {
        // Track how much nectar was successfully taken (cannot take more than is available)
        float nectarTaken = Mathf.Clamp(amount, 0f, NectarAmount);

        // Subtract the nectar
        NectarAmount -= amount;

        if(NectarAmount <= 0)
        {
            // No nectar remaining
            NectarAmount = 0;

            // Disable the flower and nectar colliders
            flowerCollider.gameObject.SetActive(false);
            nectarCollider.gameObject.SetActive(false);

            // Change the flower color to indicate that it is empty
            flowerMaterial.SetColor("_BaseColor", emptyFlowerColor);
        }

        // Return the amount of nectar that was taken
        return nectarTaken;
    }

}

5. ResetFlower()

本チュートリアルでは、リセット機能の作成方法を学びます。

今回のチュートリアルは、4. Feed()におけるNectarAmount変数がゼロとなった時の処理の逆の処理を記述すれば済むので簡単です。

  • ResetFlowerメソッドの宣言:Flowerオブジェクトのamount変数を1fにして、花に蜜がある状態にするとともに、その状態を視覚的に示すために花の色を変更するためのメソッド。

    • NectarAmount変数の値を1fに変更。
    • flowerCollider変数に格納されたオブジェクトを有効化
    • nectarCollider変数に格納されたオブジェクトを有効化
    • flowerMaterial変数に格納されたMaterialオブジェクトの色をfullFlowerColorに格納された色に変更。

上記を反映したコードは以下の通り。

Flower.cs
public class Flower : MonoBehaviour
{
 
   /*  ~ 省略 ~  */
 
    /// <summary>
    /// Resets the flower
    /// </summary>
    public void ResetFlower()
    {
        // Refill the nectar
        NectarAmount = 1f;

        // Enable the flower and nectar colliders
        flowerCollider.gameObject.SetActive(true);
        nectarCollider.gameObject.SetActive(true);

        // Change the flower color to indicate that it is full
        flowerMaterial.SetColor("_BaseColor", fullFlowerColor);
    }

}

6. Awake()

本チュートリアルでは、Awake()関数を使って花 (flower) の分類をまとめる方法を学びます。

  • Awake()関数の宣言:Awake()関数はオブジェクト生成時に一度しか呼ばれない特殊な関数です。Flowerオブジェクトが生成されたタイミングで実行され、以下の処理を行います。

    • FlowerオブジェクトのMeshRendererコンポーネントを取得して、meshRenderer変数に格納します。
    • messhRenderer変数に格納されているMeshRendererコンポーネントのmaterialオブジェクトをflowerMaterial変数に格納します。これによりFlowerオブジェクトのMaterialにアクセスできるようになり、花の色を変更できるようになります。
    • Flowerオブジェクトの子オブジェクトからFlowerColliderオブジェクトを検索します。そしてFlowerColliderオブジェクトのColliderコンポーネントを取得してflowerCollider変数に格納します。
    • Flowerオブジェクトの子オブジェクトからFlowerNectarColliderオブジェクトを検索します。そしてFlowerNectarColliderオブジェクトのColliderコンポーネントを取得してnectarCollider変数に格納します。

上記を反映したコードは以下の通り。

Flower.cs
public class Flower : MonoBehaviour
{
 
   /*  ~ 省略 ~  */
 
    /// <summary>
    /// Called when the flower wakes up
    /// </summary>
    private void Awake()
    {
        // Find the flower's mesh renderer and get the main material
        MeshRenderer meshRenderer = GetComponent<MeshRenderer>();
        flowerMaterial = meshRenderer.material;

        // Find the flower and nectar collider
        flowerCollider = transform.Find("FlowerCollider").GetComponent<Collider>();
        nectarCollider = transform.Find("FlowerNectarCollider").GetComponent<Collider>();
    }

}

完成後のFlowerクラス

Flower.cs
using UnityEngine;

/// <summary>
/// Manage a single flower with nector.
/// </summary>
public class Flower : MonoBehaviour
{
    [Tooltip("The color when flower is full")]
    public Color fullFlowerColor = new Color(1f, 0f, .3f); // Pinkish Red

    [Tooltip("The color when flower is empty")]
    public Color emptyFlowerColor = new Color(.5f, 0f, 1f); //Purple

    ///<summary>
    /// The trigger collider representing the nectar.
    ///</summary>
    [HideInInspector]
    public Collider nectarCollider;

    // The solid collider reprensenting the flower petals.
    private Collider flowerCollider;

    // The flower's material.
    private Material flowerMaterial;



    ///<summary>
    /// A vector pointing straight out of the flower.
    ///</summary>
    public Vector3 FlowerUpVector
    {
        get
        {
            return nectarCollider.transform.up;
        }
    }



    /// <summary>
    /// The centor position of the nectar collider.
    /// </summary>
    public Vector3 FlowerCentorPosition
    {
        get
        {
            return nectarCollider.transform.position;
        }
    }



    /// <summary>
    /// The Amount of nectar remaining in the flower.
    /// </summary>
    public float NectarAmount { get; private set;}


    /// <summary>
    /// Wheter the flower has the nectar remaining.
    /// </summary>
    public bool HasNectar
    {
        get
        {
            return NectarAmount > 0f;
        }
    }



    /// <summary>
    /// Attempts to remove nectar from the flower
    /// </summary>
    /// <param name="amount">The amount of nectar to remove</param>
    /// <returns>The actual amount successfully removed</returns>
    public float Feed(float amount)
    {
        // Track how much nectar was successfully taken (cannot take more than is available)
        float nectarTaken = Mathf.Clamp(amount, 0f, NectarAmount);

        // Subtract the nectar
        NectarAmount -= amount;

        if(NectarAmount <= 0)
        {
            // No nectar remaining
            NectarAmount = 0;

            // Disable the flower and nectar colliders
            flowerCollider.gameObject.SetActive(false);
            nectarCollider.gameObject.SetActive(false);

            // Change the flower color to indicate that it is empty
            flowerMaterial.SetColor("_BaseColor", emptyFlowerColor);
        }

        // Return the amount of nectar that was taken
        return nectarTaken;
    }



    /// <summary>
    /// Resets the flower
    /// </summary>
    public void ResetFlower()
    {
        // Refill the nectar
        NectarAmount = 1f;

        // Enable the flower and nectar colliders
        flowerCollider.gameObject.SetActive(true);
        nectarCollider.gameObject.SetActive(true);

        // Change the flower color to indicate that it is full
        flowerMaterial.SetColor("_BaseColor", fullFlowerColor);
    }



    /// <summary>
    /// Called when the flower wakes up
    /// </summary>
    private void Awake()
    {
        // Find the flower's mesh renderer and get the main material
        MeshRenderer meshRenderer = GetComponent<MeshRenderer>();
        flowerMaterial = meshRenderer.material;

        // Find the flower and nectar collider
        flowerCollider = transform.Find("FlowerCollider").GetComponent<Collider>();
        nectarCollider = transform.Find("FlowerNectarCollider").GetComponent<Collider>();
    }

}

まとめ

今回の記事ではFlowerクラスを実装しました。

  • Flowerクラスは蜜の量、花の色、そして2種類のColliderコンポーネントがアタッチされたオブジェクトをメンバー変数として持ちます。
  • FlowerクラスはFeed()関数とResetFlower()関数で、蜜の量、花の色、そしてColliderのON・OFFを更新します。
  • FlowerオブジェクトはAwake()関数によって、オブジェクト生成時に初期化されます。

Discussion