エンジニア

Unity(C#) 便利なEnumerable

投稿日:2014年8月29日 更新日:

今回のエンジニアブログを担当する加賀です。

Enumerableクラスを使用して、いくつかの場面を簡潔に記述していきます。
今回のコードはUnity 4.5.3f3、Mono / .NET 3.5で確認しています。

System.Linq.Enumerableクラスは、.NET Framework 3.5以降で使用できるクラスで、
主にIEnumerableに対する拡張機能を提供しているクラスです。
IEnumerableインタフェースを実装しているもの(コレクション)は、配列、ListDictionary<TKey, TValue>などがあります。

自分以外の全ての子のGameObjectのリストを取得

GetComponentsInChildren()を使用すれば自分自身を含む全ての子のTransformが取得できます。
しかし、自分自身を省いたり、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 Where (
    System.Func predicate
)
コレクションの要素1つごとに、predicateが呼び出されます。
Whereメソッドの戻り値は、predicateがtrueを返した要素の集合です。
通常、predicateには、この要素が条件に合うかどうかの判定を記述します。
必ずbool値を返却する必要があります。
IEnumerable Select (
    System.Func selector
)
コレクションの要素1つごとに、selectorが呼び出されます。
Selectメソッドの戻り値は、selectorが返した値の集合です。
通常、selectorには、型の変換処理や、この要素への値の更新などを記述します。
必ずなにか値を返却する必要があります。

関数シグネチャを見ればわかるように型指定が必要ですが、今回のような書き方をすれば、コンパイラが自動で型を推定してくれます。

Enumerableクラスの提供する拡張メソッドとラムダ式を使うことで、こんなにも短くする事が出来ました。

foreach文でもループカウンタを使用する

配列内のGameObjectを一列に並べるときに、for文でループカウンタなどを使用して実装しますが、
条件文を間違えたり、カウンタ増加を忘れたりとミスも起こりがちです。
foreach文ではループカウンタがないので、今何番目の要素なのかを知ることが簡単にはできません。
しかし、Enumerable.Rangeメソッドを使用すれば、foreach文でもループカウンタを使用できます。
(配列にはインデックスアクセスになってしまいますが・・・)

関数シグネチャ 説明
IEnumerable Range (
    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()メソッドもあります。
配列では、静的メソッドとしてForEach()が用意されています。
これらを使用して、短く、それでいて分かりやすいコードを書いていこうと思います。

採用情報

ワンダープラネットでは、一緒に働く仲間を幅広い職種で募集しております。

-エンジニア
-,

© WonderPlanet Inc.