simplestarの技術ブログ

目的を書いて、思想と試行、結果と考察、そして具体的な手段を記録します。

Unity2022.3対応VRChatアバターアップロード手順

VCC→詳細 【コラム】VCCにこれは入れておけ! アバター改変の便利ツール紹介 | メタカル最前線
をお手元に準備したら

テンプレートは Avatars 2022 を選択

Manage Packages から

を選択します

ここまでセットしたらプロジェクトを開きます
VRChat > Show Control Panel メニューから VRChat のログイン画面を開いて
ログインしておきます。

まずはアバターをインポートします。

今回例で使用するのはライムです。(例の半額セールだったので…)
komado.booth.pm

シーンに Lime Prefab を配置したら、メニューの Tools > Gesture Manager Emulator を選択
次の通りシーン内に Gesture Manager を追加します

これまたセール中のクリスマス服
rihyaco.booth.pm
を導入します(衣装Prefabをアバタールートオブジェクト直下に配置)

衣装 Prefab のコンテキストメニューの Modular Avatar > SetupOutfit を選択して衣装合わせをします。

ハイヒール用の衣装のため Body_Base の Blend Shapes から Foot_HighHeel の値を 100 に設定します。

備え付けの衣装が重なるものは取り払うか、着せ替え用の Prefab に差し替えておきます。

ひとまずこれだけで、衣装変えアバターで VRChat を楽しむことができます。

写真

続いて、シンプル眼鏡ギミックを導入します。

まずは無料の丸眼鏡アセット
0123.booth.pm
をインポートします

続いてシンプル眼鏡ギミック
simplestar-game.booth.pm
を購入してインポートします


眼鏡を装着しました
導入手順は以上です

実際に動かしてみた結果www.youtube.com

はじめて Unity 2022.3 で VRChat アバターアップロードしてみましたが
手順はこんな感じで良いでしょうか?
慣れないもので、覚えやすいように記録してみました。

もし VRChat まだやってない人いましたら、こうやってアバターアップロードしているのかと
参考になれば良いなと思います。

他人に渡せるギフトコンテナ「SimpleGiftContainer」【VRChat アバターギミック】の導入手順

なんでも掴んでワールドに配置できるよ(特にすごいのが、フレンドもツリーを掴めちゃう!)

前回の記事で紹介した
他人に渡せるギフトコンテナ「SimpleGiftContainer」【VRChat アバターギミック】booth.pm

こちらを VRChat アバターに組み込む手順を解説します

自分のアバターを用意・アップロード

この記事を書いている今日、セール中でして、大人気アバターが半額という
ほんとにすごい時期なので、買ってみました

【11/24-26限定SALE】『カリン』-Karin-【オリジナル3Dモデル】komado.booth.pm

手順は以前紹介したこちらの記事の通りで、アバター作成部分を、インポートした prefab のシーン配置に置き換えれば完成です。
simplestar-tech.hatenablog.com

まずはカリンちゃんのアバターでワールドを周遊できるようになりました
(Gesture Manger で簡易動作チェックしている様子)

ギミックの導入

他人に渡せるギフトコンテナ「SimpleGiftContainer」【VRChat アバターギミック】booth.pm
を入手して、パッケージ内の SimpleGiftContainer.unitypackage をプロジェクトにインポート

次の画像のように Prefab をアバター内直下に配置します

以上です。

動作確認

Gesture Manager を使ってメニュー操作をすると Gift という項目が選べるようになります
その下の Item00, Item01, Item02 のトグルを有効にすると、プレゼントボックスがプレイヤー正面に現れます

フレンドが掴んで移動できるオブジェクトとなっています
あとはプレゼントボックスモデルをツリーに置き換えてみたりすると、冒頭の画像のようなことができます。

VRChatアバターギミック:誰でも持てるオブジェクトをワールド固定する

Expressions Menu からオブジェクトをOn・Offする手順までを前記事に書きました
simplestar-tech.hatenablog.com
前回のヒエラルキーですと、アバター直下に配置したので
このままだとアバターの回転と移動についてきてしまいます
これを誰でも持てるオブジェクトにするため、ワールドに固定する手順を記録します

1.ワールド原点 Transform を Prefab 化

原点無回転で配置した空の GameObject をプロジェクトビューにドロップして完成です

2.Parent Constraint の親をワールド原点 Prefab に設定

Parent Constraint をアタッチしたオブジェクトを作り、Sources に先ほどの Prefab を指定します。

これだけでオブジェクトはワールド原点に固定配置されます(プレイヤーが移動しても動きません)

オブジェクト固定用 GameObject を作成

上記の原点固定のオブジェクトの子オブジェクトもまた、親子関係よりワールドに固定されます
あとは、ユーザーが Grab するアクション中だけ、その握っているオブジェクトの位置となるよう Parent Constraint を有効化し
Grab を外した瞬間に Parent Constraint を無効化すれば、誰でも持てて、手から離すとワールドに固定されるオブジェクトの完成です。

言葉だと伝わり切らない手順もあると思うので、こちらの誰でも持てるアイテム基盤のパッケージを作って公開しました。
細かい手順はパッケージ内容を確かめてみてください。
他人に渡せるギフトコンテナ「SimpleGiftContainer」【VRChat アバターギミック】booth.pm

次回は公開したパッケージの導入方法を詳しく紹介します。

VRChatアバターギミック:手で押すと音が出るオブジェクト(始め方編)

最近 Quest3 を購入しまして、試しに触るVRゲームとして
VRChat はじめたのですが、これがアバター改変とかはじめると楽しくて

今回は自分の勉強がてら、タイトルの通り
手で押すと音が出るオブジェクトを作ってみたいと思います

開発サイクル(初心者用)

VCC と略される CreatorCompanion のインストール
新規プロジェクト作成で Avatars テンプレートを選びます

パッケージ管理では
Gesture Manager と Modular Avatar をいれます

Open Project で Unity を開きましょう
Unity バージョンは固定 2019.4.31f1 で開きます

VRChat SDK の Authentication タブで認証しましょう。

Builder タブで VRChat アバターをテストビルドして試すサイクルを回します
まずは VRC AvatarDescriptor を持つアバターを作りましょう(無いとはじまらない)

booth とかで気に入ったアバターを買って置くのが良いのですが、今回はサンプルからアバター作ります

チェックマークにある通り、Name と Thumbnail の画像を登録したら
Build & Test を押して、VRChat にて Other アバターとして選択できるようになります。


Expression Menu 作成

Tutorial Robot Avatar の VRC Avata Descriptor の Expressions の Customize ボタンを押します。

VRChat Avatar の ExpressionParameters と ExpressionMenu を作ります

ExpressionMenu の Add Control で申し訳程度に何か項目を足しておきます

割り当てます

メニューの Tools > Gesture Manager Emulator を選択して、ヒエラルキーに GestureManager オブジェクトを作成
これの Editor ボタン Enter Play-Mode を押します

先ほどの Add Control したメニュー項目が Emulator によって確認できます

Modular Avatar 導入

Avatar 直下に空の GameObject を作成して MA Menu Installer を追加します

新たに ExpressionMenu を 2つ作り、Main > Sub 関係の設定をします
Main 側のメニュータイプを SubMenu として、Sub 側には Toggle を複数追加します
インストールされるメニューに、Main 側の ExpressionMenu を設定すれば

Gesture Manager の Enter Play-Mode にて

Expression Menu に追加されることが確認できます。

先ほどの MA Menu Installer のオブジェクトに MA Parameters を追加
自動で ExpressionsMenu の Toggle のパラメータが検出されて登録されます

アバターにアタッチされている Animator Controller には手を入れず
ビルド時にMenu と Animator を統合する Modular Avatar (MA)
改変機能の取り外しが容易になる仕組みです
活用していきましょう

オブジェクトの表示・非表示のギミック

MA Menu Installer, MA Parameters をアタッチしているオブジェクトに
MA Merge Animator と Animator Controller を追加し、自動検出された Parameters を
Toggle に見合った Bool 型で Animator Controller に追加します

空の Animation をオン・オフという名前となるように作り
オンオフするオブジェクトごとに Layer 分けしてレイヤーの影響値の Weight を 0 から 1 に設定するのを忘れずに行い
Bool 値の変化で遷移条件を作ります(瞬時に切り替わるように Has Exit Time のチェックを外し Transition Duration は 0 に)
ところで、このHas Exit Time のチェックを外し Transition Duration は 0 にする操作は頻出するので、自動一括設定ツール
Transition Helper を導入すると便利です。
(自分は少し改造して、選択した遷移のみ設定を変更できるようにしました
選択している遷移は次のコードで取得できました

AnimatorStateTransition[] selectedTransitions = Selection.objects.Select(x => x as AnimatorStateTransition).Where(y => y != null).ToArray();

Animation は単純に次のヒエラルキーのオブジェクトの Activate を切り替えるものとします

あとは MA Parameters の自動検出パラメータを、Animator のみのタイプから 同期する Bool に変更しておき
これで他プレイヤーとメニュー情報の Bool 値を同期します

ついでに MA Merge Animator の付属アニメーターを削除にチェックを入れておきます
(付属アニメーターは、Animation の録画用に便利なので付けてるだけ)

この状態で Gesture Manager の Emulator を起動して Expressions Menu を操作してみると
Emulator の Debug ボタンを押すと、現在の Parameter 一覧と値を確認できます
メニューの Toggle の On Of によって、各オブジェクトが
アクティブ・非アクティブとして切り替わることが確認できます

続きは次の記事へ

CubeArtWorld:ギミックの説明

CubeArtWorld とは?

2020年12月頃に公開された3Dサンドボックスオンラインゲームのこと
simplestar-game.itch.io

VRoidHub 連携で自分のアバターで始めることができ、ゲーム内で購入できるワールド編集チケットさえあれば、世界の一区画のキューブを左クリックで破壊、右クリックで配置できる。
有名な他のサンドボックス系ゲームと比べると、配置する際にキューブの形を変えられること、回転を与えられることが大きな違いとして挙げられる。
ほかにもゲーム内で購入できるキャンバス編集権があれば、自分だけのキューブに金属、プラスチック、ライト、空色など様々な絵の具で好きに絵を描いて、日々コピーして数を増やしつつ、これにも形・回転を与えて配置でき、独自の世界表現が可能になっています。

クリエイティブ要素のほかにも、坂道を転がる鉄球や乗って滑るスケートボード、過去の世界へ飛べるタイムゲートなど、動きをつけた絵が撮れる、けど世界に配置し続けられないインスタントなアイテムがいくつか存在しています。
ギミックが登場するまでは、世界はスタティックで、鑑賞するのがメインのキューブアートな世界でした。

f:id:simplestar_tech:20220129103057p:plain

ギミックキューブへの切り替え方

キャンバス所有者のみ Edit ボタンを押して、この Type のドロップダウンから Gimmick を選んで保存できます。

f:id:simplestar_tech:20220129103741p:plain
キャンバス編集権の方のみこのUIをいじれる

ギミックはこれまでのスタティックな世界をダイナミックに変えるアクセント
これを理解できる最小限の解説を以下に続けます

ギミックの再生・発動

f:id:simplestar_tech:20220129104751p:plain
再生ボタン

ギミックを再生するためには、キャンバスパネルを開いてから Play ボタンを押します。
そのほかにも Trigger アクションの条件を満たすか、他のギミックキューブから SendKeys を受け取る、Combine で結合される範囲に配置されていることで発動します。

ギミックの作成

ギミックはアクションと呼ばれる行要素のタイプを選んで、サブタイプ・パラメータを決め、これをリスト形式でつなぎながら、全体の動きをつけていきます。
タイプが多いので、まずは例を示してみます。

城門が開くギミック

何かスイッチのようなキューブを踏むと、「カチッ」と音が鳴り「ゴゴゴゴゴゴゴ」と音を鳴らして遠くの城門がゆっくりと開くギミック
しばらくすると勢いよく門が落下してピタリと門が閉じます

使用されているギミックキューブは足もとのスイッチと、門の二つ、まずは足もとのスイッチ動作をしているギミックアクションを以下に示します。

f:id:simplestar_tech:20220129105941p:plain
スイッチ側

上から順番にタイプと動作を説明しましょう

Trigger

近づいたり、上に乗ったり、下を潜ったりしたときにギミックを発動させるトリガーの役割を果たします。Sub に GetOn が指定されているので、これは上に乗ったら発動するギミックをこの一行で定義しています。Trigger は一番最初の行にしか設置できない制約があります。

Sleep

指定したフレームの数または秒数を待機します。この行は値に 0 を指定しているので待たずに次のアクションへ進みます

Sound

音を鳴らすアクションです。Sub の Switch が「カチッ」の音を意味します。Value は音の高低を指定する値です

ここまでで、上に乗ると「カチッ」と音が鳴る というギミックの完成です。

SendKeys

周囲に Key を送るアクションです。Sub には形状を指定でき、Plane は高さ 1, 幅と奥行きが今回は 8 という形ですね。キューブの配置における回転の影響を受けるので、このスイッチは門扉のもう一つのギミックキューブに届くように SendKeys をしています。

ここまでのアクションで、「カチッ」と音を鳴らした後、遠くのもう一つのギミックを発動させるという意味になります。

Sleep

ここで入れているのは、カチカチ鳴らしすぎないように少し待ちを作っています。

Return

元のギミックキューブに戻る動作となります。Return 後は最初の Trigger で待ち受けている状態になるので、上に乗ればまた「カチッ」と音が鳴ります。

スイッチで起動する側の門のギミックをここで確かめましょう

f:id:simplestar_tech:20220129112105p:plain
門側のギミック
Combine

結合を意味していて、柵の形で一つのオブジェクトになるように PhysicalPlane を Sub に選んでいます。Plane は先ほどの SendKeys と同じ形のイメージで、高さ 1 幅と奥行きが今回は 4 の結合範囲でまとめています。こちらもギミックキューブの回転に依存しているため、うまく回転させて縦方向の板の形状で周囲を結合し、門の形をとります。
Physical と 無印の形状がありますが、形で物理挙動をしたいときに Physical を指定し、物理挙動をあきらめるが、結合した形状の内側にプレイヤーが入りたい場合は無印の形状を利用します。

Sound

音を鳴らすアクションです。Explosion という「ドカーン」という音を 0.5 の速度で再生して、ゆっくりの「ゴゴゴゴゴゴゴ」という音を作っています。

Translate

移動するアクションです。LocalX は今回は回転の兼ね合いで、門を上方向に動かす軸を示しています。-5 は上方向に 5キューブ移動という指定となります。

Sub に Speed を指定すると、直前の移動の速度を変化させることができます。3と大きな数字を指定すると速く動かすことができます。

Sub に Finish を指定すると、直前の Translate の移動が完了するまで待機するアクションとなります。

ここまでのアクションで、門の形で結合して「ゴゴゴゴゴゴゴ」という音をたてながら上へ 5キューブ移動する を実現できるようになりました。

Sleep

上に移動した門が 20秒経過するまで固定されます

Physics

Subに指定した物理制約を与えることができます。SliderY の場合上下方向の軸だけ移動できるエレベータのような動作制約で動くことになります。
このタイプは回転の影響をうけません。

Sleep

門が落下する様子を見せるための待機時間です

Return

もう一度門を開かせられるように、すべてのアクションを取り消して元の状態に戻ります。

ギミックタイプ一覧

アクションを組合わせることで、奥深いギミックが作れるようになると思います。
以下に現在提供されているアクションタイプ一覧を示して説明を終えたいと思います。

タイプ 説明
Trigger 乗る、近づくとギミックが発動する
Sound 音を鳴らす
Material 光ったり、透明になったり、色変化や周囲キューブのマテリアルを指定して見た目を変えます
Logic Keyの値を足したり、引いたり、掛けたりします
Sleep 指定した時間だけ次のアクションの実行を遅らせます
Lock 直前のKeyがここで指定した値と一致しないとReturnします
SkipTo 指定したラベルへアクションをスキップします。条件分岐や繰り返し処理を作れます。
Label ジャンプ先を定義するラベルです
Combine 周囲のキューブをギミックキューブを核にして結合します
Rotate ギミックキューブを中心に回ります。プロペラやスクリューなどの表現に使えるでしょう
Translate ギミックキューブが移動します。すごい勢いで動かすとプレイヤーやボールを打ち出す発射装置になったりします
InstantItem すべてのインスタントアイテムに変化します。過去に行く装置になったり、地雷などが作れるでしょう
Disappear 周囲のキューブを巻き込んで消滅します。突然開く扉や、床が消える落とし穴などになります
Key SendKeys に乗せる情報になります。受け取ったキューブは Key の値を処理できます。Lock に差し込んで錠を鍵で開くイメージです
SendKeys 加工したKeyセットを周囲のギミックキューブへ送信し、ギミックを発動させます
Load ギミックアクションリストは必要なときにロードするのでタイミングがずれることがあります。事前に周囲のギミックキューブのロードを完了させるためのアクションです
Return すべてのアクションを取り消して、元の状態に戻します
Physics 物理挙動をギミックキューブに与えます。制約をつけるとスライダーや回転する歯車、レールに沿って動く、などの動きを作れます
HingeJoint 蝶番のような扉の表現が可能になります。角度をつけなければ回転する車軸としても表現できるでしょう。単体では表現できないので周囲にアンカーを刺す設定をします。
SpringJoint ばねのような弾性のある動きをつけられます。アンカーを刺す向きを指定します。
FixedJoint 複数の結合したギミックをさらに組み合わせて複雑な形状を作るときに、固定ジョイントを活用します

Unity: BehaviorTree 勉強2

アニメーションする Task は Running しか返さないというのはどうか?

止める条件があるのなら、その親に Conditional Evaluator を置き、これが失敗を返したら、子であるアニメーション Task は中断されるため

参考

www.slideshare.net


目的地が設定されているか?
yes→目的地へ向かう
no→次の行動を選択

selector

sequence

  • hasDestination
  • gotoDestination

sequence

  • nextAction

結局アクションを定義してから組み合わせになるのか

ランダムに目的地を決める
プレイヤーを見つけているか?
プレイヤーの方向の目的地を決める
目的地は決まったか?
目的地に向かって歩く
目的地に着いたか?
十分プレイヤーに近づいたか?
プレイヤーの方向を向いているか?
プレイヤーの方向を向く
座り込む
座り続ける
立ち上がる


うーん、一連のながれが sequence で、その一連のながれが通らないときに selector で別の sequence へ流れるかんじ?

最初は
十分プレイヤーに近づいたか?→yes→プレイヤーの方向を向く→十分プレイヤーに近づいたか?→yes→座り込む→十分プレイヤーに近づいたか?→yes→プレイヤーの方向を向いているか?→yes→座り続ける
目的地に着いたか?→yes→
目的地は決まったか?→yes→目的地に向かって歩く
プレイヤーを見つけているか?→yes→プレイヤーの方向の目的地を決める

え、ループできるのか
ループってどうやってツリー表現

考え中

なるほど、継続しつづけるアクション 待機モーションなどを running し続けるで良さそう
これが最終的な安置として

どうやって抜け出すの?

それが前回勉強したなにかの conditional なんちゃら

Selector Evaluator

実行中を返す Action が子タスクの優先度の低いものであったなら、それより高い優先度の Action の評価をもう一度行う

これもそうだけど、もっと self とか low priority とかでもできたような?

Sequence NodeのAbort TypeをNoneからSelfに変更すると、子ノードの running 中の左側を毎回評価して、failure なら runnning を中断できる
つまり、抜け出せる AbortType self で

または、同階層の右側が running でも Abort Type Lower Priority でも

なるほど Abort の目的語が Lower Priority で右がわ、Self が自身の子ノードという意味だったのか

やっと解読できてきた
つまり

Unity: Behavior Designer のノードの勉強

基本的なことはこちら
simplestar-tech.hatenablog.com

ビヘイビアツリーを構成しているノードは三つに分けられる、それぞれ Task, Composite, Decorator である。

・Task はツリーのリーフ要素で、1フレームに許された計算時間で条件判定やアクションを実行し、戻り値として1)Running(まだタスクが残っている), 2)Failure(条件に合わない、または失敗), 3)Success(条件一致、または成功) の三つのいずれかを返す。
・Composite は子ノードの実行を制御するノードで、シーケンスとセレクターの二種類が存在する。
シーケンスは最初の子ノードが実行完了するまで待機し、成功を返した場合に、次の子ノードを実行し、この操作を繰し、最後の子ノードが成功を返したら、シーケンスノードも成功を返す。一度でも子ノードが失敗を返したら、残りの子ノードは無視して失敗を返す。子ノードが実行中を返すようなら、シーケンスも実行中を返す。
セレクターは優先度順に子ノードの選択を行い、選択したノードを実行する。シーケンスと違う点は、どれか一つの子ノードが成功を返した時点で、まだ実行していない子ノードがあっても、成功を返す点である。
セレクターには指定した割合の確率で選択する Probability Selector や、1フレームにすべての子ノードを実行する Parallel がある。
・Decorator ノードは特質系で、子ノードを一つしかとることができない。ノードのタイプによって振る舞いはさまざまで、たとえば n 回子ノードを繰り返し実行してから成功を返したり、子ノードがn秒以上実行中だったら成功失敗に関わらず強制的に失敗を返したりする。

これから Behavior Tree を活用していくので、使い方に慣れるため、各ノードの動きを確認しながら、道具として使えるようになるため、覚えていこうと思います。

## Parallel

子タスクを一斉に並列実行し、いずれかが失敗を返したらすべての子タスクを停止して失敗を返す
実行中の子タスクが残るなら、実行中を返し
すべての子タスクが成功したとき、成功を返す
イメージとしては並列実行の And 条件 if 文って感じですかね

f:id:simplestar_tech:20210703130241p:plain
Parallel

## Parallel Complete

子タスクを一斉に並列実行し、最も早く答えを出した子タスクの答えを返す
全部成功していて、残る実行中のものがあったとしても、最初に成功を返した子タスクを見つけた時点で成功を返す
イメージとしては並列実行の Or 条件 if 文って感じですかね、False を返すのが先だと False が返るところが全然違う

f:id:simplestar_tech:20210703131331p:plain
Parallel Complete

## Parallel Selector

子タスクを一斉に並列実行し、すべて失敗となるまで待つが、いずれかが成功を返した瞬間に子タスクをすべて停止して成功を返す
イメージとしては並列実行の Or 条件 if 文って感じですね
f:id:simplestar_tech:20210703131954p:plain

## Priority Selector

float GetPriority(); を Action Task 側で実装して返す必要がある
この値が大きい順で子タスクを順番に実行して、先に成功を返したタスクが生まれたら成功を返すもの

f:id:simplestar_tech:20210703142958p:plain
Task class

## Random Selector

ランダムに子タスクの順番を決めて Selector として、成功を見つけるまで続け、成功を返す子タスクによって、続く子タスクの実行を停止して、成功を返す
イメージとしては順番実行の Or 条件 if 文って感じで、評価順がランダムというものですね

f:id:simplestar_tech:20210703143258p:plain
Random Selector

## Random Sequence

ランダムに子タスクの順番を決めて Sequence として、すべてが成功するまで続け、失敗を返す子タスクによって、中断して子タスクの実行を停止して、失敗を返す
最後の子タスクが成功したときにやっと成功を返すという
イメージとしては順番実行の And 条件 if 文って感じで、評価順がランダムというものですね

f:id:simplestar_tech:20210703150525p:plain
Random Sequence

## Selector

いずれかが成功したら成功を返し、残りの実行を中断する
イメージとしては順番実行の Or 条件 if 文って感じ

f:id:simplestar_tech:20210703150847p:plain
Selector

## Selector Evaluator

実行中を返す Action が子タスクの優先度の低いものであったなら、それより高い優先度の Action の評価をもう一度行う様子
実行中のタスクより低い子タスクの実行はせず
優先度が高いタスクは常にチェックしたいといったギミックに良いのかも たとえばプレイヤーを視認していること という条件に使って、続くアクションに追いかけるといった running ステータスを返す何かが挟まるなど
イメージとしては順番実行の Or 条件 if 文って感じだが、どういうわけかさっき調べた条件をもう一度しらべいにく
いくつもの Task のうち、最初に成功を返した Task の結果をもって成功を返す

f:id:simplestar_tech:20210703151759p:plain
Selector Evaluator

## Sequence

順次実行して、失敗が返ると即子タスクを停止して失敗を返す
イメージとしては順番実行の And 条件 if 文って感じ

f:id:simplestar_tech:20210703153116p:plain
Sequence

## Utility Selector

float GetUtility の値が大きいものを常に選んで実行する
子タスクすべてを見ていて、Utility 値が実行中に小さくなったなら、他の Utility 値が大きいタスクの実行へと移る
いずれかが成功を返したら、他のタスクを停止して成功を返す でも、いずれの子タスクも Running を返すようにする使い方がメインなのかな
Priority Selector との違いは、それまで最高Utility だったタスクが Running 中でも、他の Utility が高まったら中断するところかな

f:id:simplestar_tech:20210703153501p:plain
GetUtility
f:id:simplestar_tech:20210703154804p:plain
Utility Selector