シーン遷移は画面遷移(Unityメモ)
立ち絵は順調に増えてます。枚数を作ると絵同士の間にストーリー性も生まれてきていて面白いです。
前回の記事の続き。
最低限のシーン遷移を実装しました。今回はシーンという概念と状態遷移との闘いでした。
戦闘シーンの実装の目途が立ったので、機能追加のフェイズです。とりあえず出来そうな項目は、
・ゲームルールの実装
・「技」の実装=エフェクトの実装
・シーン遷移の実装
このなかで、ゲームルールはピュアC#で実装済み。技の実装はエフェクトの実装に目途が立てば物量勝負、つまり量産作業に持ち込めます。というわけで、難しそうなシーン遷移の実装を先に行うことにしました。
ゲーム制作において、シーン遷移がなぜ難しいのか。実装を考えてみてよくわかったのですが、シーン遷移の処理は様々なレベルの概念を扱う必要があるからでした。
見た目の構成と実装の違い
シーンのシステムを実装するにあたって、よくある実装はシーンごとに別のオブジェクトを作る、というものです。Unityのシステムもそうなっています。
これはアクションゲームなどステージをはっきりと区切れるゲームでは有効なのですが、RPGなど画面を行き来するゲームではシステムを実装しにくいです。作ろうとしている周回ゲームも画面を行き来するシステムなので、シーンの実装ではUnityのシーンオブジェクトは1個だけにして、UIの表示切替で見かけ上シーン遷移しているように見せることにしました。
逆接的なのですが、この記事の複数のシーンのデメリットを見て、単一シーンでゲームシステムを実装することにしました。そもそも作業者は自分一人なので…
このUnityシーンとGUI上のシーンの区別をしたのが、シーン遷移の最初の一歩でした。
シーン、ビュー、画面の概念の切り分け
よくあるアプリケーションの設計では、ビューという単位で画面表示の設計が行われ、システムの処理を行うロジックはモデルとしてビューとは分離して設計されます。
一方、ゲームではよくシーンという単位でシステムの設計が行われ、シーンの内部にゲームのロジックが実装されることも多いです。ゲームライブラリであるUnityもこちらの思想で設計されています。
次に、特にゲームにおいては論理上のビューと物理的な画面も区別して考えたほうが、結果的にシーン遷移を理解しやすかったです。どういうことか。
一般のアプリケーションでは、極端に言えば、無限に大きなモニタがあるか、無限の個数のモニタがあれば、すべてのUIを同時に表示できます。いっぽうゲームでは常に画面の大きさと個数に制限があり、そのためにゲームは普通一画面にシーン一つを表示します。画面の側から見ると、表示元となるシーンを常に一つに絞る必要があるので、ゲームではシーンと画面を個別に管理する必要があります。(さらにUnityの場合、画面はカメラと結びついています)
このように、普通のアプリケーションとゲームとでは設計の区切り方が異なります。普通のアプリケーションがモデル-ビューという横割りで設計を区切るのに対し、ゲームではシーンという縦割りで区切るイメージ。「シーン遷移」を考えるうえで、区切り方の違いに気づいたことで実装の手が動くようになりました。
状態遷移の理解、特に状態遷移を行う主体
上の図を描いたことで、シーン遷移の実装も具体的に組めるようになりました。つまり、画面状態を「どのシーンのUIを表示するか」として定義し、シーン遷移の実装ではシーンを遷移するのではなく画面状態を遷移すると構成が分かりやすくなりました。
状態遷移の主体もシーンではなく画面となります。正確には画面管理オブジェクトが主体です。「シーン遷移」だとそれぞれのシーンの中に遷移処理を書くことになり、シーン遷移の記述が散らばります。一方でこれを画面状態の遷移として書くと、状態遷移の定義を画面管理オブジェクトの初期化処理の中に集約できるので、シーンの全体の遷移を読み取りやすくなります。
状態遷移の実装そのものは、UMLのステートマシン図の概念をざっくりとコードに起こせば、最低限の機能は実現できます。
シーン遷移が良くわかっていなかったときは、UnityのStateMachineBehaviourやUniRx等を使おうとしていたのですが、そこまでやる必要もなさそうです。商用レベルの大規模なプロジェクト向けなのでしょう。
シーン遷移と同時に出すトランジション(ワイプ)演出についても、Unity Screen Navigatorを使って実装しようかとも思ったのですが、最低限のフェードアウト・フェードインであればDOTweenだけで組めました。ある表示状態のExit時に演出を実行するように組みます。
https://light11.hatenadiary.com/entry/2021/10/27/221801
とはいえ、設計意図はとても参考にしています。あと凝った演出は今のところ自分には需要がないのですが、欲しくなったら使おうとも思います。
その他もろもろ
以下の概念も重要ですが、ゲーム固有の話ではないので端折ります。
・非同期処理と同期処理(特にアセットのロード完了を待ちたい時)
・ライブラリ固有の概念と、ライブラリに依存しない概念の切り分け
・ライブラリでの実装の作法(Unityの場合、3Dや2Dの扱い、GameObject、MonoBehaviour、イベント発生タイミングなど)
Unityスクリプトのイベント発生タイミングはインスタンス初期化を書くときに大事なので、備忘録としてリンクを貼っておきます。
次はシェーダーを触ることになると思います。エフェクト用ではなく、マスクファイルを使ったアルファブレンドで立ち絵を切り抜きたいです。