【Unity】GameObjectからのコンポーネント取得方法の速度比較【検証】
- 2020.02.13
- Unity
どうもこんにちは、ちはるです。
Unityには、GameObjectクラスを使ってScene中のオブジェクトのコンポーネントを取得する方法がいくつかあります。
今回は、その各取得方法ごとに、どれだけ処理に時間がかかるのか検証していきます。
検証準備
検証対象について
今回はUnity*1で次のような環境を用意しました。
Hierarchy内に、UI要素として
- Image
- Text
- Button
を各10個ずつ配置(各要素が重なっているため、Game画面には1個ずつしか見えてない)しています。
そして、10個あるButtonのうち1個をTargetButtonとし、その中の子要素であるTextをTargetTextとしています。
今回の検証では、このTargetTextのコンポーネントをあの手この手で取得していこうと思います。
なお、TargetButtonとTargetTextには、それぞれ以下のようにオブジェクト名と同じTagを付与してあります。
検証する関数について
使用関数1:GameObject#Find()
まず、オブジェクトを取得する方法その1。
これはGameObjectクラスのFind関数を使用して、対象オブジェクト(ここで言うTargetText)を取得し、そのオブジェクトからコンポーネントを取得してやるという方法です。
using UnityEngine; using UnityEngine.UI; public class TestScript : MonoBehaviour { private void Start() { // 対象オブジェクト名を指定して取得 GameObject.Find("TargetText").GetComponent<Text>(); } }
この関数はアクティブなオブジェクト全てに対し、指定された名前に一致するものを総当たりで探します。
上記のようなStartメソッド内でならまだしも、Updateメソッドなんかで使った日には……、恐ろしいですね。
使用関数2:GameObject#FindWithTag()
続いて検証する関数その2。
この関数はFind関数と異なり、指定したTagが付与されたアクティブオブジェクトを取得します。
using UnityEngine; using UnityEngine.UI; public class TestScript : MonoBehaviour { private void Start() { // Tagを指定して取得 GameObject.FindWithTag("TargetText").GetComponent<Text>(); } }
Find関数と見た感じはかなり似ていますが、Tagを付与してやるだけで総当たり検索を回避できるようです。
オブジェクト取得後のコンポーネント取得方法は方法1と同じです。
使用関数3a:GameObject#GetComponentInChildren()
つづいてがコチラ。
このGetComponentInChildren関数は、深さ優先探索でゲームオブジェクトの子要素のコンポーネントを取得します。
ここでは、子要素の探索を行う対象のゲームオブジェクト(親要素)を、方法1のFind関数にて取得します。
using UnityEngine; using UnityEngine.UI; public class TestScript : MonoBehaviour { private void Start() { /** * 1.親要素(Button)を取得 * 2.Buttonに含まれる子要素のTextを取得 */ GameObject.Find("TargetButton").GetComponentInChildren<Text>(); } }
Advertisement
使用関数3b:GameObject#GetComponentInChildren()
こちらは、コンポーネントを取得する際に使用する関数はそのままで、親要素となるゲームオブジェクトの取得に、FindWithTag関数を用います。
using UnityEngine; using UnityEngine.UI; public class TestScript : MonoBehaviour { private void Start() { /** * 1.親要素(Button)をTag指定で取得 * 2.Buttonに含まれる子要素のTextを取得 */ GameObject.FindWithTag("TargetButton").GetComponentInChildren<Text>(); } }
使用関数4:Transform#Find()
最後に紹介する方法は、これまでとはチョット趣の違ったもの。
というのも、まず親要素を取得し、この親要素にアタッチされているTransformから、Transformの子要素を名前指定で取得するという方法です。
using UnityEngine; using UnityEngine.UI; public class TestScript : MonoBehaviour { private void Start() { /** * 1.親要素(Button)をTag指定で取得 * 2.親要素のTransformから子要素を取得 * */ GameObject.FindGameObjectWithTag("TargetButton").GetComponent<Button>().transform.Find("TargetText").GetComponent<Text>(); } }
ちなみに、ここで使用しているFind関数はGameObject#Find()とは別物なので、当然のことながらFindWithTag関数は使えません。
検証方法について
実際に利用するスクリプト
これまで列挙してきた手段を用い、TargetTextのコンポーネントを取得していきます。
実際に実行するソースは以下のもの。
using System.Diagnostics; using UnityEngine; using UnityEngine.UI; using Debug = UnityEngine.Debug; public class TestFindGameObjectScript : MonoBehaviour { private void Start() { Debug.Log("▼▼▼GetObjectFind▼▼▼"); GetObjectFind(); Debug.Log("▼▼▼GetObjectFindWithTag▼▼▼"); GetObjectFindWithTag(); Debug.Log("▼▼▼GetChildrenObjectFind▼▼▼"); GetChildrenObjectFind(); Debug.Log("▼▼▼GetChildrenObjectFindWithTag▼▼▼"); GetChildrenObjectFindWithTag(); Debug.Log("▼▼▼GetObjectTransForm▼▼▼"); GetObjectTransForm(); } private void GetObjectFind() { var sw = new Stopwatch(); sw.Start(); for (int i = 0; i < 100000; i++) { GameObject.Find("TargetText").GetComponent<Text>(); } sw.Stop(); string str = GameObject.Find("TargetText").GetComponent<Text>().text; Debug.Log(str); Debug.Log($"1回目:{sw.Elapsed}"); sw.Restart(); for (int i = 0; i < 100000; i++) { GameObject.Find("TargetText").GetComponent<Text>(); } sw.Stop(); Debug.Log($"2回目:{sw.Elapsed}"); sw.Restart(); for (int i = 0; i < 100000; i++) { GameObject.Find("TargetText").GetComponent<Text>(); } sw.Stop(); Debug.Log($"3回目:{sw.Elapsed}"); } private void GetObjectFindWithTag() { var sw = new Stopwatch(); sw.Start(); for (int i = 0; i < 100000; i++) { GameObject.FindWithTag("TargetText").GetComponent<Text>(); } sw.Stop(); string str = GameObject.FindWithTag("TargetText").GetComponent<Text>().text; Debug.Log(str); Debug.Log($"1回目:{sw.Elapsed}"); sw.Restart(); for (int i = 0; i < 100000; i++) { GameObject.FindWithTag("TargetText").GetComponent<Text>(); } sw.Stop(); Debug.Log($"2回目:{sw.Elapsed}"); sw.Restart(); for (int i = 0; i < 100000; i++) { GameObject.FindWithTag("TargetText").GetComponent<Text>(); } sw.Stop(); Debug.Log($"3回目:{sw.Elapsed}"); } private void GetChildrenObjectFind() { var sw = new Stopwatch(); sw.Start(); for (int i = 0; i < 100000; i++) { GameObject.Find("TargetButton").GetComponentInChildren<Text>(); } sw.Stop(); string str = GameObject.Find("TargetButton").GetComponentInChildren<Text>().text; Debug.Log(str); Debug.Log($"1回目:{sw.Elapsed}"); sw.Restart(); for (int i = 0; i < 100000; i++) { GameObject.Find("TargetButton").GetComponentInChildren<Text>(); } sw.Stop(); Debug.Log($"2回目:{sw.Elapsed}"); sw.Restart(); for (int i = 0; i < 100000; i++) { GameObject.Find("TargetButton").GetComponentInChildren<Text>(); } sw.Stop(); Debug.Log($"3回目:{sw.Elapsed}"); } private void GetChildrenObjectFindWithTag() { var sw = new Stopwatch(); sw.Start(); for (int i = 0; i < 100000; i++) { GameObject.FindWithTag("TargetButton").GetComponentInChildren<Text>(); } sw.Stop(); string str = GameObject.FindWithTag("TargetButton").GetComponentInChildren<Text>().text; Debug.Log(str); Debug.Log($"1回目:{sw.Elapsed}"); sw.Restart(); for (int i = 0; i < 100000; i++) { GameObject.FindWithTag("TargetButton").GetComponentInChildren<Text>(); } sw.Stop(); Debug.Log($"2回目:{sw.Elapsed}"); sw.Restart(); for (int i = 0; i < 100000; i++) { GameObject.FindWithTag("TargetButton").GetComponentInChildren<Text>(); } sw.Stop(); Debug.Log($"3回目:{sw.Elapsed}"); } private void GetObjectTransForm() { var sw = new Stopwatch(); sw.Start(); for (int i = 0; i < 100000; i++) { GameObject.FindGameObjectWithTag("TargetButton").GetComponent<Button>().transform.Find("TargetText").GetComponent<Text>(); } sw.Stop(); string str = GameObject.FindGameObjectWithTag("TargetButton").GetComponent<Button>().transform.Find("TargetText").GetComponent<Text>().text; Debug.Log(str); Debug.Log($"1回目:{sw.Elapsed}"); sw.Restart(); for (int i = 0; i < 100000; i++) { GameObject.FindGameObjectWithTag("TargetButton").GetComponent<Button>().transform.Find("TargetText").GetComponent<Text>(); } sw.Stop(); Debug.Log($"2回目:{sw.Elapsed}"); sw.Restart(); for (int i = 0; i < 100000; i++) { GameObject.FindGameObjectWithTag("TargetButton").GetComponent<Button>().transform.Find("TargetText").GetComponent<Text>(); } sw.Stop(); Debug.Log($"3回目:{sw.Elapsed}"); } }
こいつをMainCameraにアタッチしてやり、シーンを実行した際に処理が走るようにします。
スクリプトの軽い解説
紹介した5パターンのコンポーネント取得処理を、10万回3セット行います。
実際の時間計測にはStopWatchクラスを使用し、各セット毎に処理に要した時間をログ出力します。
また、ちゃんとTargetTextを取得できてるよね?という確認の意味を込めて、StopWatchでの時間計測範囲外で、それぞれコンポーネントのTextデータもログ出力するようにしてみました。
この場合、ログにボタンの文言「OK」が出力されれば、問題なくTargetTextを取得出来ているということになります。
検証実行結果
結果発表
以下、実行結果の画像。
ログ全体を見やすくまとめてみると、以下のような結果になりました。
▼▼▼GetObjectFind▼▼▼ OK 1回目:00:00:00.0618017 2回目:00:00:00.0593581 3回目:00:00:00.0615687 ▼▼▼GetObjectFindWithTag▼▼▼ OK 1回目:00:00:00.0418070 2回目:00:00:00.0435499 3回目:00:00:00.0441453 ▼▼▼GetChildrenObjectFind▼▼▼ OK 1回目:00:00:00.0729913 2回目:00:00:00.0694690 3回目:00:00:00.0693467 ▼▼▼GetChildrenObjectFindWithTag▼▼▼ OK 1回目:00:00:00.0540094 2回目:00:00:00.0555428 3回目:00:00:00.0556334 ▼▼▼GetObjectTransForm▼▼▼ OK 1回目:00:00:00.0875103 2回目:00:00:00.0783563 3回目:00:00:00.0798122
結果について
今回検証した内だと、GameObject#FindWithTag()を使用するのが最速であることが分かりました。
Tagを指定して決め打ちなので、さもありなんといったところでしょうか。
逆に親要素のオブジェクトを取得後、Transformを参照してどーのこーのやっているTransform#Find()が最も遅い結果となりました。
ちょっと面白いと思ったのが、親要素をFindWithTagで取得してやってから、その子要素をGetComponentInChildren()で取り出す手段が2番目に早いという点でしょうか。
GameObject#FindWithTag()を使えばそりゃ早いけど、Tagの設定数が膨大になりそう……、といった時に、親要素分だけTagを設定し、子要素は親から取り出してやると言った手法が処理速度的にベターと言えそうです。
おわりに
とは言え、ゲーム制作で今回の検証で用いたように、一回一回GameObjectを取得して~コンポーネントを取り出して~、といった方法は用いないかな、と。
- そのシーン中で一度しか使わないようなオブジェクトに対しては、GameObject#FindWithTag()
で十分でしょうが、一つのオブジェクトを複数回使い回す場合や、親要素と子要素どちらに対しても何かしら処理を行う場合など、場合によって使い分けが重要そうです。
- 複数回使い回す場合
- 親要素をGameObject#FindWithTag()で取得し、メンバ変数に保持
- 必要な場所ごとに、GetComponentInChildren()で取得
とか。
オブジェクトのTransformを何かしら弄りたい場合なんかは、むしろTransform#Find()が最適なのかもしれません。
まあ、今適当に考えただけなので分かりませんが()
ということで。
ゲームを制作する段階で、どのようにGameObjectを取得し、コンポーネントを取り出してやれば良いのか非常に気になっていたので、今回は『Unity オブジェクト 取得方法』などでググるとよく目にする代表的(?)な手段について、検証してみました。
これらの手段以外にももっと良い方法があるよ!などなどありましたら、教えて頂けると嬉しいです。
GameObject#GetComponentInParent()はどうしたんだよ!という声は聞こえません。
では、そんな感じで今回の記事を終えます。
ここまで読んで下さり、ありがとうございました!
*1:2019.3.0f6
-
前の記事
【Unity】ゲームリリースしました!【SushiEater】 2020.02.01
-
次の記事
【画像付き】Unity IDの作り方【簡単】 2020.02.26