初めてVRChatでワールド作って苦労した話
-
2024年5月8日
こんにちは、ココネ株式会社でクライアントエンジニアをやっているKです!
今回は、初めてVRChatでワールドを作ることになり、苦労した話を書こうと思います!
VRChat初心者的なところでは「あるある」と思ってもらえたり、他の初心者の方がまだつまづいていない何かの参考になれば幸いです!
どんなものを作った?
簡潔に言うと、
「歌い手VTuber(3D)がVRChat上でライブをする際に、曲ごとに専用演出を流せるライブステージ」
を作りました。
以下はそのうち1曲のサビへの切り替わりの時の演出です。

作成した演出の一部
作った背景
とあるきっかけで、駆け出しの歌い手VTuberのためのプログラミングをする機会があり
「VRChatでライブがしたいから、専用演出が流せるライブステージを作ってほしい!」
といった願いを叶えようと思ったことが作成の背景です(今回はボランティア的な話です!この分野が盛り上がったら嬉しい!)。
ただ、実現したいこのステージの要件が中々厳しく、
・4曲演奏する予定で、4曲それぞれのイメージに合わせて異なる演出を出したい
・歌はリアルタイムで歌うので、演出と歌を同期できるようにしたい
・VRChat初心者でもライブの参加方法がわかりやすいよう、ライブステージの前に説明用のロビーが欲しい
・開発期間は2ヶ月半
・etc…
というものでした。
一方で、私の状況はというと、
・VRChatでの開発経験はなし(普段の仕事では、UnityでC#を用いた開発をしています)
・開発期間の2ヶ月半のうち、土日などの休日のみ使えるような状態だったので、実質開発できるのは約20~30人日くらい
明らかに無理だろうなって思いながら作業をしていたのですが、結果的になんとか4曲それぞれのステージ・演出を作ることはできました。
踏み抜いた困りポイントたち
ここから先は、何も知らないところから開発を始めて踏み抜いた”困りポイント”を順々に紹介していこうと思います。
VRChatワールド作成の初心者の方や、実際につまづいて困っている方のお役に立てれば幸いです!
えっ、ワールド作ったけどすぐにアップロードできない・・・?
まず最初にハードルとなったのは、そもそも最初はVRChat用にワールドを作成しても、アップロードできないという問題でした。
どういうことかというと、
VRChatではユーザーに「Trust Rank(信用度)」というランクがつけられており、遊べば遊ぶほどランクが上がっていくシステムなのですが、
1番初めのランク”Visitor”だと、ワールドやアバターなどのアップロードができない仕組みになっており、
ワールドをアップロードするためには、最低限次のランクの”NewUser”になる必要がありました。
とりあえず私はすでにNewUser以上の方とフレンドになり、数日いろんなワールドをみたり、放置してたりすることで”NewUser”にあげることができました。が、この”NewUser”に上がるまでの間はビルドテストができないので、問題点に気づくのが遅くなったりと、少なくないダメージがありました…。
いつも使ってるC#で早速開発しy・・・え?Udon・・・?
VRChatはUnity上での開発になるので、普段Unity開発で使っているC#を使うつもりで「さぁ何か動かしてみるか!」と思った矢先に、独自文化を突きつけてきたのが、VRChat専用プログラミング言語、Udonでした。
Udonを扱うための手段として、ビジュアルスクリプティングができる「UdonNodeGraph」と、C#のコードに近い書き方ができる「UdonSharp」を選べますが、私は普段C#を扱うのに慣れているので、UdonSharpを使うことにしました。
何も知らない当初の設計では、UdonSharpはVRChat上での同期に関連する処理にのみ適用し、それ以外はMonoBehaviourやC#のコードで処理を作る予定だったのですが、以下の問題にぶち当たりました。
おや・・・? MonoBehaviourで実装したクラスのUpdateの処理がVRChat上では実行されないぞ?
上記について、今回の執筆に際して改めて検証を行ってみました。
こちらがUdonSharpで書いたコード
using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;
public class TestUdonSharpBehaviour : UdonSharpBehaviour
{
void Start()
{
Debug.Log("UdonSharpBehaviour Start");
}
void Update()
{
Debug.Log("UdonSharpBehaviour Update");
}
}
そして、こちらがMonoBehaviourで書いたコードになります。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TestMonoBehaviour : MonoBehaviour
{
void Start()
{
Debug.Log("MonoBehaviour Start");
}
void Update()
{
Debug.Log("MonoBehaviour Update");
}
}
どちらも同じGameObjectにアタッチして実行したところ、以下のスクリーンショットのようにUnityEditor上ではどちらも実行されたものの、ビルドしたVRChat上では実行されませんでした。
.png)
UnityEditor上でのログ(MonoBehaviourのログも出ている)
.png)
VRChat上のログ(UdonSharpBehaviourのログはあるが、MonoBehaviourはない)
どうやらMonoBehaviourで記載したコードはUnityEditor上では動作しますが、ビルドしたVRChat上では動作しないようです。
調べたところ、VRChatはUdonVMという仮想マシン上で動作しているようです。
そしてUdonVMはUdon Assemblyから生成されたバイトコードを実行しており、
UdonSharpやUdonNodeGrapthで作られた処理はUdon Assemblyとしてコンパイルされるため、VRChat上で動作するという事でした。
逆に、MonoBehaviourでコードを書いただけではUdon AssemblyとしてコンパイルされないためVRChat上では動作せず、UnityEditor上でのみ動作するということになります。
初心者の私は、この事にある程度作業した後に気づいたので、設計的にも書いたコード量的にも大きな手戻りが起きてしまいました(しかも作業したての頃は上述した通りTrust RankがVisitorだったので、すぐにビルドテストすることもできなかった)
さーてコード書き終わっt、えっ、コンパイルしてみたら使えない!?
UdonSharpはC#を用いてコードを書くように、Udonの処理を書くことができるので、非常に有用なものです。
ただUdonの仕様上色々な制約があり、その流れでUdonSharpにも様々な制約がある様子で、コードを書く上で「あれっ、これ使えないの!?」となることが多々あります。
さらに辛いことにIDE上では普通に書けてしまい、Udon Assemblyのコンパイルをするタイミングでようやくエラーに気づくので、あらかじめ知っておかないと随所で手戻りを食らうことになります(n敗)
以下、特に困ったものをピックアップしてみました。何かの参考になればと思います!
- ▼ List / DictionaryなどCollection
- ・ Method is not exposed to Udon
- ・ 配列は使えるが、多次元配列は使えない( Multidimensional arrays are not yet supported by UdonSharp, consider using jagged arrays instead. )
- ▼ Action
- ・ VRC.Udon.UAssembly.Assembler.TypeResolverException: Type referenced by ‘SystemAction’ could not be resolved.
- ▼ delegate
- ・ System.NotImplementedException: The method or operation is not implemented.
- ▼ ラムダ式
- ・ UdonSharp does not currently support node type ‘SimpleLambdaExpression’
VRChat上でログが見たい・・・!
UnityEditor上ではうまく動いているのにいざVRChatにビルドしてみたら動かない・・・。
そんな時はUnityで仕込んだログをVRChatでみたいということもありますが、そもそも見れるのかな?と調べてみたところ、やり方を発見しました!
右Shift+半角/全角+3 (Windowsの場合)
で表示されるようです!(半角/全角キーを含めるって珍しい・・・)
3キーのところを別の数字キーにすることで様々なログが見られるようですが、用途がよくわからないものもあったので、割愛します!
PostEffectの値を状況で変えたろ、ってあれ?
演出を作っている時、曲の進行具合によってBloomやColorGradingのかけ具体を変更したいなと思い、UdonSharpのコード上からアクセスしようとしたのですが、

Bloomを使おうとした時のエラー
アクセスできないことがわかりました・・・!(その可能性は感じ始めてた)
なんとか変更できないかなと模索したところ、
AnimationClip上からPostProcessVolumeのWeightを変更できる
ということがわかったため、PostProcessVolumeの設定値にはあらかじめ最大値を設定しておき、AnimationClip側からweight値を変更することで対応することにしました。

BloomとColorGradingを使わない時の設定

BloomとColorGradingを変更するときの設定
こちらで作成した演出が、先述の「作成した演出の一部」の画像のものになります。
なんとかこれで作りたい演出を作ることができました!
せめてTimeLineのSignalEmitter機能は動いてくれ!(が・・・・駄目っ・・・・!)
まずはじめに、TimeLineのSignalEmitterを使おうと思った背景ですが、
以下の1つ目の図のように、演出を行うオブジェクトごとに同期を走らせてしまうと、通信の量が増えたり、同期ずれで演出がバラバラで再生されてしまうのでは?という懸念があったため、以下の2つ目の図のように同期を行うコンポーネントを絞るため、演出を行うパラメータを一括管理し、そのパラメータ変更を受け取ったら演出を行う作りにしたいと思いました。


同期するコンポーネントを絞るイメージ図
そう考えた時に、Timelineからパラメータの同期用コンポーネントにシグナルを送り、演出用変数を変えたいという考えに至りました。
特にライトなどに関しては以下の画像のように、同じタイミングで複数の処理、例えば「ライト1とライト2をこのスピードで明るく」「背景の明るさはこのスピードで明るく」など、見た目の変更をまとめて実行してしまいたい状況が生まれたので、是非ともシグナルを使いたいという状況になりました。

タイミングごとの演出設定
そこで実際にシグナル用の処理を書いてみると、
VRChat上ではシグナルの処理が行われていない・・・!
ということがわかりました。
先述の内容的にも理由はわかってしまっているかもしれませんが、一応検証をしてみたので記載します。
TimeLineにはActivationTrack, AudioTrack, PlayableTrack, SignalEmitter, AnimationTrack, ControlTrackが設定できるので、それぞれで単純なCubeオブジェクトの活性非活性、あるいはAudio再生の処理を行う処理を作成し、UnityEditor上とVRChat上で試してみました。
Timelineの内容と、その結果が以下になります。

設定したTimeline

UnityEditor上での各Trackの動作

VRChat上での各Trackの動作
画像ではAudioTrackについてはわかりませんが、SignalEmitterとPlayableTrack以外は動作しています。SignalEmitterとPlayableTrackの共通点としては動作のためにC#のコードを用いていることです。先述の通り、VRChatではC#のコードは動作しないため、Timelineでも動作しないということになります。
ただ、私としてはどうしてもSignalEmitter的な動作をしてほしかったので、致し方なく、
GameObjectの活性時にイベントを飛ばす処理をUdonSharpで書き、このコンポーネントをアタッチしたGameObjectの活性をActvationTrackで制御するという力技で乗り切ることにしました・・・。
正直もっといい方法があれば是非知りたいので、ご存知の方は教えてください!
今になって思うその他の改善したいポイント
VRChatの住民の身長を意識すべきだった
こちらはほとんど作り終えた後に、ワールドを見てくださった方から「ワールドが全体的に大きいね」と言われて気づいたポイントです。
言われてみればVRChatのアバターの身長は低い方が多く(140くらいかな)、ワールド作成用のアバター身長の初期設定ではそれよりも高いため、作り終えた頃には、住人の方々と比べると、縮尺が大きいワールドが出来上がってしまいました。
いったんは全体のサイズを変更して誤魔化したのですが、それだとサイズ感が合わないところも出てきてしまい、最初から知っていれば避けられたなぁと感じました。
ライブステージだけで、ワールドを個別に作るべきだった
今回、要件に「参加方法が説明できるロビーが欲しい」とあったのもあり、
ロビーや他のエリアなど複数区画があるうちの1区画としてライブステージを追加しましたが、
Skyboxの設定やPostEffectの効果範囲など、ワールド全体の設定として扱う方が簡単に処理をかけたり、もう少しいろんな機能が使えるかもという場面がありました。
できれば、いろんな演出や機能を使うようなライブステージの場合は別のワールドに分けた方が作業はしやすかったかなと感じます。
そもそももっとVRChatを遊んでおくべきだった
そもそもですが、VRChatを遊び倒していたわけではなかったので、先述のような身長の文化を知らなかったり、他ワールドで使われている魅力的な見せ方、演出、アセットなどをあまり知りませんでした。
これらを知っていると、開発初期のイメージももっと広がり、より良いものができたかなと感じます。
おわりに
ここまで色々な苦労話を書いてきましたが、開発自体は面白い点もたくさんありました。
アプリゲームで同期通信的なものを作ろうとすると、やれルームがとか、誰がマスターでその人が切断されたらとか考えることも多いですが、同じようなことをVRChatではほぼ意識することなく、ワールド作成に専念できるのも実はありがたいことだなと思います。
そして何より、自分が表現したい世界を自由に作成し、その世界を色んな人たちに体感してもらえるというのはクリエイター冥利に尽きるところだなとひしひし感じました。
私もかなり遅くVRChatに触れたエンジニアですが、これから触れようとするエンジニアの方の助けになれば幸いです!
参考にさせていただいたサイト
UdonAssemblyで制御文を表現する
https://qiita.com/akanevrc/items/73530aa14fb66f87f68c
VRChatメモ
https://tech.framesynthesis.co.jp/vrchat/