VRChatのゲームワールドを作った際のあれこれ

概要

VRChatのGameWorld、「Balloon Poppoing」を作った際の、UnityやらUdonやらのあれこれ。
ワールドづくりの参考になるなら幸い。

あれこれ

企画の面で気を付けた事

  • できるだけ英語。でも日本人が拒否感ださない程度
  • 説明なくてもルール分かる
  • 特定の位置にいないとゲームに参加できない…みたいなのはナシ
  • 1~2分で終わる
  • なるべく軽くしたい
  • 乗り物は用意すれど徒歩も強い形にしたい
  • 広めのワールド作ってみたい
  • 広さにあったスピード感を保ちたい
  • 朝夜のうつり変わりを表現したい

終わってからの反省点

  • やや広すぎて密度が足りないかも
  • 演出面をもっとコミカルにはっちゃけてもよかった(まだクソ真面目感がある)
  • 効果音などはもっと盛り込んでもよさそう
  • もうちょっと通信部分のデバッグがやれれば…
  • VRChatではFlareが使用できないが代替手段を何か取れればよかったかも
  • エフェクトもりもりしたかったけど、軽量化のためにあきらめた
  • 炭鉱のテレポートなどは、使ってもあまりメリットがない機能になってしまった
  • 繰り返し遊ぶ事で楽しめる要素をもっと追加してもよかったかも

その他ゲーム意図

  • Pv要素が少ない
    • 乗り物の奪い合いのみ。
      • 先に奪われそうな場合は別の場所を回るとか考えてみてネ!みたいな。
    • 風船の通信同期をローカルのみにしたのは単純に重そうだったから
  • 当初は大砲が強すぎた
    • リスポーンと大砲を繰り返すだけで高得点を得られていたので、空中の風船は大分調整した
  • グライダーが強すぎるかも
    • テストプレイでは最強すぎたので、惰性を強めてバランス調整
    • 大砲と合わせて使うとスピード感あって気持ちいいので、
      むしろ使用を推奨して多めに配置した
  • 乗り物
    • SaccFlightAndVehiclesの乗り物は動かすのにリテラシーが必要なので、慣れている人なら高得点が狙えるというピーキーな立ち位置を目指した
      • 徒歩やグライダーに比べてマップを把握しないと稼げない
      • やりこみさせないと高得点が狙えないネタ枠に…
    • 春のスク水感謝祭さんとも詰めたけど(多謝!)、スピード感は確保したい事から結果的に操作感はTestPilotsやTestDriversと同じ状態に
  • 乗り物に乗らないプレイ
    • 当初徒歩が強いのは村の中で、建物の上の風船を割ることで得点を稼ぐ形を目指していた
      • 村の中だけ遊ばせるのは問題意識があった
    • 徒歩移動を強くするためにリングを作った
      • 村外への移動があまり面白くなかったからなんとかしたかった
      • リングはタイミングよくジャンプするとより高速になるので、
        ただ平地を走るよりかはゲ-ム要素がでたはず…

タイトルロゴ

ちょっとレトロでRARE社とか昔のNaughtyDogみたいな感じにしたかった。いいでしょ

クラス構成

ざっくばらんクラス相関図(※ほんとはもっと多い)

UdonGraphではなくUdonSharpを使用。アクセスコストが重いのでMVCモデルとかにはならなかった…GameControlがゲームのメイン進行を担当し、必要な情報はGameModelにいれておく。GameControlから各所に簡単な命令を出し、細かい処理はManagerクラスか下部のクラスにやらせる。環境光やSkyBox、高速移動時に聞こえる風切り音などはスタンドアローン。都度GameModelから必要な情報を引き出して動いている。

Updateが重かった

風船を300個ぐらい置いたところ、Unity上で90あったFPSが50ぐらいになった。MonoBehaviourのUpdateが原因で、便利だけど数が多いとめっちゃ重い。風船やリングなどの各Mangerクラスに、子クラスの独自のUpdate関数を毎フレーム手動で叩くようにして改善した。

スコア同期の仕組み

ざっくばらんな仕組み

UDONの同期の詳細は下部のリンク先を参考に…
この図の”スコア”のクラスはUdonBehaviourSyncModeがBehaviourSyncMode.Manualで、ヒマそうな奴をSetOwnerで自分の所有にし、ゲーム終了時に値をRequestSerializationで共有するという形。ヒマになっているかどうかは、ObjectのOwnerがワールドマスターになっているかどうかで判断。ワールドマスター以外が所有していた場合、使用中と判断し別のスコアクラスを探す。ラグなどで自分のスコアクラスが誰かに取られていたら、別の奴を新たに探す。
尚ゲームマスター自体は、マスター専用のスコアを使用する。ワールドマスターがワールドからいなくなったら、次のワールドマスターがマスター専用のスコアを使用する。

風船の表示

左がポリゴン(△116)で右がビルボード

風船の当たり判定はColliderではなくプレイヤーとの距離で行った。これはLOD(Level of detail : 遠い時に軽いポリゴンを出したり消したりする)の処理のついでに当たり判定を行えば軽いと思ったため。カリングや触った後の非表示処理は、GameObjectのActiveの切り替えではなくMeshのEnableで切り替えを行っている。GameObjectのActiveの切り替えはメモリ圧迫になるため避けた。(ゲームを繰り返すと処理落ちが激しくなった為)
距離計算の軽量化は以下が非常によかった!
UnityのVector3.DIstance()よりもVector3.sqrMagnitudeを使いましょうという話

風船はマテリアルは同じで、メッシュ(UV)が別

風船は回転させたかったので、StaticではなくGPU Instancingでバッチ化。1個1個にAnimatorを使うと重かったので、スクリプトで回転している。メッシュとビルボードでマテリアルを分けざるを得ないけど、各得点の風船同士は同じマテリアルを使用。目立たせたかったのでライティングなしEmitのみ。

風船の配置

風船のInspector

風船はすべて同じprefabを使用。得点の種類によってMeshが変更される仕組み。OnValidateを使うことで、Inspector上の値が変更されたタイミングで見た目も変わる処理にした。これで配置とゲームバランス調整もサクサク。また風船のマネージャークラス側では、シーン上に配置された風船の参照をすべて確保する必要があったものの、重いGetComponentsをゲーム中に動かしたくなかったのでカスタムエディターを使い、エディター上でAttachするようにした。
【Unity/エディタ拡張】子のコンポーネントを事前に配列へ取得

風船を割った際のエフェクト

エフェクト自体は風船1個1個に配置しておらず、ワールド内に1個だけ。割った瞬間だけオブジェクトを風船の位置へ移動してエフェクトを再生する事で、もろもろ節約している。紙屑が四角ではなく三角なのは、ポリゴン数をケチったため。四角に比べて半分で済む。連続で割ると効果音の音程が上がるが、AudioSourceのPitchを上げるだけで処理。空間を歪ませるようなエフェクトを出そうとしたが、至近距離だと目がキツいし割った瞬間に通り過ぎるので紙吹雪だけでよしとした!

リング

スピードアップするやつ

そこら中がキラキラすると困るので近くなった時だけ見えるようにした。このエフェクトもワールド内に1個だけ。常にプレイヤーに一番近いリングのポイントに移動させて使いまわすことで軽量化。風船同様、距離だけで当たり判定を処理。

リングの配置

前述の通り、リング自体は場所を指定するだけのGameObjectなので何も表示されない。編集で配置する際に何も見えないのは困るので、Tagを”EditorOnly”にした適当なメッシュをPrefab内に置いておいた。こうすると編集中は見えるけど、アップロード時には消えているオブジェクトになって便利。

独自LOD

家とか塀とかのオブジェクトは独自LODを使用。1個1個ではなく複数のオブジェクトを1つのLODで管理。Unityに用意されているLODのコンポーネントはスクリーンの描画される範囲を元に表示の切り替えをする(らしい)けれど、それとは別に距離によって切り替えるものが欲しかったので自作した。最初はColliderの範囲内に入ったら表示(OnPlayerTriggerEnter)、出たら非表示(OnPlayerTriggerExit)にするという処理を組んでいたが、リスポーン時や乗り物に乗った際に動作しないことがあったのでユーザーとの距離を毎フレーム調べる方法で処理。(※毎フレーム、Collider持ちのGameObjectをユーザー位置に移動する等ためしたがダメだった…)
こちらはGameObjectのActiveの切り替えで行っているので頻繁に移動すると重くなるけど、数が多くないので許容した。ちな家1個△500~1000ポリゴン。

大砲

最初はただのクリスタルで触るとテレポするだけだったけど、コミカルにしたくて最後は大砲になった。長距離移動のストレスを緩和するためだけに作ったけど、空中に風船を置いたら割とおもしろギミックになった気がする。バットの打球音が入っているのは、ただの爆発音だとおもろなかったから。
Synchronization MethodがManualのもの共通で言える事かもだけど、GameObjectのActiveがDisableの際にSendCustomNetworkEventが発生する(もしくはゲーム開始時に非アクティブにする)とどうもバグるっぽく?前述の独自LODのおかげで発生していたようだった。ClientSim上ではバグが発生しないので、本番動作のみで詰まったらそのあたり確認してみるとよさお。

RealtimeLightはアバターのみ

軽量化のためにアバターのみにあたるDirectionalライトだけ設置。家や木などのライティングはEnvironment LightingのAmbient ColorとFogのみ適用し陰影はなし。ベイクもなし。空はSkyboxのマテリアルでテクスチャ同士をLerpさせて遷移、マテリアル内のパラメーターをUDONから変更している形。(Animationでやりたかったけど、UDON内のパラメータにアクセスできない仕様だった)
家の光は夜だけEmitのテクスチャの適用度を上げる形にした。光の周りにグローがかかるような加算合成の板ポリを置きたかったが、夜になると全プレイヤーが結果画面前に強制集合して家を見る機会がほぼないためやめた。そのかわり、Emitを強めに調整したのでPostEffectでそれっぽくボヤっとするように見えるはず。
家と地面&木はそれぞれUnityStoreで買った別々のアセットだけど、テクスチャのサイズと彩度をいじるぐらいで大きく手は入れてない。違和感を感じないといいなあ。

Terrainまわり

木の他にひまわりもTerrain
Trrain設定

Terrain(テレイン)は地面と木や草などのオブジェクトを統括して編集できるUnityのコンポーネントで、VRChatにも使える。設定は↑の通り。調べた感じHightMapResolutionはもっと下げてもよかったかも。遠くの地面はかなり低ポリになるけど、木や家のおかげで結構ごまかせているかも。ただ坂などが低ポリになった瞬間、乗り物のコリジョンと干渉してどこか吹っ飛んでしまう事があった。そのため乗り物は直接地面の上に置かず、土台を用意した。平地は問題ない。

TerrainのTree設定

Terrainに使ったアセットは後述の”Toon Fantasy Nature”。PaintDetail(ビルボードの草とかのやつ)は使用しておらず、すべてTree扱いで配置。もともと統一マテリアルで軽量化されているめちゃよいアセットだったけど、シェーダーからアウトラインをなくしたりライティングを削ったりしてさらに軽量化した。Tree系はStaticではなくGPU Instanceで軽量化。
ゲームオプションで品質を変えた際に、TerrainのTreeの表示を変更する処理を追加したかったが、UDONではTerrainのDrawへのアクセスが禁止されてるみたい…

UI

ドットのフォント(美咲フォント)を使っているのもあり、Canvas自体のWidthとHeightは小さい。ボタンやウィンドウのデザインはドットの方が気兼ねなくデザインできた。でもCanvasサイズが小さいからと言って処理自体がそこまで軽くなるわけではないらしい…。LayoutGroupでControlChildSizeを多用しているけど、編集でラクをするためであり処理負荷は重い。離れたら消えるようにしているし、ゲーム中はUIほぼ表示されないから許容した。

DrawCallまわり

重いところでSetPassCall:110前後、Batches:180ぐらい。ここだけ見ると重そうだけど、結果的にRTX1080の方でも重くないと聞き、ほっとしている。テクスチャサイズは全体的に512~1024に抑えた。エディターで計測中はGPUよりCPUの方が負荷が高かった。オクルージョンカリングはベイクしてない。LODで十分かなって。

背景ガチ勢のレビュー

遠景に使った円柱状のポリゴン

知り合いの3D背景ガチ勢にレビューしてもらった際の項目。いくつかあきらめたものもアリ…

  • 世界観をハッキリさせる。バックボーンや歴史から道、家の配置を考える
  • 家やドアを土の上に置かない。家の基礎を用意する
  • 石畳、レンガ地面などはもっと目立たせる
  • ランドマークは大きくするなりして、もっとわかりやすく目立たせる
  • 浮遊島とかでなければ、Terrainの更に奥に円柱状のポリゴンなどで遠景を入れる
  • 家の光などは、加算の板ポリなどでグローをかける
  • 草原などには花をピンポイントに生やす
  • 花の色は色相をずらす(単色グラデにしない)
  • 遠くの木のLODは、十字にクロスした板ポリよりビルボードを使う

BGM

探してる際にいいなと思った曲全部使うことにした。全10曲。かわいいボーカル曲が無料で使えちゃうのすごい。ゲーム時間より長い曲はAdobe Auditionで尺を削って容量を削減したり、他微調整した。
たとえば上記の曲は、そのまま使うとゲーム時間的に歌詞が中途半端に切れてしまうので2番の歌のAメロから再生。ラスト30秒で流れる焦らせる曲は、カラオケバージョンのイントロをテンポアップさせたもの。

これからワールドを作る人へ

(2023/03/18追記)
まだ2つしかワールド作ってないけど、オススメできそうなことなど

ほしい情報が見つからないときに見る場所

UDONの詳細など、ググっても中々見つからない場合

QVペンのオススメ

実機デバッグやブラッシュアップするタイミングで入れたい。VR上で動作確認する際、問題があったり追加したい機能をその場でペンで書いてスクショとれば、そのままタスクリストとして機能する。超便利。
QVペン : https://booth.pm/ja/items/1555789

早めに導入/考えておきたいもの

  • QVペン
    • 前述の通り
  • ミラーの位置
    • ミラーに重いものが映ると処理落ちが激しい
    • なるべくそういうものが映らない位置にある方がよいので、そういう場所を確保しておくのはありかも
  • みんなのFPSを表示するやつ
    • 他のプレイヤーが、どこに行く/向くと重くなるかを調べるのに使用
    • boothにあったんだけどなくなってる…?

あとの方で導入した方がよいかもしれないもの

  • めちゃ重い系アセット
    • 2GBとかあるやつ
    • バックアップもだしインポートに時間がかかったりする
    • 開発中のUnityプロジェクトに全部入れるんじゃなくて、いったん別のプロジェクトにインストールして必要な奴だけExportPackageしたほうがよさお
  • 音声系ファイル
    • アップロード時に時間がかかるようになってしまった
    • 理由がなければ効果音やBGMは後の方で実装するほうがよいかも

その他参考にさせてもらったリンク

買ったUnityStoreアセット

※抜粋
※買った当時はまとめ買いセールでいくつかが$22ぐらいだった