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

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

【Unity】allowSceneActivationで遊んでいて気付いたこと

 僕はSceneManager.LoadSceneAsync()を使ってシーンの読み込み処理を作っていました。
 その際に、allowSceneActivationfalseにして、シーンの読み込み完了を自動で行われないようにし、その間に何かしらの処理を行い、その後allowSceneActivationtrueに戻し、シーンが読み込まれるような処理を作成していました。
 更にその読み込み中のタイミングで他の複数のシーンを読み込もうとしていました。
 ただ、この時の挙動が僕の思っていた挙動と違っていたのです。

 というわけで今回は、SceneManager.LoadSceneAsync()を使った際の各シーンの読み込みの完了する順番のお話です。


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

まずはSceneManager.LoadSceneAsync()を使ってみよう

 SceneManagerについては以前散々調べたのでそちらをご参照ください。

www.urablog.xyz

 では簡単にSceneManager.LoadSceneAsync()を連続して使用して、シーンを読み込むスクリプトを作成してみましょう、

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

public class TestComponent : MonoBehaviour
{
    private void Start()
    {
        StartCoroutine( LoadScene( "SceneA" ) );
        StartCoroutine( LoadScene( "SceneB" ) );
        StartCoroutine( LoadScene( "SceneC" ) );
        StartCoroutine( LoadScene( "SceneD" ) );
    }

    private IEnumerator LoadScene( string i_name )
    {
        AsyncOperation async    = SceneManager.LoadSceneAsync( i_name, LoadSceneMode.Additive );
        yield return async;

        Debug.LogFormat( "[Time:{0}] Finished Loading scene={1}", Time.time.ToString( "F2" ), i_name, gameObject );
    }

} // class TestComponent

f:id:urahimono:20170617211313g:plain
f:id:urahimono:20170617211326p:plain

 とくに問題なくシーンが読み込めてますね。
 シーンを読み込む際にログを仕込んだのでそちらも確認してみましょう。

f:id:urahimono:20170617211336p:plain

[Time:0.21] Finished Loading scene=SceneA
[Time:0.28] Finished Loading scene=SceneB
[Time:0.32] Finished Loading scene=SceneC
[Time:0.35] Finished Loading scene=SceneD

 各シーンが順番に読み込まれていることが確認できます。

allowSceneActivationを使って読み込み完了を制御してみよう

 では今度は先ほどのスクリプトを改良して、読み込み完了を待つ時間を指定できるようにしてみます。
 指定した時間の間は、allowSceneActivationfalseにすることで待つようにします。

private void Start()
{
    StartCoroutine( LoadScene( "SceneA", 1.0f ) );
    StartCoroutine( LoadScene( "SceneB", 1.0f ) );
    StartCoroutine( LoadScene( "SceneC", 1.0f ) );
    StartCoroutine( LoadScene( "SceneD", 1.0f ) );
}

private IEnumerator LoadScene( string i_name, float i_waitTime )
{
    AsyncOperation async    = SceneManager.LoadSceneAsync( i_name, LoadSceneMode.Additive );

    Debug.LogFormat( "[Time:{0}] Load scene={1}", Time.time.ToString( "F2" ), i_name, gameObject );

    // 指定した時間の間は、allowSceneActivation=falseにすることで、読み込みが完了しないようにする。
    if( i_waitTime > 0.0f )
    {
        async.allowSceneActivation  = false;
        yield return new WaitForSeconds( i_waitTime );
        async.allowSceneActivation  = true;
    }

    Debug.LogFormat( "[Time:{0}] Waited Loading scene={1}, allowSceneActivation={2}", Time.time.ToString( "F2" ), i_name, async.allowSceneActivation, gameObject );

    yield return async;

    Debug.LogFormat( "[Time:{0}] Finished Loading scene={1}", Time.time.ToString( "F2" ), i_name, gameObject );
}

f:id:urahimono:20170617211406g:plain
f:id:urahimono:20170617211417p:plain

 各シーンとも1秒だけ待つようにしています。
 シーンが途中で(is loading)の状態になっていることが見え、読み込み完了を待っているのがわかります。
 ログも確認してみましょう。

f:id:urahimono:20170617211429p:plain

[Time:0.00] Load scene=SceneA
[Time:0.00] Load scene=SceneB
[Time:0.00] Load scene=SceneC
[Time:0.00] Load scene=SceneD
[Time:1.01] Waited Loading scene=SceneA, allowSceneActivation=True
[Time:1.01] Waited Loading scene=SceneB, allowSceneActivation=True
[Time:1.01] Waited Loading scene=SceneC, allowSceneActivation=True
[Time:1.01] Waited Loading scene=SceneD, allowSceneActivation=True
[Time:1.03] Finished Loading scene=SceneA
[Time:1.14] Finished Loading scene=SceneB
[Time:1.17] Finished Loading scene=SceneC
[Time:1.21] Finished Loading scene=SceneD

特定のシーンだけ待ち時間を長くしてみよう

 さて、先ほど待ち時間を指定できるようにしたわけですが、今度は特定のシーンだけ待ち時間を長くしてみましょう。
 2番目に読み込み予定のSceneBだけ待ち時間を3秒に変更してみましょう。

private void Start()
{
    StartCoroutine( LoadScene( "SceneA", 1.0f ) );
    StartCoroutine( LoadScene( "SceneB", 3.0f ) );
    StartCoroutine( LoadScene( "SceneC", 1.0f ) );
    StartCoroutine( LoadScene( "SceneD", 1.0f ) );
}

f:id:urahimono:20170617211459g:plain
f:id:urahimono:20170617211511p:plain

 そう、これが今回僕に巻き起こった現象です!
 待ち時間を長くしたSceneBだけでなく、その後にSceneManager.LoadSceneAsync()を呼んだSceneCSceneDまで読み込み終わるのが遅くなってしまうのです。
 ログも確認してみましょう。

f:id:urahimono:20170617211525p:plain

[Time:0.00] Load scene=SceneA
[Time:0.00] Load scene=SceneB
[Time:0.00] Load scene=SceneC
[Time:0.00] Load scene=SceneD
[Time:1.00] Waited Loading scene=SceneA, allowSceneActivation=True
[Time:1.00] Waited Loading scene=SceneC, allowSceneActivation=True
[Time:1.00] Waited Loading scene=SceneD, allowSceneActivation=True
[Time:1.02] Finished Loading scene=SceneA
[Time:3.00] Waited Loading scene=SceneB, allowSceneActivation=True
[Time:3.02] Finished Loading scene=SceneB
[Time:3.07] Finished Loading scene=SceneC
[Time:3.12] Finished Loading scene=SceneD

 ログを確認しても、待ち時間はSceneAと同様のタイミングで終了しています。
 ただ、SceneAが読み込み完了したあとも、SceneBが読み込み終わるまで、SceneCSceneD読み込みが完了していません。
 一体何が起こっているのでしょうか。

他の待つ方法ではどうだろうか

 今度はallowSceneActivationを変更せずに、SceneManager.LoadSceneAsync()を呼ぶ前に指定時間待つようなスクリプトに改良してみます。
 先ほどと同じ結果になるでしょうか。

private IEnumerator LoadScene( string i_name, float i_waitTime )
{
    yield return new WaitForSeconds( i_waitTime );

    AsyncOperation async    = SceneManager.LoadSceneAsync( i_name, LoadSceneMode.Additive );

    Debug.LogFormat( "[Time:{0}] Load scene={1}", Time.time.ToString( "F2" ), i_name, gameObject );

    yield return async;

    Debug.LogFormat( "[Time:{0}] Finished Loading scene={1}", Time.time.ToString( "F2" ), i_name, gameObject );
}

f:id:urahimono:20170617211550g:plain
f:id:urahimono:20170617211603p:plain

 この場合は、SceneBだけが遅れて読み込み完了する形になっています。
 僕の望んでいた挙動はこれです。
 ログを確認してみます。

f:id:urahimono:20170617211620p:plain

[Time:1.00] Load scene=SceneA
[Time:1.00] Load scene=SceneC
[Time:1.00] Load scene=SceneD
[Time:1.05] Finished Loading scene=SceneA
[Time:1.12] Finished Loading scene=SceneC
[Time:1.15] Finished Loading scene=SceneD
[Time:3.01] Load scene=SceneB
[Time:3.04] Finished Loading scene=SceneB

 読み込み順ではなく待ち時間が長いものが後に読み込み完了している形になっています。

すごく重いシーンでも試してみよう

 ある程度、今回の現象について推測を立てれるところまで来ていますが、もう少し調べてみましょう。

 今まで使っていたSceneBをたくさんのデータを用いたとサイズの大きいシーンに差し替えてみます。
 そして、1番最初に使っていた、SceneManager.LoadSceneAsync()を呼ぶだけのスクリプト構成に戻してみましょう。
 今までの結果を見る限り、同じ結果になるのではないだろうか。

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

public class TestComponent : MonoBehaviour
{
    private void Start()
    {
        StartCoroutine( LoadScene( "SceneA" ) );
        StartCoroutine( LoadScene( "SceneB" ) );
        StartCoroutine( LoadScene( "SceneC" ) );
        StartCoroutine( LoadScene( "SceneD" ) );
    }

    private IEnumerator LoadScene( string i_name )
    {
        AsyncOperation async    = SceneManager.LoadSceneAsync( i_name, LoadSceneMode.Additive );
        yield return async;

        Debug.LogFormat( "[Time:{0}] Finished Loading scene={1}", Time.time.ToString( "F2" ), i_name, gameObject );
    }

} // class TestComponent

f:id:urahimono:20170617211649g:plain
f:id:urahimono:20170617211709p:plain

 推測通りの結果になりました、
 SceneBの読み込みが終わるまで、SceneCSceneD読み込みが完了しません。
 ログも確認してみます。

f:id:urahimono:20170617211724p:plain

[Time:0.26] Finished Loading scene=SceneA
[Time:0.32] Finished Loading scene=SceneB
[Time:0.69] Finished Loading scene=SceneC
[Time:0.74] Finished Loading scene=SceneD

 SceneCSceneDSceneBの読み込み完了を待っていますね。

感想

 今回の結果からこのような推測ができます。
 SceneManager.LoadSceneAsync()をシーンが読み込み終わる前に複数回呼んだ場合、読み込み完了する順番は、allowSceneActivationを変更したとしても、サイズの大きいシーンが間に挟んだとしても、SceneManager.LoadSceneAsync()を呼んだ順にシーンの読み込みが完了するのではないかということです、

 うーん、この情報どこかに書かれていないものだろうか。
 多分この考えであっているとは思うんだけど確証がほしいなぁ。