新しいものが生まれれば、古いものは捨てられていくものです。
Unityの機能も同様です。
新しい機能が追加される傍ら、古い機能が使えなくなったり、存在そのものがなくなったりします。
今回はUnity5.4で新しく追加されたSceneManager.sceneLoaded
と旧機能であるOnLevelWasLoaded
についてのお話です。
この記事にはUnity5.4.0f3を使用しています。
さよなら、OnLevelWasLoaded
シーンが切り替わった際をコンポーネントが検知するにはどうすればいいでしょうか。
そんなときはOnLevelWasLoaded()
の関数をコンポーネントに記述すればいいのです。
http://docs.unity3d.com/ja/current/ScriptReference/MonoBehaviour.OnLevelWasLoaded.htmldocs.unity3d.com
Awake()
やUpdate()
などと同様にMonoBehaviourクラスを継承したクラスならば、関数があるだけでシーンが切り替えた際に通知されます。
簡単に組み込むことが出来てとっても便利です。
さてそれをUnity5.4で使用しようとするとこんなメッセージがConsoleウィンドウに表示されます。
OnLevelWasLoaded was found on DemoScene
This message has been deprecated and will be removed in a later version of Unity.
Add a delegate to SceneManager.sceneLoaded instead to get notifications after scene loading has completed
いずれこの関数を削除するからもう使うなってさ!
事実、Unity5.4のドキュメントのMonoBehaviourの項目からOnLevelWasLoaded
はなくなっていました。
今までお疲れ!
ちなみに現時点では警告文こそ出ますが、OnLevelWasLoadedのメッセージは飛んできますので使用することは出来ます。
新たな希望、SceneManager.sceneLoaded
ではこれからはシーンの切り替えはどのように検知すればよいのでしょうか。
それはUnity5.3からシーン周りの処理を担当することになったSceneManager
が解決してくれました。
Unity5.4からSceneManager
には以下のデリゲートが追加されました。これらに登録することでシーンが切り替わったら読み込み終わったりが判断できますよ。
- SceneManager.activeSceneChanged
docs.unity3d.com - SceneManager.sceneLoaded
docs.unity3d.com - SceneManager.sceneUnloaded
docs.unity3d.com
ちなみに現時点ではドキュメントに記述されている情報が少なかったため、ちょっと補足です。
- activeSceneChanged
宣言は、public static event UnityAction<Scene, Scene> activeSceneChanged;
アクティブになっているシーンが切り替わったら呼ばれます。
第1引数に切り替わる前にアクティブだったシーンが代入されています。
第2引数に切り替えた後にアクティブになったシーンが代入されています。 - sceneLoaded
宣言は、public static event UnityAction<Scene, LoadSceneMode> sceneLoaded;
シーンのロードが終わったら呼ばれます。
第1引数にロードされたシーンが代入されています。
第2引数にシーンの読み込みタイプ(Single or Additive)が代入されています。 - sceneUnloaded
宣言は、public static event UnityAction<Scene> sceneUnloaded;
シーンのアンロードが終わったら呼ばれます。
第1引数にアンロードされたシーンが代入されています。
実際に使ってみよう
では早速以下のスクリプトを使って挙動を確認して見ましょう。
using UnityEngine; using UnityEngine.SceneManagement; using System.Collections; public class DemoScene : MonoBehaviour { void Awake() { SceneManager.activeSceneChanged += OnActiveSceneChanged; SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.sceneUnloaded += OnSceneUnloaded; DontDestroyOnLoad( gameObject ); } void Start() { StartCoroutine( LoadSceneProcess() ); } // void OnLevelWasLoaded( int i_level ) // { // Debug.LogFormat( "OnLevelWasLoaded() {0}", i_level ); // } void OnActiveSceneChanged( Scene i_preChangedScene, Scene i_postChangedScene ) { Debug.LogFormat( "OnActiveSceneChanged() preChangedScene:{0} postChangedScene:{1}", i_preChangedScene.name, i_postChangedScene.name ); } 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 ); } void OnSceneUnloaded( Scene i_unloaded ) { Debug.LogFormat( "OnSceneUnloaded() current:{0} unloaded:{1}", SceneManager.GetActiveScene().name, i_unloaded.name ); } IEnumerator LoadSceneProcess() { while( true ) { yield return new WaitForSeconds( 3.0f ); SceneManager.LoadScene( "SceneB" ); yield return new WaitForSeconds( 3.0f ); SceneManager.LoadScene( "SceneC" ); } } } // class DemoScene
SceneAのGameObjectに上記のDemoScene
をアタッチしてSceneB、SceneCに遷移させています。
ログを見て面白いのは、ゲーム開始はSceneAから始まるためOnSceneLoaded()
が呼ばれるのは分かるのですが、第2引数に4が代入されていることです。
sceneLoadedの第2引数の型であるLoadSceneMode
にはSingleとAdditiveの2つしかないため4という数字が何を表しているのかが不明です。
そして、OnSceneUnloaded
が呼ばれるタイミングはアンロードされるシーン中であるところも興味深いですね。
DemoScene
を改良してもう少し見て見ましょう。
今度はSceneManager.LoadScene()
をLoadSceneMode.Additive
で呼んでみましょう。
using UnityEngine; using UnityEngine.SceneManagement; using System.Collections; public class DemoScene : MonoBehaviour { void Awake() { SceneManager.activeSceneChanged += OnActiveSceneChanged; SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.sceneUnloaded += OnSceneUnloaded; DontDestroyOnLoad( gameObject ); } void Start() { StartCoroutine( LoadSceneProcess() ); } // void OnLevelWasLoaded( int i_level ) // { // Debug.LogFormat( "OnLevelWasLoaded() {0}", i_level ); // } void OnActiveSceneChanged( Scene i_preChangedScene, Scene i_postChangedScene ) { Debug.LogFormat( "OnActiveSceneChanged() preChangedScene:{0} postChangedScene:{1}", i_preChangedScene.name, i_postChangedScene.name ); } 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 ); } void OnSceneUnloaded( Scene i_unloaded ) { Debug.LogFormat( "OnSceneUnloaded() current:{0} unloaded:{1}", SceneManager.GetActiveScene().name, i_unloaded.name ); } IEnumerator LoadSceneProcess() { while( true ) { yield return new WaitForSeconds( 3.0f ); SceneManager.LoadScene( "SceneB", LoadSceneMode.Additive ); yield return new WaitForSeconds( 3.0f ); SceneManager.LoadScene( "SceneC", LoadSceneMode.Additive ); yield return new WaitForSeconds( 3.0f ); SceneManager.SetActiveScene( SceneManager.GetSceneByName( "SceneC" ) ); yield return new WaitForSeconds( 3.0f ); SceneManager.LoadScene( "SceneD", LoadSceneMode.Single ); } } } // class DemoScene
ログから複数のシーンがある状態で、別のシーンへLoadSceneMode.Single
で読み込むと、今まであるシーンの全てでOnSceneUnloaded()
が呼ばれていることが分かります。
では次に非同期読み込みならどうでしょうか。
SceneManager.LoadSceneAsync()
で見てみましょう。
using UnityEngine; using UnityEngine.SceneManagement; using System.Collections; public class DemoScene : MonoBehaviour { void Awake() { SceneManager.activeSceneChanged += OnActiveSceneChanged; SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.sceneUnloaded += OnSceneUnloaded; DontDestroyOnLoad( gameObject ); } void Start() { StartCoroutine( LoadSceneProcess() ); } // void OnLevelWasLoaded( int i_level ) // { // Debug.LogFormat( "OnLevelWasLoaded() {0}", i_level ); // } void OnActiveSceneChanged( Scene i_preChangedScene, Scene i_postChangedScene ) { Debug.LogFormat( "OnActiveSceneChanged() preChangedScene:{0} postChangedScene:{1}", i_preChangedScene.name, i_postChangedScene.name ); } 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 ); } void OnSceneUnloaded( Scene i_unloaded ) { Debug.LogFormat( "OnSceneUnloaded() current:{0} unloaded:{1}", SceneManager.GetActiveScene().name, i_unloaded.name ); } IEnumerator LoadSceneProcess() { while( true ) { yield return new WaitForSeconds( 3.0f ); yield return SceneManager.LoadSceneAsync( "SceneB" ); yield return new WaitForSeconds( 3.0f ); yield return SceneManager.LoadSceneAsync( "SceneC" ); } } } // class DemoScene
SceneManager.LoadScene
とSceneManager.LoadSceneAsync
でOnSceneLoaded
の呼ばれ方が同じことが分かります。
更にallowSceneActivation = false
にして非同期の読み込みを見てみます。
using UnityEngine; using UnityEngine.SceneManagement; using System.Collections; public class DemoScene : MonoBehaviour { void Awake() { SceneManager.activeSceneChanged += OnActiveSceneChanged; SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.sceneUnloaded += OnSceneUnloaded; DontDestroyOnLoad( gameObject ); } void Start() { StartCoroutine( LoadSceneProcess() ); } // void OnLevelWasLoaded( int i_level ) // { // Debug.LogFormat( "OnLevelWasLoaded() {0}", i_level ); // } void OnActiveSceneChanged( Scene i_preChangedScene, Scene i_postChangedScene ) { Debug.LogFormat( "OnActiveSceneChanged() preChangedScene:{0} postChangedScene:{1}", i_preChangedScene.name, i_postChangedScene.name ); } 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 ); } void OnSceneUnloaded( Scene i_unloaded ) { Debug.LogFormat( "OnSceneUnloaded() current:{0} unloaded:{1}", SceneManager.GetActiveScene().name, i_unloaded.name ); } IEnumerator LoadSceneProcess() { while( true ) { yield return new WaitForSeconds( 3.0f ); { AsyncOperation async = SceneManager.LoadSceneAsync( "SceneB" ); async.allowSceneActivation = false; // isDoneでは判断できませんでした。 // yield return new WaitWhile( () => !async.isDone ); // 1.0f にはなりませんでした // yield return new WaitWhile( () => async.progress < 1.0f ); yield return new WaitWhile( () => async.progress < 0.9f ); async.allowSceneActivation = true; } yield return new WaitForSeconds( 3.0f ); { AsyncOperation async = SceneManager.LoadSceneAsync( "SceneC" ); async.allowSceneActivation = false; // isDoneでは判断できませんでした。 // yield return new WaitWhile( () => !async.isDone ); // 1.0f にはなりませんでした // yield return new WaitWhile( () => async.progress < 1.0f ); yield return new WaitWhile( () => async.progress < 0.9f ); async.allowSceneActivation = true; } } } } // class DemoScene
isDone
がtrueにならないことやprogress
が1.0にならないなど既存の不具合?はありますが、ログを見る限り挙動は同じのようです。
SceneManagerのデリゲートになったことにより、今までMonoBehaviourを継承したクラスでなければならなかったところが、全てのクラスで使用できるようになったり、OnLevelWasLoaded()
ではシーン番号しか取得できなかったところが、Scene構造体になったためより多くの情報が取得できるようになったりと利点は多いです。
ただ、各デリゲートに登録した関数は、削除時に必ずデリゲートから解除するのを忘れないようにしなくてはいけませんね。
この機能を使って、たくさんのシーンを切り替えまくるゲームを作りたいものです。