
【VRChat】Contactsで物を出し入れする【Avatar Dynamics】
Avatar Dynamics、本実装来ちゃいましたね……! 公式が予告してからLiveに来るのが早すぎて笑っちゃいました(乾いた声)
ということで、今回はAvatar Dynamicsの機能の一つである「Contacts」を使用してアイテムを出し入れする仕組みを作っていきましょう!
(この記事はUnityの操作にある程度慣れている必要があります。「それも分からない!」という方はこちらの記事を読んでおくことを推奨します)
そもそもContactsってなに?
ContactsとはAvatar Dynamicsで追加された機能の一つで、アバター同士又は自分自身の接触を判定できるものです。
要するに、「触ったよ」ってのが分かる機能です。
触ったことが分かるので、例えばハイタッチ(手と手)して音を鳴らしてみたり、ポケットを触って(手と腰)スマホを取り出してみたり、はたまた手に持った缶と缶(オブジェクトとオブジェクト)で乾杯もできちゃいます!
と、このように触れたことを合図に何かをやるというのがContactsという機能の本質です。
このContactsはPhysBones(物を揺らしたり掴んだりする機能)と一緒にまとめて「Avatar Dynamics」と呼びます。どんな感じのことができるのかはVRChatが纏めて動画にしているので見てみてください。(動画内で球として表示されているのがContactsです)
必要なもののセットアップ
簡単にContactsについて分かったところで早速セットアップしていきましょう!
今回使用するものはこちらです。
Unity 2019.4.31f1
(UnityHub 3.1.2)
VRCSDK3-AVATAR-2022.05.04.17.47_Public
お手持ちのアバター(PhysBones対応だとなお良し)
Contactsで出し入れしたいアイテム
((Unityくんと対話する気力))
※2022/05/05時点での最新版
Unityの起動からシーンのセットアップまで
そんなの分かってるよ! って方は目次からContactsの設定の項目まですっとばしちゃってくださいな。

まずUnityHubを起動します。
右上のNew projectを押してメニューを開きます。

左のリストからAll templatesを選択、3Dをクリックして、右のProject nameとLocationにそれぞれプロジェクトの名前と保存先を指定しておきます。名前はご自由に。
設定が終わったら右下のCreate projectを押してUnityが立ち上がるまで待ちましょう。

Unityが起動しました! それぞれのウインドウの場所は異なると思いますが、表示されているものについては同じなはずです。
ではまずVRCSDK3をインポートしましょう。ダウンロードは公式サイトから行えます。

タイトルバーからAssets、Import Package、Custom Package…と進みます。

エクスプローラーが開くので、ダウンロードしたVRCSDK3本体を選択して、右下の開くをクリックします。

ロードが終わるとこのようなウインドウが出現するので、そのまま右下のImportをクリックします。
読み込みに時間がかかるので、しばらくお待ちください。

プログレスバーが消え、プロジェクトウインドウにVRCSDKというファイルができていれば読み込み完了です。
次にアバターを読み込みます。
ここではこちらのアバターを使用しますが、VRChat用のセットアップがされているものであればどんなアバターでも大丈夫です。
また読み込むアバターはPhysBonesの設定がされているものだと望ましいです。(されていない場合はDynamic BoneからPhysBonesへの変換作業が必要となります)

先ほどと同じ手順でパッケージをインポートします。アバターのunitypackageを選択してUnityに読み込ませます。
なお、プロジェクトウインドウにドラッグアンドドロップでも読み込みが可能です。

読み込み終わるとアバターのファイルが作成されていると思いますので、開いてプレファブを探します。

プレファブファイルを見つけたら、ヒエラルキーにドラッグアンドドロップします。

するとシーンの原点に自動でアバターが置かれます。
シーンウインドウでスクロールか、右クリック→WASDQEキーで移動できるので見やすい場所に移動しておきましょう。

これで準備完了です!
※この時点でアバターを選択しているのにPhysBonesの設定が見えない場合は、シーンウインドウ上部にあるツールバーのGizmozをクリックして見えるようにしておきましょう。
アイテムとアニメーションの設定
お待たせしました! ではここからContactsを使用してオブジェクトを出し入れしていきたいと思います。
まず出したいものを用意する必要がありますね。これは各自お好きなものをお選びください。(スマホとか)
ここではアジャリ雑貨店さんのShareLabelCanを使用します。
専用のテクスチャも配布しているので良かったらどうぞ(唐突なダイマ)
コムソーさんとこの缶モデルがリリースされたみたいなので、お試しにアリシアちゃんのテクスチャ対応させときました( •̀ ω •́ )✨
— 風庭ゆい (@Yui0471) April 10, 2022
そのままAlbedo差し替えでいけますが、缶底の文字が被っちゃったので気になる方はMetallicを↓に差し替えるときれいになります……!#ShareLabelCan pic.twitter.com/XIvmKTihaF
では、サクッとアイテムのデータをUnityに読み込ませて使える状態にしましょう。

使いたいアイテムをヒエラルキーにドラッグアンドドロップして、


シーン上で移動させて、

持ちたい場所に移動させておきます。(今回は左手に出したいので左手に)

次にヒエラルキーを見ます。アバターのプレファブとアイテムのプレファブが並んでいますね。

今回はアバターの構造を変更するので、アバターのプレファブを右クリックしてUnpack Prefabでプレファブを解除しておきます。

解除出来たら、▷マークを押してアイテムを持たせたいアバターのボーンまで開いていきます。

今回は左手にアイテムを持たせたいので、Armature→Hips(腰)→Spine(背)→Chest(胸)→Neck(首)→Shoulder.L(左肩)→UpperArm.L(上腕)→LowerArm.L(下腕)→Hand.L(左手)まで開きました。
※アバターによって名前が違う場合がありますが、構造は一緒なので頑張って目的の場所を見つけてください。

開けたら、アイテムを持たせたいボーンにドラッグアンドドロップして子に置きます。
これで、アイテムが手に付いてくるようになりました。

さて、次にアイテムを出し入れするアニメーションを作成する必要があるので作ります。
プロジェクトウインドウで右クリック、CreateからAnimationで新しいアニメーションファイルを作ります。

作成するとファイル名を求められますが、自分で分かりやすい名前を設定しておきましょう。(ここではCan_ONにしました)

次にヒエラルキーでアバターのコピーを作成します。
アバターを選択して、

Ctrl+Dでコピーができます。
コピーができたら、(1)と付いたコピー先に先ほど作ったアニメーションファイルをドラッグアンドドロップします。

成功すると自動的にアニメーションコントローラーファイルができます。(ここでは使わないので後で削除します)

アニメーションファイルをダブルクリックすると、アニメーションウインドウが開きます。

このウインドウを開いたまま、ヒエラルキーでコピー先のアバターをクリックすると、アニメーションウインドウのAdd Propertyが押せるようになります。

Add Propertyを押すとアバターの階層構造が出てくるので、この中から先ほどアバターに入れたアイテムまで向かいます。

同じ階層を辿るとアイテムが出てくるので、アイテムの名前のすぐ下にあるIs Activeを押します。

するとこのようにアニメーションウインドウにキーが追加されます。

一番右のキーは使用しないので、右クリック、Delete Keyで削除しておきましょう。
削除できたら、一旦アニメーションウインドウは消してしまいます。

ヒエラルキーのコピーを消して、

プロジェクトのコントローラーも消します。

次にアニメーションファイルをCtrl+Dでコピーして、

右クリック、Renameで

分かりやすい名前を設定しておきます。(ここではCan_OFF)

コピーしたアニメーションファイルをダブルクリックしてアニメーションウインドウを開き、

プロパティの数字を1から0にします。
できたらアニメーションウインドウは消してしまって大丈夫です。

これでアイテムを出し入れするアニメーションを作成できました!
Contactsの設定と説明
お待たせしました! ではここから、Contactsを使用して設定したアイテムを出し入れする仕組みを作っていきましょう。
まず、Contactsは最初に説明した通り接触を判定するものです。つまり、どこかに触って、指定したアニメーションを再生させることができます。
今回はアイテムを出し入れしたいので、先ほど作ったアイテムをオンオフするアニメーションをContactsで再生させます。
ではまずどこにContactsを設定するのか決めましょう!
ここでは缶を出したいので……、一番自然な場所はポケットのある腰でしょうか。
ということで、今回は腰に触ったら缶が出現するようにしてみます。
※設定方法は場所で変わらないので、自分で好きな場所を選んでおきましょうね

ということでHipsボーンを選択しました。インスペクターにAdd Componentという項目があるのでクリックします。

上部に検索欄があるので、そこに「vrc」と打ち込んでみましょう。

すると名前に「VRC」が含まれるスクリプトが絞られますので、この中から「VRC Contact Receiver」をクリックします。

するとインスペクターにこのような項目が追加されます。これがContact Receiverの設定項目になります。
さきほどContact Receiverを追加する際に、「VRC Contact Sender」というものがあったことに気が付いたかと思います。
(しつこいですが)Contactsとは接触を判定するものなので、当たり前ですが触れるものと触れられるものが存在します。
Senderが触れるもの、Receiverが触れられるものに相当します。
あれ、じゃあReceiverだけじゃなくてSenderも必要だよね……?
そうです、必要です。ですが今回は設定しません。
実はこのContact Sender、VRChatがアバターに自動的に割り当ててきます。
アバターの核となる設定をするVRC Avatar Descriptorの最下部にこんな設定が追加されています。

Collidersという設定ですが、これはPhysBonesの設定をする以外にもContactsのSenderとしての機能も持っています。
つまり何も弄っていなければ、Head(頭)、Torso(上半身)、Hand(手)、Foot(足)、Finger(各手指)にContact Senderが既に割り当てられていることになります。
なので、触れるものがこの中にあれば基本的にSenderを設定する必要はありません(触れるものがこの中にない、Sender特有の機能を使用する場合はSenderを設定する必要があります)

では戻ってContact Receiverの設定をしていきます。上から説明しますね。

Root Transform
Receiver(触れられるもの)を設定する項目です。ここにオブジェクトを設定するとそのオブジェクトに接触判定が追従します。NoneならこのReceiverが設定されている場所になります。

Shape Type
Sphere
Capsule
接触判定の形を選択します。Sphereは球体、Capsuleはカプセル状になります。

Radius
接触判定の大きさです。

Position
X
Y
Z
接触判定のオブジェクト原点からの位置です。ここに数字を入れると、その分オブジェクトから位置がずれます。位置の調整に使用します。

Rotation
X
Y
Z
接触判定の回転位置です。数字を入れると、その分接触判定が回転します。位置の調整に使用します。

Allow Self
自分自身の接触判定をオンにします。ここにチェックを入れると、自分で触ったときに判定をすることができます。逆にオフにすると自分で触っても判定されません。
Allow Others
自分以外からの接触判定をオンにします。これをオンにすると、自分以外(他の人など)から触られると判定されます。オフにすると自分以外からの接触は判定されなくなります。
Local Only
オンにするとローカルでのみ動くように制限します。Receiverの接触判定自体はその場にいる各ユーザーのローカルで動いているので、これを有効にすると接触判定の処理を自分のみに制限できます。

Collision Tags
触れる側(Sender)を設定します。Addを押して項目を追加し、Contact Senderを選択することで、それが触れたときに接触判定をするようになります。
なにも設定しないと、Senderが触れたとしても一切接触判定をしなくなるので必ず設定が必要です。
またCustomは自分または自分以外のアバターに設定されたSenderを判定する際に使用します。

Receiver
このReceiverの核の設定です。ここで接触判定をどこにどうやって出力するか指定します。

Receiver Type : Constant
これを選択すると、SenderがReceiverに触れている間のみ1を出力します。触れていないと0を出力します。
Receiver Type : On Enter
これを選択すると、SenderがReceiverに触れた1フレームだけ1を出力します。つまり触れた一瞬だけ1になります。それ以外は0を出力します。
Receiver Type : Proximity
これを選択すると、Senderが触れているときどれだけReceiverの中心に近いかによって0.0から1.0を出力します。必ずfloatを使用します。触れていないときは0.0、触れ初めから中心に近づくにつれて1.0に近づきます。
さて、今回は左手にアイテムを持っていて、腰を叩くと出現するようにしたいので、このような設定にします。

Allow SelfとLocal Onlyにチェックを入れ、Collision TagsにHandLを追加。Receiver TypeはOn Enderに、ParameterにCan_Contactという名前を付けました。
こうすると、自分だけ触れて(Allow Self)、左手(HandL)が触れると、Can_Contactというパラメータが一瞬だけ1になる(On Enter)という設定になります。
またLocal Onlyをオンにしたことで接触判定の処理が自分のみになるので、同期がズレてオンオフが逆転することを防ぎます。
次にShapeの設定をしましょう。

シーンを見ると、青い線で描かれた球体がありますね。これがContact Receiverの接触判定です。
でかすぎですね。
なので、サイズを小さくして、丁度いい場所に移動させましょう。

とりあえずRadiusに小さい数字を入れて小さくし、位置を移動させてこんな感じに。
Radiusは0.04、XYZはそれぞれ左腰辺りになるように設定しました。
これで、ここに左手が触れるとCan_Contactパラメータに一瞬だけ1が入るようになりました。

最終的な設定はこんな感じ。RadiusとPositionはアバターによって変わるので好きな大きさと場所に設定しましょう。
FX Layerの編集
さてさて、Contactsの設定は終わりましたが、まだ触れたときにパラメータを出力するだけで何もしていませんね。
ということで、ここからはパラメータ出力を使ってアイテムがオンオフする仕組みを作ります!


まずヒエラルキーでアバターをクリックし、インスペクターのVRC Avatar DescriptorのBaseから、使用しているFXレイヤーを参照しておきます。

タイトルバーからWindow、Animation、Animatorでアニメーターウインドウを開きます。

これでアバターのFXレイヤーを開くことができました。
ここにある項目はアバターによって異なるので、違いがあっても問題ありません。

まず左上のParameters、+ボタンからBoolを選択してパラメータを追加します。

入れる名前は、先ほどContact Receiverで入れた名前にします。
※大文字小文字も判定します。一字でも間違えると反応しませんので注意してください!

次にもう一度Boolを追加して、名前を入力します。これはアイテムのオンオフを管理するパラメータなので自由な名前でOK。(ここではCan_ONOFFとしました)

二つパラメータを作ることができたら、Layerに戻って+ボタンを押して新しいLayerを追加します。

このレイヤーでは、Contactからくるパラメータを処理します。名前はご自由に。(ここではContact_Buttonと付けました)

新しくレイヤーを作ったら、必ず歯車マークを押してWeightを1に設定してください!!!!!!!
作らないと動きません!!!!!!!

ここから新しく作ったレイヤーにステートを組んでいきます。
何もないところを右クリック、Create State、Emptyを押します。これを三回繰り返します。

するとオレンジ色のステートが一つ、灰色のステートが二つできました。
ステートを左クリックしてインスペクターから名前が変更できるので、

適当な名前を付けておきます。処理に関係しないのでお好きな名前を。
ここではオレンジ色のステートにはEmptyを、上のステートにはアイテムをオンにするアニメーションを、下のステートにはアイテムをオフにするアニメーションをいれるのでそれぞれEmpty、CanOn、CanOffとしました。

名前を決めたらすべて選択して、

インスペクターにてWrite Defaultsのチェックを、

外しておいてください。

次に、ステートを右クリック、Make Transitionで矢印が引けるので、

このように引いておきます。

オレンジ色のステートをクリックしてインスペクターを見るとこのような項目になると思いますので、Motion欄に空のアニメーションをセットしておきます。

※ここで言う空のアニメーションとは、プロパティやキーが設定されていない文字通り空のアニメーションファイルです。
VRCSDK3のアバターならすでに存在するはずなので、Motion欄の◎からemptyと打って探しましょう。無ければ先ほどの手順でアニメーションファイルを作って入れても構いません。


そしてオンのステートとオフのステートですが、こちらにもEmptyのアニメーションをセットして下さい。

次に条件を指定します。
EmptyからCan Onに向かう矢印を選択すると、

インスペクターにこのような表示が出ます。


Settingsを開いて、Has Exit Timeのチェックを外し、Transition Durationを0にします。

下にConditionsという項目があるので、+を二回押して、

プルダウンから先ほど作ったパラメータを指定、画像のようにtrue、falseを設定します。

Emptyからオフのステートへ向かう矢印も、


同じように設定しますが、こちらでは両方trueに設定してください。


そしてオン、オフのステートからExitへ向かう矢印ですが、


こちらでは両方ともCan_Contactにfalseを設定します。


そしてまたオンのステートをクリックしてインスペクターに戻りますが、ここでAdd Behaviourをクリックします。
この中から、VRCAvatarParameterDriverをクリックして追加します。

このようなものが追加されます。これはステートを通った時に、任意のパラメータの値を変更してくれるスクリプトです。
今回はこれを使って、アイテムのオンオフを制御します。

Add Parameterを押して項目を追加、NameのプルダウンからONOFFするパラメータを選択、Change TypeはSetに、ValueとLocal Onlyにチェックを入れます。
これでこのステートを通った時に、Can_ONOFFというパラメータがオンになります。

同じようにオフにするステートにもVRCAvatarParameterDriverを追加して設定します。こちらではValueのチェックは外してください。
これで、このステートを通った時はCan_ONOFFがオフになります。

次にまた新しくレイヤーを作成します。このレイヤーでは、アイテムをオンオフを制御します。

名前は自由に。Weightはもちろん1にします。

さてこのレイヤーでステートを二つ作ります。

名前を分かりやすくして、WriteDefaultsを外し、相互に矢印を引きます。
今回は初期状態をアイテムオフの状態にしたいので、オレンジ色のステートにはOFFのアニメーションを入れます。


オレンジ色のステートに先ほど作成したアニメーションをMotion欄に入れます。ここではCan_OFFアニメーションをOFFステートに。

ONステートにはCan_ONアニメーションをドラッグアンドドロップでセットしました。


次にOFFからONに向かう矢印ですが、Has Exit Timeのチェックを外し、Transition Durationを0にし、ConditionsにはCan_ONOFFとtrueを入れます。
これでCan_ONOFFパラメータが1になるとここを通過するようになります。


逆の矢印には、falseを入れておきます。これで0になったときにアイテムがオフになります。
Expression Parametersへの登録

最後にExParametersにパラメータを登録して、グローバルに反映されるようにします。
アバターを選択して、VRC Avatar DescriptorからParametersを参照しておきます。

参照したものをクリックするとインスペクターにこのような項目が並びます。

Addを押して、アイテムをオンオフするパラメータの名前(一字も間違えないように!)を入れ、Boolに設定します。
これでグローバルに同期されるようになりました!

最後にアイテムのチェックを外して非表示にして設定は完了です!
アップロードしてチェックしてみましょう。

左手で腰に触れると、アイテムが出現しました!
まとめと細かい説明(あとSenderも)
今回はContactsを使ってアイテムを出し入れする仕組みを作りました。
Contactsは自分または相手から触られたことが分かるので、それをトリガーに自分好みのメニューを作ってみたり、はたまた魔法のように手を動かしてパーティクルを発動させてみたりと様々なことができます。
さて、今回はレイヤーとパラメータを二つずつ使ってアイテムのオンオフを制御したわけですが、その理由を簡単に説明します。

Contact Receiverからは一瞬しかパラメータが1になりません。(On Enterの場合)
ステートにあるアニメーションは、一瞬しか通らなくても問題なく再生されます。
ですが、これだと後からjoinした人や、アニメーションが発火した後にアバターを読み込み直した人などに同期されません。
それは、Contact Receiverからの信号は、触れないと1にならないからです。つまりそれは常時0なのであって、アニメーションは1にならないと発火しませんよね。
これでは同期されませんし、boolだった場合オンオフが人によってひっくり返ることもあります。これは困った。
ではどう解決するか。一瞬しか1にならないなら、ずっと1になるパラメータを作って、そのパラメータを書き換えるしかありません。
レイヤーを作って矢印の設定をしたとき、TrueとFalse、TrueとTrueの設定をしたのを覚えているでしょうか。
あれは「Contactsから信号が来て(True)、アイテムがオフ(False)になっていたらオンにする(DriverでパラメータをTrueに)」、「Contactsから信号が来て(True)、アイテムがオン(True)になっていたらオフにする(DriverでパラメータをFalseに)」という設定だったわけです。
こうすることで、アイテムがオンならずっとTrue、オフならずっとFalseになり、このパラメータをExpressionParametersに登録してグローバルに同期させれば、みんなと見え方が一緒になるという寸法です。
Contact Senderの説明
今回は使用しなかったので説明を省きましたが、ざっと軽く説明しておきますね。
Contact Senderは、触る触られるの「触る」方です。

Root Transform
Shape Type
Radius
Position
Rotation
以上はContact Receiverと一緒で、オブジェクト、接触判定の形と大きさ、場所と回転を設定します。
Collision Tags
違うのはこのCollision Tagsです。

Receiverと似た……どころか全く同じUIですが、設定内容は全然違います。ここに入力されたものは、「タグ」として扱われます。
Receiverで「左手で触るならHandLを登録する」と説明しましたよね。あれは左手にあるSenderに「HandL」というタグが付いているためです。
つまりこのSenderで言うCollision Tagsは「自分はどこにあるSenderです」という自己紹介のようなものです。
そして、このSenderのタグとReceiverのタグ、両方が一致しないと接触したという判定になりません。
つまりSenderにHandL、ReceiverにHandLと、両方に同じタグがないと触っても反応しないんです。
Receiverと違うのはこの点で、Receiverは「接触したら判定してほしい場所」を選びましたが、Senderでは「自分は○○という名前です、Receiverに名前があったら接触判定に入ります」のような感じ。
相互に関係性があるのがこのCollision Tagsという項目になります。
最後に
今回は実際の実装に沿ってAvatar DymamicsのContactsについて説明しました。触ってアニメーションを発動できるので、発想次第でいろんなことができそうですね。 PhysBonesやOSCも併用してもっと面白いものも作れるかもしれません……!
ぜひ使ってみてくださいね!