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.FindAwakeのタイミングに変更し、メンバ変数として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の呼び出しが便利に感じるかもしれませんが、

プロジェクトが大きくなるにつれてパフォーマンスへの影響が顕著になることがあります。

効率的な開発を目指すなら、こうしたメソッドの使い方を見直し、適切なタイミングでコンポーネントをキャッシュすることが重要です。

🔗関連ページ