Contents — 12 sections
VRChatをMMOっぽくしたかった。
VR空間に5スロットのAction Barがある。
手で決めたジェスチャーをすると、FireballやBarrierが出る。
見た目としては、それだけ。
ただ、最初に作ったのは派手なOverlayではなく、かなり地味なAction Engineだった。
理由は単純。
VRでは「出ない」より「勝手に出る」の方が困る。
ゴールは30秒の「VRChatをMMO化してみた」
MVPの完成条件は5つ。
- VR空間に5スロットのAction Bar
- Skill選択
- Gesture Cast
- Cast / CooldownのUI feedback
- Avatar側の見える演出
Milestoneは、
- P0: Gesture → App → Jump
- P1: Gesture → Skill_Fire → Avatar FX
- P2: JSON + Cooldown + 5 Skills
- P3: Desktop Action Bar
- P4: SteamVR Overlay
- P5: 30秒動画
という順番にした。
ポイントは、OverlayをCoreにしないこと。
UIから始めると、VR Runtime、表示、入力、座標、Window、Overlay APIまで一気に問題が増える。
まず「入力をスキルへ変換する部分」だけを切り出した。
Architectureはかなり単純
VRChat GestureLeft/Right
↓ OSC
VRChat Adapter
↓
Gesture Detector ← Keyboard / Remote
↓
Action Engine
↓ ↓
OSC out Cooldown state
↓ ↓
VRChat FX UI / Overlay
Action EngineはUIを知らない。
時刻も外から渡す。
だから実機がなくてもTestとReplayができる。
最初の5 Skills
仮のSkillはこうした。
-
Fireball
HandOpen → Fist → HandGun -
Thunder
Victory → FingerPoint -
Barrier
Both HandOpen hold -
Dash
Fist → FingerPoint -
Dance
RockNRoll → Victory
ここで大事なのは、「それっぽいGestureを決めた」ことではない。
これは最終案ではなく、実機ログを取ってから変える前提の仮案だった。
P0はFireballではなくJump
最初の疎通は、Avatar FXすら使わない。
右手で、
HandOpen → Fist → HandGun
を1.4秒以内に入力。
成立したら /input/Jump へ 1 → 0 を80ms Pulseで送る。
これで、
VR gesture
→ OSC
→ C#
→ OSC
→ VRChat action
の一周だけを先に通す。
最初からFireballの見た目まで作るより、失敗点を減らせる。
一番面倒なのは認識より誤爆
たとえばFireballは、
HandOpen → Fist → HandGun
Dashは、
Fist → FingerPoint
とする。
Fireballを入力中、最後のHandGunがFingerPointとして崩れたらどうなるか。
Fist → FingerPoint
だけを見れば、Dashが成立する。
ThunderとDashもFingerPointで終わる。
2 stepの短いSequenceは、日常動作でも成立しやすい。
だからAction Engineには、
- 左右手の区別
- Sequence window
- Hold
- Both hands
- Skill個別Cooldown
- Global Cooldown
- 入力消費
- 長いSequence優先
- minStableMs
- Neutral無視
- suffix衝突警告
を入れた。
JSONでSkillを差し替えられるようにした
GestureやCooldownを変えるたびにC#を書き換えるのは面倒なので、Skill定義はJSONへ出した。
持たせたのは、
- id
- name
- slot
- cooldownMs
- trigger
- actions
など。
TriggerはSequence / Hold。
ActionはPulse / Set。
OSC valueはBool / Int / Float / Stringを扱う。
これで、実機ログを見ながら、
- Gesture
- window
- Cooldown
- OSC address
を差し替えられる。
VRの入力はログに落としてから考える
全GestureはCSVへ保存する。
種類は、
- raw: VRChatから来た生値
- commit: stable filter後
- cast
- reject
- ready
など。
そのCSVを --replay で再生する。
一度HMDを被ってログさえ取れば、Detection parameterを変えるたびにVRへ戻る必要はない。
VR依存の入力を、一度「再生可能なデータ」に落とす。
これはかなり効いた。
成功率と誤爆率を別に測る
P0の受入基準案は、
意図入力:
- 10回試す
- 9回以上認識
自然動作:
- Comboを意識せず30回ほど普通に手を動かす
- 誤爆1回以下
さらに、
- 体感遅延なし
- 中間GestureがちらつくならminStableMs 40〜80msを試す
とした。
Gesture recognitionでは「出したいときに出る」だけを見ると危ない。
出したくないときに出ないことも別に測る。
初期実装は49 Test
初期PR時点では、
dotnet build: 0 warning / 0 errordotnet test: 49 / 49 PASS
まで通した。
Pythonの擬似VRChatを使ったUDP E2Eでは、
- Fireball cast
- Cooldown reject
- Both HandOpen hold → Barrier
- ready notification
を確認。
250ms設定のPulseは、実測252.5msだった。
ただしこれは擬似VRChatを使ったE2E。
HMD上でGesture認識精度が証明されたわけではない。
ここは分けて扱う。
後からStream Deckも同じEngineへ繋げた
その後、Stream Deck MK.2からSkillを撃つ経路も追加した。
Stream Deck
→ localhost HTTP
→ Action Engine
→ OSC
→ VRChat
GET /cast/<1-9> で発動。
Cooldown中は409。
GET /state で残りCooldownを取る。
この経路では、実VRChatで5 Skillsすべての Skill_* parameterがTrueになることを確認した。
ここで確認できたのは、
Action Engine → OSC → VRChat parameter
の後半経路。
Gesture Detectorの認識率や誤爆率とは別の試験なので、混ぜない。
入力源をCoreへ埋め込まなかったのは正解だった
Gesture。
Keyboard。
Stream Deck。
どれも同じAction Engineへ入る。
Cooldownも共有する。
入力デバイスが変わっても、Skillの状態機械は変わらない。
最初から「VR専用Gesture Engine」にしなかったことで、後から別入力を足しやすくなった。
いまの結論
VRChatをMMO化するなら、一番最初に派手なAction Barを作りたくなる。
でも、今回先に作って良かったのは、
- 誤爆を抑える
- Sequenceを検証する
- Cooldownを持つ
- 入力ログをReplayする
- Input sourceとSkill logicを分ける
という地味なCoreだった。
Overlayは目立つ。
でも、勝手にFireballが出るMMOはたぶん遊びづらい。
PicoTowaでは、こういう「見た目より先に作った地味な部分」も残していく。