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の呼び出しが便利に感じるかもしれませんが、
プロジェクトが大きくなるにつれてパフォーマンスへの影響が顕著になることがあります。
効率的な開発を目指すなら、こうしたメソッドの使い方を見直し、適切なタイミングでコンポーネントをキャッシュすることが重要です。