見出し画像

Unityで他のオブジェクトを取得する方法|GameObject.Find・Transform.Find・FindObjectByType・FindWithTag などの解説

Unityでゲームを作っていると、別のオブジェクトをスクリプトから操作したい場面がよくあります。

たとえば、

・プレイヤーからGameManagerを呼び出したい
・敵からPlayerの位置を取得したい
・ボタンを押したらUIパネルを表示したい
・親オブジェクトの中にある子オブジェクトを取得したい
・タグを使って特定のオブジェクトを探したい
・生成したCloneをスクリプトから操作したい

といった場面です。

このようなときに使うのが、Unityで他のオブジェクトを取得する方法です。

今回は、初心者がよく使う取得方法として、

・Inspectorで直接指定する方法
・GameObject.Find
・Transform.Find
・FindObjectByType
・FindObjectsByType
・GameObject.FindWithTag
・GameObject.FindGameObjectsWithTag
・Instantiateの戻り値を使う方法

について、使いどころを整理して解説します。



まず結論|取得方法は状況によって使い分ける

Unityでは、他のオブジェクトを取得する方法がいくつもあります。

次のように考えると分かりやすいです。

・固定のオブジェクトならInspectorで指定
・子オブジェクトならTransform.Find
・Playerなど役割が決まっているものはFindWithTag
・GameManagerなど管理用クラスはFindObjectByType
・生成したオブジェクトはInstantiateの戻り値を使う

というイメージです。


1,Inspectorで直接指定する方法

まず、一番分かりやすいのがInspectorで直接指定する方法です。

using UnityEngine;

public class Sample : MonoBehaviour
{
    [SerializeField] GameObject targetObject;

    void Start()
    {
        targetObject.SetActive(false);
    }
}

このように書くと、Inspectorから対象のオブジェクトをドラッグ&ドロップで登録できます。

この方法のメリット

Inspectorで指定する方法には、次のようなメリットがあります。

・名前の入力ミスが起きにくい
・どのオブジェクトを使っているかInspector上で確認できる
・inactive(Inspectorでチェックを外し無効状態)のオブジェクトでも登録できる
・実行中にシーン内を探す必要がない

そのため、シーン上に最初から存在している固定オブジェクトを扱う場合には、とても分かりやすい方法です。


Inspector指定が使いにくいケース

ただし、Inspectorで指定する方法がいつでも使いやすいとは限りません。
たとえば、次のような場面です。

・スクリプトがPrefabについている場合
・ターゲットとなるオブジェクトが実行中にCloneで生成される場合
・敵やアイテムなど、同じ種類のオブジェクトが複数生成される場合

このような場合は、Inspectorで事前に登録するのが難しくなります。


2,GameObject.Findで名前から探す

GameObject.Find は、シーン内にあるオブジェクトを名前で探す方法です。

using UnityEngine;

public class Sample : MonoBehaviour
{
    GameObject player;

    void Start()
    {
        player = GameObject.Find("Player");
    }
}

この場合、Hierarchy上にある「Player」という名前のオブジェクトを探して、変数 player に入れています。

GameObject.Find()で指定したオブジェクトを探す

GameObject.Findの注意点

GameObject.Find は、オブジェクト名に依存します。
たとえば、Hierarchy上の名前が

Player

なら取得できます。
しかし、名前が

Hero
PlayerObject
Player(Clone)
player

のようになっていると、別の名前として扱われます。
そのため、名前を変更すると取得できなくなることがあります。


inactiveのオブジェクトを探せない

GameObject.Find は、Hierarchy上でinactiveになっているオブジェクトを取得できません。

たとえば、最初は非表示にしているUIパネルがあるとします。

Canvas
 └ ResultPanel  ※ inactiveh

この状態で、

GameObject panel = GameObject.Find("ResultPanel");

としても、ResultPanel がinactiveの場合は見つかりません。
この場合、panel には null が入り、この状態で次のように使うと、

panel.SetActive(true);

NullReferenceException というエラーになることがあります。

最初から非表示にしておきたいUIパネルなどは、Inspectorで直接指定するか、次に説明する Transform.Find を使う方が分かりやすいです。


3,Transform.Findで子オブジェクトを探す

Transform.Find は、自分の子オブジェクトを名前で探す方法です。

using UnityEngine;

public class Sample : MonoBehaviour
{
    void Start()
    {
        Transform child = transform.Find("Button");

        if (child != null)
        {
            child.gameObject.SetActive(false);
        }
    }
}

GameObject.Find がシーン全体から探すのに対して、Transform.Find は指定したTransformの子オブジェクトを探します。


inactiveの子オブジェクトも探せる

Transform.Find の大きな特徴は、子オブジェクトであればinactiveでも取得できることです。
たとえば、次のような構成があるとします。

Canvas
 └ ResultPanel  ※ inactive

Canvasにスクリプトが付いている場合、次のように子オブジェクトを探せます。

using UnityEngine;

public class UIManager : MonoBehaviour
{
    Transform resultPanel;

    void Start()
    {
        resultPanel = transform.Find("ResultPanel");
    }

    public void ShowResult()
    {
        if (resultPanel != null)
        {
            resultPanel.gameObject.SetActive(true);
        }
    }
}

ResultPanel がinactiveでも、親であるCanvasからたどれる子オブジェクトなので取得できます。
非表示のUIをあとから表示したい場合などに便利です。


子の階層に注意する

ただし、Transform.Find は基本的に指定した階層から探します。
たとえば、

Canvas
 └ ResultPanel

であれば、上で確認した通り次のコードで取得できます。

transform.Find("ResultPanel");

しかし、下のように間にオブジェクトを挟んだ場合

Canvas
 └ Menu
    └ ResultPanel

以下のように、"/" を使ったパスで指定します。

transform.Find("Menu/ResultPanel");

親子関係が分かっている場合は、Transform.Find はとても使いやすい方法です。


4,GetComponentでコンポーネントを取得する

オブジェクトを取得したあと、そのオブジェクトについているスクリプトやコンポーネントを使いたい場合は、GetComponent を使います。

using UnityEngine;

public class Sample : MonoBehaviour
{
    void Start()
    {
        GameObject player = GameObject.Find("Player");
        PlayerController playerController = player.GetComponent<PlayerController>();

        playerController.Damage(10);
    }
}

GameObject.Find で取得できるのは、あくまでGameObjectです。
そのオブジェクトについているスクリプトを使いたい場合は、GetComponent でコンポーネントを取得します。

安全に書くなら、次のようにnullチェックも入れておくとよいです。

void Start()
{
    GameObject player = GameObject.Find("Player");

    if (player == null)
    {
        Debug.Log("Playerが見つかりませんでした");
        return;
    }

    PlayerController playerController = player.GetComponent<PlayerController>();

    if (playerController == null)
    {
        Debug.Log("PlayerControllerが見つかりませんでした");
        return;
    }

    playerController.Damage(10);
}

5,FindObjectByTypeでコンポーネントから探す

FindObjectByType は、シーン内から指定したコンポーネントを持つオブジェクトを探す方法です。

using UnityEngine;

public class Sample : MonoBehaviour
{
    GameManager gameManager;

    void Start()
    {
        gameManager = FindObjectByType<GameManager>();
    }
}

この例では、シーン内にある GameManager コンポーネントを探しています。

GameObject.Find はオブジェクト名で探しますが、FindObjectByType はコンポーネントの種類で探します。

FindObjectByTypeが向いている場面

FindObjectByType は、シーン内に1つだけ存在する管理用クラスを取得するときに便利です。

たとえば、

・GameManager
・SoundManager
・ScoreManager
・UIManager

のようなオブジェクトです。
オブジェクト名ではなく、スクリプトの型で探せるため、名前変更に少し強くなります。
ただし、同じコンポーネントがシーン内に複数ある場合は、どれを取得するのか分かりにくくなることがあります。
そのため、基本的には「シーン内に1つだけあるもの」に使うと分かりやすいです。


6,FindObjectsByTypeで複数取得する

同じコンポーネントを持つオブジェクトをまとめて取得したい場合は、FindObjectsByType を使います。

using UnityEngine;

public class Sample : MonoBehaviour
{
    Enemy[] enemies;

    void Start()
    {
        enemies = FindObjectsByType<Enemy>(FindObjectsSortMode.None);
    }
}

この例では、シーン内にある Enemy コンポーネントを持つオブジェクトをすべて取得しています。

活用シーン

FindObjectsByType は、同じコンポーネントを持つオブジェクトをまとめて取得したいときに使います。
例えば、

・すべての敵を取得する
・すべてのアイテムを取得する
・ステージ上に残っている特定のオブジェクトを調べる

といった場面です。

Enemy[] enemies = FindObjectsByType<Enemy>(FindObjectsSortMode.None);

Debug.Log("敵の数:" + enemies.Length);

このコードでは、シーン内にある Enemy コンポーネントをすべて取得しています。

FindObjectsSortMode.None は、取得した結果を並び替えないという意味です。
敵の数を数えるだけなら、順番は関係ないので None で問題ありません。
並び替えをしない分、処理も軽くなります。

また、inactive のオブジェクトも含めて探したい場合は、次のように書くこともできます。

Enemy[] enemies = FindObjectsByType<Enemy>(
    FindObjectsInactive.Include,
    FindObjectsSortMode.None
);

FindObjectsInactive.Include を指定すると、非表示や無効になっているオブジェクトも検索対象に含められます。

ただし、たくさんのオブジェクトを探す処理なので、これも毎フレーム実行するのではなく、必要なタイミングで実行するのが基本です。


7,FindWithTagでタグから探す

GameObject.FindWithTag は、指定したタグが付いているオブジェクトを1つ探す方法です。

using UnityEngine;

public class Sample : MonoBehaviour
{
    GameObject player;

    void Start()
    {
        player = GameObject.FindWithTag("Player");
    }
}

この例では、Player タグが付いたオブジェクトを探しています。

タグを使うメリット

タグを使うと、オブジェクト名ではなく「役割」で探せます。
たとえば、Hierarchy上の名前が

Hero
PlayerObject
Player(Clone)

のように変わっても、タグが Player であれば取得できます。
そのため、Playerのように役割がはっきりしているオブジェクトを探す場合に向いています。

PrefabからPlayerを探す場合にも

敵Prefabや弾Prefabのように、スクリプトがPrefabについている場合、シーン上のPlayerをInspectorで直接指定しにくいことがあります。
このような場合、生成後に FindWithTag でPlayerを探す方法はよく使われます。

public class EnemyController : MonoBehaviour
{
    GameObject player;

    void Start()
    {
        player = GameObject.FindWithTag("Player");
    }
}

FindGameObjectsWithTagで複数検索

同じタグが付いたオブジェクトをすべて取得したい場合は、GameObject.FindGameObjectsWithTag を使います。

using UnityEngine;

public class Sample : MonoBehaviour
{
    GameObject[] enemies;

    void Start()
    {
        enemies = GameObject.FindGameObjectsWithTag("Enemy");
        Debug.Log("敵の数:" + enemies.Length);
    }
}

この例では、Enemy タグが付いたオブジェクトをすべて取得しています。
同じ役割のオブジェクトをまとめて扱いたいときに便利です。


8,InstantiateしたCloneは戻り値で取得する

実行中にPrefabからオブジェクトを生成する場合は、Instantiate の戻り値を使う方法も重要です。

GameObject enemy = Instantiate(enemyPrefab);

Instantiate は、生成したオブジェクトを返してくれます。
そのため、生成した直後に変数へ入れておけば、そのCloneをすぐに操作できます。

GameObject enemy = Instantiate(enemyPrefab);

enemy.name = "Enemy";
enemy.transform.position = new Vector3(0, 1, 0);

この方法なら、あとから GameObject.Find で探し直す必要がありません。


生成したCloneのスクリプトを操作

生成したオブジェクトについているスクリプトに、Playerなどの情報を渡したい場合もあります。
たとえば、敵を生成したあと、その敵にPlayerを追いかけさせたい場合です。

using UnityEngine;

public class EnemySpawner : MonoBehaviour
{
    [SerializeField] GameObject enemyPrefab;
    [SerializeField] Transform player;

    void Start()
    {
        GameObject enemy = Instantiate(enemyPrefab);

        EnemyController enemyController = enemy.GetComponent<EnemyController>();
        enemyController.SetTarget(player);
    }
}

敵側のスクリプトは次のようにします。

using UnityEngine;

public class EnemyController : MonoBehaviour
{
    Transform target;

    public void SetTarget(Transform newTarget)
    {
        target = newTarget;
    }

    void Update()
    {
        if (target == null) return;

        transform.position = Vector3.MoveTowards(
            transform.position,
            target.position,
            2f * Time.deltaTime
        );
    }
}

このように、生成したあとに必要な情報を渡す方法は、実際のゲーム制作でもよく使います。

Prefab側ではシーン上のPlayerを直接指定しにくいですが、生成する側がPlayerを知っていれば、その情報をCloneに渡すことができます。


まとめ

Unityで他のオブジェクトを取得する方法はいくつかあります。
それぞれの方法には、向いている場面があります。
最初はすべてを完璧に覚える必要はありません
まずは、

・固定オブジェクトはInspectorで指定
・子オブジェクトはTransform.Find
・PlayerはFindWithTag
・GameManagerはFindObjectByType
・生成したCloneはInstantiateの戻り値

くらいで覚えておくと、かなり使いやすくなります。

また、GameObject.Find は便利な方法ですが、

・名前変更に弱い
・inactiveのオブジェクトは探せない
・Updateの中で毎フレーム使うのは避ける

という注意点があります。
Find系は悪い方法ではありません。
ただし、どこから何を取得しているのかが分かるように、場面に合わせて使い分けることが大切です。

「Inspectorで指定できるものはInspectorで指定する」
「PrefabやCloneではFind系や参照渡しを使う」
「生成したものはInstantiateの戻り値を使う」

このように考えると、Unityで他のオブジェクトを取得する処理がかなり整理しやすくなります。


Unity初心者向けの記事をテーマ別にまとめています。

▼ Unity初心者向け記事まとめはこちら


こちらのマガジン「ミニゲーム作成講座 全8回セット」でもシーンの切り替えなどを活用して、初心者でも知識ゼロから作れるミニゲーム作成講座を紹介しています。


ファイブボックスでは、スクラッチやUnityの個別指導のオンラインレッスンを行っています。

ご興味のある方は当サイト、オンラインレッスンから、無料体験授業へお問い合わせ下さい。


いいなと思ったら応援しよう!

ファイブボックス よろしければ応援お願いします! いただいたチップはクリエイターとしての活動費に使わせていただきます!