Oculus GoのコントローラからレーザーポインタによりUIを操作するまで
2018/5/27
2018/6/10 配布スクリプトの不具合の対策記事を追加
英語が読めるならリンク先を読んだ方が手取り早いのでそちらをおすすめ。
準備
Unity (Ver 2018.1.1f1) 上で以下の準備をする。
新規プロジェクトを作成してAsset StoreよりOculus Integrationをインストール。
Build SettingをOculus用にカスタマイズ
カメラをOVRCameraRigに差し替え、{Left/Right}HandAnchorにTrackedRemoteを追加して設定する。
わからないようなら
などを参照する。
手順
上記リンク先からUpdatedControllerInteraction.part1〜3.rarをダウンロード、展開する。 UpdatedControllerInteraction.unitypackageをプロジェクトにインポートする。
注:UpdatedControllerInteraction.unitypackageは上記rar全てに入っているがどれを使うべきはよくわからないのでここでは〜3.rarを利用する
なお、2018.5.27現在はこのコントロールは独自にDLが必要だが、将来的にOculus Integration Assetに統合されると思われる。
https://gyazo.com/67d53b2006b6983057a6dfbeaa6e6d0b
AssetにOVRInputSelectionが追加される。
Selection Visualizer作成
Hierarchy上でCreate Emptyして名前は"SelectionVisualizer"にする。
インスペクタよりAdd Componentにて Effects > Line Renderer を追加して以下の設定にする。
Cast Shadows : Off
Receive Shadows : チェックしない
Materials : レーザー用の任意のマテリアルを作成する
MaterialのShaderはUnlit/Colorにする
Width : 0.02
同様にSelectionVisualizerの子としてSphereを追加して、x,y,zのscaleを0.05にする。
(注この設定はポインタの先に表示するもののはずだが、現在のスクリプトでは衝突判定が反映されていないので表示されることはない。対策は後の項で説明する)
Assets > OVRInputSelection > InputSystem > OVRPointerVisualizerを追加して、インスペクタを以下の設定にする
Tracking Space = OVRCameraRig > TrackingSpace
Line Pointer = Selection Visualizer自身
Gaze Pointer = Selection Viualizer > Shpere
https://gyazo.com/73dc15a6703d033167cae0f25a3cdc7a
Selection Visualizerのインスペクタ例
Canvasの追加
Hierarchy上で UI > Canvasを追加してインスペクタを以下の設定にする
Canvas > Render Mode : WorldSpace。(これを行わないとCanvasのTransform指定が出来ない)
Canvas > Event Camera : OVRCameraRig > TrackingSpace > CenterEyeAnchor
Transform > Pos(x,y,z) : (0,0,200)
Width: 500
Height: 200
Graphic Raycasterコンポーネントがあるので削除(Remove)し、代わりにAssets > OVRInputSelection > InputSystem > OVRRaycasterを登録する。
Transform, Width, Heightは必要に応じて調整する。
ここではカメラPos, Rotation共に(0,0,0)である前提で配置した。
https://gyazo.com/67a1e091f8c6d9d909901d207faf95f5
Canvasのインスペクタ例
EventSystemの設定
Canvas追加時、同時にEventSystemも作成されている。EventSystemのインスペクタを以下の設定にする。
Standalone Input Moduleを削除(Remove)し、代わりにAssets > OVRInputSelection > OVRInputModuleを登録する。
OVRInputModuleのTracking Space = OVRCameraRig > TrackingSpace に設定する(必須では無いっぽい)
https://gyazo.com/f660f71b44c3e679961e707d6a492e82
EventSystemのインスペクタ例
ここまでで既にUI上のコントロールが操作可能になる。
検証用にCanvasに以下のコントロールを追加して操作可能か試してみる。
UI > Buttonを追加
Pos (x,y,z) : (0, 20, 0)
UI > Sliderを追加
Pos(x,y,z): (0,-20,0)
https://youtu.be/-oQftDgQsuY
このようにUIコントロールが操作可能となる。
ここでUIに反応が無いようなら設定がうまくいっていないので遡って検証する。
イベント確認
一応UIのイベント通知を確認する。ここから先は通常のUI処理なので他のサイトのより詳しい説明を読んだ方が良い。
Canvasに以下を追加
UI > Text
Pos(x,y,z): (0, 70, 0)
Width: 400
Height: 60
Hierarchy上でCreate Emptyして名前は"GameController"にする。
GameControllerに Assets > OVRInputSelection > Scripts > UIInteractionを追加する。
UIInteractionのOut Text に Canvas > Textを登録する。
なおこのUIInteractionはデモ用のスクリプトなので特に重要では無い。
Canvasのコントロールに以下を設定する。
ButtonのOnClickにイベントを追加(+ボタン押下)して以下を設定する
Object : GameController
Function : UIInteraction > OnButtonClicked
SliderのOnValueChangedにイベントを追加(+ボタン押下)して以下を設定する
Object : GameController
Function : UIInteraction > OnSliderChanged
https://youtu.be/-sGjPUJ1ah8
このようにUIコントロールのイベント通知からスクリプトを呼び出して処理が可能となる。
おまけ:LineRenderの線がオブジェクトを貫通する問題
事例の通りに行い、3Dオブジェクトを配置するとLineRenderがオブジェクトを貫通してしまっている。
これはOVRPointerVisualizerスクリプトがraycast判定を行なっていないためである。
何故そうしているかは不明だが、とりあえず以下のように変更することで対応できる。
Asset > OVRInputSelection > OVRPointerVisualizerをエディタで開く
65〜74行のSetPoint関数を以下のように変更する
code:OVRPointerVisualizer.cs
public void SetPointer(Ray ray) {
float distance = rayDrawDistance;
if (linePointer != null) {
RaycastHit hit;
if (Physics.Raycast(ray, out hit, rayDrawDistance)){
distance = hit.distance;
}
linePointer.SetPosition(0, ray.origin);
linePointer.SetPosition(1, ray.origin + ray.direction * distance);
}
if (gazePointer != null) {
gazePointer.position = ray.origin + ray.direction * distance;
}
}
で、これでrayのlineは衝突位置で止まるようになるのだが、相変わらずgazePointer(衝突位置のマーカー)が表示されない。
よく見たらSetPointerVisibility()がバグっているので以下のように修正する
code:OVRPointerVisualizer.cs
public void SetPointerVisibility() {
if (trackingSpace != null && activeController != OVRInput.Controller.None) {
if (linePointer != null) {
linePointer.enabled = true;
}
if (gazePointer != null) {
//gazePointer.gameObject.SetActive(false);
gazePointer.gameObject.SetActive(true);
}
}
else {
if (linePointer != null) {
linePointer.enabled = false;
}
if (gazePointer != null) {
//gazePointer.gameObject.SetActive(true);
gazePointer.gameObject.SetActive(false);
}
}
}
で、このまま実行すると面白いことになる(やって見るとわかる)。
面白いので放置してもいいのだがやはり困るので対策する。
対策はgazePointer用のSphereのlayerを"Ignore Raycast"に変更すれば良い。
https://gyazo.com/e7c43447845c59d7408bbe407b906593
これでようやくマーカーが表示される。
https://gyazo.com/2d4936856492fb74905d6d27a4533037
わかりづらいけど光の先に小さくマーカーが付いているのがわかる
オブジェクトの衝突判定
あとで清書する。
OVRCameraRigにOVRPhysicsRaycasterを追加
ヒエラルキー上でCubeを追加。カメラから見える位置に調整する。
オブジェクトはColliderが必須なのでチェックを外したりremoveしてはいけない
新規にイベントハンドラー用スクリプトを書く
code:PointerEventController.cs
...
public void onPointerClick(BaseEventData data){
Debug.Log("clicked");
}
ヒエラルキー上から"Create Empty"でオブジェクトを作成し、PointerEventControllerを追加する。名前はPointEventContollerとしておく。
配置したCubeオブジェクトにAdd ComponentでEvent Triggerを追加し、その中で"Pointer Click"を追加する。
"Pointer Click"の中で"+"ボタンを押して、上記のPointEventContollerのonPointerClickを指定する。
以上がオブジェクト側でのClickイベント受信の実装になる。
この方法ではPointerがホバーでEnter/Exitしたタイミングでイベントが取れない。
そのため、別の方法で実装する。
ヒエラルキー上の任意のオブジェクトにOVRRawRaycasterを追加する。(先ほどのPointerEventControllerに追加しても構わない)
OVRRawRaycasterのインスペクタには最初から5種類のイベントが用意されている
OnHoverEnter
OnHoverExit
OnHover
OnPrimarySelect
OnSecondarySelect
OnPrimarySelectは上記のonPointerClickと同じタイミングでPrimaryTrigger(Oculus Goではガントリガー)のアクションの時のみ反応する。
OnSeconardySelectは同様にSecondaryTrigger(Oculus Goではタッチパッドのクリック)の時のみ反応する。
イベント通知を受診するためのコードを書く。ここでは先ほどのPointerEventControllerに追記する。
code:PointerEventController.cs
...
public void OnHoverEnter(Transform t) {
Debug.Log("OnHoverEnter");
}
public void OnHoverExit(Transform t) {
Debug.Log("OnHoverExit");
}
ここでHover状態のみMaterialを切り替えるなどしてユーザーにクリック可能であると伝えることができる。
地面との接触位置
あとで書く
適当なスクリプト(なければ追加して適当なオブジェクトに登録する)に
code:csharp
public void OnGroundClick(UnityEngine.EventSystems.BaseEventData data) {
var pData = (ControllerSelection.OVRRayPointerEventData)data;
Vector3 destinationPosition = pData.pointerCurrentRaycast.worldPosition;
...
}
のようにBaseEventDataを引数に持つ関数を用意する。
上記のdestinationPositionが、ポインタで示された位置となる。
地面オブジェクトにColliderがなかったら追加する(Is Triggerはoffで良い)
地面オブジェクトにEvent Triggerを追加し、Pointer Click(BaseEventData)と登録し、先ほどのオブジェクト&関数を登録する。