みかづきメモ

主にプログラミング関連のメモ帳 ♪(✿╹ヮ╹)ノ 書いてあるコードは自己責任でご自由にどうぞ。記事本文の無断転載は禁止です。

Re: VRChat Inventory System の動作の仕組みについての解説

もしかしたら気になる人がいるかもしれない、 VRChat Inventory System
動作の仕組みについて解説します。
このシステムでは、 Particle System、 Animation および隠しオプションを使用し、
動作を実現しています。

なおこの解説は、現在配布している v0.1.0 時点のものです。
将来の更新によって仕組みが多少変わる可能性があります。
また、私自身は Unity を触ってまだ浅いので、より良い処理方法がある可能性もあります。


Particle System について

Inventory System では、 Particle System を「当たり判定」として使用しています。
Ver 0.1.0 において、 Particle System は以下の GameObject にアタッチされています。

  • InventoryTrigger/Shims/Trigger
  • InventoryObject/Enabler/Activator
  • (略)/StateWithProxy/StateSwitch/StateMachine/Deactivator

このうちの Trigger では Triggers モジュールが有効にされており、
有効範囲内に設定された Collider がはいった後、範囲外に出たタイミングで、
Sub Emitters を発動させます。

Sub Emitters モジュールには上記の Deactivator / Activator が設定されています。
これが、「触れると何かしらの反応が起こる」システムの正体です。

Animation

VRChat では、 C# スクリプトによる GameObject の操作を行うことが出来ないので、
Animation を使用して GameObject の Enable / Disable の切替と、
Animation の Enable / Disable の切替を行っています。

隠しオプション

Inspector の一番右側のハンバーガーメニューを押してもらうと、
以下のようなメニューが出てくるとおもいます。

f:id:MikazukiFuyuno:20200603130251p:plain

ここの「Normal」にチェックがはいっている部分を「Debug」にすることで、
シリアライズ可能な全てのオプションが表示されるようになります。
Inventory System では、ここに表示されている以下のオプションを有効にしています。

  • Keep Animator Controller State On Disable

このオプションは、本来 GameObject を無効にすると失われる Animator Controller State を
無効にされても状態を維持することが可能になる設定です。
この Inventory System が Unity 2018.3 以降でしか動作しない理由がここにあり、
このオプションが Unity 2018.3 で追加されたものだからです。

動作の仕組み

ここまで、使っている仕組みを解説してきました。
ここからは、実際の動作の仕組みについて解説していきます。

Inventory System は以下の2つの GameObject ツリーから構成されています。

f:id:MikazukiFuyuno:20200603130420p:plain

InventoryTrigger が当たり判定を行っているところ、
InventoryObject が当たり判定に応じて表示・非表示を切り替えているところです。

まずは処理が単純な InventoryTrigger から見ていきます。

一番深いところにある Trigger は、先ほど説明したとおり、
Particle System が設定された当たり判定本体の部分です。
Trigger の Particle System には、以下の動作が設定されています。

  1. 設定されたコライダーが範囲から出たタイミングで、自身を殺す
  2. 自身が死んだタイミングで、以下の2つの Particle System を起動する
    • Deactivator
    • Activator

何度も言うとおり、 VRChat では C# スクリプトが使えず、
実質 Kill で自殺する以外の方法がないため、それで動作を定義しています。

Shims はなにもしていません、導入方法の解説にあるとおり、ただの Sphere です。
これは、ユーザーに任意の GameObject に置き換えられることを想定したオブジェクトです。

InventoryTrigger は、 Animator が1つくっついています。
これは、定期的に Shims/Trigger の Enable / Disable を切り替えるだけのものです。 というのも、 Triggers は役目を果たすと自身を殺すので、一度発動した場合、
二度と発動しなくなります。
それを防ぐため、定期的に有効・無効を切り替えているのです。
また、 Particle System にて Play on Awake を有効にしているので、
有効になったタイミングで Particle がでて、当たり判定が復活するわけです。

これが InventoryTrigger の動作の全てです。
まとめると、以下の動作になります。

  1. InventoryTriggerTrigger を定期的に有効・無効を切り替えている
  2. Shims はなにもしていない
  3. Trigger は当たり判定の役割をもつ

さて、次はかなり複雑な InventoryObject の解説にうつります。
InventoryObject は以下の役割を持っています。

  • 出し入れしたいオブジェクトの有効・無効状態の保持
  • 出し入れしたいオブジェクトの有効・無効状態の切り替え

なお、 Inventory System には初期状態が二つあり、微妙に動作が異なるのですが、
ここでは初期状態が「無効」の状態のものを解説します。

こちらは、処理の流れの順に説明していきます。
まず、アバターが初期化されるタイミングにて、以下の3つの処理が走ります。

  • InventoryObject : ActivatorAnimator コンポーネントを有効にする
  • Enabler : Activator を有効にし続ける

1つずつ解説していきます。
まずは簡単な Enabler から見ていきます。
これは、無限ループで Activator を有効するだけの処理が定義されています。
というのも、 Activator も自殺 (正確には非アクティブ化) を行うからです。
自殺された後、復活しないと永遠にオブジェクトが表示されることはありません。
また、二度目以降の動作が行われなくなります。

InventoryObject は Animation の再生を行います。
どういうわけかは知りませんが、この方法で有効にすることで、
後に出てくる「自身のアニメーションを止める」処理がスキップされるのです。
そのため、初めから有効にならずに手動で有効にしているのです。

では、有効化される側である Activator について見ていきます。
Activator は Particle System と Animator を持ったコンポーネントです。
この GameObject はいろいろ設定されていますが、主な動作は以下の流れになります。

  1. 先に解説した Trigger 経由で Particle が有効にされる
  2. 一定期間経過後、 Particle は自殺 (自身を非アクティブ化) する
  3. Enabler によって即時アクティブ化される
  4. 有効になったことで、 StateMachineProxy に設定された Animation が再生される

また、初期化時には以下の処理が行われます。

  1. 設定された Animation が再生される
    • Initialize 処理を走らせる

さらに、最初の1回目の反応については、以下の処理が行われます。

  1. 設定された Animation が再生される
    • OnTriggerActivated 処理を走らせる
    • 自身のアニメーションを止める

ここで、任意の初期化処理を行っています。
それぞれ何をしているか見ていくと、初期化時のアニメーションでは、
StateSwitchProxy のアニメーションを止めています。
次に最初の1回目の反応にて、再度 StateSwitchProxy が走るようにしています。

なんでこうやったか覚えてませんが、改良できる気がするのでできたらやっときます。

次は StateSwithProxy を見ていきます。
このコンポーネントにも Animator があり、これは親オブジェクトが復活する度に、
設定されたアニメーションが再生されるようになっています。
動作としては、 StateSwitch を呼び出しているだけです。

次は StateSwitch です。
このコンポーネントは、子のStateMachine の State を切り替える役割を持ちます。
動作としては、一定時間だけ子の Animator を有効にしています。
有効にする時間が終わったら、自身のアニメーションを無効にして処理を終わらせます。

これらは、2つで1つの動作「ステートを切り替える」を実装しています。

そして、次は StateMachine オブジェクトを見ていきます。
ここの Animator コンポーネントは、隠しオプションが有効にされています。
というのも、親に当たる Activator が死んだタイミングで、ここの State が失われると、
ActivateDeactivate を切り替える為の状態維持が出来なくなるためです。
ここの Animator は一見複雑に見えますがやってることは単純で、
Activate State になったら Deactivator を有効に、
Deactivate State になったらなにもしないをやっているだけです。
ステートが切り替わったタイミングで自身のアニメーションを無効にすることで、
状態の維持を行っています。

最後が Deactivator です。
名前の通り、子の Object を消す処理を行っています。
これも Activator を同じで、

  1. 先に解説した Trigger 経由で Particle が有効にされる
  2. 一定期間経過後、 Particle は自殺 (自身を非アクティブ化) する

という処理を行っています。
このとき、 Deactivator の生存期間を短くすることによって、
ちらつく現象が発生しないようにしています。
ちょっとわかりにくいかと思うので、実際に GitHub から Clone して、
Start LifetimeActivator (0.15) 以上にするとわかるかと。
まれにですがちらつきが発生するのが分かると思います。

ということで、長いですが Inventory System の動作の仕組みでした。
なお、何度も言いますがこれは Unity 2018.3 以降でしか動作しません。
それ以前に実装されている Particle での Inventory System とは、
動作が根本的に異なるのでご注意ください。

ではでは٩( 'ω' )و