RPG というジャンルのゲームを、Unity を使って製作し、コンシューマ機含めたマルチプラットフォーム展開。
前作『いけにえと雪のセツナ』に続き、今作『ロストスフィア』でも Unity を使って実現することができました。
せっかく頂いた機会なので、何かの参考になればという思いとともに、『ロストスフィア』のプログラム設計や開発中に作成したツール、UI のデータ構造、レンダリング手法についてほんの少しだけではありますがご紹介できればと思います。
Unity x RPG
状態管理
RPG ではゲーム中、ユーザーの操作を含めた様々な要因で状態遷移が発生します。
ユーザー操作によってキャラクターが移動している最中にメニューボタンを押すとメニュー画面に遷移したり、決定ボタンを押すと目の前にキャラクターがいれば話しかけ、宝箱があれば開けようとします。
橋を渡ろうとすれば何かイベントが発生するかもしれませんし、敵と接触するとバトルが始まることもあります。
バトル中にイベントを発生させたい、といった(面倒な)実装をする必要が出てくるかもしれません。

このような様々な状態遷移が発生します。
これを実現するために以下のようなクラス設計を行いました。

各種状態を表す State クラスを用意

Manager を用意して登録
使い方は単純です。
初めに基本状態を Add
Add(new NormalState());
→ NormalState の Run が実行され始めます。
バトルが開始
Add(new BattleState());
→ BattleState の Run 関数内でバトルの処理を行います。
バトルが終了した際に true を返却すれば、自動で NormalState の OnActivate がコールされ NormalState の Run が実行され始めます。
バトル中にイベントが発生
Add(new EventState());
→ BattleState の Run が止まり、EventState の Run が実行され始めます。
1 度 new したクラスを使いまわせるように設計しても良いと思います。
『ロストスフィア』ではこの仕組みで状態遷移を管理していました。
正直、珍しい設計でも何でもないのですが、Unity の各機能の使い方の情報ではなく、実際に販売された製品の基礎設計の情報って意外と少ないのでは? というところでのご紹介です。
配置ツール
RPG では同じ町、ダンジョンにユーザーが何度も訪れ、訪れるタイミングによってキャラクターや宝箱の配置情報が変わります。
Unity ではシーンに prefab を直接配置するのが主流ですが、全てのパターンを考慮するためにシーンに直接配置してしまうと大量のオブジェクトが配置されてしまい、シーン切り替えに時間がかかる、場合によってはメモリ不足に陥いる、といったことが発生してしまいます。
これを回避するためにシーンに直接配置はしても、シーンにオブジェクトを配置したまま保存せずに、Editor 拡張で座標や姿勢情報のみを ScriptableObject に出力する仕組みを用意しました。

専用の prefab を実際にシーンに配置した後、Editor に追加したメニューから Export することで、配置 ID、座標、姿勢情報のみを ScriptableObject に出力します。
Collider も配置されているように見えますが、これもシーンには保存しておらず、座標や姿勢情報のみを ScriptableObject に出力しています。
別のパラメーターで配置条件(ゲームの進行度等)とともにこの配置 ID を設定してゲームに反映させます。

イベントでのキャラクター配置位置、宝箱含めたギミック類など、一括で管理していたため、配置情報が多い個所では訳が分からない状態に…(分類して管理すればよかったですね)。
UI
RPG(に限らずですが)では、大量の UI が必要となります。
基本的に全て uGUI を使ってはいますが、非 Editor 実行時に GameView で確認しやすくするために、各 UI は Camera を付与した状態のシーンで扱うようにしています。

メインメニューの例。Camera や Canvas なども含めた状態でシーンとして保存。
この UI シーンを SceneManager.LoadSceneAsync を LoadSceneMode.Additive 指定でロードします。
UI は制作中にレイアウト調整や変更が頻繁に行われるため、確認しやすい環境を整えることが必須だというところで、こういったデータ構成にしています。
Camera が無駄に生成されてしまわないように、ビルド作成時には前処理で不要なオブジェクト類などは削除した状態にしています。
Unity x マルチプラットフォーム
『ロストスフィア』の描画環境
『ロストスフィア』は、Nintendo Switch, Play Station4, Steam の 3 つのプラットフォームで発売されましたが、ゲームの開発初期段階ではプラットフォームは未決定で、モバイルも開発の対象になるかもしれず、Nintendo Switch に関しては詳細は全く分からない状態でした。
一方、ゲームの性質を鑑みて、多数の動的ライトが必須という内容ではなく、半透明の表現も多く使いたいという要望もあり、フォワードレンダリングで、カラースペースはガンマで行う事になりました。
『ロストスフィア』を彩る様々なシェーダ
『ロストスフィア』には、その世界観を表現する為にシェーダを用いて様々な表現にトライしています。
前述の通りプラットフォーム的にモバイルも開発の対象となるかもしれないという事情から、複雑なシェーダや高機能なシェーダはあまり使用せず、レガシーでシンプルなシェーダを記述する事にしました。
シンプルながらも、ロストを表現する「ジェネシェーダ」や、大空の旅を彩る「ベンドシェーダ」、敵ボスやロストの消滅を表現する「ディゾルブフェード」や、ゲームの象徴的な場面に登場する雨も「レインシェーダ」というシェーダで表現するなど様々なシェーダを実装しています。

ロストを表現する「ジェネシェーダ」

大空の旅を彩る「ベンドシェーダ」

こちらはベンドシェーダーがかかっていない状態

消滅表現の「ディゾルブフェード」

雨表現の「レインシェーダ」

レインシェーダを下から見た様子
こだわりの波紋シェーダ
数あるシェーダの中でも、今回は「波紋シェーダ」について紹介します。
このシェーダは、その名の通り、キャラクターが水面に接地するとその場所に波紋を発生させるシェーダです。

波紋を発生させるには、接地した場所を特定し、その場所に波紋を発生させ、波紋を拡大するという手順が必要になります。
これらの処理は、頂点シェーダを用いる方法や、テッセレーションを用いる方法や、テクスチャへの描画を行うなど、いくつかの方法がありますが今回はストレートにテクスチャへの書き込みを行う手法を選択しました。
接地場所については、水面と同じ形状のメッシュコライダーを付与し、波紋を管理するスクリプトも一緒に付与する事で管理する事にしました。
接地のタイミングについては、ゲーム側で管理しているシグナルコールバックのタイミングでレイキャストを行い、UV を取得します。
これを波紋用のテクスチャに波紋を書き込む際の入力値としてシェーダに渡します。

波紋発生については、接地場所をレイキャストで取得してあるので、その場所に波紋となるデータを書き込みます。
波紋を管理するスクリプトに対応したカメラが生成され、そのカメラが保持しているレンダーテクスチャに書き込みを行います。
波紋の発生は、1 フレームに複数発生する場合がありますが、前述のシグナルコールバックで全て検知可能なので、そこでレイキャストしたデータをリストとして保持しておき Graphics.DrawMesh で波紋の起点となる「点」を描画します。
頂点の座標は UV から算出できるので DrawMesh の引数に算出したテクスチャ座標を座標として指定します(プラットフォームによる UV 座標の違いに注意が必要になります)。
波紋拡大については、ポストプロセスのシェーダで処理しています。
波紋を発生させる「点」を描画するカメラに、コマンドバッファを用いて CameraEvent.AfterForwardOpaque を指定し、そこでシェーダを実行しています。
波紋の拡大シェーダの考え方も単純で、波紋発生の時点で描画された「点」が描画されたテクスチャと、前のフレームのバッファと、その前のフレームのバッファを参照しながら、周囲のピクセルと色をブレンドします。
どのくらい周辺のピクセルを取得するか、を外部から指定可能にしてありますので、水面によって波紋が速く広がったり、遅く広がったりの調整も可能です。
また、このプロセスでは波紋が発生しないフレームでは既に発生している波紋の拡大が減衰していく処理を行います。

後は、波紋を描画したテクスチャを参照しながら、リフレクションのシェーダを実行すると、良い感じに波紋の影響で水面が歪んだように見えます。
実際には UV にも歪みを適用させています。ライティング行う場合は、法線に対しても作用させると良い感じになりそうです。
こうして振り返ってみると、実装はストレートでシンプルなものに落ち着きましたが、開発中は実機のビルドでパフォーマンスが出ず、正直苦労しました。
最初はカメラを生成して点を描画する処理を、テクスチャに直接ピクセルを書き込んでいましたが、どうしてもパフォーマンスが出ませんでした。
そこでコンピュートシェーダに処理を書き換えてみましたが、思ったよりパフォーマンスが伸びず、複数の水面で同時に波紋が発生する場合などは、なかなか実用的なパフォーマンスにはなりませんでした。
プロファイラを見ながら、最終的に今の実装がパフォーマンスが出せるという事を確認し、現在の実装に落ち着きました。
ゲーム中に登場する「うつしみの湖」というロケーションでは、複数の水面にリフレクションが設定されており、それらの水面に別々に波紋が発生します。
このロケーションの最適化については、かなり苦労しましたが、おかげで良い見せ場となったと思います。
『ロストスフィア』をプレイして頂いた際には、是非、水面を歩いてみて下さい。

Unity x RPG x マルチプラットフォーム

いかがでしたでしょうか。
RPG x マルチプラットフォーム自体は特に新しいものではないですが、Unity x RPG x マルチプラットフォーム x (加えてコンシューマ機)という例はまだ少ないのではないかと思います。
もし宜しければ『ロストスフィア』をお手に取って頂き、これ Unity 製かぁ、というのを感じながらプレイしてみてください(笑)。
お読みいただき、ありがとうございました。