プログマのプログラマ日記

技術メモや自社サービスに関する記事を書いていく予定です。

Unity + Oculus Integrationでボタンを押す(その2:ボタンの大きさ・高さの調整)

Unity + Oculus Integrationを使ってコントローラやハンドトラッキングでボタンを押すまでの手順をStep by stepで説明します。
やっていることはOculus Integrationのサンプルシーン「PokeExamples」と同様ですが、これを0から組み立ててみてSceneを構成するコンポーネントの役割への理解を深めようという趣旨の記事です。
ボタンを押す動作はOculus IntegrationのPoke Interaction | Oculus Developersという機能を使って実装します。
Pokeには「つつく」という意味があります。

以下のステップで実装を進めていきます。

  • 無地のボタンの作成
  • ボタンの大きさ・高さの調整(本記事)
  • ラベル付きボタンの作成
  • 好きな形のボタンを作成
  • Canvas上にボタンを作成

2022/11/7: githubにプロジェクトを共有しました。
github.com

環境構築・プロジェクト作成

環境構築とプロジェクト作成の手順については、こちらの記事を参照してください。

rhikos-prgm.hatenablog.com

利用するコンポーネント

今回の記事で新たに使用するコンポーネントは以下の通りです。

PokeInteractable

Pokeされる側に追加するprefabです。

前回はこのprefabを素のままで使いましたが、今回は見栄えや挙動に関連するコンポーネントの設定をいじっていきます。

PokeInteractable (PokeInteractable)
 +- Model
 |   +- Surface (PointablePlane)
 |   +- BoxProximityField (BoxProximityField)
 +- Visuals
     +- ButtonVisual
        (PokeInteractableVisual, QuadMesh(MeshFilter), MeshRenderer, 
        MaterialPropertyBlockEditor, RoundedBoxProperties, InteractableColorVisual)
ボタンの挙動に関係するコンポーネント
PokeInteractable

PokeInteractableではボタンがHover状態やSelect状態になるための距離や、一定以上押し込んだ時に開放状態にするための距離などの設定できます。
詳細は「ボタンの高さの変更」や「補足」で解説します。

BoxProximityField(IProximityField)

PokeInteractorから最も近いPokeInteractableを検索するために使います。
インタフェースIProximityFieldを実装しています。
IProximityFieldは、任意の点からPokeInteractableへの最も近い点を計算するためのインタフェースです。
PokeInteractableの形状(当たり判定の形状)に応じてBoxProximityField, CircleProximityField, CylinderProximityField, PointProxymityFieldなどを使い分けるイメージでしょうか。
ボタンの横幅・縦幅を変更した場合、BoxProximityFieldのサイズも変更してあげないと、ボタンの見た目上のサイズが変わっても当たり判定部分が変わらないなどの不整合が起きます。

BoxProximityField(緑線)の範囲がボタンの範囲より小さくなっている例

ボタンの見栄えに関係するコンポーネント

PokeInteractable を素のままで置くと、ラベルが無い角丸のボタンが表示されます。
ボタンにラベルを付けたり、ボタンの外見を変える場合は、このprefabを目的に応じてカスタマイズしていく必要があります。
カスタマイズするにあたって、理解しておいたほうがよさそうなコンポーネントについてざっくりと解説していきます。

PokeInteractableVisual

押しボタンのVisualを調整するためのコンポーネントです。
このスクリプトがアタッチされたGameObjectは、ボタンの押下状態に応じて位置が更新されます。
PokeInteractableVisualをアタッチしたオブジェクトの子要素としてボタンの形状(MeshFilter, MeshRenderer)を配置するのが一般的な使い方になるのかと思います。

InteractableColorVisual

ボタンの状態(Normal, Hover, Select)に応じて色を変えるためのスクリプトです。

RoundedBoxProperties

角丸ボタンの形状を決めているスクリプトです。
ボタンのサイズ・コーナーの丸み・塗りつぶし色・境界線の色・境界線の太さ(有無)などを設定できます。
インスペクタ上でパラメータを色々いじってみるのが理解の早道だと思います。

Scene構築手順

Sceneの作成

空のシーンを作成します。(Assets/Sandbox/HandPoke/Scenes/PokeScene1)
途中までは以下の記事のPokeScene0と同様に進めます。
(PokeScene0が作成済みならコピーしてリネームすれば良いです)

rhikos-prgm.hatenablog.com

ボタンを作成する

Interactables/Buttons配下に「Assets/Oculus/Interaction/Runtime/Prefabs/Poke/PokeInteractable」のprefabを配置して「ResizedButton」にリネームします。

ボタンの大きさの変更

ボタンの大きさは以下の手順で変更します。

  • ボタンのサイズの変更
    ResizedButton/Visuals/ButtonVisualのコンポーネントRoundedBoxPropertiesのWidth, Heightを変更します(TransformのScaleの値もWidth/ Heightの値と連動して更新されます)
  • ボタンの当たり判定部分の変更
    ResizedButton/Model/BoxProximityFieldのScaleを変更後のボタンサイズに合うように調整します(下図緑色の枠)

ボタンの高さの変更

ボタン高さ(ボタンが押されていないときの高さ)や、ボタンがHover状態に入る距離を変更していきます。
ボタンがHover状態に入る距離はMaxDistanceで設定します。
ボタン高さはPokeInteractableVisualがアタッチされているオブジェクト(ResizedButton/Visuals/ButtonVisual)のTransformの値で設定します。
ボタン高さはSurfaceからMaxDistanceの間に設定することになると思います。(下図参照)

MaxDistance・ボタン高さ・ReleaseDistanceとボタンの状態

※指がMaxDistanceの距離に到達するとボタンがHover状態になり、ボタン高さに到達するとボタンが動き始める。Surfaceに到達するとSelect状態になり、さらに押し込み続けてReleaseDistanceに到達するとSelect解除(Normal)になる。

以下、具体的な手順です。

MaxDistanceの変更

ResizedButtonのコンポーネントPokeInteractableのMaxDistanceの値を変更します。
元々の値は0.015(=0.015m = 1.5cm程度?)なので、これを0.06(6cm)に変更してみましょう。
シーンビューで確認すると、MaxDistanceの変更に伴い水色の線が伸び縮みするでわかりやすいと思います。

ボタンの高さの変更

ResizedButton/Visuals/ButtonVisualのTransformのz値を変更します。
MaxDistanceが0.06の場合は、z値を-1.2にすることでMaxDistanceの位置とボタンの高さが一致するようになります。
MaxDistanceと同じ値(-0.06)ではないのか?と思うかもしれません。(私は最初戸惑いました)
これはPokeInteractableのprefabのScaleが0.05(1/20)なので、その子要素であるButtonVisualのz値は20倍に設定してあげる必要がある(0.06 * 20 = 0.12)という理由でした。

ButtonVisualのz値を-1.2(-0.06 * 20)に設定

ボタンの高さとMaxDistanceが一致

動作確認

ボタンの大きさや深さが変わりました。

補足:EnterHoverDistanceの値について

本記事の「ボタンの深さの変更」のところではMaxDistanceのみをいじりました。
ボタンの深さに関連するパラメータとしては、他にもReleaseDistance、EnterHoverDistanceの設定項目があります。
公式ドキュメントではMaxDistance(最大距離)とReleaseDistance(一定以上押し込んだ時に開放される距離)についての言及がありますが、EnterHoverDistanceについては触れていません。
PokeInteractableのソースコードや、他サイトの記事を見るかぎりMaxDistance の値とEnterHoverDistance の値の小さいほうがHover判定に使われるようです。
MaxDistanceとReleaseDistanceはなんとなくわかりましたがEnterHoverDistanceはよく理解できなかったため実際に動かしてみました。
(結果から言えば、動かしてみてもよくわかりませんでした。。)

MaxDistance < EnterHoverDistanceの場合

まずはMaxDistance < EnterHoverDistanceの動きを試してみました。設定した値は以下の通りです。

  • MaxDistance ... 0.06
  • EnterHoverDistance ... 0.12
  • ReleaseDistance ... 0.1

MaxDistance < EnterHoverDistance

シーンビューで見るとこの通り。水色の十字マーク(Surfaceの位置)~水色の円までの距離がMaxDistanceです。
わかりやすくするためにRelease, Surface, EnterHover, MaxDistanceの場所にラベル付きの板を配置しています。

実際の動きは以下の通りです。
(ボタンの色はNormalが白、Hoverが青、Selectが赤)
また、ボタンがSurfaceに到達(ボタンを押し切った)したあとで手が分身していますが、これはReleaseDistanceの挙動を調べるためにHandPokeLimiterVisualともともとのHandVisualの両方をアクティブにしているためです。
(HandPokeLimiterVisualで描画している手はSurfaceのところで止まっていて、HandVisualで描画している手はSurfaceを突き抜けている)

EnterHoverDistanceに入ってもHoverせずにMaxDistanceでHover状態になります。
ドキュメントにも、Hover距離はEnterHoverDistanceとMaxDistanceの小さいほうになると書いてあるので、予想通りの動きですね。
ただ、EnterHoverをMaxDistanceより大きい値に設定して嬉しい状況ってあるのか、ちょっとわかりませんでした。

MaxDistance > EnterHoverDistanceの場合

次にMaxDistance > EnterHoverDistanceの動きを試してみました。設定した値は以下の通りです。

  • MaxDistance ... 0.12
  • EnterHoverDistance ... 0.06
  • ReleaseDistance ... 0.1

MaxDistance > EnterHoverDistance

シーンビューで見るとこの通り。

これを動かしてみた時の挙動が以下の通りです。
EnterHoverの距離でHoverになるのかと思ったのですがMaxDistanceの距離でHoverになり、なぜこうなるのかよくわかりませんでした。
(識者の方、教えていただけると嬉しいです...)

参考サイト

公式:Oculusスタートガイド(Unity)
Get Started with Oculus in Unity | Oculus Developers

公式:入力データの概要
※サンプルシーンのInteractionRigOVR~prefab配下のコンポーネント群が何をやっているのか、ざっくり理解するための最適なドキュメントだと思います。
Input Data Overview | Oculus Developers

公式:APIリファレンス
Reference