うら干物書き

ゲームを作っています。

【Unity】君はどこのシーン所属のGameObjectかね

 Hierarchy上に複数のシーンが存在する場合、GameObjectがどこのシーンにあるかを調べる必要があったので方法を調べてみました。
 ピンポイントでそのようなことが分かる関数などがあればよかったのですが、ちょこっと調べた限りでは見つからなかったので、Unity既存の関数を組み合わせることで実現しました。


この記事にはUnity5.6.0f3を使用しています。

シーン直下にあるGameObjectを取得しよう

 各シーンの情報を持つSceneクラスには、GetRootGameObjects()という関数があります。

docs.unity3d.com

 この関数を使うことでルートオブジェクトが取得できます。
 SceneクラスはSceneManagerクラス経由で取得できます。

 たとえばシーンがこのような状態の場合。

f:id:urahimono:20170423161903p:plain

 A,B,C,D,E,FのGameObjectが存在し、
 Bの子にC。
 Cの子にE。
 Dの子にF。

 この状態で以下のスクリプトのコンポーネントを使って、GetRootGameObjects()を呼ぶとどのGameObjectが返るか見てみましょう。

using UnityEngine;
using UnityEngine.SceneManagement;

public class TestComponent : MonoBehaviour
{
    private void Start()
    {
        Scene scene = SceneManager.GetSceneByName( "SceneA" );
        GameObject[] rootObjects    = scene.GetRootGameObjects();
        foreach( var obj in rootObjects )
        {
            Debug.LogFormat( "RootObject = {0}", obj.name );
        }
    }

} // class TestComponent

f:id:urahimono:20170423161936p:plain

RootObject = Object A
RootObject = Object B
RootObject = Object D

 シーン直下においてあるGameObjectであるA,B,Dが取得できることが確認できました。

GameObjectのルートオブジェクトが誰なのかを調べよう

 GameObjectが持つTransformのプロパティrootは、このTransformの親をたどっていき、ルート直下にあるGameObjectTransformを取得することが出来ます。
 自分自身がルートオブジェクトの場合は自分自身が返るのでnullになることはないみたいです。

docs.unity3d.com

 先ほどのシーンにて、各オブジェクトに以下のスクリプトのコンポーネントを付けてどのような値が返るか調べてみましょう。

f:id:urahimono:20170423161903p:plain

using UnityEngine;

public class TestObject : MonoBehaviour
{
    private void Start()
    {
        Transform   rootTransform   = transform.root;
        GameObject  rootObject      = rootTransform.gameObject;
        Debug.LogFormat( "Object={0}, RootObject={1}", gameObject.name, rootObject.name );
    }

} // class TestObject

f:id:urahimono:20170423163009p:plain

Object=Object A, RootObject=Object A
Object=Object B, RootObject=Object B
Object=Object C, RootObject=Object B
Object=Object D, RootObject=Object D
Object=Object E, RootObject=Object B
Object=Object F, RootObject=Object D

 シーン直下においてあるGameObjectが取得できることが確認できました。

各ルートオブジェクトを比較してみよう。

 これで各シーン上にあるルートオブジェクトGameObjectのルートオブジェクトが取得できました。
 あとはこれらを比較してあげることで、このGameObjectがどのシーンにあるものなのかがわかりそうですね。

public string GetSceneContainObject( GameObject i_object )
{
    GameObject rootObject   = i_object.transform.root.gameObject;

    for( int i = 0; i < SceneManager.sceneCount; ++i )
    {
        Scene scene = SceneManager.GetSceneAt( i );
        foreach( var sceneRootObject in scene.GetRootGameObjects() )
        {
            if( sceneRootObject == rootObject )
            {
                return scene.name;
            }
        }
    }

    return null;
}

 このような複数のシーンがある状態で、各GameObjectで先ほどの関数を呼んでみましょう。

f:id:urahimono:20170423162328p:plain
f:id:urahimono:20170423162335p:plain

Object=Object A, Scene=SceneB
Object=Object B, Scene=SceneA
Object=Object C, Scene=SceneA
Object=Object D, Scene=SceneC
Object=Object E, Scene=SceneA
Object=Object F, Scene=SceneC

 これで指定したGameObjectがどのシーンにあるかがわかるようになりました。

 先ほどの関数を少し変えれば、指定したGameObjectが現在アクティブなシーンにあるかの判定などもできたりします。

public bool IsObjectInActiveScene( GameObject i_object )
{
    GameObject rootObject = i_object.transform.root.gameObject;

    Scene scene = SceneManager.GetActiveScene();
    foreach( var sceneRootObject in scene.GetRootGameObjects() )
    {
        if( sceneRootObject == rootObject )
        {
            return true;
        }
    }

    return false;
}

 先ほどのシーンで試すと以下のような結果になります。

f:id:urahimono:20170423162328p:plain
f:id:urahimono:20170423162425p:plain

Object=Object A, IsinActiveScene=False
Object=Object B, IsinActiveScene=True
Object=Object C, IsinActiveScene=True
Object=Object D, IsinActiveScene=False
Object=Object E, IsinActiveScene=True
Object=Object F, IsinActiveScene=False

DontDestroyOnLoadのオブジェクトの場合はどうなるの

 先ほどまで使っていたシーンに新しくオブジェクトGを追加してみましょう。

f:id:urahimono:20170423162439p:plain

 このオブジェクトGはアクティブなシーンであるSceneA内にありますが、Awake()にてDontDestroyOnLoad()を呼んでシーン移動では破棄されない状態にします。

f:id:urahimono:20170423162448p:plain

 この状態ではどのような結果になるのでしょうか。
 先ほどのコンポーネントを使って検証してみます。

f:id:urahimono:20170423162500p:plain

Object=Object G, Scene=
Object=Object G, IsinActiveScene=False

 どのシーンにも所属していないことがわかりました。
 なるほどDontDestroyOnLoad()を使ったGameObjectは、各シーンと関係が解消されるようですね。

所感

 GameObjectがどのGameObjectの子なのか親なのかを判断が必要な場面に比べれば、どのシーンにあるかを気にすることは少ないため、今回の件を利用できる機会は少ないかとは思います。
 それでも、いろいろな関数やプロパティを組み合わせることで、何とかなるみたいですね。