【Unity】子オブジェクトを列挙する方法

次のようなヒエラルキー上の子オブジェクトをスクリプトから全部取得したい場合はどうすればいいの?

Transformのプロパティやメソッドを使えばできるわ!

ある特定のゲームオブジェクトの子オブジェクトの一覧を列挙する方法の紹介です。

結論を述べると、スクリプトからTransformコンポーネントのAPIを用いれば可能です。
子オブジェクトの列挙には、直下の子のみ取得する方法孫含めて再帰的に取得する方法が挙げられます。

また、Linqが使える場合は非常に簡潔なコードで書けるようになります。

本記事では、ゲームオブジェクトの子オブジェクトを列挙する方法をサンプルコードを示しながら解説していきます。

この作品はユニティちゃんライセンス条項の元に提供されています

動作環境
  • Unity2021.1.21f1

直下の子オブジェクトを列挙する

孫を含まない、指定されたオブジェクト直下の子オブジェクトを列挙する方法です。

for文を使う方法foreach文を使う方法の2種類があります。必要に応じて使いやすいほうを選択すれば良いでしょう。

for文を使って列挙する

あるゲームオブジェクトの子オブジェクトは、Transform.GetChild()メソッドで取得できます。

public Transform GetChild(int index);

引数には取得する子要素のインデックスを指定します。インデックスは0始まりです。

子オブジェクトの総数は、Transform.childCountプロパティから取得できます。

これらのメソッドとプロパティを用いて子オブジェクトを取得するコードは次のようになります。

// parent直下の子オブジェクトをforループで取得する
public static Transform[] GetChildren(this Transform parent)
{
    // 子オブジェクトを返却する配列作成
    var children = new Transform[parent.childCount];

    // 0~個数-1までの子を順番に配列に格納
    for (var i = 0; i < children.Length; ++i)
    {
        children[i] = parent.GetChild(i);
    }

    // 子オブジェクトが格納された配列
    return children;
}

foreach文を使って列挙する

Transformコンポーネントは、foreachステートメントに対応しています。

foreach文を用いると、子要素を先頭から順番に取得できます。

// parent直下の子オブジェクトをforeachループで取得する
public static Transform[] GetChildren(this Transform parent)
{
    // 子オブジェクトを返却する配列作成
    var children = new Transform[parent.childCount];
    var childIndex = 0;

    // 子オブジェクトを順番に配列に格納
    foreach (Transform child in parent)
    {
        children[childIndex++] = child;
    }

    // 子オブジェクトが格納された配列
    return children;
}

なお、内部的にはTransform.GetChild()メソッドを呼び出して子要素を取得しているようです。

Transform.GetEnumerator()メソッドでEnumeratorクラスのインスタンスを生成しているため、処理コストはTransform.GetChild()メソッド呼び出しよりも僅かに高くなります。 [1]

ただ、処理コストに大差は無いため、状況に合わせて使いやすい方法を選択する形で問題ないでしょう。

孫オブジェクトも含めて再帰的に列挙する

孫オブジェクトも含めて列挙したい場合は、Component.GetComponentsInChildren()メソッドを用いると簡単です。

Transform parent;


・・・(中略)・・・

// 親を含む子オブジェクトを再帰的に取得
var parentAndChildren = parent.GetComponentsInChildren<Transform>();

上記のコードの場合、親オブジェクトも含んだ子オブジェクトが再帰的に得られます。

親オブジェクトを含まない子オブジェクトの配列を得たい場合は、次のように配列を新規作成する必要があります。

// parent直下の子オブジェクトを再帰的に取得する
public static Transform[] GetChildrenRecursive(this Transform parent)
{
    // 親を含む子オブジェクトを再帰的に取得
    var parentAndChildren = parent.GetComponentsInChildren<Transform>();
    // 子オブジェクトの返却用配列作成
    var children = new Transform[parentAndChildren.Length - 1];

    // 親を除く子オブジェクトを結果にコピー
    Array.Copy(parentAndChildren, 1, children, 0, children.Length);

    // 子オブジェクトが再帰的に格納された配列
    return children;
}

配列を2重に確保しなければいけないため、処理コストは高くなります。

しかしながら、後述するLinqを用いることである程度回避することが可能です。

Linqを用いた子オブジェクトの列挙

ここまで紹介した方法は、すべてTransform型の配列で結果を返却していました。

インデックスによるランダムアクセスが必要ない場合、Linqを用いてコレクションとして返却すれば、簡潔に実装できるだけでなく、親要素を除く操作などを配列を新規作成せずに実現できます。 [2]

直下の子オブジェクトをLinqで列挙する

次のようなコードになります。

// parent直下の子オブジェクトLinqで列挙する
public static IEnumerable<Transform> EnumChildren(this Transform parent)
{
    return parent.OfType<Transform>();
}

TransformクラスはIEnumerable継承クラスですが、これをEnumerable.OfType()メソッドによりIEnumerable<Transform>型に変換しています。

孫含む子オブジェクトをLinqで再帰的に列挙する

次のようなコードになります。

// parent直下の子オブジェクトLinqで再帰的に列挙する
public static IEnumerable<Transform> EnumChildrenRecursive(this Transform parent)
{
    return parent
        .GetComponentsInChildren<Transform>() // 親を含む子を再帰的に取得
        .Skip(1); // 親をスキップする
}

GetComponentsInChildren()で親を含む孫オブジェクトを取得し、Skip(1)で先頭要素(=親オブジェクト)のみを除外しています。

Linqを使えば凄く簡単に書けてとっても便利だね。

ただ、気を付けないと思わぬところで重たい処理をやりかねないので注意が必要だわ。

Linqでコレクションを扱うときの注意点

コレクションを扱う際、次のようなコードを実行すると、無駄なメモリ領域を確保する処理が走ってしまいます。

// コレクションで子を取得
IEnumerable<Transform> children = transform.EnumChildrenRecursive();
// 配列に変換
Transform[] childrenArray = children.ToArray();

IEnumerable.ToArray()を実行すると、新しい配列を確保し、この配列に列挙した要素を格納する処理が走ります。

CPU、メモリどちらのリソースも消費してしまうため、必要最小限の使用に留めておくべきでしょう。

さいごに

子オブジェクトをTransformとして列挙する方法を紹介しました。

取得自体は簡単ですが、子オブジェクトの数が多くなるとパフォーマンスに影響が出る点には注意する必要があります。

頻繁な呼び出しはなるべく避け、結果を使いまわすなどの工夫をして使うとよいでしょう。

参考サイト