【Unity】【C#】ヒエラルキー(シーン)の全てのオブジェクト(Transform)をスキャンして処理をする 
2020/11/10 Tue [edit]
どちらかというとエディタ拡張(ツール)用。まぁ、使い方によってはランタイムでも良い。
前回の FindTransformByPath() では再帰ルーチンを用いてヒエラルキーを探索していたが、それを取り出して汎用的にしたものと考えても良い(というより、FindTransformByPath() の方が、パス検索用に最適化したものと言える)。
内容的には深さ優先探索で、全ての Transform を無条件に走査するので、負荷はそれなりに高い(アクティブ・非アクティブに関係無く、全てを対象とするため、ヒエラルキー上のオブジェクトの数に比例して時間がかかる)。だが、開発用のツールを自作するときなどは、かなり便利だと思うので、好きに使って欲しい。
(※) Unity 2019.4.14f1 / Windows10(x64) で確認
●深さ優先探索で現在のシーンの全ての Transform を走査(非アクティブ, ""(空の名前) も含む)
using UnityEngine;
/// <summary>
/// 深さ優先探索で現在のシーンの全ての Transform を走査(非アクティブ, ""(空の名前) も含む).
/// 2020/11/10 Fantom (Unity 2019.4)
/// http://fantom1x.blog130.fc2.com/blog-entry-381.html
/// ・再帰での全探索のため、負荷が高く遅いので、ランタイム時の使用は注意.
/// ・コールバックのパス名(path)の頭に '/' は付かない.
/// ・コールバックの階層(rank)はルート or 開始Transform 自身が 0 であり、1つ下階層から 1, 2, 3,… n となる.
/// </summary>
/// <param name="transform">検出開始の Transform (null のとき全て)</param>
/// <param name="callback">Transform, パス名, 階層(ルート,開始は0) のコールバック</param>
/// <param name="maxRank">検索する最大階層: n階層下まで対象 (-1 のとき全ての階層)</param>
public static void DepthFirstScan(this Transform transform, Action<Transform, string, int> callback, int maxRank = -1)
{
if (transform != null)
{
ScanRecursive(transform, callback, "", 0, maxRank);
}
else
{
var roots = GetRootTransforms(); //ルートにある Transform 全て取得(非アクティブも含む)※以前の記事を参照
//全てのルート Transform 以下に再帰で走査する
foreach (var tr in roots)
{
ScanRecursive(tr, callback, "", 0, maxRank);
}
}
}
//再帰処理用
/// <summary>
/// 指定 Transform から、深さ優先探索で Transform を走査する(非アクティブ, ""(空の名前) も含む).
/// 2020/11/10 Fantom (Unity 2019.4)
/// http://fantom1x.blog130.fc2.com/blog-entry-381.html
/// ・同じパス名が複数ある場合もあるので注意(自由に付けられる名前のため).
/// ・パス名は "/" 区切りで表すので、名前に "/" が入っている場合は上手く検出できない(曖昧になる).
/// ・コールバックのパス名(path)の頭に '/' は付かない.
/// ・コールバックの階層(rank)はルート or 開始Transform 自身が 0 であり、1つ下階層から 1, 2, 3,… n となる.
/// ・Transform.Find() に近い結果になるが、最後が '/' の場合、配下の ""(空の名前)として検索する.
/// </summary>
/// <param name="transform">検出開始の Transform</param>
/// <param name="callback">Transform, パス名, 階層(ルートは0) のコールバック</param>
/// <param name="currentPath">再帰での現在のパス名</param>
/// <param name="rank">再帰での現在の階層 (0 がルート,開始)</param>
/// <param name="maxRank">検索する最大階層: n階層下まで対象 (-1 のとき全ての階層)</param>
/// <returns></returns>
private static Transform ScanRecursive(Transform transform, Action<Transform, string, int> callback, string currentPath, int rank, int maxRank = -1)
{
if (transform == null || (maxRank > -1 && rank > maxRank))
return null;
if (string.IsNullOrEmpty(currentPath))
currentPath = transform.name;
if (callback != null)
callback.Invoke(transform, currentPath, rank);
if (transform.childCount == 0)
return null;
for (int i = 0; i < transform.childCount; i++)
{
var child = transform.GetChild(i);
var childPath = currentPath + "/" + child.name; //""(空の名前) でもヒットできる
Transform tr = ScanRecursive(child, callback, childPath, rank + 1, maxRank);
if (tr != null)
return tr;
}
return null;
}
●使用例(メインコード等)
using UnityEngine;
int count = 0;
DepthFirstScan(
null,
(tr, path, rank) =>
{
count++;
Debug.Log($"{tr.name} : path = {path}, rank = {rank}");
});
Debug.Log($"Count = {count}");

GetRootTransforms() は以前の記事からコピペして欲しい。テストも前回のサンプルとほぼ同様なもので試してみよう。
内容的にはただ全てのオブジェクトを走査するだけで、何もしないので、自分の処理を挟むには Action<Transform, string, int> の引数を与える必要がある。コールバックハンドラ(delegate)を作成、または例のように匿名メソッドで書いても良いだろう。
コールバックの第一引数の Transform が現在走査しているオブジェクトなので、プロパティやメソッドを使えば色々できる。GameObject が欲しいなら .gameObject プロパティを使えば良い。ただ、一定のコンポーネントを取得するだけの処理なら、以前に作った GetComponentsAll() を使った方が速いし簡単。複雑な条件分岐を挟むには DepthFirstScan() の方が書きやすいかも知れない。
また、パス名は空白の名前でも利用できるようになっているため、頭に '/' は付けないパス名となっている。頭に '/' を付けたときは親が ""(空の名前)で子が名前として認識する。また GameObject.Find() では最後の '/' の有無は関係ないが、このメソッドの場合、最後のオブジェクトが ""(空の名前) になる。
コメントにも書いておいたが、引数の rank は検索開始の階層(自身の階層)を 0 とし、下の階層を 1, 2, 3,… で表しているので、テストのように "E/F" の名前が付いているものと、E が親で F が子の関係になっているオブジェクトでも、rank を見れば、区別は付けられる(ただし、名前自体に '/' が入っていると、GameObject.Find などでも上手く検出できなくなるので、なるべく避けた方が良い)。
ScanRecursive() は再帰処理用なので private にしてある。DepthFirstScan() の第一引数が null ならヒエラルキーのルートから、null 以外なら指定 Transform から検索開始するので、public にする必要は無いだろう。
これらメソッドは再帰処理してるので、ランタイムでの利用する場合は、開始 Transform を指定したり、階層制限(maxRank) を指定して、検索範囲を絞った方が良い。走査するオブジェクト数が多くなるほど処理時間がかかるので、注意しよう。
(関連記事)
【Unity】【C#】シーン(ヒエラルキー)のルートにある Transform を全て取得する(非アクティブも含む)
【Unity】【C#】非アクティブも含めて、全ての GameObject からコンポーネントを取得する
【Unity】【C#】非アクティブも含めて、Transform (GameObject) をパス名で取得する
【Unity】【C#】Transfrom (GameObject) のパス名を取得する
- 関連記事
トラックバック
トラックバックURL
→http://fantom1x.blog130.fc2.com/tb.php/381-33a52923
この記事にトラックバックする(FC2ブログユーザー)
| h o m e |