はじめに
これはSTYLYアドベントカレンダーの16日目の記事です。
今回はNEWVIEW CYPHER 2021 #Gameで作ったVRゲームの開発tips的な話をします。
作ったゲームはこちら
PCVRやOculusQuestなどVR機器でプレイしてね。
VR機器を持ってない人向けの、雰囲気が分かるプレイ動画
ざっくりとしたゲーム内容
毎回ランダムで変わるテーマに合わせて、2択でインテリアを選びます。
イイ感じにお部屋に配置していくと、最終的にスコアが表示されます。
同じインテリアでもテーマによってスコアが変わります。
データ仕様
インテリアにはそれぞれ豪華さ(luxury)、便利さ(convenience)、安らぎ(calm)、面白み(fun)の4つのパラメータを持つ。
テーマは4つのパラメータそれぞれに対する係数を持つ。
ランダムに選出されたテーマの係数と、置かれたインテリアのパラメータを掛け合わせた合計がスコアとなる。
開発する上で課題になりそうなポイント
ゲームのアイデアが決まったところで、厄介そうなポイントについて考えてみます。
複雑な処理
STYLYではPlayMakerを使って処理を記述します。
ノードベースのプログラミング環境では、特に処理全体の見通しが悪くなりがちです。
そのためうまく工夫しないと不具合を生みやすかったり、テスト工数がかさんだりする恐れがあります。
大量のデータとアセット管理
このゲームでは部屋に様々なインテリアを選んで置いていく楽しさをテーマにしているため、単純にアセットとそれに伴うパラメータデータの量が多いです。
それらを全てInspectorで手入力していくのは非常に時間と根気が必要となりますが、私にはそのどちらもありません。
バランス調整のための開発イテレーション高速化
1インテリアにつき4パラメータあり、インテリアは最終的に40個になりました。
パラメータの総数が多いということは何度も数値調整とテストプレイを繰り返す必要があるということです。
そのため、高速でなるべく手間のかからないデータ反映手段が必須となります。
ではこれらのポイントを踏まえた上で、どのような工夫をしたかを解説していきます。
あまり詳細な説明はしませんが、エッセンスを感じて貰えればと思います。
大まかな処理系
まずはこれがゲーム全体の流れを制御しているGameManagerです。
ここでは具体的な処理は一切書かずに、純粋にState管理だけを行います。
各Stateでの処理を小分けのFSMに分散し、それぞれへのメッセージングのみを行っています。
何故このようにしているかと言うと、細々した処理がゲーム全体の流れと共に書かれていると全体の見通しが悪くなって、バグが発生した時にそれを調査することが大変になるからです。
では次に、例としてランダムにルールを決定している部分を見てみましょう。
ここでは細かい処理については割愛しますが、比較的シンプルな流れになっています。
この処理を全体の流れの中に直接書いてしまうとやたらとごちゃごちゃしてしまうので分けているんですね。
あとポイントとして、想定しないようなことをあった場合にError遷移してログ出力するようにしています。
起こり得ない遷移ではあるのですが、何度もデータを作り直したりするのでこういうチェックを挟んでおくとバグへの対応がしやすかったりします。
そして一連の処理が終わったらGameManagerへSendEventを行っています。
このように抽象度の高い全体の流れを定義する部分と、詳細な処理をする部分を分けてSendEventし合って動くようにしています。
また関数的な内部状態を持たない処理の固まりは、FSM templateとして追い出してRun FSMアクションで呼び出したりすると良いでしょう。
とにかく処理を小さな単位に分割して部品を分けるように作るのが、大きくて複雑なものを作る時のコツです。
この考え方自体はPlayMakerだけでなく、プログラム全般に言えることです。
ヒエラルキー構造をうまく使う
PlayMakerで処理を書いたことがある人は分かると思うのですが、配列やリストなどコレクションの扱いがちょっと難しいんですね。
このゲームでは大量のアセットがあり、またそれぞれが選択中だったり設置中だったり選ばれなかったりと状態を持っています。
これらをうまく管理するためにヒエラルキー構造を使って表現しています。
C#のスクリプトを使えるならこんなやり方はするべきではないのですが、STYLYにはスクリプトを持ち込めないので苦肉の策だったりします。
しかしこれには利点があって、デバッグ中はヒエラルキーを見れば管理状態を把握しやすいです。
ルールデータの管理
RuleHolderの下にはこのようにランダムに選択されるテーマを表すアセットが納められています。
こんな感じで実際に選択されて使用されるものだけをUsedRuleの下に移動します。
ちなみにルールを表すGameObjectは見た目の実体がなく、またFSMが付いていますが処理も一切なくVariablesだけを持っています。
そのためRuleHolderなどは非アクティブにしています。
まぁアクティブでも大して違いはないですが、FSMのMonoBehaviourなどに処理が回るとほんのちょっとだけ処理負荷がかかるので、その無駄を削るためにこうしています。
実際にはこの程度の数ならアクティブでも動作に差はないと思いますが、個人的に削れる無駄は削っておきたいんですよね。
インテリア(Item)データの管理
Rule同様にItemHolderの下にはゲームに登場しうる全てのItemが納められています。
Itemの場合はこんな感じで、2択で選ばれたものはUsedItemに、選ばれなかったものはUnUsedItemにぶら下げています。
そして部屋に置くものはこれをInstantiateしてItemInstanceBucketの下にぶら下げています。
こんな感じでヒエラルキー構造を使ってうまくコレクション表現すると、非常に見通しが良く開発しやすいです。
高速なイテレーションのために
アイテムには名前や豪華さ(luxury)などのパラメータがあり、共通のItemPropertyという処理のないVariables定義だけのFSM templateで表現されています。
ではこのパラメータをInspectorでポチポチと設定するのでしょうか?
前述の通りこのゲームでは40個のアイテムが登場します。
アイテム数が3~4個なら手作業で頑張っても良いですが、たくさんのアイテムの設定をInspectorで打ち込むのは非常に大変です。
またバランス調整のために何度もパラメータを変更することを考えると現実的ではありません。
そこでアイテムデータをSpreadSheetで管理し、そこからtsv形式にしたものをプロジェクトに入れています。
そしてtsvファイルの内容から各アイテムのprefabを生成・更新して自動的にシーンに配置し直すエディタスクリプトを作りました。
STYLYにC#スクリプトを持ち込むことはできませんが、prefabを作ったりシーンを自動生成したりするようなエディタスクリプトを作ることは非常に有用なテクニックです。
これにより気軽に何度でも全アイテムのパラメータ変更を一発で出来ます。
たくさんのアイテムが登場するようなゲームでは、大元になるマスターデータから何度でもデータを生成し直せるようにしておく仕組みを作ることは非常に重要です。
あらかじめデバッグ機能を用意しよう
バランス調整のために何度もプレイすることになります。
そうなるとゲーム開始時の演出を毎回見ることになり、例えそれが短いものでも待つ時間が無駄です。
そのためデバッグ機能で演出をスキップできるようにしたり、ゲームの途中でもボタン一発で開始状態に戻れるようにしておくと捗ります。
1つ1つのデバッグ機能はそこまで難しい処理ではないのですが、作り始める時点で考えておかないと後から作ると大変だったりするんですよね。
特にシーンを再読み込みせずにリトライできるように、うまく初期化処理を分けたりと行ったことは普段から意識しておくと良いでしょう。
さいごに
書いてみて思ったんですが、これは実際にプロジェクトファイルがないといまいちピンと来ないかもしれないですね。。。
本当はプロジェクトを配布しようと考えていたんですが、再配布できないアセットを抜いて動く状態にするのが難しかったので断念してしまいました。
いっそUnityEditorを操作しながら口で説明した動画の方が分かりやすかったかもしれませんね。
気になる点とかあれば何でも答えるので、お気軽にご質問ください。