Findメソッド
Unity
には名前でヒエラルキー上のGameObject
を検索するメソッドが用意されています。
GameObject.Find
GameObject.Find
はヒエラルキー上のアクティブなGameObject
を取得します。
private void Awake()
{
var player = GameObject.Find("Player");
}
また、パスを区切って階層化することも出来ます。
private void Awake()
{
var weapon = GameObject.Find("Player/Weapon");
}
Transform.Find
同様にTranform
を検索する、Transform.Find
もあります。
こちらはGameObject.Find
と異なり、インスタンスのメソッドになります。
そのため対象のTransform
の子供から探してくることになります。
private void Awake()
{
var weapon = transform.Find("Weapon");
}
コストの問題
スクリプトリファレンスにもあるようにパフォーマンスの面から、
Update
など頻繁に呼ばれるメソッドで毎回Find
するような処理は避けた方がよいです。
時々そのようなコードを見かけます。
良くないコードの改善
以前、以下の様なコードを見かけました。
private void Update()
{
if (GameObject.Find("Canvas/Scroll View/Viewport/Content/Button/Text (TMP)") != null)
{
var text = GameObject.Find("Canvas/Scroll View/Viewport/Content/Button/Text (TMP)").GetComponent<TextMeshProUGUI>();
// 以下省略
}
}
ここで良くないのはUpdate
メソッド内でGameObject.Find
を呼んでいる点です。
しかも同じパスで2回も。
まずGameObject.Find
の結果をキャッシュするようにしてみましょう。
private void Update()
{
GameObject textObject = GameObject.Find("Canvas/Scroll View/Viewport/Content/Button/Text (TMP)");
if (textObject != null)
{
var text = textObject.GetComponent<TextMeshProUGUI>();
// 以下省略
}
}
これでGameObject.Find
の呼び出しは1回になったので、検索のコストも半分になりました。
更に改善するには、GameObject.Find
をAwake
のタイミングに変更し、メンバ変数としてtextObject
を持っておく方法があります。
private GameObject _textObject;
private void Awake()
{
_textObject = GameObject.Find("Canvas/Scroll View/Viewport/Content/Button/Text (TMP)");
}
private void Update()
{
if (_textObject != null)
{
var text = _textObject.GetComponent<TextMeshProUGUI>();
// 以下省略
}
}
更に改善するなら、GameObject.Find
をやめてしまいましょう。
[SerializeField] private GameObject _textObject;
private void Update()
{
if (_textObject != null)
{
var text = _textObject.GetComponent<TextMeshProUGUI>();
// 以下省略
}
}
_textObject
を[SerializeField]
にして、インスペクタ上から予め指定しておけば、
実行時の検索にかかるコストは一切なくなりました。
長いパスの指定もなくなりすっきりしました。
構成が変わった場合の問題
以下の様に、ソースコード上にパスを直接書いている場合、
ヒエラルキー上の構成を変更すると問題が出てきます。
private void Update()
{
if (GameObject.Find("Canvas/Scroll View/Viewport/Content/Button/Text (TMP)") != null)
{
var text = GameObject.Find("Canvas/Scroll View/Viewport/Content/Button/Text (TMP)").GetComponent<TextMeshProUGUI>();
// 以下省略
}
}
例えばButton
の名前を変えたり、階層を移動したりすると、
これまでのコードでは目的のGameObject
を取得できなくなり、
ソースコードを修正する必要が出てきます。
[SerializeField]
に設定している場合は、
名前を変更しても問題なく(そもそも名前を扱っていない)、
階層を変更しても同じGameObject
を保持してくれます。
まとめ
Unity
の入門書やサンプルコードではよく、Find
メソッドを使っているような印象があります。
Unity
にある程度慣れてきたらコードを見直していてもいいかもしれません。
最初はFind
メソッドや頻繁なGetComponent
の呼び出しが便利に感じるかもしれませんが、
プロジェクトが大きくなるにつれてパフォーマンスへの影響が顕著になることがあります。
効率的な開発を目指すなら、こうしたメソッドの使い方を見直し、適切なタイミングでコンポーネントをキャッシュすることが重要です。