今回から、シーンによるプロジェクトの分割に挑戦します。
「シーンによるプロジェクトの分割」と呼んでいるのは、ゲーム自体を複数のシーンに分けて作業性を高め、分割・結合を簡単にできるようにすることです。ゲーム全体を一つのマネージャー(ゲーム全体のデータを管理する構造体)に管理させるだけでなく、全てのシーンが揃っていなくても、最低限のシーンだけでテストができるようにします。
<目次>
前回までの流れ
マネージャーシーンの追加(#29)
第29回で、加算ロードを使った、マネージャーシーン(シーン同士のデータ受け渡し役)の追加に挑戦しました。
tomo-mana.hatenablog.com
テストモジュール、シーンの透過性アップ(#30)
第30回で、フィールドや戦闘シーンを、シーン単位でデバッグできるように、ドライバの役目を果たすモジュールを追加しました。これによってシーン間のインターフェースを明確にする必要が出て、透過性がアップします。
tomo-mana.hatenablog.com
シーン切り替えを仮想化(#31-1)
今回の記事に先立って、ドライバを意識しないでシーン切り替えができるように、仮想化の可能性について検討しました。また、仮想化がどれくらい複雑になりそうかを想定しました。
tomo-mana.hatenablog.com
今回やりたいこと
前回までの流れをまとめると、こんな感じです。
例えば、FIELD から BATTLE にシーンを切り替えるケースを考えます。
通常は、SceneManager.LoadScene(BATTLE)
のように指定し、FIELDシーン を破棄して BATTLEシーン に移ります(図の黒線)。もしくは、FIELD→MANAGER→BATTLEというように、すでに加算ロードされたシーン同士で、MANAGER を介してシーンを切り替えます(図の赤線)。
シーン切り替えの階層化
今回は、後者の加算ロードを使った切り替えを、もう少し階層的に組めるようにします。
以下の図で言うと、Map0 は BattleRegion0 に移るために、先ほどのSceneManager.LoadScene()
のようにシーン切り替え(BattleRegion0)
のような呼び方をすると(図の黒線)、実際は Map0→作業者A→FIELD→Manager→Battle→作業者B→BattleRegion0の順にアクティブなシーンを切り替えて(図の赤線)、最終的にMap0→BattleRegion0にシーンが切り替わるようにします。
このシーンの切り替えを仲介する機能を、サービス(SceneMessageService)と呼ぶことにします。
今回、目指すのはこれです。
※図の矢印は、誰から誰をロードするかを示しています。ただし、マネージャーに繋がっているシーンは、マネージャーを呼び出すものとします。
階層の記述
先程のサービスには、誰を意識するか、誰を起動しなければならないか、を記述します。この時、意識するのは自分と直接繋がっている(階層になっている)サービスだけです。
先程の図では、
⚫︎マネージャーはフィールドとバトルドライバを意識します。また、マネージャーから起動した場合は全部のシーンを起動します。
⚫︎フィールドはマネージャーだけを意識します(他のシーンへの切り替えはマネージャーに任せる)。フィールドから起動した場合はマネージャーを起動します。
⚫︎バトルはバトルドライバだけを意識します(他のシーンへの切り替えはバトルドライバに任せる)。バトルから起動した場合はバトルドライバを起動します。
⚫︎バトルドライバは少し変則的です(ここが今回やりたかったところです)。バトルドライバは、マネージャーの代理なので、マネージャーがいるかどうかを意識しますが、マネージャーがいなかったら自分がマネージャーの代わりになります。バトルドライバから起動した場合はバトルを起動します。
上の例から、サービスは以下の2つを持てば良さそうです。
●親と子(階層構造)
●関係するシーンをロードするか(強制起動フラグ)
コード
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; // シーン登録 [System.Serializable] public class SceneRegistration { // シーン名 public string name; // ロードされていなければロードする(強制起動フラグ) public bool forceLoad; } public class SceneMessageService : MonoBehaviour { [SerializeField] public SceneRegistration sceneParent; // 親 [SerializeField] public SceneRegistration[] sceneChildren; // 子 void Start() { if( NeedToLoad(sceneParent) ){ SceneManager.LoadSceneAsync( sceneParent.name, LoadSceneMode.Additive ); } foreach( SceneRegistration sceneChild in sceneChildren ){ if( NeedToLoad(sceneChild) ){ SceneManager.LoadSceneAsync( sceneChild.name, LoadSceneMode.Additive ); } } } bool NeedToLoad(SceneRegistration r) { if( !string.IsNullOrEmpty( r.name ) ){ if( !(r.name == SceneManager.GetSceneByName( r.name ).name) ){ if( r.forceLoad ){ return true; } } } return false; } }
コードの配置(Hierarchy、Inspector)
これを、各シーンに、以下のように配置します。
各シーンでの設定を以下のようにします。
Field
Manager
Battle
BattleDriver
動作テスト
上記設定後、動作テストを行います。
フィールドからの起動は、マネージャー経由でバトルドライバ、バトルまで起動します。これは一般的な加算ロードでの起動方法と思います。
それに対して、バトルからの起動は、バトルドライバだけ起動します。これが今回のサービスでやりたかったことです。
フィールドからの起動
マネージャーからの起動
バトルからの起動
バトルドライバからの起動
つまづいた点
最終的に出来上がったコードはシンプルになりましたが、途中いろんな試行錯誤がありました。。(同じシーンが重複起動できてしまう、延々と起動してしまうなど)
今回のコードを作るために、加算ロードのいくつかの特徴について調べた内容は、以下にまとめました。
tomo-mana.hatenablog.com
次回
サービスへの参照を取得して、他のシーンをアクティブにできるようにします。