
ExpressionsMenuの実装について(MA無し)
※コンテンツ部分はすべて無料で最後まで読めます。
※この記事は前編です。内容がMA無しの実装に大きく偏っています。今風の実装について関心がある方は後編の「ExpressionsMenuの実装について(MA有り) 」をご覧ください。

はじめに:ExMenuについて
皆さんVRChatやっていますか?
YボタンまたはRキーで出すことができる上の円形のメニュー。誰もが使ったことがあると思いますが、今回はそのうちのExpressionsの項目配下についての話です。
このメニュー、名称がいろいろあるのですが(アクションメニュー、ラジアルメニュー等)、今回は単にメニューと表記した場合は上のような円形のメニューを指すものとします。
メニューはいくつかの項目を持ちます。項目は単にクリック式のボタンであったり、トグル式であったり、サブメニューだったりします。
Expressionsの項目配下のメニューをここではExメニューと表記します。
Exメニューはデフォルトではここからダンスエモートなどを選択してコミュニケーションに使えるのですが、このExメニューはアバターごとに項目を改変・設定できます。

1メニューにつき項目の個数は(BackやQuickActions等の自動挿入される項目は含まずに)8個までという制限はありますが、サブメニューで入れ子にして階層を深くしていけばかなりの数の設定項目を用意することができます。boothで販売されている有料アバターは、おおよそコスチュームのオンオフやアバター特有の機能を制御するためにこのExメニューを利用している場合が多いです。ユーザーからの入力を受け付けて、望む挙動を実装しているわけですね。
概要
"ユーザーからの入力を受け付けて、望む挙動を実装している" と書きましたが、では実際どのような仕組みで動いているのでしょうか?
これにはベースはunityのアニメーションシステムが利用されていますが、VRC特有の仕様との兼ね合いでかなり魔改造されており、影響を及ぼす要素が広範囲に散らばっています。
ユーザーのExメニュー項目からの入力から、実際にアバターの挙動に反映されるまでのシーケンス図は以下のようになります。(後にunity上で追いかけ方を解説しますのでこの図の見方が分からなくても大丈夫です。)

登場人物を紹介します。
User
VRC内でそのアバターを操作して使うユーザー(プレイヤー)です。
多くの場合はアバター制作者とはイコールではなく、アバター改変者とはイコールなはずです。
あなたです。
ExMenu
冒頭で用語の定義をしたExメニューです。unity上では VRC Expressions Menu という形式のファイルに実体が記録されています。ユーザーから直接見えるメニュー構造を取り扱い、表現します。
Parameter
パラメータと書くと汎用的ですがここではunity上の VRC Expression Parameters ファイルのことを指します。
内部でAnimatorへのやりとりのための変数等を定義する役割となります。
変数の型等の情報も含めて、使用する変数の一覧が定義されますが、実際にどう使われるかの動作部分は前後レイヤー(ExMenu, Animator)によって決まります。
Animator Controller
正式にはAnimator Controllerですが長いのでAnimatorと表記します。
上記パラメータの変化を監視し、値が変動した場合に定められた状態へ遷移するディレクションの部分を担当します。
状態(state)と、トランジション(transition)で構成されるステートマシンで表現されるのが特徴です。
実体としては Playable Layers にある、各種レイヤーファイルで表されます。この記事内ではFXレイヤーのみ扱います。
Animation
アニメーションファイルです。
アニメーションはAnimator内のある状態(ステート)に紐づき、その状態に遷移してきたときに特定の動作をします。
動作は各GameObject(アバター)に紐づき、それらのどのオブジェクトを操作するかを示すキーと、その変化量であるバリューを持ちます。
キーとバリューは一対一ですが、一つのアニメーションファイル内で複数のキーバリューペアを含むことは可能です。
時間的に複雑な動作(例:ダンスモーションなど)も記録できますが、この記事内では「オブジェクトの表示をオフにする」等の単発かつ簡単なもののみ扱います。

ExMenu, Parameter, Animatorが設定されている

中身には「BagオブジェクトのIs_Activeを0にする」という動作が記録されている。(左下)
詳細な設定の確認方法
ここでは「シアンちゃんの肩掛けバッグをオンオフするメニュー項目」に着目してその動作がどのように実現されているのか追いかけてみます。
お手元のシアンちゃんをunity上で開いてください。シアンちゃんがいない?では買いましょう(手持ちの他のアバターでも全然問題ありません。ただし項目名等は適宜読み替えてください。)
ExMenu

着目するメニュー項目はExMenu->Costume->Bag ON/OFFになります。Bag ON/OFF配下にはONとOFFの(トグル)ボタンがあり、OFFのボタンを押すとバッグが非表示になり、ONのボタンを押すとバッグが表示状態になります。


ではこの動作がどのように実装されているか見てみましょう。
アバタールートのインスペクターから各種ExMenu, Parameter, Animatorをクリックすることで実体ファイルの場所を参照できます。
そのままファイルを選択すると中身の情報がインスペクター(Animatorは専用のタブ)で表示されます。ExMenuから見ていきましょう。


インスペクターに内容が表示されたかと思います。Controlsにあるのが各項目となります。ここはルートなのでサブメニュー項目が並んでいますね。円形メニューを操作したときと同じ項目であることが分かると思います。Costumeを選択し、サブメニューの実体へ深堀りしていきましょう。

Bag ON/OFFがありました。選択してサブメニューをさらに深堀りします。

Controlsを見るとONとOFFがあり、それぞれはToggleであることが分かります。
注目すべきはParameterとValueで、「 Bag_OFF という名前のパラメータをtrue(チェックボックスにチェックが入っている)にする」ということが表現されています。
(余談:OFFなのにtrue?→一つのbool型変数、たとえばBag_Visibleとだけ定義して、0ならばオフ、1ならばオンのように定めても良いかと思われますが複雑なことをしようとすると2値では都合が悪い場面が出てきます ※don't careを表現できない。
では3値にするためにint型の変数一つでもいいじゃないかと思われるかもしれないですが、int型の変数一つとbool型の変数二つだと後者の方が容量が小さく済みます。そういう面もあり、シアンちゃんはbool型の変数二つで制御する設計になっているのだと思います。)
Parameter
では次にParameterを見てみましょう。上記のExMenu画像上部の場所から、またはアバタールートからExpressions->Parametersで参照できます。

Parameterにはアバター内で使うすべてのパラメータが一覧で定義されています。さきほど確認したBag_OFFもちゃんとここにいることが分かりますね。
パラメータ単体では特に動作の定義は含まれていないのでここで見るものはこれ以上はあまりないですね。次に進みましょう。
Animator
Animatorの内容を確認するためには、アバタールートからPlayeble Layers->Base->FXに割り当てられているファイルをクリックします。(Playeble Layersが畳まれている場合は展開してください。)

するとアニメータータブが別タブで開きます。

ここで着目すべきはまずアニメータータブ内の、パラメータータブです。選択して開きます。

ここを見ると先程Parameterでも見た Bag_OFF が存在します。ここのパラメータータブと、Parameterで見た一覧は連動していませんが、正常に動作させるためには基本的に名前を一致させる必要があります。
購入したアバターならば特にいじる必要はないですが、自分で編集する場合は気をつける必要があります。

アニメーターのレイヤータブに戻り、Bagオンオフについて扱っているレイヤーを特定します。Bag_ON/OFFレイヤーを選択すると、対応するステートマシンが表示されます。
ステートマシンの見方ですが、Entryから始まり、オレンジ色のデフォルトステートに辿り着きます。今回の例ではEmptyステートに到達し、インスペクターを見ると割り当てられたアニメーションはEmptyです。Emptyは何もしないアニメーションなので、アバターロード時は(Bagについては)何もしない状態であるということを意味しています。

次にEmptyステートから伸びているトランジション(矢印)をクリックするとインスペクターに内容が表示されます。
Conditionsの内容が満たされた場合、この矢印に従ってステートが遷移します。Conditionsを見るとパラメータタブで見た Bag_OFF が true であるならば遷移すると設定されています。(AND条件ですがFXについては今は無視してください。)
Animation

矢印に従ってBag_OFFステートに到達したときを考えます。Bag_OFFステートに割り当てられたアニメーションはBag_OFFです。ステート名と同じで紛らわしいですがこちらはアニメーションファイルです。Bag_OFFアニメーションファイルを参照し、中身を確認してみましょう。

Bag_OFFアニメーションの中身には「BagオブジェクトのIs_Activeを0にする」という動作が記録されています。(画像左下)
Bag_OFFステートに到達時にこの動作が発火(実行)します。

「BagオブジェクトのIs_Active」がどこを指すかというと、インスペクター上で言う上部の表示部分のチェックボックスを指します。(画像右上)
ここが0になる=チェックが外れるということは非表示になることを意味します。
よって結果としてBagオブジェクトが非表示になります。
以上が詳細な動作の追いかけ方の例となります。余力があればバッグを再表示させるBag_ONについて追いかけてみてください。また、他のパーツや表情の制御についても同様に解析することが可能となったはずです。アバターの機能がどのように実装されているか仕組みを見てみると勉強になるかと思います。
ExMenu追加の手順(MA無し)
まだ終わりません。
というかこの記事で書きたいことをまだ一つも書いていません。今まで説明した仕組みを前提としてここからさらに発展した内容を書きます。(unityのアニメーションシステムが複雑すぎる。)
さて、今までの説明でアバターの既存の仕組みについてどのように機能が実装されているかを調べることができるようになりました。
では新しく、自分で独自の機能を組み込んでみましょう。このときの手順は以下になります。
Animationファイルを作る
Animatorにパラメータを追加する
Animatorにレイヤーを追加してステートマシンを作る
Parameterにパラメータを追加する
ExMenuに項目を追加する
動作確認
独自の機能の追加例として、「シアンちゃんのスマートウォッチの表示をオンオフするメニュー」を実装する方法を見ていきます。
Animationファイルを作る


ではまず「スマートウォッチの表示をオンオフする操作」を表すアニメーションファイルを作ります。
Bagの実装を見たときと同じように、「スマートウォッチを非表示(オフ)する操作」を表すアニメーションと「スマートウォッチを表示(オン)する操作」を表すアニメーションの、二つのアニメーションファイルを作ります。
Bagの実装を確認したときに "「BagオブジェクトのIs_Active」は表示部分のチェックボックスを指す。ここが0になる=チェックが外れるということは非表示になることを意味する。" と書きましたがこのIs_Activeのような内部名と、どの操作が対応するかは覚える必要はありません。
というのもアニメーションファイル作成時に、unity上の実際の操作を記録(録画のようにレコーディングできる)することができるので、unity上でどう動かせば良いかだけ知っていれば良いからです。(今回は同じく表示のオンオフなので結局Is_Activeになりますが。)
アニメーションファイルの作成方法はクセがあり、アバタールートのAnimatorに対象アニメーターが設定されていないとアニメーションを作ることができません。ただしここにそのままFXレイヤーのAnimatorを入れてしまうと復元が面倒です。
よってヒエラルキー上でアバターを丸ごとコピーし、コピーした方で作業をしてアニメーションファイルを作り、その後コピーは丸ごと削除し、本来のアバターでアニメーションファイルだけ使う。という手順をとります。

AnimatorにFXレイヤーのAnimatorを挿入

設定したコントローラーを選択し、アニメーションタブを開きます。左上のプルダウンからコントローラーに含まれるアニメーションの一覧を開き、一番下の新しいクリップ作成を選択します。
選択すると.animファイルを作成するように問い合わせてきますので、名前をSmartwatch_OFF.anim にして適当な場所に保存しましょう。

再度アニメーションタブを見ると、新しく作成したばかりなので当然ながら空っぽです。ここで次のことを確認します。(1)プルダウンが新規作成したSmartwatch_OFF となっていること。(2)プロパティを追加ボタンが無効化されておらず、押せる状態になっていること。(3)左上の収録ボタンが押せる状態になっていること。
どれか一つでも満たされていない場合は「ヒエラルキー上でアバターを丸ごとコピーし~」の段落を再度読み返してきてください。
用語定義の段落でアニメーションファイルはキーとバリューを含むと言いました。ではSmartwatchオブジェクトを操作するキーを実際に追加してみましょう。
上の画像の状態で左上の収録ボタンを押してみてください。そして収録ボタンが赤くなったのを確認したら、ヒエラルキー上で、コピーした方のアバター構造を辿り、Smartwatchオブジェクトのインスペクターから表示のチェックマークを外してください。
するとアニメーションファイルにキーとバリューが追加されます。


Bagのオンオフのときに見たものとほぼ同じものができあがっていることが分かるかと思います。
Is_Activeを知らなくてもこのように操作に対応して記録してくれるので問題ないです。(知っているとプロパティを追加ボタンからも設定できる。)
記録が終わったら収録ボタンを再度押して収録モードを終わります。
同じ要領でオンにするアニメーションも作りますが、操作するキーが同一な場合に限りアニメーションファイルのコピペでいけます。
つまりAssets上(エクスプローラ上)でSmartwatch_OFFをコピペして、Smartwatch_ONにリネームします。その後、Smartwatch_ONを開きアニメーションタブでバリューを1に書き換えます。


(余談:操作するキーが同一な場合に限りについて
アニメーションタブでSmartwatch_ONを開いている上画像を見ると、プロパティを追加ボタンがグレーアウトしていて押せません。収録モードにもなれません。よってキーの追加を行うことはできません。
できることは値の変更とキーの削除のみです。何故そのような制限がかかるかは私も分からないので割愛します。)
とにかくこれで「スマートウォッチを非表示(オフ)する操作」を表すアニメーションSmartwatch_OFFと「スマートウォッチを表示(オン)する操作」を表すアニメーションSmartwatch_ONの二つのアニメーションファイルを作成することができました。
おめでとうございます。一番めんどくさい部分が終わったのであとはウィニングランです。
Animatorにパラメータを追加する
次の作業に入る前に忘れずに、「ヒエラルキー上でアバターを丸ごとコピーし~」の段落で作成したコピーしたアバターをヒエラルキー上から削除しましょう。
成果物であるAssets上(エクスプローラ上)のSmartwatch_OFFとSmartwatch_ONまで消さないように注意してください。使います。
次はAnimatorにパラメータを追加します。
(元の)アバタールートからPlayeble Layers->Base->FXに割り当てられているファイルをクリックします。


アニメータータブが開いたらパラメータータブを開き、左上のプラスマークからプルダウンを展開->bool型を選択し、名前をそれぞれSmartwatch_ONとSmartwatch_OFFとして二つパラメータを追加します。
無事にパラメータは追加できましたか?
この段落はこれで終わりです。
Animatorにレイヤーを追加してステートマシンを作る
ステートマシンを作ると聞くと大仰に聞こえますが、今回は既存のもののコピペで済みますので安心してください。

アニメータータブのレイヤータブに戻り、左上のプラスマークを押下して新しいレイヤーを作成します(一番下にできます)。
名前をSmartwatch_ON/OFFとします。
歯車マークをクリックしてウェイトを1にしてください。(超重要)
肝心のステートマシン部分ですが、Bag_ON/OFFレイヤーからコピペします。EntryとAny Stateは既にあるのでそれ以外を選択してコピペしてきます。

ステートをスマートウォッチ用に修正していきます。
やることは (1)ステート名のリネーム。(2)ステートに割り当てられているアニメーションファイルの差し替え。(3)トランジション(矢印)の条件部分にあるパラメータの差し替え。 です。
まずSmartwatch_OFF側から見ていきます。

やることに挙げたステート名のリネームとモーションの差し替えは問題ないかと思います。差し替え先のモーションは作成した「スマートウォッチを非表示(オフ)する操作」を表すアニメーションSmartwatch_OFFです。
下部のParameter Driver部分はパラメータがONとOFF両方trueになるのを防ぐためについています。OFFステートに入った場合はON側のパラメータをチェックなし=falseにするという意味です。
同様にONステート側も設定します。

トランジション(矢印)の条件部分にあるパラメータの差し替えですが、基本的にはBag_とついている部分をSmartwatch_に変えれば問題ありません。本数が多いのと、ON・OFFを間違えないようにだけ気をつけてください。

これでステートマシンが完成しました。
あと少しですので頑張りましょう。
Parameterにパラメータを追加する
アバタールートからExpressions->Parametersを参照します。

右下のプラスマークからパラメータを追加します。Animatorのパラメーターレイヤーで追加したものと同じ名前を設定してください。型はbool型で、右のチェックはひとまず画像の通りで良いです。
パラメータの追加は以上で終わりです。
ExMenuに項目を追加する
では実際にプレイヤーが操作するExMenuを作りましょう(ようやくここまできた)。
ところでこのスマートウォッチのオンオフの項目はどこにあるべきでしょうか。コスチュームや小物関連はデフォルトのものはExMenu->Costume配下にあるのでここが良さそうです。項目の数もちょうど8になりなんとか収まります。

アバタールートからExpressions->Menuを参照します。その後、Costumeのサブメニューまで辿っていきます。

ここにスマートウォッチ用の項目を作っていくのですが、先にオンオフを含むサブメニュー部分を作ります。
Bag ON/OFFを覗くとBag_SubSubというサブメニューを表すファイルがありますので、これを(Assets上で)コピペしましょう。
名前はSmartwatch_SubSubにします。

上部のParametersにアバタールートに設定してあるパラメータを指定します。
コピペ元のBag_*をSmartwatch_*に書き換えます。
アイコンはなんでもいいのですが、スマートウォッチっぽい画像を用意するとベターかと思います。(今回は汎用画像でごまかします)
再度Costumeのサブメニューまでルートから辿ります。

右下のプラスマークから新規作成し、名前をSmartwatch ON/OFF、アイコンを良さげなもの、タイプをサブメニューとして、先程作成したSmartwatch_SubSubを指定します。
これで全工程が完了です。
お疲れさまでした。
動作確認
GestureManagerで動作確認してみましょう。
プレイモードに入り、GestureManagerからExMenuを辿っていきます。

まずExMenuを辿って追加した項目が存在するかを確かめます。
次にオンオフしてみて実際にオブジェクトの表示が制御できるか確認します。


一発で想定通り行くことは稀かと思います。
アニメーションファイルの作り方、パラメータのスペルミス、ウェイトの設定などを見直してみてください。
前編の終わりに
お疲れさまでした。
とても冗長で長い内容ながらここまで読んでくださりありがとうございました。
後編へ続きます。
(有料部分にはコンテンツはありません。この記事を気に入っていただけたなら、ご支援いただければ幸いです。)
終
2024/09/17
written by shift
ここから先は
¥ 100
この記事が気に入ったらチップで応援してみませんか?