XR Interaction Toolkitでコントローラーを振動させる
仮想空間上の物体に触ったり、コントローラーのトリガーボタンやグリップボタンなどを押下した時に、手応えがあるとより没入感が高まります。
この手応えを表現する方法として、Oculus Questでは、コントローラーを振動させる事ができます。
コントローラーを振動させるには、振動を発生させるイベント(物体と接触した、ボタンが押下された等)に対して、振動するようにスクリプトを作成します。
具体的には、XR Grab Interacableコンポーネントに定義されているイベントに対して、コントローラーを振動させる処理をスクリプトに記述します。
この記事では、物体に触った時(hoverEntered)と物体を掴んでいる時にコントローラーのActivate Action(トリガーボタンの押下)が発生した時(activated)に振動させてみます。
環境
Unityバージョン:2021.1.12f1
XR Interaction Toolkit:1.0.0-pre.4
Oculus Quest2: v29 ソフトウェア
いままでLTSの最新バージョンを使用していましたが、今回からLTSでない最新バージョンを使用しています。
Oculus Quest2のテスト機能である「VRにデスクを持ち込もう」を有効にしていると起動しません。(回避方法が知りたい・・・)
プロジェクトの作成及び設定
以前の記事「XR Interaction Toolkitで移動する - プロジェクトの作成及び設定」を参考にしてください。
プログラムの作成
■前準備
前回の記事「XR Interaction Toolkitで指定した物を掴む - プログラムの作成」を参考にして、仮想空間上に触る為の物体とコントローラーの設定及びモデルの表示を行い、物体を掴める状態にしてください。
■イベントに対する実装
掴む物体に設定したXR Grab Interacableコンポーネントには、設定可能なイベントが定義されています。
HierarchyビューのCubeを選択し、InspectorビューのXR Grab InteracableコンポーネントのInterctable Eventsを見ると設定可能なイベントが列挙されており、それぞれに対してイベントが発生した時に実行する処理を指定できるようになっています。
これらは、スクリプト上からも設定可能なので、スクリプトから設定するようにします。
ProjectビューのAssetsで右クリックして表示されるコンテキストメニューからCreate > C# Scriptを選択します。作成されたスクリプトの名称を「Haptics」に変更します。
作成したHapticsを開いて以下のように記載します。実装内容については、コード内のコメントを参考にしてください。
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
// 必須コンポーネントとしてXRGrabInteractableを指定
[RequireComponent(typeof(XRGrabInteractable))]
public class Haptics : MonoBehaviour
{
// 外部から設定するパラメータ
[Header("Trigger haptics parameters")]
[SerializeField] float triggerAmplitude = 0.95f; // 振幅 (0.0〜1.0)
[SerializeField] float triggerDuration = 0.2f; // 持続時間(秒) ()
[Header("Grip haptics parameters")]
[SerializeField] float gripAmplitude = 0.3f; // 振幅 (0.0〜1.0)
[SerializeField] float gripDuration = 0.1f; // 持続時間(秒) ()
[Header("Collided haptics parameters")]
[SerializeField] float collidedAmplitude = 0.1f; // 振幅 (0.0〜1.0)
[SerializeField] float collidedDuration = 0.1f; // 持続時間(秒) ()
// コンポーネントが有効化された時に実行
private void OnEnable()
{
// GameObjectに設定されているXRGrabInteractableコンポーネントを取得
XRBaseInteractable interactable = GetComponent<XRGrabInteractable>();
// Activate Action発生時に実行する理を追加
interactable.activated.AddListener(pullTrigger);
// Select Action発生時に実行する処理を追加
interactable.selectEntered.AddListener(pullGrip);
// 接触発生時に実行する処理を追加
interactable.hoverEntered.AddListener(collided);
}
// コンポーネントが無効化された時に実行
private void OnDisable()
{
// GameObjectに設定されているXRGrabInteractableコンポーネントを取得
XRBaseInteractable interactable = GetComponent<XRGrabInteractable>();
// Activate Action発生時に実行する処理を削除
interactable.activated.RemoveListener(pullTrigger);
// Select Action発生時に実行する処理を削除
interactable.selectEntered.RemoveListener(pullGrip);
// 接触発生時に実行する処理を削除
interactable.hoverEntered.RemoveListener(collided);
}
// Activate Action(トリガー押下)発生時に実行する処理
private void pullTrigger(ActivateEventArgs arg)
{
// イベント発生元のXRBaseControllerに振動させる。
arg.interactor.GetComponent<XRBaseController>().SendHapticImpulse(triggerAmplitude,triggerDuration);
}
// Select Action(グリップ押下)発生時に実行する処理
private void pullGrip(SelectEnterEventArgs arg)
{
// イベント発生元のXRBaseControllerに振動させる。
arg.interactor.GetComponent<XRBaseController>().SendHapticImpulse(gripAmplitude,gripDuration);
}
// 接触の発生時に実行する処理
private void collided(HoverEnterEventArgs arg)
{
// イベント発生元のXRBaseControllerに振動させる。
arg.interactor.GetComponent<XRBaseController>().SendHapticImpulse(collidedAmplitude,collidedDuration);
}
}
■スクリプトをオブジェクトに設定
作成したスクリプトHapticsをオブジェクトにコンポーネントとして追加し、スクリプトの設定を行います。
HierarchyビューのCubeを選択し、Inspectorビュー「Add Component」ボタンから作成したHapticsを追加します。
スクリプトで外部から設定するパラメータとして指定した各種パラメータが初期値と共に表示され指定可能になっています。オブジェクト毎に異なる値を設定し、振動を変える事が出来ます。
Cubeと同様に、Sphere、CylinderにもHapticsを設定します。
実行
保存(File > Save)してから、Oculus Quest2が接続されている事を確認し、Build And Run(File > Build And Run)を実行します。
コンパイルが開始され、コンパイル終了後に接続されているOculus Quest2にプログラムが自動転送され実行されます。
右のコントローラーで円柱や正六面体、球に触れると、僅かにコントローラーが振動します。グリップボタンを押下し物体を掴むと振動し、掴んだ状態でトリガーボタンを押下しても振動します。それぞれの振幅や時間が異なる振動となっています。
左のコントローラーでは、コントローラーから伸びている線(Ray)を物体に当てて白色になると僅かに振動します。これは、右のコントローラーの物体に触れた場合と同様の振動になります。グリップボタン押下時、トリガーボタン押下時の挙動は、右のコントローラーと同様です。
参考
■公式APIリファレンス
XRBaseInteractable
ActionBasedController.SendHapticImpulse
■公式スクリプトリファレンス
RequireComponent
Header
SerializeField
終わりに
仮想空間上での動作に対応した物理的なリアクションがあると、実際にそこにある感覚が強まり、なかなか楽しいです。
将来の自分への備忘録、ついでに誰かの何かのきっかけになれば幸いです。