Gear VR Frameworkで視線選択を実装するにあたって、ユーザーがオブジェクトを見ているかどうかを判定する必要があります。これはGVRPicker
とIPickEvents
を使って簡単に実装できます。やることは次の4つです。
-
IPickEvents
を実装したクラスを作成する -
GVRScene.getEventReceiver().addListener()
で1.のオブジェクトを登録する -
GVRSceneObject.attachCollider()
で当たり判定を設定する -
GVRPicker
のインスタンスを作成する
以下、順番に見ていきます。
IPickEvents
を実装したクラスを作成する
IPickEvents
インターフェースはユーザーがオブジェクトを見始めた時、見ている間、オブジェクトから目を離した時、といったタイミングでのコールバック処理を定義します。
class PickHandler implements IPickEvents {
/**
* オブジェクトを見始めた時に呼ばれる。
*
* @param sceneObj 見ているオブジェクト
* @param pickInfo 見ている位置等の情報
*/
public void onEnter(GVRSceneObject sceneObj, GVRPicker.GVRPickedObject pickInfo) {
}
/**
* 何かを見ている間、毎フレーム呼ばれる。
*
* @param sceneObj 見ているオブジェクト。
* @param pickInfo 見ている位置等の情報
*/
public void onInside(GVRSceneObject sceneObj, GVRPicker.GVRPickedObject pickInfo) {
}
/**
* オブジェクトから視線を外した時に呼ばれる。
*
* @param sceneObj 見ていたオブジェクト
*/
public void onExit(GVRSceneObject sceneObj) {
}
/**
* 何かを見始めた時に呼ばれる。
*
* @param picker
*/
public void onPick(GVRPicker picker) {
}
/**
* 何も見ているものがなくなった時に呼ばれる。
*
* @param picker
*/
public void onNoPick(GVRPicker picker) {
}
}
GVRScene.getEventReceiver().addListener()
で1.のオブジェクトを登録する
先程作成したクラスをシーン中の視線選択に使用するために、シーン内で発生したイベントを受け取るEventReceiverにインスタンスを登録します。
scene.getEventReceiver().addListener(new PickHandler());
GVRSceneObject.attachCollider()
で当たり判定を設定する
シーン中のオブジェクトを視線選択するためには、オブジェクトに当たり判定が必要です。一番簡単なのはオブジェクトに設定されている頂点情報をそのまま当たり判定に使用する方法です。そのためにはGVRMeshCollider
を使用します。
sceneObject.attachCollider(new GVRMeshCollider(gvrContext, false));
GVRPicker
のインスタンスを作成する
毎フレーム視線選択判定を行うようにするため、GVRPicker
のインスタンスを作成します。
GVRScene scene = gvrContext.getMainScene();
new GVRPicker(gvrContext, scene);
インスタンスを作成するだけというのがとても違和感あるのですが、ソースを覗くとGVRPicker
のコンストラクタの内部で毎フレーム視線選択判定を行うように自身をフレームワークに登録するGVRPicker.startListening()
を呼び出していることがわかります。なので、インスタンスを作成するだけで動作するのです。
GVRPicker
には視線選択を開始・停止するメソッドが用意されておらず、GVRPicker
をコンポーネントとしてGVRSceneObject
にattachComponent
したりdetachComponent
したりすることで間接的に視線選択の開始・停止を行えます。しかしインスタンスを作成した直後に自動的にも視線選択を開始するようになっていたりと、明らかに実装がおかしいです。
おそらく本来の使い方の想定に近いのは、このようにカメラやシーンのルートオブジェクトのコンポーネントとしてアタッチさせる方法だと思うのですが……このようにするとうまく動作しません。
scene.getMainCameraRig().getOwnerObject().attachComponent(new GVRPicker(ctx, scene));
scene.getRoot().attachComponent(new GVRPicker(ctx, scene));
これで一通りの実装ができました。最後にまとめて書いた例を紹介します。
import android.util.Log;
import org.gearvrf.GVRAndroidResource;
import org.gearvrf.GVRContext;
import org.gearvrf.GVRMain;
import org.gearvrf.GVRMeshCollider;
import org.gearvrf.GVRPicker;
import org.gearvrf.GVRRenderData;
import org.gearvrf.GVRScene;
import org.gearvrf.GVRSceneObject;
import org.gearvrf.IPickEvents;
public class Main extends GVRMain {
private static final String TAG = "Main";
/**
* オブジェクトに視線を合わせたり外したりしたときに呼ばれる処理。
*/
private class PickHandler implements IPickEvents {
/**
* オブジェクトを見始めた時に呼ばれる。
*
* @param sceneObj 見ているオブジェクト
* @param pickInfo 見ている位置等の情報
*/
public void onEnter(GVRSceneObject sceneObj, GVRPicker.GVRPickedObject pickInfo) {
Log.d(TAG, "onEnter: " + sceneObj);
sceneObj.getTransform().setScale(1.1f, 1.1f, 1.1f); // ちょっと大きくする
}
/**
* 何かを見ている間、毎フレーム呼ばれる。
*
* @param sceneObj 見ているオブジェクト。
* @param pickInfo 見ている位置等の情報
*/
public void onInside(GVRSceneObject sceneObj, GVRPicker.GVRPickedObject pickInfo) {
// Log.d(TAG, "onInside: ");
}
/**
* オブジェクトから視線を外した時に呼ばれる。
*
* @param sceneObj 見ていたオブジェクト
*/
public void onExit(GVRSceneObject sceneObj) {
Log.d(TAG, "onExit: ");
sceneObj.getTransform().setScale(1.0f, 1.0f, 1.0f); // 元の大きさに戻す
}
/**
* 何も見ているものがなくなった時に呼ばれる。
*
* @param picker
*/
public void onNoPick(GVRPicker picker) {
Log.d(TAG, "onNoPick: ");
}
/**
* 何かを見始めた時に呼ばれる。
*
* @param picker
*/
public void onPick(GVRPicker picker) {
Log.d(TAG, "onPick: ");
}
}
@Override
public void onInit(GVRContext ctx) throws Throwable {
super.onInit(ctx);
GVRScene scene = ctx.getMainScene();
scene.getEventReceiver().addListener(new PickHandler());
new GVRPicker(ctx, scene);
// 視線選択カーソル
GVRSceneObject cursor = new GVRSceneObject(ctx, 0.5f, 0.5f, ctx.getAssetLoader().loadTexture(new GVRAndroidResource(ctx, R.raw.cursor)));
cursor.getTransform().setPosition(0, 0, -10);
cursor.getRenderData().setRenderingOrder(GVRRenderData.GVRRenderingOrder.TRANSPARENT);
scene.getMainCameraRig().addChildObject(cursor);
// 選択ボタン
GVRSceneObject btn1 = new GVRSceneObject(ctx, 1, 1, ctx.getAssetLoader().loadTexture(new GVRAndroidResource(ctx, R.raw.btn1)));
btn1.setName("btn1");
btn1.getTransform().setPosition(-3, 0, -10);
btn1.attachComponent(new GVRMeshCollider(ctx, false));
ctx.getMainScene().addSceneObject(btn1);
GVRSceneObject btn2 = new GVRSceneObject(ctx, 1, 1, ctx.getAssetLoader().loadTexture(new GVRAndroidResource(ctx, R.raw.btn2)));
btn2.setName("btn2");
btn2.getTransform().setPosition(0, 0, -10);
btn2.attachComponent(new GVRMeshCollider(ctx, false));
ctx.getMainScene().addSceneObject(btn2);
GVRSceneObject btn3 = new GVRSceneObject(ctx, 1, 1, ctx.getAssetLoader().loadTexture(new GVRAndroidResource(ctx, R.raw.btn3)));
btn3.setName("btn3");
btn3.getTransform().setPosition(3, 0, -10);
btn3.attachComponent(new GVRMeshCollider(ctx, false));
ctx.getMainScene().addSceneObject(btn3);
}
}
IPickEvents
の呼ばれる順番
IPickEvents
のメソッドは、以下の順番で呼ばれます。
onEnter
onPick
-
onInside
(複数回) onExit
onNoPick