Menu

【Unity】シーンを駆使して開発を行う

シーンの考え方、概念

個人的なイメージの話として・・・ゲーム開発は一種の家づくりのようなもの。
シーンは家の基礎作りのようなものという考え方をしています。
基礎がしっかりできた家は災害でも簡単に崩れないし、リフォームする際にも大きな影響を与えない。

開発のイメージとしては、ゲームをパート毎に基礎を立てて全体の大まかなイメージを作ること。
例えば先にUI周りとか固めてしまったり、シナリオ作りとかデータ作りとかしたのに、
最後に本編部分がうまくいかない状態で進めても作り直しが発生した場合の被害は甚大だ。

そこでシーンの中にゲームの各要素をパートごとに構成しておくことで、
作業を分業したり、不具合の回避のために内部設計をシンプルにすることが出来る。

このイメージでゲーム本編部分だけで完結するプロトタイプをまずは作ってみて、
面白くなりそうな感じになると判断してから周りにシーンを作って埋めていくようにする。

ゲームの遊びのコア部分を一つのシーンで設計しておくことで、データの管理がしやすくなる。
シーンを読み込めばゲームがすぐに開始できるし、
終了して次のゲームの間にいる間はシーンを破棄することでメモリの管理がしやすくもなる。

またUnityではデフォルト起動時はシーンが一つのみHierarchyに展開されているが、
シーンは複数同時にHierarchy上に展開することが可能だ。

この点を利用して、Boot用のシーンを用意しておき、
残りのシーンを追加読み込みで切り替えていく設計を考えられる。


実際に使ったシーン構成

以下が実際のゲームシーン中のHierarchyだ。

今回はシーンの配置にのみ言及することとするが、
Bootシーンの下にingameシーンを追加している。

このBootの下に追加するシーンを、順次切り替えていくことで大まかなゲームの遷移に対応していく。
サンプルとして「三角環形」で用意したのは以下のシーンだ。

Boot

一番ベースとなるシーンであり、基本的に破棄されない。
アプリケーションランディングで配置しているシーンとなるので、
このシーンにリソースはできる限り詰め込まないことが理想的だ。
これが重いとゲームの起動に時間がかかってしまう。
管理用のシステムコンポーネント等を置いておくと便利。

Title

タイトルの表示を行うシーン。
Bootでの理由も踏まえて、大きめのテクスチャを読む可能性を考慮すると別シーンが良さそう。
Boot画面でロゴを出している間に裏で必要なデータを展開させたり初期化していることが理想的だ。
場合によってはingameの切り替えを行う分岐まで任せてもよい。

ingame

ゲームのコア部分。これがなければ当然ゲームにならない。(本編だし)
ゲームの構成によってはこのシーンが複数用意されることもあるかもしれない。
ミニゲーム集なども、それぞれのシーンを選択によって読み替えることで実現が容易だ。

StageSelect

基本的なゲームの設計と動きが完成してから追加したシーン。
パズルのマップを増やす際に、パズルの読み込み先を制御するための中間シーンが必要だった。

ソーシャルゲームの場合はホーム画面・ロビーやガチャ画面なども子シーンにしている場合があるかもしれない。
適当な紙にゲームの大まかな役割を分けて書いて設計してみよう。

スポンサーリンク[pc-mieru][/pc-mieru][sp-mieru][/sp-mieru]

実装はどのようにするか?

シーンによる構成の大きなメリットであり、人によってはデメリットでもあるのが、
オブジェクトの共有が基本的にシーン間でなされないことだ。

稀に開発をしていて、ほかのオブジェクトの設計が不具合を引き起こしているという場面に
遭遇することもあるけれど、共有されないということは小規模でテストができるということである。

全体を通して必要なオプション情報やシステム変数などはBootに
シングルトン形式のシステムを用意しておけば不都合もないでしょう。
シーンを遷移するときに外部からの入力が必要な場合は入口は一つにまとめたほうが都合がよいのだ。

さてシーンについての素晴らしさを語りすぎて前置きは長くなってしまったけれども、
実際にシーン切り替えを行うメソッドは以下の通りに用意してみた。

// AppMain.cs
//--------------------------------------
using UnityEngine.SceneManagement;

/// <summary>
/// アプリケーション統括
/// </summary>
public class AppMain : SystemManager<AppMain>
{
    // ①
    public bool mbSceneChangeWait = false;  // シーン遷移中
    
    // ・・・中略・・・
    
    // ②
    /// <summary>
    /// シーンタイプ
    /// </summary>
    public enum eSceneType
    {
        NONE = -1,
        TITLE,
        MENU,
        INGAME,
    }
    private eSceneType mSceneType = eSceneType.NONE;    // 現在のシーン
    // シーン対応表
    private Dictionary<eSceneType, string> mSceneNameDict = new Dictionary<eSceneType, string>()
    {
        { eSceneType.TITLE, "Scene/Title" },
        { eSceneType.MENU, "Scene/Menu" },
        { eSceneType.INGAME, "Scene/ingame" },
    };
    
    // ③
    /// <summary>
    /// シーン遷移要求
    /// </summary>
    /// <param name="nextScene">遷移先のシーン</param>
    public void requestChangeScene(eSceneType nextScene)
    {
        mbSceneChangeWait = true;
        StartCoroutine(_changeScene(nextScene));
    }

    /// <summary>
    /// シーン遷移待機
    /// </summary>
    /// <param name="nextScene">遷移先のシーン</param>
    /// <returns></returns>
    private IEnumerator _changeScene(eSceneType nextScene)
    {
        AsyncOperation operation;
        var wait = new WaitForEndOfFrame();
        // 元のシーンがあればアンロードを先に行う。
        if (mSceneType != eSceneType.NONE)
        {
            operation = SceneManager.UnloadSceneAsync(mSceneNameDict[mSceneType]);
            while (!operation.isDone)
            {
                yield return wait;
            }
        }

        // 次のシーンのロードを行う。
        operation = SceneManager.LoadSceneAsync(mSceneNameDict[nextScene], LoadSceneMode.Additive);
        while (!operation.isDone)
        {
            yield return wait;
        }
        mSceneType = nextScene;
        mbSceneChangeWait = false;
    }
}

①のmbSceneChangeWaitのフラグが立っている間、シーン遷移をしている状態であることを示している。
②のrequestChangeSceneに任意のシーンタイプを指定することで、mbSceneChangeWaitが立ち、
現在のカレントシーンから、指定したシーンタイプへの遷移を開始する。

呼び出した側の制御では、コルーチンで遷移が行われているので、
その間待機を行い(必要があればローディング画面を出すのもよい)
mbSceneChangeWaitが落ちたことを確認して、次のシーンに対する制御を開始する。

呼び出しは基本的にBootシーンに配置されたAppMainクラスから制御している。
AppMainでは、現在のゲーム全体の遷移を監視しているクラスで、
現在のシーンの状態や、フェードアウト画面、アプリケーションポーズ状態や、
基本サウンドのシンプルコールアクセサなどのみを用意している。

Update()でステップ処理を行っており、下のシーンから遷移の依頼が飛んできた時点で、
ステップを切り替えてシーンの遷移を管理しているだけの管理職クラスだ。
呼び出しの手続きに手間がある基本サウンドの呼び出しまでやらされている雑用クラスでもあるのだが・・・。

ちなみにここでは③の通りeSceneTypeという形でenum型を用意している。
このように辞書で用意しておけば、新しいシーンを追加する時は上記に記述を追加することで簡単にできる。

ただ、シーンを追加する時に一点気を付けておかなければならないことがある。
LoadSceneAsyncがSceneManagerを利用しているため、BuildSettingsに登録されていないシーンを読み込むことはできない。

上記の Add Open Scenes から読み替えに使うシーンの登録は忘れないようにしよう。
またLoadSceneAsyncのオプション、LoadSceneModeがデフォルトはSingleだけれども、
今回はBootの上に載せているシーンだけを乗せ換えているのでAdditiveにすることも忘れずに。


大規模開発などになれば、いちいち実装を確認するのにタイトルから
順番に確認していくのは非効率的なので、該当シーンだけを読み込んで動くようにしておくと
開発が効率的になり便利になるというメリットも当然存在する。

小規模開発ではいちいちこのようにする手間もないかもしれないが、
それでも開発を続けると、知らぬ間に大きな設計になっていることもあるので、
予め意識した開発をしたいところである。

今回の開発に関する情報は以下のリンクを参考にしてほしい。

マルチシーン編集

というところで今回はこれまで。見ている人の開発の助けになれば幸いです。

スポンサーリンク

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です