MRTKでシアーハートアタックを手の甲に追従させる

# はじめに

こんにちは。
今回、[Iwaken Lab.アドベントカレンダー2023](https://qiita.com/advent-calendar/2023/iwakenlab)の15日目の記事を担当するジョジョ好きなXRクリエイターの [もふるね](https://twitter.com/mofurune_k) です。
初めてのアドベントカレンダー参加です！気合入れていきます！

先日「[手乗りシアーハートアタックをMRTKで実現する](https://qiita.com/mofurune/items/99c170a7d704a1808a58)」は手の平に追従する機能を作りましたが、その時ふと思いました。
「シアーハートアタックって手の甲から出てくるんじゃなかったっけ...」と。

なので今回は、3Dモデルが手の甲に追従するARアプリを作っていきます。

開発環境
・Unity 2021.3.29f1
・NRSDK v1.10.2
・MRTK v2.8.3
使用機材
・Xreal Light
・Xreal Light Devkit（またはXreal Light対応スマートフォンでも可能）

実装するとこんな感じになります。

<img width="600" alt="kou01.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3518924/6855aecf-bafc-e2e6-329f-1329afa7c4d7.jpeg">


# 参考資料

・Xreal lightでMRTKを使えるようにできるリポジトリ（吉永さん[@Taka_Yoshinaga](https://twitter.com/Taka_Yoshinaga)）
[MRTK-Profiles-for-NrealLight](https://github.com/TakashiYoshinaga/MRTK-Profiles-for-NrealLight)
・ホロモンさんのブログ記事
[MRTK v2.4.0を使ってHoloLens2で手乗りキャラクターアプリを作成する その１](https://bluebirdofoz.hatenablog.com/entry/2020/07/21/232553)
・ちょまぎょさんのQiita記事
[手乗りちょまぎょアプリ開発で学ぶ MRTK 入門 (MRTK 2.5 対応)](https://qiita.com/chomado/items/d4ff1edae797dfbd3c45)


# １．準備

始めに、MRTKとNRSDKと[MRTK-Profiles-for-NrealLight](https://github.com/TakashiYoshinaga/MRTK-Profiles-for-NrealLight)の導入を済ませます。

手乗りするキャラの3Dモデルデータを用意します。
今回使うのは自作の[シアーハートアタック3Dモデル](https://mofurune.booth.pm/items/4675058)です。

Unity作業に入ります。
ヒエラルキーに空のゲームオブジェクトを作ります。
名前を「Tenori BackOfHand」にしておきます。
Tenori BackOfHandの子にシアーハートアタックを置きます。（位置は後々調整します）

<img width="600" alt="kou02.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3518924/79df64eb-47b7-52f5-5836-9250bcbddb91.jpeg">


# ２．手の甲に追従する機能

## 2.1.前回を踏まえての改善案
前回「[手乗りシアーハートアタックをMRTKで実現する](https://qiita.com/mofurune/items/99c170a7d704a1808a58)」は手の平に追従する機能を作りました。
ここで判明した問題は、・手の平を自分側に向けた時のみしか追従しないということ・手の平にのみ追従することです。
これの解決策として、・「Hand Constraint Palm Up」コンポーネントを「Hand Constraint」コンポーネントに変更すること・コンポーネントの設定で「Rotation Behavior」と「Offset Behavior」を変更することを考えました。

以下に[ホロモンさんのブログ記事](https://bluebirdofoz.hatenablog.com/entry/2020/07/21/232553)から引用した、コンポーネントの詳細を示します。

<details><summary>「Solver Handler」コンポーネントの詳細</summary>

```
Solver Handler（Tracked Target Type：Hand の場合）

Tracked Target Type：追従するターゲットを設定します。以下のタイプから選択可能です。
  - Head：頭部
  - Controller Ray：ポインター
  - Hand Joint：手
  - Custom Override：カスタム
Tracked Handness：追従する手の種類を設定します。例えば以下のタイプを選択可能です。
  - Both：両手
  - Left：左手
  - Right：右手
  etc..
Tracked Hand Joint：追従する手の位置を設定します。例えば以下のタイプを選択可能です。
  - Wrist：手首
  - Palm：手の平
  - Index Tip：人差し指の指先
  etc..
Additional Offset：追従対象からのオフセット距離（X/Y/Z座標）
Additional Rotation：追従対象との相対回転角（X/Y/Z回転）
Update Solvers：True時、追従位置を更新し続ける
```
</details>

<details><summary>「Hand Constraint」コンポーネントの詳細</summary>

```
Hand Constraint

Update Linked Transform：False時、ソルバーはオブジェクトを直接更新し、他のソルバーとの共有位置を使用しません。
Move Lerp Time：0の場合、位置が即座に更新されます。値が大きいほど移動が補完されて徐々に移動します。
Rotate Lerp Time：0の場合、回転が即座に更新されます。値が大きいほど回転が補完されて徐々に回転します。
Scale Lerp Time：0の場合、拡縮が即座に更新されます。値が大きいほど拡縮が補完されて徐々に拡縮します。
Maintain Scale：True時、ソルバーはオブジェクトの元のスケール値を利用します。
Smoothing：True時、動きがターゲットに対して平滑化(ノイズ除去)されます。
Lifetime：0より大きい場合、ソルバーの状態がアクティブであっても、この時間が経過すると非アクティブになります

Safe Zone：手のどの部分にオブジェクトを追跡させるか設定します。以下のタイプから選択可能です。
  - UlnarSide：小指側
  - RadialSide：親指側
  - AboveFingerTips：指先
  - BelowWrist：手首
  - Atop Palm: 手の平
Safe Zone Buffer：オブジェクトの追跡位置からのオフセット距離を設定します。
Update When Opposite Hand Near：True時、反対側の手が重なって手が隠れた場合も追従を継続します。
Hide Hand Cursors On Active：True時、手を追跡している間はカーソルを無効化します。
Rotation Behavior：追跡中にオブジェクトが回転する向きを指定します。以下のタイプから選択可能です。
  - None：手の回転に合わせて回転する
  - Look At Main Camera：カメラの方向を向くように回転する
  - Look At Tracked Object：追跡中の手の方向を向くように回転する
Offset Behavior：手に対するオブジェクトのオフセットの計算方法を指定します。以下のタイプから選択可能です。
  - Look At Camera Rotation：カメラの方向を元に計算します。
  - Tracked Object Rotation：手の回転方向を元に計算します。

On Hand Active：手を検出中の間、常に実行されるイベントを登録します。
On Hand Deactive：手を検出していない間、常に実行されるイベントを登録します。
On First hand Detected：手を検出した時、一回のみ実行されるイベントを登録します。
On Last Hand Lost：手をロストした時、一回のみ実行されるイベントを登録します。
```

</details>


## 2.2.手の甲に追従する仕組み

１．「Hand Constraint」コンポーネントを付けます。これは手が画面内にあれば、その手がどこを向いているか関係なく発動します。

２．`Solve Handler`の`Tracked Target Type`を`Hand Joint`にすることで「手に追従する」ようにします。`Hand Constraint`の`Rotation Behavior`を`Look At Tracked Object`にすることで「追跡中の手の方向を向くように回転する」ようにして、`Offset Behavior`を`Tracked Object Rotation`にすることで「手の回転方向を元に計算する」ようにします。これで、オブジェクトの移動・回転が手を元にして行われるようになります。

３．`Hand Constraint`の`SafeZone`を`Atop Palm`にすることで「手の平に追従する」ようにします。`Solver Handler`の`Addutuional Offset`とオブジェクト自体の位置・回転を調節して「手の甲に付随する」ようにします。

４．`Hand Constraint`の`Move Lerp Time`と`Rotate Lerp Time`を`0`にすることで、「手の移動に瞬時に付いてくる」ようにします。この値は大きいほど移動が補完され滑らかな移動になるのですが、手の移動とオブジェクトの移動にラグが生じます。なので、今回は0にします。

# ３．Unity作業

上記で示した「手の甲に追従する仕組み」をUnityで設定します。
Tenori BackOfHandに「Hand Constraint」コンポーネントを付けます。
すると、「Hand Bounds」と「Solver Handler」コンポーネントも一緒に付いてきます。
これらのコンポーネントの設定をします。

Solver Handler
・Tracked Target Type: Hand Joint
・Addutuional Offset: X:0, Y:0, Z:-0.15

Hand Constraint
・Move Lerp Time: 0
・Rotate Lerp Time: 0
・Hand Constraint
　・Safe Zone: Atop Palm
　・Safe Zone Buffer: 0
　・Rotation Behavior: Look At Tracked Object
　・Offset Behavior: Tracked Object Rotation

<img width="600" alt="kou03.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3518924/698413cc-5369-ca00-22b8-381afdf536ec.jpeg">

次に、Unityの再生ボタンを押して手とシアーハートアタックとの位置を確認しながら調節します。

操作|手のエミュレーション
---|---
シフトキー + マウス移動|手の移動
マウスホイール回転|手の奥行き
マウスホイール押し込み|手を内向きに

今回は以下のようにしました。（オブジェクトのモデルによって値は変わるので一例として参考にして下さい）

Tenori BackOfHandのSolver Handler
・Addutuional Offset: X:0, Y:0, Z:-0.15

シアーハートアタックのTransform
・Position: X:0, Y:0.14, Z:0.06
・Rotation: X:-90, Y:180, Z:0

<img width="600" alt="kou04.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3518924/b0201d33-89b4-1936-7104-9f23289e3f9d.jpeg">

ちなみに、SHA(空のゲームオブジェクト)の子にSheerHeartAttack（3Dモデル本体）としているのは、SHAで位置・回転を指定し、SheerHeartAttackでモデルのスケールを変更するためです。こうするとスケール変更の際に位置・回転の値まで変わることを防げます。


# おわりに

以上で完成になります。
Xreal Lightで実際にやってみるとこうなります。
動画ではシアハが手前に描画されてますが、実際は(ARグラスの特性上)半透明に映るのでちゃんと手の甲についてる感じがします。

<iframe width="560" height="400" src="https://www.youtube.com/embed/JfHgyjA4Czk?si=vWioX-xaC6IG1NS5" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

これで原作通りですね！
これを活かして、シアハを向いてる方向に発射したりすることを思いついたので後の私に任せます。
シアハ飛ばして的あてゲームできるんじゃないかとも思ったのですが、ハンドトラッキングはカメラからの推定で行うため、手が隠れてしまうことや素早い動き等に弱く、精密動作を要求するゲームは難しそうですね。
ここはアイデアでどうにか工夫したいところ...うん、頑張れ後の私！（丸投げ

次の[Iwaken Lab.アドベントカレンダー2023](https://qiita.com/advent-calendar/2023/iwakenlab)の記事は[@sorasido123](https://qiita.com/sorashido123)さんです。お楽しみに！


今までのシアーハートアタック記事

https://qiita.com/mofurune/items/532da1396dedb4ae9cf4

https://qiita.com/mofurune/items/99c170a7d704a1808a58


私は普段3DモデリングやUnityでXRアプリ開発、VRChat謎機能商品を作ったりしてます。
面白いものが作れたらツイッターでつぶやいたりしてるので気になる人は見に来てみてね～！

<blockquote class="twitter-tweet" data-media-max-width="560"><p lang="ja" dir="ltr">&gt;&gt;&gt; 吉良吉影になれるARアプリ &lt;&lt;&lt;<br><br>シアーハートアタックは手に乗って喋るぞ！<br>もちろん爆発もする。手の上で。<br><br>うっ！こ...このパワーッ！！💥うぐっ！！<a href="https://twitter.com/hashtag/AR?src=hash&amp;ref_src=twsrc%5Etfw">#AR</a> <a href="https://twitter.com/hashtag/XREAL?src=hash&amp;ref_src=twsrc%5Etfw">#XREAL</a> <a href="https://twitter.com/hashtag/JOJO?src=hash&amp;ref_src=twsrc%5Etfw">#JOJO</a> <a href="https://t.co/f4f6brXTNC">pic.twitter.com/f4f6brXTNC</a></p>&mdash; もふるね𓇶 (@mofurune_k) <a href="https://twitter.com/mofurune_k/status/1723716647876497884?ref_src=twsrc%5Etfw">November 12, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>