Unity+Oculus Touch開発メモ
最終更新日:2018年11月18日
UnityでOculus Touchを使用する方法やTipsをまとめています。

更新履歴
(2018年11月18日)Oculus Integrationに簡単に対応
(2018年2月4日)Unity本体にVRサポート機能を使用する方法についての記述を「Unity標準のVR機能(UnityEngine.XR)メモ」に移動
(2016年12月26日)「手で物をつかむには」を追加、「Oculus Avatar SDKについて詳しく教えて」を追加
(2016年12月19日)「手やTouchコントローラを表示するには」に、ビルドすると手が表示されない問題について追記(thx: @z_zabaglioneさん)
Oculus Touchについて
Riftには、Touchという両手に握って操作するモーションコントローラが同梱されています。
TouchはRift同様、主に外周リング部に埋め込まれた赤外線LEDと位置トラッキングカメラでトラッキングされます。トラッキング精度の向上と、手や身体でLEDが隠れて位置をロストしないようにするために、2つ以上の位置トラッキングカメラを使用します。Touchを購入するともう1つの位置トラッキングカメラがついてきます。
Touchとの無線通信はRift(ヘッドセット)を介して行われます。また、HTC Viveと異なり、展示等でたくさん並べて使用しても干渉の心配はないようです。
バッテリーは左右それぞれ単三電池一本です。エネループ等も使用できます。展示に使用したところ、連続使用で3時間から4時間ほどもちました。
Touchの位置を取得するには
Asset StoreにあるOculus IntegrationをUnityのプロジェクトにインポートしてください。
ProjectビューのOculus/VR/PrefabsフォルダにあるOVRCameraRigをシーンにドロップし、再生すると、OVRCameraRigの孫のLeftHandAnchor、RightHandAnchorがTouchの位置になります。
また、OVRInput.GetLocalControllerPosition/GetLocalControllerRotationで取得することもできます。
Touchの入力を取得するには
OVRInputクラスでTouchのボタン・スティック・トリガーの状態を取得できます。
OVRInputは入力を抽象化して異なる種類のコントローラを共通して使用できるように設計されていますが、ややこしいので、とにかくTouchの入力を一通り読む方法をコードの形で示します。正確なところはOVRInputのドキュメントページを参照してください。
ボタン・トリガーを押したか調べるには
if (OVRInput.GetDown(OVRInput.RawButton.A)) {
Debug.Log("Aボタンを押した");
}
if (OVRInput.GetDown(OVRInput.RawButton.B)) {
Debug.Log("Bボタンを押した");
}
if (OVRInput.GetDown(OVRInput.RawButton.X)) {
Debug.Log("Xボタンを押した");
}
if (OVRInput.GetDown(OVRInput.RawButton.Y)) {
Debug.Log("Yボタンを押した");
}
if (OVRInput.GetDown(OVRInput.RawButton.Start)) {
Debug.Log("メニューボタン(左アナログスティックの下にある)を押した");
}
if (OVRInput.GetDown(OVRInput.RawButton.RIndexTrigger)) {
Debug.Log("右人差し指トリガーを押した");
}
if (OVRInput.GetDown(OVRInput.RawButton.RHandTrigger)) {
Debug.Log("右中指トリガーを押した");
}
if (OVRInput.GetDown(OVRInput.RawButton.LIndexTrigger)) {
Debug.Log("左人差し指トリガーを押した");
}
if (OVRInput.GetDown(OVRInput.RawButton.LHandTrigger)) {
Debug.Log("左中指トリガーを押した");
}
GetDown()をGet()に変えると押しっぱなしの状態でtrue、GetUp()でリリースの瞬間にtrueになります。
右手のOculusボタンの状態は取得できないようです。
トリガーの入力をアナログ値で取得するには
// 右人差し指トリガー
float rTrigger1 = OVRInput.Get(OVRInput.RawAxis1D.RIndexTrigger);
// 右中指トリガー
float rTrigger2 = OVRInput.Get(OVRInput.RawAxis1D.RHandTrigger);
// 左人差し指トリガー
float lTrigger1 = OVRInput.Get(OVRInput.RawAxis1D.LIndexTrigger);
// 左中指トリガー
float lTrigger2 = OVRInput.Get(OVRInput.RawAxis1D.LHandTrigger);
離すと0.0、押し込むと1.0になります。ただし、完全に押し込んでも1.0まで上がらなかったり、すぐに1.0にならなかったりしますので、押し込んだかどうか判定する場合には閾値を設ける必要があります。
選択、キャンセル入力を取得するには
Button.One、Twoを使用すると便利です。それぞれTouchのA/Xボタン、B/Yボタンにマップされていますが、セットアップ時の説明にも表示される標準的な操作となっている上に、Gear VRやOculus Remote、Xboxコントローラーでも変更なしで使用できます。
if (OVRInput.GetDown(OVRInput.Button.One)) {
Debug.Log("選択した");
}
if (OVRInput.GetDown(OVRInput.Button.Two)) {
Debug.Log("キャンセルした");
}
アナログスティックの入力を取得するには
Vector2で取得できます。X軸が左-1.0~右1.0、Y軸が下-1.0~上1.0になっています。
// 左手のアナログスティックの向きを取得
Vector2 stickL = OVRInput.Get(OVRInput.RawAxis2D.LThumbstick);
// 右手のアナログスティックの向きを取得
Vector2 stickR = OVRInput.Get(OVRInput.RawAxis2D.RThumbstick);
アナログスティックをボタンとして押し込むこともできます。
if (OVRInput.GetDown(OVRInput.RawButton.LThumbstick)) {
Debug.Log("左アナログスティックを押し込んだ");
}
if (OVRInput.GetDown(OVRInput.RawButton.RThumbstick)) {
Debug.Log("右アナログスティックを押し込んだ");
}
また、デジタル4方向の入力も取得できます。メニュー選択等に便利かもしれません。
if (OVRInput.GetDown(OVRInput.RawButton.LThumbstickUp)) {
Debug.Log("左アナログスティックを上に倒した");
}
if (OVRInput.GetDown(OVRInput.RawButton.LThumbstickDown)) {
Debug.Log("左アナログスティックを下に倒した");
}
if (OVRInput.GetDown(OVRInput.RawButton.LThumbstickLeft)) {
Debug.Log("左アナログスティックを左に倒した");
}
if (OVRInput.GetDown(OVRInput.RawButton.LThumbstickRight)) {
Debug.Log("左アナログスティックを右に倒した");
}
if (OVRInput.GetDown(OVRInput.RawButton.RThumbstickUp)) {
Debug.Log("右アナログスティックを上に倒した");
}
if (OVRInput.GetDown(OVRInput.RawButton.RThumbstickDown)) {
Debug.Log("右アナログスティックを下に倒した");
}
if (OVRInput.GetDown(OVRInput.RawButton.RThumbstickLeft)) {
Debug.Log("右アナログスティックを左に倒した");
}
if (OVRInput.GetDown(OVRInput.RawButton.RThumbstickRight)) {
Debug.Log("右アナログスティックを右に倒した");
}
ボタン・スティック・トリガーに触れているか調べるには
中指トリガーとメニューボタン、Oculusボタン以外について、指が触れているかどうかを調べることができます。気づきにくいのですが、ABXYボタンの内側にもタッチに反応する箇所があります(円形の部分)。
if (OVRInput.Get(OVRInput.RawTouch.LIndexTrigger)) {
Debug.Log("左人差し指用トリガーに触れている");
}
if (OVRInput.Get(OVRInput.RawTouch.LThumbstick)) {
Debug.Log("左アナログスティックに触れている");
}
if (OVRInput.Get(OVRInput.RawTouch.LThumbRest)) {
Debug.Log("X、Yボタンの右の丸いところに触れている");
}
if (OVRInput.Get(OVRInput.RawTouch.RIndexTrigger)) {
Debug.Log("右人差し指用トリガーに触れている");
}
if (OVRInput.Get(OVRInput.RawTouch.RThumbstick)) {
Debug.Log("右アナログスティックに触れている");
}
if (OVRInput.Get(OVRInput.RawTouch.RThumbRest)) {
Debug.Log("A、Bボタンの左の丸いところに触れている");
}
if (OVRInput.Get(OVRInput.RawTouch.A)) {
Debug.Log("Aボタンに触れている");
}
if (OVRInput.Get(OVRInput.RawTouch.B)) {
Debug.Log("Bボタンに触れている");
}
if (OVRInput.Get(OVRInput.RawTouch.X)) {
Debug.Log("Xボタンに触れている");
}
if (OVRInput.Get(OVRInput.RawTouch.Y)) {
Debug.Log("Yボタンに触れている");
}
近接センサーの入力を調べるには
人差し指用トリガーとアナログスティックには近接センサーがあり、指が近づいた(ちょっと浮いている)状態を取得できるようになっています。
if (OVRInput.Get(OVRInput.RawNearTouch.LIndexTrigger)) {
Debug.Log("左人差し指用トリガーの近くに指がある");
}
if (OVRInput.Get(OVRInput.RawNearTouch.LThumbButtons)) {
Debug.Log("左アナログスティックの近くに指がある");
}
if (OVRInput.Get(OVRInput.RawNearTouch.RIndexTrigger)) {
Debug.Log("右人差し指用トリガーの近くに指がある");
}
if (OVRInput.Get(OVRInput.RawNearTouch.RThumbButtons)) {
Debug.Log("右アナログスティックの近くに指がある");
}
この近接センサーを使用して、指をさしている状態を取得できます。人差し指を伸ばすとRawNearTouch.RIndexTriggerがfalseになります。
Touchを振動させるには
OVRHapticsClipで振動のクリップを生成して、OVRHapticsで左右のコントローラーを指定して再生します。OVRHapticsClipはAudioClipをもとに生成できます。
public class HapticExample : MonoBehaviour
{
public AudioClip audioClip;
OVRHapticsClip hapticsClip;
void Start()
{
hapticsClip = new OVRHapticsClip(audioClip);
}
void Update()
{
// Aボタンで振動
if (OVRInput.GetDown(OVRInput.RawButton.A)) {
OVRHaptics.RightChannels.Mix(hapticsClip);
}
}
}
AudioClipはモノラルの音声を使用するか、OVRHapticsClip(audioClip, 0)またはOVRHapticsClip(audioClip, 1)でステレオ音声の左右どちらかのチャンネルを指定します。音量をもとに振動の大きさが作られ、音声にあわせた速度で再生されます。
または、バイト列からOVRHapticsClipを作成できます。バイト列は0~255の振動の大きさで表され、一秒間に320バイトの速度で再生されます。
byte[] samples = new byte[8];
for (int i = 0; i < samples.Length; i++) {
samples[i] = 128;
}
hapticsClip = new OVRHapticsClip(samples, samples.Length);
OVRHapticsクラスでは、LeftChannelかRightChannel、またはChannels[0]かChannels[1]で左右どちらのコントローラーを振動させるかを指定します。
以下の関数で振動を制御できます。Mix関数で再生すると、恐竜が歩いている振動にショットガンの振動を重ねたりといったことができるようです。
関数名 | |
---|---|
Mix | 再生中の振動に重ねて再生する |
Preempt | 再生中の振動を停止して再生する |
Clear | 振動を停止する |
Queue | 再生キューに積む |
手やTouchコントローラを表示するには
Oculus VRのダウンロードページからOculus Avatar SDKをダウンロードして、OVRAvatarSDK/Unityフォルダに入っているパッケージをプロジェクトにインポートします。Oculus Utilities for Unityも必要ですのでこちらもインポートしてください。
ProjectビューのOculus/Avatar/SamplesフォルダにTouchコントローラや手、頭のアバターを表示するサンプルシーンが含まれています(詳しくはいずれ)。
立って体験するソフトを作るには
OVRCameraRigプレハブをシーン内の地面と同じ高さに配置し、OVRCameraRigオブジェクトのOVR ManagerコンポーネントのTracking Origin TypeをFloor Levelに変更します。
これで再生するとVR空間と現実世界の地面の高さが合っているはずですが、合っていない場合は、OculusソフトウェアのDevices > Configure Rift > Reset Sensor Trackingで位置トラッキングカメラのキャリブレーションをやり直します。途中で身長を正確に入力し、立った状態でキャリブレーションする必要があります。ボタンを押したときのヘッドセットの高さと入力した身長をもとに床の高さが決定されます。
手で物をつかむには
下記記述はおそらく古すぎて現在動作しません。そのうち更新します。
公式フォーラムのこのスレッドで、Oculus Avatar SDKを使って物を掴むサンプル(AvatarGrabSample.zip)が提供されています。以下の手順に従ってください。
- 新規プロジェクトを作成
- Oculus Utilitiesをインポート
- Oculus Avatar SDKをダウンロードして、UnityフォルダのOvrAvatarパッケージをインポート
- AvatarGrabSampleパッケージをインポート
- Player SettingsでVirtual Reality Supportedをオン
- Samples/Content/AvatarWithGrabシーンを開く
再生するにはOculusのApp IDが必要です(ないと「No Oculus Rift App ID has been provided」のエラーが出ます)。開発者用ダッシュボードにアクセスして、アプリケーションを登録していない場合は「Create New App」をクリック、Oculus Riftを選択、適当なアプリ名(「Avatar Grab Sample」など)を入力してSave and Continue、App IDの数字をコピーして、UnityのOculus Avatars > Edit ConfigurationでインスペクタのOculus Rift App Idに入力してください。
HierarchyビューのDynamic以下のオブジェクトを参考に、GrabbableスクリプトとRigidbodyをアタッチすると掴めるオブジェクトになります。
ビルドすると手が表示されない場合、「手やTouchコントローラを表示するには」の注釈を参考にシェーダを登録してx86_64ビルドに変更してください。
また、そのままだと掴んだオブジェクトがカクついて動くはずです。Edit > Project Settings > TimeでFixed Timestepを0.0111111(90fps相当)に変更するととりあえずなめらかになります。
手順で詰まった場合は、きゅーこんさんのページも参考にしてみてください。
Oculus Avatar SDKについて詳しく教えて
凹みTipsを見てください(丸投げ)。
Unity本体のVRサポートでTouchを使用する場合
下記ページを参照してください。
参考リンク
- Oculus
- Oculus公式フォーラム
- Unity公式フォーラム - VR
- Oculusデベロッパー助け合い所(Facebookグループ)