Skip to content

102 1 視線コントローラー

miuccie-miurror edited this page Jul 26, 2017 · 2 revisions

102 - VRコントローラー

ここからはVR空間における効果的な入力ユーザーインタフェースをいくつか実装していきます。 現状のVR市場では日々様々なVR機器やアプリがリリースされており、それぞれがVR空間内での最適なUIを模索している状況です。 その中でも物理空間とのインタラクション、またVIVE独自の高精度な位置トラッキングと入力コントローラーを活かしたUIを中心に学んでいきます。

2-1. 視線入力コントローラーの実装   

まずは空間の一箇所を一定時間注視し続けることで対象を選択する視線入力型コントローラーを実装してみます。 このような入力UIは位置トラッキングや手持ちコントローラーを搭載していない、GearVRやハコスコ等に向けたVRアプリで特によく採用されています。

** 今回のサンプルシーンの場所 **
https://github.com/yumemi-inc/vr-studies/tree/master/vol1/VR-studies/Assets/VR-studies/2_VR-controller/2-1_EyeController


新規シーンの構築

新規シーンを作成したら、まずは前章の通りSteamVR Pluginをインポートして、[CameraRig]プレハブを配置後、MainCameraを削除します。


ステージの作成

まずは床を作ります。シーンに適当な大きさで3D Object/Planeを追加してください。

次に視線コントローラーの選択対象となるオブジェクトを作成します。シーンに適当な大きさで3Dオブジェクトを追加してください。 ここではCubeとSphereとCapsuleを以下のように配置してみました。

それぞれのオブジェクトに重力を与えるためにRigidBodyコンポーネントをアタッチします。 あとでスクリプトで重力の切替えを行いたいので、Use GravityをOffにしておきます。


マーカー用Canvasの作成

カメラの中央に表示されるマーカーを作成します。 Hierarchyパネル上の[CameraRig]/Camera (head)の子に、EyeCanvasという名前でUI/Canvasを新規追加します。

この際CanvasのRenderModeをWorldSpaceに変更してワールド座標上にキャンバスを置くようにします。 またScaleを0.0005に、Z位置を0.5以上にすることで、VRデバイスで見たときにちょうど目の前に表示されるようにします。

さらにそのCanvasの子に、MarkerとIndicatorの2つのUI/Imageを追加します。

Markerにはシンプルな円を描いたテクスチャをアタッチし、Indicatorにはその周りを取り囲む円のテクスチャをアタッチします。 この際、IndicatorのImageコンポーネントは、下記のようにImage Typeを設定して円形にアニメーションさせやすくしておきます。


EyeController.csの作成

次にマーカーの機能を実装するスクリプトを作成します。Projectパネル内にEyeControllerという名前でC#Scriptを作成し、 先ほど作成したEyeCanvasオブジェクトにアタッチします。

まずインジケーターの参照を取得して、プレイヤーの焦点が固定されている間に表示するローディングアニメーションを実装します。

void Start () {
  indicator = transform.FindChild ("Indicator").GetComponent<Image>();
}

void AnimateIndicator(bool isOn) {
  if (isOn) {
    indicator.fillAmount += 0.8f * Time.deltaTime;
  } else {
    indicator.fillAmount = 0;
  }
}

次にプレイヤーの視線の中心と選択対象オブジェクトが重なったことを検知するため、Physics.Raycast()を使います。 Physics.Raycast()は、第一引数の座標から第二引数のベクトル方向にレイを飛ばし、最初にぶつかったColliderコンポーネントを持つGameObjectとその衝突情報を返してくれるメソッドです。

bool Physics.Raycast(Vector3 origin, Vector3 direction, out RaycastHit hitInfo, float maxDistance )

FixedUpdate()の中で、EyeCanvasオブジェクトの中心座標からレイを飛ばし、オブジェクトとヒットした場合にヒットイベントを、 さらにヒットした状態で一定時間が経過した際にはクリックイベントを発行するようにします。

void FixedUpdate () {

  // 物理オブジェクトのヒットテスト
  bool hasHit = Physics.Raycast (transform.position, transform.forward, out hitInfo, 10);
  if ( hasHit ) {

    //ターゲットが変更された場合
    if ( hitObject != hitInfo.collider.gameObject ) {

      // 以前のターゲットを無効に
      if ( hitObject ) {
        AnimateIndicator (false);
        DispatchHitEvent (false);
      }

      //ヒットイベント発行
      hasClicked = false;
      hitObject = hitInfo.collider.gameObject;
      DispatchHitEvent (true);

    } else {

      //インジケーターアニメーション開始
      if( hasClicked == false ){
        AnimateIndicator(true);
      }

      //インジケーターが100%になったらクリックイベント発行
      if ( indicator.fillAmount >= 1 ) {
        hasClicked = true;
        indicator.fillAmount = 0;
        DispatchClickEvent();
      }
    }

  } else {

    //インジケーターアニメーション停止
    AnimateIndicator(false);
    DispatchHitEvent(false);
    hitObject = null;
    hasClicked = false;
  }
}

またヒット時のイベントを受け取るオブジェクトは、下記のようにIEyeControllerTargetインタフェースを実装したクラスがアタッチされているオブジェクトのみとします。

public interface IEyeControllerTarget {
  void OnEyeContollerHit(bool isOn);
  void OnEyeContollerClick();
}

void DispatchHitEvent (bool isOn) {
  if (hitObject) {
    var target = hitObject.GetComponent<IEyeControllerTarget> ();
    if (target != null) {
      target.OnEyeContollerHit( isOn );
    }
  }
}
void DispatchClickEvent () {
  if (hitObject) {
    var target = hitObject.GetComponent<IEyeControllerTarget> ();
    if (target != null) {
      target.OnEyeContollerClick();
    }
  }
}

EyeControllerTarget.csの作成

IEyeControllerTargetインタフェースを実装したEyeControllerTarget.csを作成し、これをCube、Sphere、Capsuleの各オブジェクトにアタッチします。

public class EyeControllerTarget : MonoBehaviour, EyeController.IEyeControllerTarget {

	public void OnEyeContollerHit( bool isOn ) {

		// 視線マーカーがヒットしたら色を変える
		gameObject.GetComponent<Renderer> ().material.color = isOn ? new Color ( 1, 1, 0 ) : Color.white;
	}

	public void OnEyeContollerClick() {

		// 視線マーカーでクリックしたら重力を付加する
		var rigid = gameObject.GetComponent<Rigidbody>();
		rigid.useGravity = true;
		rigid.mass = 5.0f;

		var colider = gameObject.GetComponent<Collider> ();
		colider.material.dynamicFriction = 0.95f;
		colider.material.bounciness = 0.99f;
	}
}

シーンの実行

シーンを実行すると、プレイヤーの視線の中心がオブジェクト領域内に侵入した時点で色が変わり、一定時間後に落下したでしょうか。

さらに以下のサンプルシーンではオブジェクトをスクリプトで動的に増やしてランダムに配置してみました。

** 今回のサンプルシーンの場所 **
https://github.com/yumemi-inc/vr-studies/tree/master/vol1/VR-studies/Assets/VR-studies/2_VR-controller/2-1_EyeController