
割れるサングラスギミック(Contactを使ったギミック)の解説
はじめに
皆様こんにちは。
今回は私がVRChat向けにBoothに出している、割れるサングラスギミックについての解説になります。
単純なギミックのオンオフ、当たり判定の設定、メッシュとマテリアル1つでどのようにサングラスが割れた表現を作っているか、を解説していきます。
よろしければお付き合い下さい。
前提
この記事を読んで頂くにあたって、以下の項目が少し分かることが望ましいです。
Unityでアバター改変ができる
ONOFFのギミックを作ったことがある
AnimatorとAnimationの中身がわかる
1.構成と機能の紹介
1-1.構成
早速ですが、割れるサングラスギミックの構成、UnityでHierarchyに表示される部分からです。
まずは下のスクリーンショットを御覧ください。

文字で説明すると次のようになります。
0をルート階層の最上位、1以降はその下、として見て下さい。
枝番は、同一の階層に複数あるオブジェクトに便宜的に番号を振ったものです。
0.割れるサングラス:ルートオブジェクト
1-1.丸サングラス:ボーンのルート(アーマチュア)
1-2.丸サングラスメッシュ:サングラスのメッシュ、見た目の部分
1-3.Break_Sound:割れたときの効果音、AudioSource
2.丸サングラスボーン:ボーン、丸サングラスメッシュのウェイトはここ
3-1.Sunglass_Break_Recive:サングラスが割れるかどうかの当たり判定
3-2.Sunglass_Break_Recive2:上記と同じ当たり判定だが対象が違う(後述)
3-3.Sunglass_Repair_Recive:このギミックを着けている自身が触れた場合に割れた状態を戻すための当たり判定
以上の構成です。
結構単純にできているのが分かって頂けると思います。
1-2.機能
まずギミックとしての機能ですが、次の通りとなっています。
自分以外のプレイヤーの手または足が、サングラス付近の当たり判定に触れた時に割れる。
自分がサングラス付近の当たり判定に手で触れた場合、割れていればその状態を戻す。
割れる際の当たり判定はExpressionMenuより頭を追加できる。
割れた状態はExpressionMenuからでも戻せる。
ExpressionMenuから当たり判定を無効化して割れない状態にできる。
ExpressionMenuよりサングラスをオン・オフできる。
以上の6つです。
ExpressionMenuでどのように表示されるか見てみましょう。

文字で説明すると次の通りです。
Sunglass_NotBreak:サングラスの当たり判定を無効化
Sunglass_OFF:サングラスを消す
Head_Hit:当たり判定に頭を追加
BreakRepair:割れた状態を戻す
このようになっています。
では、次の項目で各機能がどのように実装されているのか解説します。
2.機能の実装方法と解説
ここでは、ここまで紹介した各機能がどのように実装されているのかを解説します。
主な内容としてはAnimator、Animationとその関連になります。
前提として、割れるサングラスギミックはModularAvatar設定済みです。
なので、導入頂くユーザーの方は位置・サイズ合わせをしてアバタールートに本ギミックのプレハブを入れて頂くだけです。
2-1.Animator(FXレイヤー)の構成とパラメーター
メインとなるAnimator(FXレイヤー)とそこで使われているパラメーターについての解説です。
ある程度ギミックを触った人なら、この辺まで見ればどうやって作られてるか分かるかもしれません。
早速スクリーンショットから御覧ください。


大分シンプルです。
レイヤーはこの後1つずつ解説するので、パラメーターを頭に入れつつこの先を読んで頂ければと思います。
2-2.レイヤー1:Sunglass_Int
1番上のレイヤー、Sunglass_Intについてです。
例によってまずはスクリーンショットです。

このレイヤーではサングラスが割れた状態か否かを管理しています。名前を直して無くて恥ずかしいのですが、
上から順に、
NewState:サングラスが割れていない状態
Sunglass_Break:サングラスが割れている状態
となっています。
次から遷移条件と中身を見てみましょう。


遷移条件はSunglass_Intというパラメーターが0の時、
Sunglass_Break_OFF2というAnimationを再生する。
という中身です。
では、再生されるAnimation、Sunglass_Break_OFF2の中を見てみます。

上から次のようになっています。
()内は簡単な解説、頭の番号は解説のために便宜的に振ったものです。
Break_Sound:Is Active(オブジェクトBreak_Soundの表示・非表示)
Is Active・・・0(Break_Soundというオブジェクト自体を非表示)
Is Active・・・0(Break_Sound内のAudioSourceを非表示)
丸サングラス_メッシュ:Material._Dissolve Parameter(丸サングラスメッシュに使用しているliltoonマテリアルのDissolveパラメーターをどうするか)
Material._Dissolve Parameter.x・・・0(各数字は有効・無効、またはシェーダーの設定の数値)
Material._Dissolve Parameter.y・・・0
Material._Dissolve Parameter.z・・・0.23
Material._Dissolve Parameter.w・・・0.273
続けて、割れている状態であるSunglass_Breakへの遷移条件と中身を見てみましょう。


遷移条件はSunglass_Intというパラメーターが1の時、
Sunglass_BreakというAnimationを再生する。
という中身です。
では、再生されるAnimation、Sunglass_Breakの中を見てみます。

これも上から次のようになっています。
()内は簡単な解説、頭の番号は解説のために便宜的に振ったものです。
Break_Sound:Is Active(オブジェクトBreak_Soundの表示・非表示)
Is Active・・・1(Break_Soundというオブジェクトを表示)
Is Active・・・1(Break_Sound内のAudioSourceを表示)
丸サングラス_メッシュ:Material._Dissolve Parameter(丸サングラスメッシュに使用しているliltoonマテリアルのDissolveパラメーターをどうするか)
Material._Dissolve Parameter.x・・・1(各数字は有効・無効、またはシェーダーの設定の数値)
Material._Dissolve Parameter.y・・・1
Material._Dissolve Parameter.z・・・0.23
Material._Dissolve Parameter.w・・・0.273
ここまでで1つめのレイヤーSunglass_Intの中身が出揃いました。
一旦まとめましょう。
DefaultStateはサングラスが割れていない状態。
遷移条件はSunglass_Intというパラメーターが0のとき。
サングラスが割れた状態はSunglass_Intというパラメーターが1のとき。
それぞれの遷移先ではAnimationを設定している。
と、大まかに4つにまとめられます。
ここまで詳しく分からなくても大丈夫です、一旦まとめた先の4つを流れとして頭に留めておいて下さい。
2-3.レイヤー2:Sunglass_Bool
このレイヤーでは当たり判定を受けてどのような状態に遷移するか、という内容を定義しています。
上から順に、

NewState:当たり判定が動作していない
Sunglass_Break:割るための当たり判定(Sunglass_Break_Recive)が動作している
Sunglass_Repair:割れたのを直すための当たり判定(Sunglass_Repair_Recive)が動作している。
となっています。
次から遷移条件と中身を見てみましょう。
NewState、いずれの当たり判定も動作していない状態の解説です。


遷移条件はSunglass_TrigとSunglass_Repairというパラメーターが両方ともfalseのとき。
条件を満たしたらEmptyというAnimationを再生する。
というものです。続けてAnimationの中を見てみましょう。

はい、ご覧の通り何もありません。
当たり判定が動作していないときは特に何か変化をする必要が無いので、このようになっています。
では、そもそもEmptyという空のAnimation自体不要では?と思われるかもしれません。
うろ覚えですが、Unityの仕様上、遷移したStateで何のAnimation(Motion)も無いのはエラーになることへの対策です。(もしかしたらVRChatの仕様だったかも…?)
では次に、割るための当たり判定が動作した状態、Sunglass_Breakへの遷移条件から見ていきます。


遷移条件はSunglass_Trigというパラメーターがtrueのとき。
条件を満たしたらEmptyというAnimationを再生する。
と、ここまでは先程のNewState、いずれの当たり判定も動作していない状態と一緒です。
加えて、
VRC Avatar Parameter Driverというコンポーネントを使用して、Sunglass_Intというパラメーターに1という数字を渡す。
となっています。
ではレイヤー2の最後のState、Sunglass_Repairです。


遷移条件はSunglass_Trigというパラメーターがtrueのとき。
条件を満たしたらEmptyというAnimationを再生する。
VRC Avatar Parameter Driverというコンポーネントを使用して、Sunglass_Intというパラメーターに0という数字を渡す。
となっています。
Sunglass_Breakとの違いは、Sunglass_Intに渡す値が0になっている、ということですね。
2つめのレイヤーSunglass_Boolの中身が出揃いました。
一旦まとめましょう。
DefaultStateであるNewStateはいずれの当たり判定も動作していない状態。
当たり判定Sunglass_Trigという値がtrue(有効)になったとき、Sunglass_Breakで設定した内容を行う。この内容はSunglass_Intという値に1を渡す。
直す判定Sunglass_Repairという値がtrue(有効)になったとき、Sunglass_Repairで設定した内容を行う。この内容はSunglass_Intという値に0を渡す。
それぞれの遷移先では空のAnimationを設定している。
以上4つがレイヤー2、Sunglass_Boolのまとめです。
レイヤー1の内容を踏まえるとお気づきの人も居るかもしれませんが、
ここまでの設定でSunglassを割る・割れたのを直す、というメイン部分は動作します。
2-4.レイヤー3:Sunglass_ONOFF
いよいよ最後のレイヤーSunglass_ONOFFです。
このレイヤーでは、サングラスの当たり判定とサングラスそのものの表示・非表示を管理しています。
ExpressionMenuから操作するのも主にこの部分です。

上から、
Sunglass_Head_Hit:割れる当たり判定に頭を追加する。
NotBreak:割れる当たり判定を無効にする。
Sunglass_OFF:サングラスを非表示にする。
Sunglass_ON:サングラスを表示する。
となっています。
DefaultStateはSunglass_ONです。
遷移条件から見ていきましょう。


遷移条件はSunglass_ONOFF_Intというパラメーターが3のとき。
Sunglass_Head_HitというAnimationを再生する。
となっています。
Sunglass_Head_HitというAnimationの中身です。

上から次のようになっています。
()内は簡単な解説、頭の番号は解説のために便宜的に振ったものです。
Sunglass_Break_Recive:Game Object.Is Active・・・0(Sunglass_Break_Reciveという割れる当たり判定の有効・無効)
Sunglass_Break_Recive2:Game Object.Is Active・・・1(Sunglass_Break_Recive2という割れる当たり判定の有効・無効)
Sunglass_Break_Recive2:VRC Contact Receiver.Enabled・・・1(Sunglass_Break_Recive2というコンポーネント、割れる当たり判定の有効・無効、2の項目と機能としては重複してます…)
Sunglass_Repair_Recive:Game Object.Is Active・・・1(Sunglass_Repair_Reciveという割れたのを直す当たり判定の有効・無効)
丸サングラス_メッシュ:Game Object.Is Active・・・1(サングラスそのものの表示・非表示)
2つ目、NotBreakへの遷移条件と内容を見ていきます。


遷移条件はSunglass_ONOFF_Intというパラメーターが2のとき。
NotBreakというAnimationを再生する。
となっています。
NotBreakというAnimationの中身です。

上から次のようになっています。
()内は簡単な解説、頭の番号は解説のために便宜的に振ったものです。
Sunglass_Break_Recive:Game Object.Is Active・・・0(Sunglass_Break_Reciveという割れる当たり判定の有効・無効)
Sunglass_Break_Recive:VRC Contact Receiver.Enabled・・・0(Sunglass_Break_Reciveというコンポーネント、割れる当たり判定の有効・無効、1の項目と機能としては重複してます…)
Sunglass_Break_Recive2:Game Object.Is Active・・・0(Sunglass_Break_Recive2という割れる当たり判定の有効・無効)
Sunglass_Break_Recive2:VRC Contact Receiver.Enabled・・・0(Sunglass_Break_Recive2というコンポーネント、割れる当たり判定の有効・無効、3の項目と機能としては重複してます…)
Sunglass_Repair_Recive:Game Object.Is Active・・・0(Sunglass_Repair_Reciveという割れたのを直す当たり判定の有効・無効)
3つ目、Sunglass_OFFへの遷移条件と内容です。


遷移条件はSunglass_ONOFF_Intというパラメーターが1のとき。
Sunglass_OFFというAnimationを再生する。
となっています。
Sunglass_OFFというAnimationの中身です。

上から次のようになっています。
()内は簡単な解説、頭の番号は解説のために便宜的に振ったものです。
Sunglass_Break_Recive:Game Object.Is Active・・・0(Sunglass_Break_Reciveという割れる当たり判定の有効・無効)
Sunglass_Break_Recive2:Game Object.Is Active・・・0(Sunglass_Break_Recive2という割れる当たり判定の有効・無効)
Sunglass_Repair_Recive:Game Object.Is Active・・・0(Sunglass_Repair_Reciveという割れたのを直す当たり判定の有効・無効)
丸サングラス_メッシュ:Game Object.Is Active・・・0(サングラスそのものの表示・非表示)
では最後、Sunglass_ONの遷移条件から見ていきます。


遷移条件はSunglass_ONOFF_Intというパラメーターが0のとき。
Sunglass_ONというAnimationを再生する。
となっています。
Sunglass_ONというAnimationの中身です。

上から次のようになっています。
()内は簡単な解説、頭の番号は解説のために便宜的に振ったものです。
Sunglass_Break_Recive:Game Object.Is Active・・・1(Sunglass_Break_Reciveという割れる当たり判定の有効・無効)
Sunglass_Break_Recive:VRC Contact Receiver.Enabled・・・1(Sunglass_Break_Reciveというコンポーネント、割れる当たり判定の有効・無効、1の項目と機能としては重複してます…)
Sunglass_Break_Recive2:Game Object.Is Active・・・0(Sunglass_Break_Recive2という割れる当たり判定の有効・無効)
Sunglass_Break_Recive2:VRC Contact Receiver.Enabled・・・0(Sunglass_Break_Recive2というコンポーネント、割れる当たり判定の有効・無効、3の項目と機能としては重複してます…)
Sunglass_Repair_Recive:Game Object.Is Active・・・1(Sunglass_Repair_Reciveという割れたのを直す当たり判定の有効・無効)
丸サングラス_メッシュ:Game Object.Is Active・・・1(サングラスそのものの表示・非表示)
ここまでで3つめのレイヤーSunglass_ONOFFの中身が出揃いました。
一旦まとめましょう。
DefaultStateはサングラスが表示されていて割れる状態。
遷移条件にはSunglass_ONOFF_Intというパラメーターを使っている。
それぞれの遷移先ではAnimationを設定し、Animationによってサングラスの表示・非表示、当たり判定の有効・無効を変化させている。
と、大まかに3つにまとめられます。
ここまででレイヤー3つの内容が出揃いましたので、各レイヤーの条件等を踏まえてまとめましょう。
2-5.Animatorの内容まとめ
サングラスが割れる当たり判定はレイヤー2:Sunglass_Boolで管理している。
当たり判定の条件を満たした場合、レイヤー2:Sunglass_Bool内のSunglass_Breakへと遷移し、Sunglass_Intパラメーターに1という値を渡す。
レイヤー1:Sunglass_Intではサングラスが割れた状態か否かを管理している。
レイヤー1:Sunglass_IntではSunglass_Intというパラメーターを使用しており、当該パラメーターの値が1になるとサングラスが割れるAnimationを再生する。
レイヤー3:Sunglass_ONOFFではサングラスの表示・非表示や当たり判定の変更を管理している。
と、以上5点になります。
ここまで長々書いた割に簡単ですね。
ここまで読んで頂けたなら、各機能がどのように管理され遷移するか、までは何となく分かって貰えるかと思います。
次からは、当たり判定とExpressionMenuの内容です。
記事が長くなって恐縮ですが、今暫くお付き合い下さい。
3.VRC Contactを使った当たり判定
この項目では、前項で出てきたパラメーターと当たり判定がどのように設定されているかを解説します。

上記スクリーンショットのサングラスの前にある、青いカプセル状のモノが当たり判定です。
2つ重なっていますが、大きい方が割れたのを直す当たり判定、小さいほうが割れる当たり判定です。
3-1.割れる当たり判定(VRC Contact Receiver)
割れる方の当たり判定から見ていきましょう。



ここまでが当たり判定の設定項目です。
SceneとHierarchyは大体見たままなので、Inspectorの各項目について解説していきます。
とは言いつつも、公式の資料を見ましょう。
https://creators.vrchat.com/avatars/avatar-dynamics/contacts/
見たり触ったりすればすぐ分かりそうな事はざっくり書いていきます。
Root Transform:文字通り、今回は不使用。
Shape
Shape Type:Sphere=球、Capsule=カプセルから形を選べます。今回はカプセルを選択。
Radius:幅、横方向の大きさ
Height:高さ、縦方向の大きさ
Position:そのまま
Rotation:そのまま
Filtering
Allow Self:自分でこの当たり判定に触れるか。自分で触れた時に割れて欲しくないので、今回はチェックを外します。
Allow Other:自分以外がこの当たり判定に触れるか。他の人が触れたときに割れてほしいので、ここはチェックを入れます。
Local Only:ローカル動作とするか。VRChatにおけるローカルとリモートは非常にややこしいですが、大概ローカルでいいです。今回はチェックを入れます。
Collision Tags:この当たり判定に対して当たり判定をもつモノ一覧
Add:当たり判定を追加
Up:選択した当たり判定を一覧の中で上にする
Down:選択した当たり判定を一覧の中で下にする
Delete:選択した当たり判定を一覧から削除する
Hand R:右手(Hand L、Finger、Footは省略)
Receiver
Receiver Type:今回はConstant。Constantは当たり判定が作動してる時に1またはTrueを返し、作動してないときは0またはFalseとなります。
Parameter:この当たり判定はどのParameterに値を渡すか。
こんな感じです。
続けて割れる方の当たり判定2ですが…Collision TagsにHead(頭)の判定が増えるだけですので省略します。
3-2.割れたのを直す当たり判定
サングラスが割れた状態を直す当たり判定です。
これも大きくは変わらないので、違うところだけ取り上げます。


以下が違うところです。
Shape
Height:割れるサングラスより縦に大きく。これは自分が触って直すという動きの都合上、やや大きくしています。
Filtering
Allow Self:割れた状態で自分が触ると割れているのが直る、という動きをしたいので、今回はチェックをいれます。
Allow Other:割れたのを直すのは自分だけなので、ここではチェックを外します。
Receiver
Parameter:割るときの判定はSunglass_Trigだったので、別のパラメーターSunglass_Repairを指定します。
3-3.当たり判定についてのまとめ
では、当たり判定の設定と画面上でどうなっているかを解説したので、ここでまとめです。
当たり判定は割るためのモノ、割れたのを直すためのモノの2種類を設定している。
各判定はどのParameterに値を渡すか設定しておく必要がある。
大事なのはこの2つです。
特にParameterは文字で入力するタイプの設定なので、誤字脱字で動作しないなんてこともあります。
自分が組んだAnimatorのParameterと相違がないことを確認しましょう。
Contactの作動状況によって、設定したParameterにtrue、falseを渡すことでAnimatorで設定した条件に一致した場合、設定した内容が動作する、というモノです。
4.ExpressionMenuの内容と解説
この項目では、ExpressionMenuの内容について主に解説していきます。
冒頭でもお見せしたMenuをもう1度見てみましょう。

見た目上はこのようになっていますが、その中身がどうなっているか、というところです。

最初のスクリーンショットと同じように4つの項目が並んでいます。
1つずつ解説…はしないですが、設定と解説を簡単に書き出します。
左から、メニューに表示される項目、対応するParameter、項目のタイプです。
Sunglass_NotBreak:Sunglass_ONOFF_Int:Toggle
Sunglass_OFF:Sunglass_ONOFF_Int:Toggle
Head_Hit:Sunglass_ONOFF_Int:Toggle
BreakRepair:Sunglass_Repair:Button
Toggleは1回倒したら(押したら)戻すまで倒れたまま。
Buttonは1回倒したら勝手に戻る。
という感じです。
次に各項目が対応するParameterにどの値を渡すかの設定です。
が、MA設定済みなので、MA Menu Itemの設定画面です。
先程のExpressionMenuの内容を頭の片隅に浮かべながら読み進めて下さい。

Sunglass_NotBreak
パラメーター値:2
Sunglass_OFF
パラメーター値:1
Head_Hit
パラメーター値:3
BreakRepair
パラメーター値:1
となっています。
なので、ExpressionMenu上の各項目を作動させると、対応するParameterにパラメーター値で設定した値を渡す、という内容です。
この値を各Parameterに渡すことでAnimatorで設定した条件に一致した場合、設定した内容が動作する、というモノです。
5.サングラスが割れたときのヒビ割れ表現
本項では、サングラスが割れたときのヒビ割れをどのように表現しているかについて解説します。
割れた状態は以下のようになります。

この状態はメッシュを差替える訳ではなく、シェーダーに使用しているliltoonの設定を変更して表現しています。
設定はDissolveを使用しており、その内容は次の通りです。

設定はご覧の通りですが、数値以外の設定としてマスク画像を設定しています。
このマスク画像がヒビ割れの形を表現しています。
レイヤー1の解説で出てきたAnimationの中で、この項目を一部変更している、というのがヒビ割れのON・OFFの中身です。
6.1~5までのおさらい
さて、非常に長くなってしまいましたが全体をおさらいしていきます。
ここまで読んで頂いたら繋がりが見えている人も居るかと思いますが、もう少しお付き合い下さい。
割れるサングラスギミックは次の要素で構成されています。
Animator(FXレイヤー)とそれに付随するParameter、Animation(ヒビ割れと効果音を含む)
上記を動かす引金としてのContact
Contact以外で管理したい項目を動かすExpressionMenu
と、簡単にまとめると3つです。
詳細はこれまでの項目を改めて見て頂ければと思います。
7.なぜVRC Avatar Parameter Driverを使うのか
割と肝心な説明が抜けていました。
途中から出てきたVRC Avatar Parameter Driverについてです。
VRC Contactというのは指定したParameterに値を渡すことができる。
と記載しました。
であるなら、そのParameterの値をそのまま使って何らかの状態遷移と、見た目の変化を起こしても良いのでは?と思った方も居るかと思います。
実際に、そういったギミックもあります。
顔や特定部位に自分以外の人が触れた時に、表情が変わるアバターギミックなどがそうです。
しかし、今回解説している割れるサングラスギミックは、当たり判定に特定部位が触れたら割れる、というギミックです。
触れている間だけ変化するのではなく、触れた結果を保持して欲しい、となります。
文字でわかりにくいですが、当たり判定と直接連動したParameterで変化する場合を1、VRC Avatar Parameter Driverを間に入れて連動させたParameterにより変化した場合を2、として以下にその流れを例示します。
当たり判定に特定部位が触れる→指定したParameter AがTrueになる→Animator内でAがTrueになることを条件にした遷移をする→遷移先のAnimation等を実行する(サングラスが割れる)→当たり判定から特定部位が離れる→指定したParameter AがFalseになる→Animatorの遷移条件を満たさなくなり割れた状態が戻る。
当たり判定に特定部位が触れる→指定したParameter AがTrueになる→Animator内でAがTrueになることを条件にした遷移をする→遷移先のAnimationは空で見た目の変化は無い、VRC Avatar Parameter DriverでBへTrueを渡す→BがTrueになることを条件にした遷移遷移をする→遷移先のAnimation等を実行する(サングラスが割れる)→当たり判定から特定部位が離れる→指定したParameter AがFalseになる→サングラスが割れる遷移条件にはBが使われているので変化はない。
となります。
実際は自分と自分以外、その処理が走った時に居た人、居なかった人など差で同期処理の問題とかあるんですが、この流れが主にVRC Avatar Parameter Driverを使う理由です。
おわりに
非常に長くなってしまいました。
長くなった割に内容がイマイチ纏まっていないというか、繋がっていない気もしますが…
こんな内容が欲しい、ここが分からない、というところがありましたら指摘等頂けると助かります。
ここまでお読み頂きありがとうございました。