Contents — 12 sections

VRChatをMMOっぽくしたかった。

VR空間に5スロットのAction Barがある。

手で決めたジェスチャーをすると、FireballやBarrierが出る。

見た目としては、それだけ。

ただ、最初に作ったのは派手なOverlayではなく、かなり地味なAction Engineだった。

理由は単純。

VRでは「出ない」より「勝手に出る」の方が困る。

ゴールは30秒の「VRChatをMMO化してみた」

MVPの完成条件は5つ。

  1. VR空間に5スロットのAction Bar
  2. Skill選択
  3. Gesture Cast
  4. Cast / CooldownのUI feedback
  5. 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はこうした。

  1. Fireball
    HandOpen → Fist → HandGun

  2. Thunder
    Victory → FingerPoint

  3. Barrier
    Both HandOpen hold

  4. Dash
    Fist → FingerPoint

  5. 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 error
  • dotnet 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では、こういう「見た目より先に作った地味な部分」も残していく。