🍣

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

2024/01/23に公開

プロジェクト:②FlowerAreaクラス

このプロジェクトでは、ハチドリの環境にあるすべての花を見つけて管理する Flower Area のスクリプトを作成します。このスクリプトは、新しいトレーニングやゲームを開始したときに、花をリセットする役割も担っています。

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

本チュートリアルでは、FlowerArea クラスの変数を設定する方法を学びます。

  • AreaDiameter変数(float型)の宣言:Agent(=ハチドリ)が動き回ったり、Flowerオブジェクトが配置される領域の直径。修飾子としてconstを付して定数として宣言。
  • flowerPlants変数(List<GameObject>型)の宣言:ゲーム領域内のすべてのPlantsを格納するリスト。1つのPlantには複数のFlowerオブジェクトが付いている。
  • nectarFlowerDictionary(Dictionary<Collider,Flower>型)の宣言:KeyはCollider型(本件においてはFlowerオブジェクトのnectarCollider)、ValueはFlower型。nectarColliderとFlowerを一意にマッピングするための変数。
  • Flowersプロパティ(List<Flower>型)の宣言:getはpublic、setはprivate。

上記をまとめたコードは以下の通り。

FlowerArea.cs
///<summary>
/// Manage a collection of flower plants and attached flowers
/// </summary>

public class FlowerArea : MonoBehaviour
{
    // The diameter of the area where the agent and flowers can be
    // used for observing relative distance from agent to flower
    public const float AreaDiameter = 20f;

    // The list of all flower plants in this flower area (floewr plants have multiple flowers)
    private List<GameObject> flowerPlants;

    // A lookup dictionary for looking up a flower from a nectar collider
    private Dictionary<Collider, Flower> nectarFlowerDictionary;

    ///<summary>
    /// The list of all flowers in the flower area
    /// </summary>
    public List<Flower> Flowers { get; private set; }

}	

ResetFlower()、GetFlowerFromNectar()、Awake() と Start() 関数

本チュートリアルでは、FlowerAreaクラスに3種類の関数を作成する方法を学びます。

  • ResetFlowers()メソッドの宣言:flowerPlantオブジェクトを回転させるためのメソッド。
    • 1つ目のforeach():Dictionary型のflowerPlants変数に含まれるflowerPlantオブジェクトのそれぞれに対して、foreach()メソッドを用いて以下の通り処理します。
      • xRotation変数を宣言して、x軸方向の回転値を格納。値はランダム。
      • yRotation変数を宣言して、y軸方向の回転値を格納。値はランダム。
      • zRotation変数を宣言して、z軸方向の回転値を格納。値はランダム。
      • 上記3つの変数をQuaternion.Euler()メソッドの引数に代入して、flowerPlantオブジェクトのlocalRotationの値を設定。これによりflowerPlantオブジェクトは回転する。
foreach(GameObject flowerPlant in flowerPlants)
	{
            float xRotation = UnityEngine.Random.Range(-5f, 5f);
            float yRotation = UnityEngine.Random.Range(-180f, 180f);
            float zRotation = UnityEngine.Random.Range(-5f, 5f);
            flowerPlant.transform.rotation = Quaternion.Euler(xRotation, yRotation, zRotation);
        }
    • 2つ目のforeach():List型のFlowersプロパティに含まれるFlowerオブジェクトのそれぞれに対して、foreach()メソッドを用いて以下の通り処理します。
      • FlowerオブジェクトのResetFlower()メソッドを呼び出し。これによりFlowerオブジェクトに蜜を充填し、色を変更します。
foreach(Flower flower in Flowers)
        {
            flower.ResetFlower();
        }
  • GetFlowerFromNectar()メソッドの宣言:nectarColliderが帰属するFlowerオブジェクトを取得するためのメソッド。このメソッドにCollider型オブジェクトを引数として渡すと、この引数がnectarFlowerDictionary変数のKeyとして設定され、結果として引数として渡されたCollider型オブジェクトとセットになっているFlowerオブジェクトが返り値となります。
    /// <summary>
    /// Get the <see cref="Flower"/> that a nectar collider belongs to
    /// </summary>
    /// <param name="collider">The nectar collider</param>
    /// <returns>The matching flower</returns>
    public Flower GetFlowerFromNectar(Collider collider)
    {
        return nectarFlowerDictionary[collider];
    }
  • Awake()メソッドの宣言:FlowerAreaオブジェクトが生成されると実行されるメソッド。
    • flowerPlants変数(List<GameObject>型)の初期化
    • nectarFlowerDictionary変数(Dictionary<Collider, Flower>型)の初期化
    • Flowers変数(List<Flower>型)の初期化
    /// <summary>
    /// Called when the area wakes up
    /// </summary>
    private void Awake()
    {
        // Initialize variables
        flowerPlants = new List<GameObject>();
        nectarFlowerDictionary = new Dictionary<Collider, Flower>();
        Flowers = new List<Flower>();
    }
  • Start()メソッドの宣言:最初のUpdate()メソッドが実行される前に実行されるメソッド。注:Awake()メソッドが先に実行される。(コードは省略)
    • FindChildFlowers()メソッドの実行。(注:このメソッドはこの後の「FindChildFlowers()」セクションで宣言する。)

上記をまとめたコードは以下の通り。

FlowerArea.cs
public class FlowerArea : MonoBehaviour
{
   /*  ~ 省略 ~  */

    // Reset each flower
    public void ResetFlowers()
    {
        foreach(GameObject flowerPlant in flowerPlants)
        {
            float xRotation = UnityEngine.Random.Range(-5f, 5f);
            float yRotation = UnityEngine.Random.Range(-180f, 180f);
            float zRotation = UnityEngine.Random.Range(-5f, 5f);
            flowerPlant.transform.rotation = Quaternion.Euler(xRotation, yRotation, zRotation);
        }

        foreach(Flower flower in Flowers)
        {
            flower.ResetFlower();
        }
    }

    /// <summary>
    /// Get the <see cref="Flower"/> that a nectar collider belongs to
    /// </summary>
    /// <param name="collider">The nectar collider</param>
    /// <returns>The matching flower</returns>
    public Flower GetFlowerFromNectar(Collider collider)
    {
        return nectarFlowerDictionary[collider];
    }

    /// <summary>
    /// Called when the area wakes up
    /// </summary>
    private void Awake()
    {
        // Initialize variables
        flowerPlants = new List<GameObject>();
        nectarFlowerDictionary = new Dictionary<Collider, Flower>();
        Flowers = new List<Flower>();
    }

    /// <summary>
    /// Called when the game starts
    /// </summary>
    private void Start()
    {
        // Find all flowers that are children of this GameObject/Transform
        FindChildFlowers(transform);
    }
}
	

FindChildFlowers()

本チュートリアルでは、FinChildFlowers関数の書き方を学びます。

  • FindChilidFlowers()メソッドの宣言:親であるTransform型オブジェクトの全ての子オブジェクト(FlowerPlantsオブジェクトやFlowerオブジェクト)を取得するためのメソッド。取得においては再帰処理を用いる。
    • 引数として渡されたTransform型オブジェクト(=親オブジェクト)の子オブジェクトに対してforループを用いて順番に処理。
      • 子オブジェクトのタグが"flower_plants"である場合、List型のflowerPlants変数にこの子オブジェクトを追加格納する(これを繰り返すことにより、ゲーム領域に存在するflowerPlantオブジェクトを全て格納したリストが出来上がっていく)。その後、この子オブジェクトを親としてFindChildFlowers()メソッドの引数として渡し、同様の処理を再帰的に実行。
    if (child.CompareTag("flower_plants"))
    {
	// Found a flower plant, add it to the flowerPlants list
	flowerPlants.Add(child.gameObject);

	// Look for flowers within the flower plant
	FindChildFlowers(child);
    }
      • 子オブジェクトのタグが "flower_plants"ではない場合、子オブジェクトからFlower型のコンポーネントを取得してflower変数に格納。(Note:子オブジェクトのタグが"flower_plants"ではない場合、その子オブジェクトはFlower型のオブジェクトである。)
        • flower変数の中身がnullではない場合、
          • flowerオブジェクトをList型のFlowers変数に追加。
          • flowerオブジェクトのnectarColliderオブジェクトをKey、flowerオブジェクトをValueとして、Dictionary型のnectarFlowerDictionary変数へ追加。
	Flower flower = child.GetComponent<Flower>();

	if(flower != null)
	{
	    // Found a flower, add it to the Flowers list
	    Flowers.Add(flower);

	    // Add the nectar collider to the lookup dictionary
	    nectarFlowerDictionary.Add(flower.nectarCollider, flower);

	    // Note: there are no flowers that are children of other flowers
	}
        • flower変数の中身がnullである場合、
          • 子オブジェクトを親としてFindChildFlowers()メソッドの引数として渡し、同様の処理を再帰的に実行。

上記をまとめたFindChildFlowers()メソッドのコードは以下の通り。

    /// <summary>
    /// Recursively finds all flowers and flower plants that are children of a parent transform
    /// </summary>
    /// <param name="parent">The parent of the children to check</param>
    private void FindChildFlowers(Transform parent)
    {
        for(int i = 0; i < parent.childCount; i++)
        {
            Transform child = parent.GetChild(i);

            if (child.CompareTag("flower_plants"))
            {
                // Found a flower plant, add it to the flowerPlants list
                flowerPlants.Add(child.gameObject);

                // Look for flowers within the flower plant
                FindChildFlowers(child);
            }
            else
            {
                Flower flower = child.GetComponent<Flower>();

                if(flower != null)
                {
                    // Found a flower, add it to the Flowers list
                    Flowers.Add(flower);

                    // Add the nectar collider to the lookup dictionary
                    nectarFlowerDictionary.Add(flower.nectarCollider, flower);

                    // Note: there are no flowers that are children of other flowers
                }
                else
                {
                    // Flower component not found, so check children
                    FindChildFlowers(child);
                }
            }
        }
    }

完成後のFlowerAreaクラス

FlowerArea.cs
using System.Collections.Generic;
using UnityEngine;

///<summary>
/// Manage a collection of flower plants and attached flowers
/// </summary>

public class FlowerArea : MonoBehaviour
{
    // The diameter of the area where the agent and flowers can be
    // used for observing relative distance from agent to flower
    public const float AreaDiameter = 20f;

    // The list of all flower plants in this flower area (floewr plants have multiple flowers)
    private List<GameObject> flowerPlants;

    // A lookup dictionary for looking up a flower from a nectar collider
    private Dictionary<Collider, Flower> nectarFlowerDictionary;

    ///<summary>
    /// The list of all flowers in the flower area
    /// </summary>
    public List<Flower> Flowers { get; private set; }

    // Reset each flower
    public void ResetFlowers()
    {
        foreach(GameObject flowerPlant in flowerPlants)
        {
            float xRotation = UnityEngine.Random.Range(-5f, 5f);
            float yRotation = UnityEngine.Random.Range(-180f, 180f);
            float zRotation = UnityEngine.Random.Range(-5f, 5f);
            flowerPlant.transform.rotation = Quaternion.Euler(xRotation, yRotation, zRotation);
        }

        foreach(Flower flower in Flowers)
        {
            flower.ResetFlower();
        }
    }

    /// <summary>
    /// Get the <see cref="Flower"/> that a nectar collider belongs to
    /// </summary>
    /// <param name="collider">The nectar collider</param>
    /// <returns>The matching flower</returns>
    public Flower GetFlowerFromNectar(Collider collider)
    {
        return nectarFlowerDictionary[collider];
    }

    /// <summary>
    /// Called when the area wakes up
    /// </summary>
    private void Awake()
    {
        // Initialize variables
        flowerPlants = new List<GameObject>();  
        nectarFlowerDictionary = new Dictionary<Collider, Flower>();
        Flowers = new List<Flower>();
    }

    /// <summary>
    /// Called when the game starts
    /// </summary>
    private void Start()
    {
        // Find all flowers that are children of this GameObject/Transform
        FindChildFlowers(transform);
    }

    /// <summary>
    /// Recursively finds all flowers and flower plants that are children of a parent transform
    /// </summary>
    /// <param name="parent">The parent of the children to check</param>
    private void FindChildFlowers(Transform parent)
    {
        for(int i = 0; i < parent.childCount; i++)
        {
            Transform child = parent.GetChild(i);

            if (child.CompareTag("flower_plants"))
            {
                // Found a flower plant, add it to the flowerPlants list
                flowerPlants.Add(child.gameObject);

                // Look for flowers within the flower plant
                FindChildFlowers(child);
            }
            else
            {
                Flower flower = child.GetComponent<Flower>();

                if(flower != null)
                {
                    // Found a flower, add it to the Flowers list
                    Flowers.Add(flower);

                    // Add the nectar collider to the lookup dictionary
                    nectarFlowerDictionary.Add(flower.nectarCollider, flower);

                    // Note: there are no flowers that are children of other flowers
                }
                else
                {
                    // Flower component not found, so check children
                    FindChildFlowers(child);
                }
            }
        }
    }
}

まとめ

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

  • FlowerAreaクラスは、、、
    • Agent(ハチドリ)の行動範囲やFlowerの咲く範囲を保持するAreaDiameter変数を持ちます。
    • 複数のFlowerオブジェクトへの参照をリストで格納するflowerPlants変数を持ちます。
    • nectarColliderをKey、FlowerをValueとする辞書型のnectarFlowerDictionary変数を持ちます。
    • リスト型のFlowersプロパティを持ちます。
    • flowerPlantの向きをランダムに変更し、FlowerオブジェクトのResetFlower()メソッドを呼び出すResetFlowers()メソッドを持ちます。
    • 辞書型のnectarFlowerDictionaryを返すGetFlowerFromNectar()メソッドを持ちます。
    • Awake()メソッドを持ち、flowerPlants変数、nectarFlowerDictionary変数、Flowers変数を初期化します。
    • Start()メソッドを持ち、FindChildFlowers()メソッドを呼び出します。
    • FindChildFlowers()メソッドを持ち、flowerPlants変数、nectarFlowerDictionary変数、Flowers変数に、領域内の全てのflowerPlantsやflowerを収集するとともに、flowerとnectarの一対一の対応付けを行います。

Discussion