🍣
Unity Learnチュートリアル「ML-Agents:ハチドリ」で強化学習を始めよう|②FlowerAreaクラスの実装
プロジェクト:②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オブジェクトは回転する。
- 1つ目のforeach():Dictionary型のflowerPlants変数に含まれるflowerPlantオブジェクトのそれぞれに対して、foreach()メソッドを用いて以下の通り処理します。
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オブジェクトに蜜を充填し、色を変更します。
- 2つ目のforeach():List型のFlowersプロパティに含まれるFlowerオブジェクトのそれぞれに対して、foreach()メソッドを用いて以下の通り処理します。
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()メソッドの引数として渡し、同様の処理を再帰的に実行。
- 引数として渡されたTransform型オブジェクト(=親オブジェクト)の子オブジェクトに対してforループを用いて順番に処理。
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変数の中身がnullではない場合、
- 子オブジェクトのタグが "flower_plants"ではない場合、子オブジェクトからFlower型のコンポーネントを取得してflower変数に格納。(Note:子オブジェクトのタグが"flower_plants"ではない場合、その子オブジェクトはFlower型のオブジェクトである。)
-
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()メソッドの引数として渡し、同様の処理を再帰的に実行。
- flower変数の中身がnullである場合、
-
-
上記をまとめた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