徳島ゲーム開発ごっこ 技術ブログ

ゲームを作るために役に立ったり立たなかったりする技術を学んでいきます!

【Unity】俺はまだSceneManagerを全力で使っていない!

 シーンを管理するシステムを作りたいのですが、それにはまずUnityのSceneManagerについて詳しく知っておきたいものです。
 ドキュメントを見る限り、LoadScene()ぐらいしか主に使ってないですね。
 せっかくなのでこの機会に、SceneManagerで遊びつくしましょう。


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

Get関数と取得するSceneデータについて。

 ドキュメントを見ると、Get○○関数が多いので、まずそれらから調べていきましょう。
 ちなみにこの、Get○○関数で取得するUnityEngine.SceneManagement.Scene構造体。クラスではなく構造体
 値型のためにnullにはならなかったりします。
 じゃあ、Get○○関数でSceneデータの取得に成否の判断はどうすればいいんだ。
 「私、失敗しないので」とでも言う気なのだろうか。

 その点は無問題。
 SceneにはIsValid()という関数があります。これで有効なScene情報か判断していきましょう。

docs.unity3d.com

GetSceneByName()

docs.unity3d.com

 SceneManager に追加されているシーンの中から、指定した名前のシーンを検索します。

 指定した名前のシーン情報を取ってくる関数のようですね。
 早速以下の条件で使ってみます。

f:id:urahimono:20170409171440p:plain f:id:urahimono:20170409171505p:plain

using UnityEngine;
using System.Collections;
using UnityEngine.SceneManagement;

public class TestController : MonoBehaviour
{
    private void Awake()
    {
        // シーンを切り替えていくから、俺は死ぬわけにはいかない!
        DontDestroyOnLoad( gameObject );
    }

    private IEnumerator Start()
    {
        Scene sceneA = SceneManager.GetSceneByName( "SceneA" );
        Debug.LogFormat( "sceneA ={0}", sceneA.IsValid() );

        Scene sceneB = SceneManager.GetSceneByName( "SceneB" );
        Debug.LogFormat( "sceneB ={0}", sceneB.IsValid() );

        Scene sceneX = SceneManager.GetSceneByName( "SceneX" );
        Debug.LogFormat( "sceneX ={0}", sceneX.IsValid() );


        yield return null;
    }

} // class TestController

f:id:urahimono:20170409171453p:plain

 取得に成功したのは、sceneAだけですねー。

 SceneBはAssetとしては存在するが、Hierarchy上に存在しないので、情報の取得は出来ない。
 SceneXはAssetとしてもHierarchy上にも存在しないので、情報の取得は出来ない。

 そしてBuildSettgingsにシーンが登録されているかどうかは、取得に影響しない

 GetScene○○シリーズ中でも一番使いそう。

GetSceneByPath()

docs.unity3d.com

SceneManager に追加されていて、指定したアセットパスを持つすべてのシーンを検索します。

 今度はパスを指定して取得するタイプのようですね。

f:id:urahimono:20170409171440p:plain
f:id:urahimono:20170409171505p:plain

private IEnumerator Start()
{
    Scene sceneA1 = SceneManager.GetSceneByPath( "Scenes/SceneA" );
    Debug.LogFormat( "sceneA1 ={0}", sceneA1.IsValid() );

    Scene sceneA2 = SceneManager.GetSceneByPath( "Assets/Scenes/SceneA" );
    Debug.LogFormat( "sceneA2 ={0}", sceneA2.IsValid() );

    Scene sceneA3 = SceneManager.GetSceneByPath( "Assets/Scenes/SceneA.unity" );
    Debug.LogFormat( "sceneA3 ={0}", sceneA3.IsValid() );


    Scene sceneB = SceneManager.GetSceneByPath( "Assets/Scenes/SceneB.unity" );
    Debug.LogFormat( "sceneB ={0}", sceneB.IsValid() );

    Scene sceneX = SceneManager.GetSceneByPath( "Assets/Scenes/SceneX.unity" );
    Debug.LogFormat( "sceneX ={0}", sceneX.IsValid() );


    yield return null;
}

f:id:urahimono:20170409171457p:plain

 取得に成功したのは、sceneA3のみですね。

 GetSceneByPath()に渡す引数は、プロジェクトフォルダーの相対パスである必要があるみたいですね。
 そのため、Assetsから記述しなくてはならないようです。めんどくさ……。

 SceneA1はパスがAssetsから開始していないので、情報の取得は出来ない。
 SceneA2はパスは正しいが、拡張子(.unity)がないため、情報の取得は出来ない。
 SceneBはパスは正しいが、Hierarchy上に存在しないので、情報の取得は出来ない。
 SceneXはAssetとしてもHierarchy上にも存在しないので、情報の取得は出来ない。

 そして今回も、BuildSettgingsにシーンが登録されているかどうかは、取得に影響しない

 実際に使う際もAssets/と拡張子の書き忘れは多発しそう……。

GetSceneAt()

docs.unity3d.com

SceneManager の追加されたシーンのリストから指定したインデックスのシーンを取得します。

 今度はインデックスを指定して取得するタイプですね。

f:id:urahimono:20170409172004p:plain f:id:urahimono:20170409171505p:plain

private IEnumerator Start()
{
    for( int i = 0; i < 5; ++i )
    {
        Scene scene = SceneManager.GetSceneAt( i );
        Debug.LogFormat( "scene{0} = {1}, name = {2}", i, scene.IsValid(), scene.name );
    }

    yield return null;
}

f:id:urahimono:20170409172017p:plain

 当たり前かもしれませんが、GetSceneAt()に現在あるシーン数以上の数値を入れると例外が発生しました。

IndexOutOfRangeException: Scene index "2" is out of range.

private IEnumerator Start()
{
    Scene scene = SceneManager.GetSceneAt( -1 );
    Debug.LogFormat( "scene{0} = {1}, name = {2}", -1, scene.IsValid(), scene.name );

    yield return null;
}

f:id:urahimono:20170409172159p:plain

 そして、GetSceneAt()に負の数値を入れても例外が発生しました。
 そんなことする人いないと思いますけど……。

IndexOutOfRangeException: Scene index "-1" is out of range.

 一応確認のため、エディタ上でシーンの並び方を変えてみましょう。
 SceneAをアクティブなシーンにしたままの状態で、SceneBをHierarchy上で上の方に配置します。

f:id:urahimono:20170409172216p:plain f:id:urahimono:20170409172221p:plain

 結果、0がSceneBで、1がSceneAになる。
 エディタ上の並びがインデックスの順になっているようですね。

 読み込んだり、破棄したりを繰り返していると、そのシーンが何番目にあるかなんてスクリプト上で把握するのは難しそうですね。
 ピンポイントでこのインデックスのシーンが欲しい、というよりも、今存在する全てのシーンの情報が欲しい、というときに使えそうですね。

GetSceneByBuildIndex()

docs.unity3d.com

 えーと、今度はBuildSettgingsのインデックスから取得するタイプのようですね。
 現在、日本語ドキュメントは5.4までしかなく、そこにはまだないようなので、新しい関数なのかな。

f:id:urahimono:20170409171440p:plain f:id:urahimono:20170409171505p:plain

private IEnumerator Start()
{
    for( int i = 0; i < 5; ++i )
    {
        Scene scene = SceneManager.GetSceneByBuildIndex( i );
        Debug.LogFormat( "scene{0} = {1}, name = {2}", i, scene.IsValid(), scene.name );
    }

    yield return null;
}

f:id:urahimono:20170409172357p:plain

 わざわざ調べる必要はないかとは思いましたが、一応GetSceneAt()と同様に、BuildSettgingsに現在あるシーン数以上の数値を入れると例外が発生。負数も同様。

ArgumentException: GetSceneByBuildIndex: Invalid build index: 1

 ふーむ、ただ妙だな。
 そもそもBuildSettgingsには何のシーンも登録していないんだけどな。
 だから0もダメだと思うんだけど。

 一度SceneBをBuildSettgingsに登録してみましょう。

f:id:urahimono:20170409172416p:plain

 この状態で同じ検証をもう一度行います。

f:id:urahimono:20170409172425p:plain

 うーむ、なんだこの結果は。

 BuildSettgingsの0番に登録したSceneBは、Hierarchy上に存在しないので、情報の取得は出来ない。
 BuildSettgingsに登録していないが、Hierarchy上に存在するSceneAは、1番に登録されている。
 なんでさ!

 さらにもう少し検証が必要なようだ。

private IEnumerator Start()
{
    Debug.LogFormat( "sceneCountInBuildSettings = {0}", SceneManager.sceneCountInBuildSettings );

    for( int i = 0; i < SceneManager.sceneCountInBuildSettings; ++i )
    {
        Scene scene = SceneManager.GetSceneByBuildIndex( i );
        Debug.LogFormat( "scene{0} = {1}, name = {2}", i, scene.IsValid(), scene.name );
    }

    yield return null;
}

f:id:urahimono:20170409172509p:plain

 Build Settingsのシーン数を取得するsceneCountInBuildSettings2を返しよった。
 どうやら、BuildSettgingsに登録していないが、Hierarchy上に存在するシーンはBuild Settingsに連番で登録されるみたいだね。

 エディタで実行した際のシーンによって、Build Settingsに影響があるのだね。
 意図的に使うことはないだろうけど、この挙動は覚えておこう。

GetActiveScene()

docs.unity3d.com

現在アクティブなシーンを取得します。

 以下の画像の状態なら、SceneAの情報が取得できました。

f:id:urahimono:20170409172915p:plain

アクティブなシーンってなんぞ

 ただ、なんなのさ。アクティブなシーンって!

 new GameObject()GameObject.Instantiate()などでオブジェクトを作った際に配置されるのが、アクティブなシーンだ、ぐらいしか知らないなぁ。
 テラシュールブログさんの記事では、ライトマップやナビメッシュなどにも関係があるそうみたいですな。

tsubakit1.hateblo.jp

アクティブなシーンが無い状態ってあり得るの?

 あ、あるのかな。アクティブなシーンが無い状態なんて。
 試してみよう!

private IEnumerator Start()
{
    yield return new WaitForSeconds( 2.0f );

    while( SceneManager.sceneCount > 0 )
    {
        Scene scene = SceneManager.GetActiveScene();
        Debug.LogFormat( "ActiveScene = {0}", scene.name );


        yield return SceneManager.UnloadSceneAsync( scene );

        yield return new WaitForSeconds( 0.5f );
    }

    yield return null;
}

f:id:urahimono:20170409173031g:plain

 SceneBが消えない……。
 ログを見てみましょう。

f:id:urahimono:20170409173108p:plain

Unloading the last loaded scene Assets/Scenes/SceneB.unity(build index: 0), is not supported. Please use.

 なるほど、UnloadSceneAsync()が失敗しておる。
 どうやらシーンが無い状態にすることは出来ないようですね。
 そのため、最低でも1つのシーンがあり、そのシーンはアクティブなシーンになるため、アクティブなシーンが無い状態はありえないみたい。

SetActiveScene

docs.unity3d.com

シーンをアクティブにします。

 関数名そのまんまの挙動。
 渡す引数が有効なScene情報でないと、返り値falseが返って失敗するようだね。

LoadScene()

docs.unity3d.com

Build Settings の名前かインデックスでシーンを読み込みます。

 この関数は結構使ってきているから、ある程度挙動はわかっているつもりですが一応調べておきます。

f:id:urahimono:20170409171440p:plain f:id:urahimono:20170409171505p:plain

private IEnumerator Start()
{
    SceneManager.LoadScene( "SceneB" );

    yield return null;
}

f:id:urahimono:20170409173319p:plain

 BuildSettgingsに登録されていないシーン名を指定すると例外が発生します。

Scene 'SceneB' couldn't be loaded because it has not been added to the build settings or the AssetBundle has not been loaded.
To add a scene to the build settings use the menu File->Build Settings...

 BuildSettgingsに無い番号や負数を指定しても同様です。

f:id:urahimono:20170409173334p:plain

Scene with build index: 1 couldn't be loaded because it has not been added to the build settings.
To add a scene to the build settings use the menu File->Build Settings...

 まあ、でしょうね。

 そして、第二引数にシーンの読み込み方式を決めることが出来ます。
docs.unity3d.com

f:id:urahimono:20170409173412p:plain

private IEnumerator Start()
{
    yield return new WaitForSeconds( 2.0f );

    SceneManager.LoadScene( "SceneB", LoadSceneMode.Additive );

    yield return new WaitForSeconds( 2.0f );

    SceneManager.LoadScene( "SceneC", LoadSceneMode.Single );

    yield return null;
}

f:id:urahimono:20170409173526g:plain

 さて、ここからが、くえすちょんだ!

既にHierarchy上にあるシーンは読み込めるのか

 出来るのかな。出来ていいのかもわかんないけど。

private IEnumerator Start()
{
    while( true )
    {
        yield return new WaitForSeconds( 2.0f );

        SceneManager.LoadScene( "SceneB", LoadSceneMode.Single );
    }
}

f:id:urahimono:20170409173758g:plain

 再読み込みが走ってますね。gifだとわかりにくいけど。

private IEnumerator Start()
{
    while( true )
    {
        yield return new WaitForSeconds( 1.0f );

        SceneManager.LoadScene( "SceneB", LoadSceneMode.Additive );
    }
}

f:id:urahimono:20170409173921g:plain

 Additiveだと動きがわかりやすい。
 気持ち悪いぐらいシーンが増えていってる。

 ただ、出来るとはいえ実際やっていいのかと言えば、正直ちょっとお勧めできそうにないですな。
 なぜなら、同名のシーンが複数存在する場合、GetSceneByName()などでシーン情報を取得するのが極めて難しくなってしまう。
 同じ名前だから、どれがどれなのかわからない。大混乱だ!

 結論、既にHierarchy上にあるシーンも読み込める。
 ただし、GetScene系でシーン情報取得が難しくなる。

最初にHierarchy上にあるシーンとBuildSettgings

 GetSceneByBuildIndex()を調べた際に、実行時にHierarchy上にあるシーンは自動でBuildSettgingsに登録されてました。
 そのシーンはLoadScene()出来るのだろうか。

f:id:urahimono:20170409174003p:plain

private IEnumerator Start()
{
    while( true )
    {
        yield return new WaitForSeconds( 1.0f );

        SceneManager.LoadScene( "SceneA", LoadSceneMode.Additive );
    }
}

f:id:urahimono:20170409174043g:plain

 出来た! 出来よった!
 ただ、ビルドを作成した際は、BuildSettgingsの0番からロードされ開始されるので、エディタ上でのみ有効な手段。
 使うタイミングはあるのだろうか。

 結論、実行時にHierarchy上にあるシーンはBuildSettgingsに登録されていなくても読み込める。
 ただし、エディタ時のみしか使えないため、あんまり意味がない。

LoadSceneAsync()

docs.unity3d.com

バックグラウンドで非同期的にシーンを読み込みます。

 LoadScene()の非同期版。
 返り値のAsyncOperationはそのままコルーチンでyield returnで使うことが出来ますぜ。

private IEnumerator Start()
{
    yield return SceneManager.LoadSceneAsync( "SceneB", LoadSceneMode.Additive );
}

 AsyncOperationで気を付けたいのは、allowSceneActivationの存在だ。
 allowSceneActivationfalseにすることで、シーンを読み込み終わった後、すぐにシーンが有効にならなくなります。
 上手く使うことで、シーンが有効になるタイミングを任意で制御できるようになるのだけれど、allowSceneActivationfalseの場合、AsyncOperationisDonefalseのままで、progressは0.9で止まるので注意
 これは仕様
 もっかい言っときます。これは仕様
 ドキュメントにも書かれますからねー。

docs.unity3d.com

 どうやら、読み込んだシーンが有効にされるまでがAsyncOperationの完了を意味するようですな。

UnloadSceneAsync()

docs.unity3d.com

 非同期によるシーンの破棄。
 Unity5.4ぐらいまではUnloadScene()だったけど、今ではシーンの破棄は非同期のみとなっているみたい。
 すでにUnloadScene()Obsolete扱いになっており、使用が推奨されるものではなくなっています。
 使っている人は切り替えよう!

 GetActiveScene()の時に調べたけど、最低でもシーンは一つ以上存在する必要があるため、最後のシーンを破棄しようとすると以下の警告文が出て失敗します。

Unloading the last loaded scene Assets/Scenes/SceneB.unity(build index: 0), is not supported. Please use.

 ちなみに、現在読み込まれていないシーンをアンロードすると以下の例外が発生しました。

ArgumentException: Scene to unload is invalid.

CreateScene()

docs.unity3d.com

ランタイムに指定した名前で新しい空のシーンを作成します。

 ほー、ランタイム中にシーンが作れるとは面妖な。

 ちなみに空の文字列を渡すと以下の例外が発生。

ArgumentException: The input scene name cannot be empty.

 既にHierarchy上にあるシーン名と同じ文字列を渡しても例外。

ArgumentException: Scene with name "SceneA" already exists.

 まあ、ドキュメントにもそんな文字列は入れんなって言ってますから入れんでください。

 ではこの場合はどうだろう。
 BuildSettgingsに登録されているシーンの名前をCreateScene()に渡す場合。

f:id:urahimono:20170409171440p:plain
f:id:urahimono:20170409172416p:plain

private IEnumerator Start()
{
    SceneManager.CreateScene( "SceneB" );

    SceneManager.LoadSceneAsync( "SceneB", LoadSceneMode.Additive );

    Debug.LogFormat( "sceneCountInBuildSettings = {0}", SceneManager.sceneCountInBuildSettings );

    yield return null;
}

f:id:urahimono:20170409174554p:plain

 どうやら、Hierarchy上にさえ無ければCreateScene()にBuildSettgingsに登録されている同名のシーン名を渡すことは可能であるみたいです。
 この後に、SceneManager.CreateScene( "SceneB" );を呼ぶと、上記の例外が発生してしまいました。

 ちなみにこの後にSceneManager.sceneCountInBuildSettingsの数を調べてみたけど、増えてなかった。
 CreateScene()を使っても、SceneManager.sceneCountInBuildSettingsは増えないみたい。

MoveGameObjectToScene()

docs.unity3d.com

現在のシーンから新しいシーンにゲームオブジェクトを移動します。 ルートオブジェクトだけをあるシーンから他のシーンへ移動できます。つまり、移動するゲームオブジェクトは、シーンのゲームオブジェクトの子であってはなりません。

 オブジェクトをシーンをまたいで移動する関数のようですね。
 注釈に「移動するゲームオブジェクトは、シーンのゲームオブジェクトの子であってはなりません。」と書かれてありますね。
 もちろんやってみます。

f:id:urahimono:20170409174627p:plain

private IEnumerator Start()
{
    yield return SceneManager.LoadSceneAsync( "SceneB", LoadSceneMode.Additive );

    Scene sceneB    = SceneManager.GetSceneByName( "SceneB" );
    SceneManager.MoveGameObjectToScene( m_objectB, sceneB );

    yield return null;
}

f:id:urahimono:20170409174701p:plain

ArgumentException: Gameobject is not a root in a scene.

 「やらないでください」と注釈に書かれている以上、やったら当然例外が出ます。
 ルートにあるObjectAをを引数に渡した場合は、ちゃんと移動しました。

f:id:urahimono:20170409174722p:plain

 上記でも紹介したテラシュールブログさんの記事で書かれていますが、シーンのアクティブを切り替えを頻繁に行うのはよくないようなので、オブジェクトの生成するシーンを変更する場合には、MoveGameObjectToScene()を使ったほうがよさそうですね。

MergeScenes()

docs.unity3d.com

sourceScene を destinationScene にマージします。

 注釈に「危険」と書かれているとは楽しそうな関数

この関数は sourceScene のコンテンツを destinationScene にマージし、 sourceScene を削除します。 sourceScene のルートにあるすべてのゲームオブジェクトは destinationScene のルートに移動します。 注意: souceScene はマージが完了すると破棄されます。そのため、この関数は使い方を間違えると危険です

 さっそく遊んでみる。

using UnityEngine;
using System.Collections;
using UnityEngine.SceneManagement;

public class TestController : MonoBehaviour
{
    [SerializeField]
    private GameObject  m_objectA   = null;
    [SerializeField]
    private GameObject  m_objectB   = null;

    private void Awake()
    {
        // シーンを切り替えていくから、俺は死ぬわけにはいかない!
        DontDestroyOnLoad( gameObject );
    }

    private IEnumerator Start()
    {
        Scene sceneA = SceneManager.GetSceneByName( "SceneA" );

        yield return new WaitForSeconds( 1.0f );

        yield return SceneManager.LoadSceneAsync( "SceneB", LoadSceneMode.Additive );
        Scene sceneB    = SceneManager.GetSceneByName( "SceneB" );

        yield return new WaitForSeconds( 1.0f );

        SceneManager.MergeScenes( sceneB, sceneA );

        yield return new WaitForSeconds( 1.0f );

        yield return SceneManager.LoadSceneAsync( "SceneC", LoadSceneMode.Additive );
        Scene sceneC    = SceneManager.GetSceneByName( "SceneC" );

        yield return new WaitForSeconds( 1.0f );

        SceneManager.MergeScenes( sceneC, sceneA );

        yield return new WaitForSeconds( 1.0f );

        SceneManager.CreateScene( "SceneX" );
        Scene sceneX    = SceneManager.GetSceneByName( "SceneX" );

        yield return new WaitForSeconds( 1.0f );

        SceneManager.MergeScenes( sceneA, sceneX );


        yield return null;
    }

} // class TestController

f:id:urahimono:20170409174846g:plain

 マージされるシーンが次々に無くなっていっていますね。
 見てる分には面白いですが、ちゃんと管理しないと面倒なことになりそうですね。

Events

  • activeSceneChanged:アクティブなシーンが変更時のイベント
  • sceneLoaded:シーンが読み込まれた際のイベント
  • sceneUnloaded:シーンが解放された際のイベント

 この三種のイベント関連については、以前調べましたな。そういえば。

www.urablog.xyz

 一応、バージョンも更新しましたし確認しておきましょうか。

using UnityEngine;
using System.Collections;
using UnityEngine.SceneManagement;

public class TestController : MonoBehaviour
{
    private void Awake()
    {
        // シーンを切り替えていくから、俺は死ぬわけにはいかない!
        DontDestroyOnLoad( gameObject );

        SceneManager.activeSceneChanged += OnActiveSceneChanged;
        SceneManager.sceneLoaded        += OnSceneLoaded;
        SceneManager.sceneUnloaded      += OnSceneUnloaded;
    }

    private IEnumerator Start()
    {
        yield return new WaitForSeconds( 1.0f );

        yield return SceneManager.LoadSceneAsync( "SceneB", LoadSceneMode.Additive );

        yield return new WaitForSeconds( 1.0f );

        yield return SceneManager.LoadSceneAsync( "SceneC", LoadSceneMode.Single );

        yield return new WaitForSeconds( 1.0f );

        SceneManager.CreateScene( "SceneX" );

        yield return new WaitForSeconds( 1.0f );

        Scene sceneC    = SceneManager.GetSceneByName( "SceneC" );
        Scene sceneX    = SceneManager.GetSceneByName( "SceneX" );

        SceneManager.MergeScenes( sceneC, sceneX );

        yield return null;
    }

    private void OnActiveSceneChanged( Scene i_preChangedScene, Scene i_postChangedScene )
    {
        Debug.LogFormat( "OnActiveSceneChanged() preChangedScene:{0} postChangedScene:{1}", i_preChangedScene.name, i_postChangedScene.name );
    }

    private void OnSceneLoaded( Scene i_loadedScene, LoadSceneMode i_mode )
    {
        Debug.LogFormat( "OnSceneLoaded() current:{0} loadedScene:{1} mode:{2}", SceneManager.GetActiveScene().name, i_loadedScene.name, i_mode );
    }

    private void OnSceneUnloaded( Scene i_unloadedScene )
    {
        Debug.LogFormat( "OnSceneUnloaded() current:{0} unloaded:{1}", SceneManager.GetActiveScene().name, i_unloadedScene.name );
    }

} // class TestController

f:id:urahimono:20170409175459g:plain

 ログの結果として、

  • activeSceneChanged:SceneA
  • sceneLoaded:SceneA
  • SceneManager.LoadSceneAsync( "SceneB", LoadSceneMode.Additive );
  • sceneLoaded:SceneB
  • SceneManager.LoadSceneAsync( "SceneC", LoadSceneMode.Single );
  • sceneUnloaded:SceneA
  • sceneUnloaded:SceneB
  • activeSceneChanged:SceneC
  • sceneLoaded:SceneC
  • SceneManager.MergeScenes( sceneC, sceneX );
  • activeSceneChanged:SceneX
  • sceneUnloaded:SceneC

 まとめると、

  • 最初にHierarchy上にあるものは、activeSceneChanged,sceneLoadedの順で呼ばれる
  • SceneManager.LoadScene()LoadSceneMode.Singleで読み込む場合は、現在あるシーンのsceneUnloaded,新しいシーンのactiveSceneChanged,sceneLoadedの順で呼ばれる
  • SceneManager.CreateScene()はシーンを作成したのであって、シーンを読み込んではいないため、sceneUnloadedは呼ばれない
  • アクティブのシーンがマージされる場合は、マージシーンのactiveSceneChanged,マージされたシーンのsceneUnloadedの順で呼ばれる

SceneUtility

 なんか新しいクラスが出来てますね。
 Sceneを扱う際の便利関数ですかね。
 二つしかないようですが……。

  • GetBuildIndexByScenePath()
  • GetScenePathByBuildIndex()

docs.unity3d.com

docs.unity3d.com

 GetBuildIndexByScenePath()はシーンのパスからそれがBuildSettgingsで何番目か調べる関数みたいですね。
 パスの指定はGetSceneByPath()と同じ。

f:id:urahimono:20170409172416p:plain

private IEnumerator Start()
{
    int sceneAIndex     = SceneUtility.GetBuildIndexByScenePath( "Assets/Scenes/SceneA.unity" );
    Debug.LogFormat( "sceneAIndex = {0}", sceneAIndex );

    int sceneBIndex     = SceneUtility.GetBuildIndexByScenePath( "Assets/Scenes/SceneB.unity" );
    Debug.LogFormat( "sceneBIndex = {0}", sceneBIndex );

    int sceneCBIndex    = SceneUtility.GetBuildIndexByScenePath( "Assets/Scenes/SceneC.unity" );
    Debug.LogFormat( "sceneCIndex = {0}", sceneCBIndex );

    yield return null;
}

sceneAIndex = 1
sceneBIndex = 0
sceneCIndex = -1

 パスが誤っていたり、BuildSettgingsにないものだと、-1が返るようです。

 GetScenePathByBuildIndex()はその逆。

private IEnumerator Start()
{
    string scene0   = SceneUtility.GetScenePathByBuildIndex( 0 );
    Debug.LogFormat( "scene0 = {0}", scene0 );

    string scene2   = SceneUtility.GetScenePathByBuildIndex( 2 );
    Debug.LogFormat( "scene2 = {0}", scene2 );

    string sceneN1  = SceneUtility.GetScenePathByBuildIndex( -1 );
    Debug.LogFormat( "sceneN1 = {0}", sceneN1 );


    yield return null;
}

scene0 = Assets/Scenes/SceneB.unity
scene2 =
sceneN1 =

 BuildSettgingsにない数値を指定すると空文字が返るようです。

調査結果その後

 えらく長くなってしまった……。
 長くなった理由がの大半が、明らかに間違った使い方をすると例外が発生するということに関してだ……。

 ただこれでSceneManagerに関して、知識が深まった気がします。
 いまならSceneManagerマネージャーを名乗れそうだ!