VRC Avatar Parameter Driver を併用した物の出し入れ・左右の持ち替えの実装例と同期の話（前編）

# はじめに
どうも。「バーチャルためにならない改変お姉さん」の水無月せきなです。
Avatars 3.0 が導入されてから早くも１年が経とうとしている頃合い（執筆時点）ですが、いかがお過ごしでしょうか。

ちょっと凝ったやり方で物の出し入れをしようとしたら、見てる人に同期させるのに四苦八苦したので、その方法と経験を書きます。一般的な着替えや、物の出し入れではそうそう使わないであろう「VRC Avatar Parameter Driver」を使います。

一般的な方法や Unity の説明は目的ではないので、「そっちの方が知りたい！」という方は他の方の記事を読んでください（余裕があればリンクを貼ろうと思います）。

実装にあたって、フレンドのゼイサーさんより教えていただいた下記のツイートと動画を参考にさせていただきました。
お二人とも、ありがとうございます！

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Bool8つで疑似ピックアップ・ギミック動作・左右入れ替え・アイテムロック<br>不安定な部分あるけどもうﾑﾘﾎﾟ<br>動画はかなり揺れますので注意<a href="https://t.co/r84gPVqDQb">https://t.co/r84gPVqDQb</a> <a href="https://t.co/oStqLpPz4o">pic.twitter.com/oStqLpPz4o</a></p>&mdash; しちや🦊 (@k_sitiya) <a href="https://twitter.com/k_sitiya/status/1415209551696039938?ref_src=twsrc%5Etfw">July 14, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 環境
Unity：2018.4.20f1
VRChat：VRChat 2021.3.1（Build 1114）
VRCSDK：VRCSDK3-AVATAR-2021.01.28.19.08_Public

# 目標
ここから先、「出し入れしたい物」を「オブジェクト」と呼称することにします。

オブジェクトを出し入れするにあたり、下記の要件を満たすことが目標です。

・最小限のパラメータ、メニュー操作であること。（たぶん実現？）
・右手へ優先的に持たせるが、右手が既に何かを持っている場合は左手に持たせること。（実現）
・手は２本しか無いので、オブジェクトは２個までしか出せないこと。（実現）
・左右の持ち替えが可能であること。（実現：ちなみに constraint を使用）
・同期ズレしないこと。（たぶん実現）
・一部のオブジェクトについては特定の条件下でのみ出せること。（未実装）
・後から物が増えても対応可能であること。（一部実現？）

# 方針
下記の方針で実装しました。
〇オブジェクトのON・OFFと左右の状態（constraint の切り替え）は分けて考える。
　⇒一緒に考えると分岐が複雑になりそうだったため。それぞれレイヤーを分けて処理します。
〇右手が優先かつ排他のため、「右手に何を持っているか」で判断する。
　⇒オブジェクトの中の１つを右手が持っている場合、目標の条件から、他のすべてのオブジェクトの constraint が左手に向いていても問題ないはず。
〇「既に２個持っている場合に ON になった別のオブジェクトのパラメータを OFF にする」などのパラメータの自動的な処理は「VRC Avatar Parameter Driver」を使用する。

### VRC Avatar Parameter Driver とは

https://docs.vrchat.com/docs/state-behaviors#avatar-parameter-driver

VRChatが用意している State Behaviors の内の１つです。
この State Behavior が付けられたステートに入った際、指定したパラメータを指定した値にします（他にもできますが、今回はしないので割愛します）。

パラメータは Expression Parameter に登録されていないパラメータでも指定可能です。その場合は他の人に同期されないというだけです。逆に、Expression Parameter に登録されているパラメータであれば（大文字小文字とかのよくある間違いをしていたりしなければ）他の人に同期されます。そう、__同期する__んです。

「よし、できたぞ」という段階で「同期するはずなのに同期しない」と悩んだのですが、ここに"も"原因があったとは思わなかったのでした……（意味深な伏線）

# 実装
## １．パラメータ
![キャプチャ.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1843472/dd9f2af5-3590-a544-c438-c0021f333ec5.jpeg)

現時点で使用しているパラメータは７ビットです。
ビット数だけだと Int の方が……となりますが、Int の場合だと組み合わせごとに値を振るのは良いものの、同時にその値ごとにメニューの項目も増えてしまうので、この方式を取ったという理由があります。

方針の中で「右手に何を持っているか」というのがありましたが、その情報を保持するのが「LR_Bit_0」「LR_Bit_1」です。
それぞれのオブジェクトごとに値を割り当て（１⇒オブジェクト１、２⇒オブジェクト２……）、２進数としてパラメータの True と False を組み合わせて判断します。

ただし、右手に何も持っていない場合（＝値が０）、本当に何も持っていないのか、右手に持っていないだけで左手にはそのオブジェクトを持っているのかが判断できない場合があるため、「L_Handed」のパラメータを使用しています。

## ２．メニュー
![メニューキャプチャ.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1843472/24593a68-f065-fbd5-8c83-9e66bed2e736.jpeg)

メニュー側はこのようにシンプルになりました。というか、これくらいシンプルにするのが目標です。

## ３．オブジェクトの ON・OFF
![leeme3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1843472/ecccc09e-c990-8020-88a3-8974142fef32.jpeg)

オブジェクトの ON・OFF については、既に ON になっている個数をカウントして制御しようと思いました（パラメータの画像にその名残があります）。しかし後述する同期の問題で断念し、ひとまず他のパラメーターとの組み合わせをトランジションの条件に設定しています。

画像の「DE_OFF」⇒「DE_OFF_Keep」が「既に２個ある状態で ON になった」時であり、「DE_OFF_Keep」に付けた VRC Avatar Parameter Driver で当該パラメータを OFF にしています。「DE_OFF」「DE_OFF_Keep」はどちらもオブジェクトが OFF のアニメーションを入れているので、見かけ上は変化がなく、パラメータの ON だけがキャンセルされます。

## ４．オブジェクトの左右入れ替え
![leeme1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1843472/044f740b-8ea9-c921-70d8-bb5ba0ea59d8.jpeg)

「Default」⇒「Change_Cancel」は、「何も持っていない状態で左右の入れ替えを ON にした」時です。オブジェクトの ON をキャンセルするのと同様に、「Change_Cancel」に付けた VRC Avatar Parameter Driver で「LR_Change」を OFF にしています。

「Reset」はその名の通り、左右の制御に関するパラメータを VRC Avatar Parameter Driver ですべて初期化します。すべてのオブジェクトが OFF になった場合はここを通って「Default」に戻る寸法です。

以上３つのステートマシンのアニメーションは、すべてのオブジェクトの  constraint が左手に向いているアニメーションです。

「Item1_LR」「Item2_LR」「Item3_LR」はそれぞれのオブジェクトの左右の状態です。画面上にスパゲッティができあがりつつあったので、サブステートマシンにそれぞれをまとめてみました。

https://docs.unity3d.com/ja/2019.4/Manual/NestedStateMachines.html

それぞれのサブステートマシンの中は同じ構成です。
![leeme2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1843472/902edbe1-42b2-ca42-9044-15f461ff928e.jpeg)

何もオブジェクトが ON になっていない状態からオブジェクトが ON になると、そのオブジェクトの「～_R」に移動します(画像だと「Item_1_R」。以降はこれで話を進めます)。
「Item_1_R」にも VRC Avatar Parameter Driver を付けており、「LR_Bit_0」「LR_Bit_1」を割り当てられた値に対応するようにそれぞれ変更し、「LR_Change」「L_Handed」を False にします（「LR_Change」はここで False にしないと、この構造だとループします）。

「Item_1_R」は__そのオブジェクト１つだけ constraint が右手に向いている__（他はすべて左手）アニメーションを入れています。
他のオブジェクトが ON になれば必然的に左手に出ますし、右手のオブジェクトが OFF になると、左手に ON のオブジェクトがある場合はそのオブジェクトの「～_L」へ、ない場合は「Reset」へ移動します。

「Item_1_L」でも VRC Avatar Parameter Driver でパラメータを変更します。「LR_Bit_0」「LR_Bit_1」「LR_Change」は False 、「L_Handed」は True です（「～_R」と同様、「LR_Change」はここで False にする必要があります）。
「Item_1_L」は、すべてのオブジェクトの  constraint が左手に向いているアニメーションを入れています。
オブジェクトが OFF になれば「Reset」へ、別のオブジェクトが ON になるとそのオブジェクトの「～_R」に移動します。

# おわりに
私の実装の紹介は以上となります。
すべてを書ききるつもりでしたが、トランジションが複数に入り乱れる時点で無理でしたね……
説明が足りない部分も多いと思いますが、適宜直していければと思います。

VRChat初心者の方がこれを読まれるかはわかりませんが、念のために書いておくと、物を出し入れしたり左右で持ち替えたりするのに、ここまでする必要はありません（おすすめもしません）。
冒頭にも書きましたが、単純に出し入れしたりする方法であれば他の方が既に公開されていますし、ツールもあります。
ご自分にあった方法で実装されてください。

じゃあ何で書いたのかと言われると、冒頭で書いた通り、四苦八苦した「同期」のお話をしたかったからです。
ただ、思いの外長くなってしまったので次回に続きます。