Microsoft Mesh - Custom Immersive Experience : Mesh Trigger,Anchor,Tether概要

# Microsft Meshのサービスの1つとして提供される Custom Immersive Experience

前回に引続きMeshでカスタム環境を構築技術の解説をしていきたいと思います。

https://qiita.com/miyaura/items/423414fb052e45a907a4

今回はMeshで構築された空間の中でアバターが行動に対して何か制御を加えたい時に利用できるいくつかの機能を紹介します。これらの機能はMesh Toolkitを利用することで簡単に導入することが可能になります。公式での紹介はこちら。

https://learn.microsoft.com/ja-jp/mesh/develop/enhance-your-environment/avatar-and-object-interactions/triggers-anchors-and-tethers?wt.mc_id=WDIT-MVP-5003104

## Mesh Trigger,Mesh Anchor,Mesh Tether概要

Mesh空間の中では様々な事をおこなえます。その中でもアバターつまり、参加者の行動に対して制御を加える場合に使える機能が、Mesh Toolkitには存在します。それが「Mesh Trigger」「Mesh Anchor」「Mesh Tether」の3つです。Unityを使ったことがある人であれば、特にこの機能がなくても実装は可能なのですがとても楽に実装できます。

![2024-12-24-15-43-18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/919205e3-b51a-4622-274e-2b380756aded.png)

「Mesh Trigger」はその名の通り、アバターがColliderオブジェクトと衝突しているかどうか判定するために利用します。アバターがある領域に入ったところで何か動きをつけるような場合に利用できます。
「Mesh Anchor」は、このコンポーネントを追加したオブジェクトをアバターが接触している時、アバターがオブジェクトの動きと同期して移動するというものです。空間内で乗り物を作りたい場合に利用します。
「Mesh Tether」は、アバターが占有することができるオブジェクトを作る際に利用できます。これは例えば座席等の１人だけが利用できるようなオブジェクトで利用可能です。

次にそれぞれの詳細に説明します。

### Mesh Trigger
Mesh Triggerがわかりやすい機能だと思います。Unityでもよく行うIsTrigger利用したオブジェクトの衝突をイベントとして実装することがあると思います。このMesh Triggerはそのアバター版です。IsTriggerが指定されたColliderにアバターが触れているかどうかを判定として処理を実装する場合に利用します。

#### オブジェクトの設定

Mesh Triggerを利用する場合は同じオブジェクトにColliderが必要です。Colliderが設定されているオブジェクトにMesh Triggerコンポーネントを追加することで利用できます。この際ColliderはTriggerモードの変更しておきます。

![2024-12-24-15-54-04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/66bb017d-3a0b-93c6-70f9-8725ca968a75.png)

#### 実装方法

実装はVisual Scriptingで行います。任意のコンポーネントにScript Machineを追加し最初に以下のようにScript Graphを構成します。MeshTrigger～ifまではMesh Triggerを使った定番の実装になると思います。

![2024-12-24-16-04-41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/bca9a44c-6aec-a2ad-fb51-775e960e0467.png)

1. MeshTrigger.GetLocalAvatarInTriggerの値を取得します。
1. Mesh Toolkitで提供されているOn State Changedイベントのインプットに代入します。On State Changedイベントは入力の情報が変化した場合にイベントが発生します。今回であれば以下の２パターンでイベントが発生します。
    * fale→True : アバターが対象のColliderに入った時
    * true→false : アバターが対象のColliderから出た時
1. On State Changedイベントあとの値によって実際に作りたい処理を繋げます。ifブロックを追加しTrueの時に処理をつなげることで、アバターがColliderに触れた際に処理を実行することが出できます。

### Mesh Anchor
Mesh Anchorも便利な機能の１つです。ColliderとMeshAnchorが設定されたオブジェクトにアバターを触れた状態にすると、オブジェクトの移動に同期してアバターが移動します。乗り物を作る際に便利な機能です。

#### 実装方法
実装は、アバターが一緒に移動するオブジェクトに対してColliderとMeshAnchorを追加します。

![2024-12-24-16-14-59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/3494b0ef-9a4c-1e12-c774-dc24b4bb3d2d.png)

この実装を行ったオブジェクトにアバターが接触するとオブジェクトの動きに同期してアバターを移動させることができます。

### Mesh Tether

Mesh Teherはかなり独特な機能だと思います。Mesh Tetherは特定のオブジェクトを占有させることことができる機能です。例えば、会議やカンファレンスでの座席などに活用できます。座席は通常、1人1席で利用します。こういった場合に誰も座っていなければ座席に座り、他の人はその人が席を立つまでは他の席を利用する。。。こういった実装が可能になります。

![2024-12-28-16-47-10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/4114f439-f56e-c84a-8e6f-a16805aac873.png)

#### 実装方法
実装についてはMesh Tetherとアバターによって占有するオブジェクトを用意します。この時、Mesh Tetherコンポーネントは占有するオブジェクトとは別に定義しても問題ありません。Mesh Tetherのパラメータの中にTether Transformという項目があり、ここに占有するオブジェクトを設定します。あとはシートを選択した際にMesh Tetherで処理を実行すると、「1.誰も先約がいない場合は、座席に移動し占有が開始」、「2.既にユーザが座席に座っている場合は何もしない」「3.一定の条件を満たすと占有が解除され誰でも利用できるようになる」という便利な機能を活用することができます。

![2024-12-28-01-14-13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/61cc26ea-9994-eb4b-8624-b66656ea72ce.png)

|パラメータ名|概要|
|:-:|:--|
|Tether Transform|Tether時のオブジェクトのTransform|
|Avatar Tether Point|Avatarが拘束される体の部位|
|Allow Rotation|Tether時にAvatarが回転できるか|
|Allow Teleport|Tether時にTeleportを許可するか|
|Automatic Untethering|Tether自動解除の設定<dl><dt>Untether Via Attempted Movement</dt><dd>Tether地点から少しでも動けば解除する（PC用）</dd><dt>Track HMD Movement</dt><dd>Tether地点から移動したら解除する(HMD用)</dd><dt>Enforce HMD Tether</dt><dd>Tether地点を強制するか</dd></dl>|

実際にこの機能を利用する為にはVisual Scriptingを利用して処理を記述する必要があります。例えば、何かオブジェクトを選択した際にMesh Tetherを有効にするロジックの例は以下の通りです。
ポイントとしてはローカルのイベントとして実行するという事です。Visual Scriptingは処理が影響を与える範囲としてlocalとshareの2通りがあります。Mesh Tetherを利用する場合、ローカルのイベントとして実行しないと、ログイン中のユーザに影響が及んでしまい、挙動がおかしくなります。具体的には全員が同じTetherで設定したオブジェクトを占有する形になってしまいます。

![2024-12-17-17-10-27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/18f69588-5ce5-b844-ad14-6b7ee2bb0d31.png)



## 実装例

実際にこれらを使って作ることが出来るコンテンツのアイデアについて紹介します。

<iframe width="560" height="315" src="https://www.youtube.com/embed/OY8SpM_eS_M?si=74ljTAB451TQF_AA" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

Mesh Tetherを利用してアバターを移動体に拘束後、Mesh Anchorで移動体とアバターの動きを同期させます。おまけとしてMesh Triggerを利用してMesh Tetherを有効にするための操作用オブジェクトを表示/非表示も作成しました。以下はVisual Scriptingでの実装を交えて紹介します。

### 移動体の実装

移動体については、今回は半透明の球体を実装しています。このオブジェクトにはMesh AnchorとMesh Tetherコンポーネントを追加しています。参加者がMesh Tetherを有効にすると、最初に移動体にテレポートします。その後、このオブジェクトは移動を開始します。このオブジェクトにはMesh Anchorが設定されているためColliderの範囲内にアバターがいる限りこの移動体と一緒に移動することができます。最後に終端に達した時、Mesh Tetherを解除すると同時に、Travel pointで指定された降車場所に移動して終了します。

今回作成したオブジェクトです。移動体はModulesで定義しています。このオブジェクトには[Is Trigger]にチェックを入れたColliderとMesh Anchor,Mesh Tetherのコンポーネントを追加します。またこのオブジェクトは自身を移動させるための処理が必要なためScript Machineも追加します。

![2024-12-23-02-17-18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/ee021807-be45-b4c8-30d3-56af53009181.png)
以下は今回作成したロジック部分です。

![2024-12-23-02-30-19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/4d47a561-2bfb-6cf3-8280-c1860fc5ccff.png)

今回作成した移動体は、Mesh Tetherの機能を利用しているため1人乗りです。ロジックについては次の通りです。最初にMesh Tetherコンポーネントを監視し、Tether状態である事を検知するとロジックでオブジェクトを移動させます。また、この時Mesh Tetherの機能によりアバターは移動体の座標上にテレポートします。移動体はMesh Anchorを追加しているため、移動体のCollider内にいるアバターは移動体の移動と同期して乗り物に乗って移動するような表現が可能になります。
移動終了時には、アバターを移動体の外にテレポートさせるとともにTetherを解除します。
注意点としては、Mesh TetherのGetAvatarInTetherメソッドの変更タイミングとアバターのテレポートの関係です。テレポートの完了よりもMesh Tetherの状態変更の方が早くなります。このため状態変更を検知した直後に移動を開始すると、アバターのテレポートが完了しておらず、アバターが乗れない状態が発生します。Tether状態を検出した後、数秒待ち時間をおいてから移動を開始させる必要があります。

Mesh Tetherのチェック部分を抜粋すると以下のようになります。

![2024-12-23-02-47-02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/5ca09bd8-3fd3-86cf-f3fd-8391f2aec509.png)

Microsoft Meshでは値の監視はパフォーマンスを考慮してUpdateメソッドで定期的に監視するのではなく、OnStateChangedイベントで監視します。今回はTether状態になった事を検知するためGetAvatarInTetherの状態がfalse -> trueになった時に処理を実施するようにします。

次にオブジェクトの移動ロジックです。

![2024-12-23-02-56-50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/db96b605-c4af-42db-0679-86f21777e40f.png)

今回はシンプルに往復運動をするだけのロジックを実装しています。先に解説した通り、Tether状態を検知した直後に移動を行うとアバターが取り残されてしまうため、0.5s程待ち時間を入れてから移動をするように実装しています。

最後に降車の処理です。

![2024-12-23-02-59-35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/22716f0a-ca93-67d3-31cb-f318ef6a6136.png)

オブジェクトの移動が完了した時に、移動体からアバターを出るようにするためにTravel Pointを用意しそこに移動させます。さらにTether状態を解除して再びアバターが移動体に乗ることができるように処理を実装します。

### Mesh TetherをTether状態にする実装

次にアバターをTether状態にする実装を行います。

![2024-12-24-03-08-53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/1bbd738d-6d04-4531-06ad-320e819a07cf.png)

今回はMesh Interactable Bodyを利用し、Select状態の時にTether状態にするロジックを用意しました。オブジェクトは赤い球体で表現しています。またHover状態でアウトライン強調されるように実装しています。また、処理を実装するためのScript Machineも追加しています。

ロジックは以下の通りです。

![2024-12-24-03-09-52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/0a6b4120-b2a2-55d5-a525-f3f8285e241c.png)

ポイントとしては監視するパラメータをMeshInteractableBody.IsSelectedLocallyに設定することです。IsSelectedLocallyは他のユーザとIsSelectedの状態を共有しません。Tether状態にするための処理はあくまで1ユーザでの操作にする必要があるためIsSelectedLocallyを利用します。もしIsSelectedプロパティの方を利用してしまうと、参加者全員がこの変更を検知し、以降のMesh Tetherの処理を実行してします。これによって全てのユーザがTether状態に変更してしまい正しく動作しません。
このような1ユーザで操作を行うのか、全ユーザで処理をおこなうのかといったことを意識することが重要になります。

### おまけ：Mesh Triggerを利用した操作オブジェクトの表示/非表示

折角なので、Mesh Triggerを利用し、待機所にアバターが入ると操作用オブジェクトが表示されるロジックも実装してみました。

![2024-12-25-04-28-06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/bff4f977-993e-88ba-5f7f-c2a0db886e9e.png)

Mesh Triggerは名前の通りIsTriggerがチェックされているColliderとセットで利用します。アバターがColliderに接触しているかどうかを状態として取得することが可能です。このMesh Triggerの状態を監視することで、オブジェクトが乗車所に入ると操作用オブジェクトを表示するといった実装が可能になります。実装例としては以下のようなものです。


操作用ボタンについてはGetLocalAvatarInTriggerを監視しています。このプロパティを使うと、自分だけがColliderに接触しているときのみ表示/非表示のロジックが動作します。つまり、他のユーザがColliderに接触していても操作用オブジェクトは変化しません。もし、誰かがColliderに接触している場合に表示/非表示を行う場合は、GetAvatarInTriggerを利用します。

![2024-12-25-04-36-25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/afcc7067-9cf7-b847-a55e-a82683cf4035.png)