今回のエンジニアブログを担当する加賀です。
Enumerableクラスを使用して、いくつかの場面を簡潔に記述していきます。
今回のコードはUnity 4.5.3f3、Mono / .NET 3.5で確認しています。
System.Linq.Enumerableクラスは、.NET Framework 3.5以降で使用できるクラスで、
主にIEnumerable
IEnumerable
自分以外の全ての子のGameObjectのリストを取得
GetComponentsInChildren
しかし、自分自身を省いたり、GameObjectに変換したりするコードが手間だったりします。
using UnityEngine; using System.Collections.Generic; using System.Linq; public class Example1 : MonoBehavior { /// <summary> /// 自分以外の全ての子のGameObjectのリストを取得する /// </summary> /// <param name="obj">子を取得したいGameObject</param> /// <return>自分以外の全ての子のGameObjectのリスト</return> public static List<GameObjects> GetAllChildGameObjects(GameObject obj) { // objがnullなら、空のリストを返す if (!obj) return new List<GameObject> (); // 自分自身を含む全ての子のTransformを取得 Transform[] allTransform = obj.GetComponentsInChildren<Transform> (); // 自分自身を除いたTransformをGameObjectに変換する List<GameObject> gameObjects = new List<GameObject> (); foreach(Tranform t in allTransform) { // 自分自身でないなら if (t != obj.transform) { // GameObjectに変換し、リストに追加する gameObjects.Add (t.gameObject); } } // GameObjectのリストを返す return gameObjects; } }
このように、結構長いコードを書く必要があります。
しかし、Enumerableクラスの提供する拡張メソッドを使用すれば、こんなに長く記述する必要はありません。
/// <summary> /// 自分以外の全ての子のGameObjectのリストを取得する /// </summary> /// <param name="obj">子を取得したいGameObject</param> /// <return>自分以外の全ての子のGameObjectのリスト</return> public static List<GameObject> GetAllChildGameObjects(GameObject obj) { // objがnullなら、空のリストを返す if (!obj) return new List<GameObject> (); // 自分自身を含む全ての子のTransformを取得 return obj.GetComponentsInChildren<Transform> () // 自分自身以外の全ての子のTransformを取得 .Where (delegate (Transform t) { if (t != obj.transform) return true; return false; }) // TransformをGameObjectに変換 .Select (delegate (Transform t) { return t.gameObject; }) // リスト化 .ToList (); }
これは匿名メソッドを使用しましたが、1文程度ならラムダ式を使用することもできます。
ラムダ式を使用するなら、横に長くなりますが1行で書くことも可能です。
ラムダ式を使わない上と、一時変数名は同じにしておきます。
/// <summary> /// 自分以外の全ての子のGameObjectのリストを取得する /// </summary> /// <param name="obj">子を取得したいGameObject</param> /// <return>自分以外の全ての子のGameObjectのリスト</return> public static List<GameObject> GetAllChildGameObjects(GameObject obj) { // objがnullなら、空のリストを返す if (!obj) return new List<GameObject> (); // 自分自身を含む全ての子のTransformを取得 return obj.GetComponentsInChildren<Transform> () // 自分自身以外の全ての子のTransformを取得 .Where (t => t != obj.transform) // TransformをGameObjectに変換 .Select (t => t.gameObject) // リスト化 .ToList (); }
上のラムダ式は、引数を1つ取り、値を返すタイプのデリゲートのようなものです。
=>の左側が引数を受け取る変数名で、=>の右側にその値を代入するというイメージです。
Enumerableクラスの提供する拡張メソッドの説明は以下です。
関数シグネチャ | 説明 |
---|---|
IEnumerable System.Func ) |
コレクションの要素1つごとに、predicateが呼び出されます。 Whereメソッドの戻り値は、predicateがtrueを返した要素の集合です。 通常、predicateには、この要素が条件に合うかどうかの判定を記述します。 必ずbool値を返却する必要があります。 |
IEnumerable System.Func ) |
コレクションの要素1つごとに、selectorが呼び出されます。 Selectメソッドの戻り値は、selectorが返した値の集合です。 通常、selectorには、型の変換処理や、この要素への値の更新などを記述します。 必ずなにか値を返却する必要があります。 |
関数シグネチャを見ればわかるように型指定が必要ですが、今回のような書き方をすれば、コンパイラが自動で型を推定してくれます。
Enumerableクラスの提供する拡張メソッドとラムダ式を使うことで、こんなにも短くする事が出来ました。
foreach文でもループカウンタを使用する
配列内のGameObjectを一列に並べるときに、for文でループカウンタなどを使用して実装しますが、
条件文を間違えたり、カウンタ増加を忘れたりとミスも起こりがちです。
foreach文ではループカウンタがないので、今何番目の要素なのかを知ることが簡単にはできません。
しかし、Enumerable.Rangeメソッドを使用すれば、foreach文でもループカウンタを使用できます。
(配列にはインデックスアクセスになってしまいますが・・・)
関数シグネチャ | 説明 |
---|---|
IEnumerable int start, int count ) |
startの値からcount個分、1ずつ増える値を返却するオブジェクトを作成する。 主に、ループカウンタが欲しい場合に、foreach文のコレクション記述位置(inの後ろ)に記述する。 |
using UnityEngine; using System.Collections.Generic; using System.Linq; public class Example2 : MonoBehavior { /// <summary> /// オブジェクト配列 /// </summary> [SerializeField] GameObject[] _gameObjectArray = new GameObject[0]; /// <summary> /// オブジェクト配列のオブジェクトを一列に並べる /// </summary> public void Start() { // _gameObjectArrayの要素の個数回ループする foreach(int i in Enumerable.Range (0, _gameObjectArray.Length)) { // X+方向に並べる _gameObjectArray[i].transform.localPosition = new Vector3 (5f * i, 0f, 0f); } } }
終わり
今回はこの2つだけですが、匿名メソッド、ラムダ式を使えばさらに応用は広がります。
List
配列では、静的メソッドとしてForEach
これらを使用して、短く、それでいて分かりやすいコードを書いていこうと思います。