今回は、前回の続きで、サービス同士を連結させて、任意のシーンにアクセスできる環境を作ります。
前回までの流れ
tomo-mana.hatenablog.com
プロジェクトをシーンで分割して、作業、デバッグをしやすくする方法について検討してきました。
シーン切り替えのメッセージを仮想化して、シーンを担当者や場面別に分けて開発しても、プロジェクトを結合した時に、実装を変えずに済むようにしたい。また、マネージャーなど一部のシーンが無い状態でも、シーン単体や担当者が受け持つシーン内でデバッグできるようにしたい。
そこで、シーンを以下のように連結して、シーン切り替えを指示するとアクティブなシーンを切り替えながら目的のシーンまで切り替える方法を検討しました。また、目的のシーンが無い場合に、マネージャーの代わりをしてくれるシーンが代理応答できるようにしたいと思います。
(本当はシーンにこの機能があれば、と思うのですが、シーンに機能を追加するわけにもいかないので、サービスに親子認識を持たせます。「サービス」とは、シーンに親子関係を持たせるために前回定義したクラス(SceneMessageService)を指します)
今回は、サービス同士が親子関係を持てるように、サービス同士を連結する処理を追加します。(具体的には、互いへの参照を持たせます)
考え方
前回の記事で、シーン同士の関係を持つサービスを、以下のように定義しました。
お互いに関係のないシーン同士を、サービスで関連付けます(図中のSがサービス)。
サービスを取得するには、シーンの RootGameObjects
にアクセスできる必要があります。
ロードが開始されたかどうかは、LoadSceneAsync()
の直後でも認識できましたが、ロード完了となると話は違います。ロード完了を知る方法はいくつかありそうですが、今回は目的のサービスが相手先のシーンから取得できるようになるまで、シーンからサービスの取得をリトライすることにします。
コード
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; // シーン登録 [System.Serializable] public class SceneRegistration { // シーン名 public string name; // ロードされていなければロードする public bool forceLoad; // シーン(保持) [System.NonSerialized] public Scene scene; // サービス(保持) [System.NonSerialized] public SceneMessageService service; // 初期化完了 public bool initialized = false; } public class SceneMessageService : MonoBehaviour { [SerializeField] public SceneRegistration sceneParent; [SerializeField] public SceneRegistration[] sceneChildren; bool initialized = false; void Start() { // 略(#31) } void Update() { if( !this.initialized ){ bool r = true; if( !sceneParent.initialized ){ r = false; GetService( sceneParent ); } foreach( SceneRegistration sceneChild in sceneChildren ){ if( !sceneChild.initialized ){ r = false; GetService( sceneChild ); } } this.initialized = r; } } List<GameObject> rootObjects = new List<GameObject>(); void GetService(SceneRegistration r) { if( !string.IsNullOrEmpty( r.name ) ){ r.scene = SceneManager.GetSceneByName( r.name ); if( r.scene.name == r.name ){ r.scene.GetRootGameObjects( rootObjects ); if( rootObjects != null ){ foreach( GameObject obj in rootObjects ){ SceneMessageService[] s = obj.GetComponents<SceneMessageService>(); if( s.Length > 0 ){ r.service = s[0]; r.initialized = true; break; } } } } else { if(!r.forceLoad){ r.initialized = true; } } } else { r.initialized = true; } } }
テスト時には、以下のログを追加しています。s[0]
には親のシーン名、s[1]
、s[2]
には子のシーン名が入ります。サービスからシーンを参照できるかで進捗を確認します。(Component→GameObject→Sceneの関係:以前の記事)
void ServiceLog() { string[] s = new string[3]{ "--", "--", "--" }; if( Loaded(sceneParent) ){ s[0] = sceneParent.service.gameObject.scene.name; } for(int i = 0; i < sceneChildren.Length; i++){ if( Loaded(sceneChildren[i]) ){ s[i+1] = sceneChildren[i].service.gameObject.scene.name; } } Debug.Log($"me:{gameObject.scene.name}, p:{s[0]}, c0:{s[1]}, c1:{s[2]}"); // ←確認用のログ } bool Loaded(SceneRegistration r) { if( !string.IsNullOrEmpty( r.name ) ){ if( (r.name == SceneManager.GetSceneByName( r.name ).name) ){ return true; } } return false; }
動作テスト
シーンのロード完了までそれなりに時間はかかるものの、数回のポーリングでサービスが全て取得できました。
前回の設定に従って出力すると、以下のようになります(今回はフィールドシーン経由で起動テストしました)。ツリーの通りに起動しています。
ログ
(前回の設定)
サービスの関連付けができたかは、initializedフラグでも確認できます。
フィールドシーンの例
次回
いよいよ、仮想化してメッセージをターゲットとなるシーンに、他のシーン経由で伝達する方法を実装します。