Skip to content

103 2 ポイントテレポーター

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

今回は、空間上の特定地点に置かれた点をレーザーポインタで示すことでワープ出来る移動方法を実装してみます。 またこのワープの際に視界全体が大きく動くとVR酔いが起こりやすいため、一度画面を暗転させてから移動して再びフェードインするようにしてみます。

この移動方法であれば、プレイヤーが実際に歩くことが出来ない範囲にも移動可能なため、より大きな空間を採用できます。 またVIVEのような位置トラッキングを持たないVRシステムでも移動可能になるため、現在様々なVRアプリで採用されている移動方法です。

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


新規シーンの構築

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


ステージの作成

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

前章と同じようにCubeを使用して階段や障害物を作りますが、今回の移動方法ではVIVEルームの外側やプレイヤーが乗り越えられない高さの場所にでもワープできるので、 その点が確認できるよう下記のように少し広い範囲にオブジェクトを配置してみました。

また、ワープする目印となるポイントをSphereを使って作成します。いくつか作成して適当なオブジェクトの上に配置しておきます。


PointTeleporter.csの作成

レーザーポインタを実装するスクリプトを作成します。Projectパネル内にPointTeleporterという名前でC#Scriptを作成し、Hierarchyパネル上の[CameraRig]/Controller (right/left)オブジェクトにアタッチします。

レーザーポインタを表現する処理は、[2-2. レーザーコントローラーの実装]で作成したLaserController.csと同じなのでこれを継承します。 ただし今回はuGUIとのヒットテストではなく、物理オブジェクトとのヒットテストを行いたいので、VIVEコントローラーの参照とPhysics.Raycast()用のプロパティを宣言しておきます。

public class PointTeleporter : LaserController {

	SteamVR_Controller.Device hand = null;

	RaycastHit hitInfo;
	GameObject hitObject;

	//------------------------------------------------------------------------------------------------------------------------------//
	void Start() {

    SteamVR_TrackedObject handTO = transform.GetComponent<SteamVR_TrackedObject>();
		hand = SteamVR_Controller.Input( (int) handTO.index );

		//レーザーポインタを作成する
		base.CreateLaserPointer();
	}

  ...

次にFixedUpdate()の中で、[2-1. 視線入力コントローラーの実装] で行ったのと同じように、Physics.Raycast()を使用して、空間上に配置されたワープポイントとのヒットテストを行います。

void FixedUpdate () {

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

    //レーザーの長さを調節
    AdjustLaserDistance( hitInfo.distance );

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

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

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

    } else {

      //トリガーを引いたらクリックイベントを発行
      if ( hand.GetPressDown(SteamVR_Controller.ButtonMask.Trigger) ) {
        DispatchClickEvent();
      }
    }
  } else {
    DispatchHitEvent(false);
    hitObject = null;
  }
}

同じようにIPointTeleporterTargetインタフェースを作成して、ヒットイベントを発行できるようにしておきます。

//------------------------------------------------------------------------------------------------------------------------------//
public interface IPointTeleporterTarget {
  void OnPointTeleporterHit(bool isOn);
  void OnPointTeleporterClick();
}
void DispatchHitEvent (bool isOn) {
  if (hitObject) {
    var target = hitObject.GetComponent<IPointTeleporterTarget> ();
    if (target != null) {
      target.OnPointTeleporterHit( isOn );
    }
  }
}
void DispatchClickEvent () {
  if (hitObject) {
    var target = hitObject.GetComponent<IPointTeleporterTarget> ();
    if (target != null) {
      target.OnPointTeleporterClick();
    }
  }
}

PointTeleporterTarget.csの作成

先ほど作成したIPointTeleporterTargetインタフェースを実装するPointTeleporterTarget.csを作成し、各ワープポイントオブジェクトにアタッチします。

このスクリプトでは、レーザーヒット時にHMDの位置を自身がアタッチされているオブジェクトの位置へワープさせる処理を行います。

ここでワープの処理は、SteamVR Pluginに含まれている、シーンのフェードイン/フェードアウトのアニメーションを行ってくれるSteamVR_Fadeクラスを使用することにします。 SteamVR_Fade.csは、[CameraRig]/Camera (head)/Camera (eye)オブジェクトにアタッチすることで使用可能になるので、Start()の中でこのアタッチ処理を行います。

public class PointTeleporterTarget : MonoBehaviour, PointTeleporter.IPointTeleporterTarget {

	GameObject CameraRig = null;
	GameObject CameraEye = null;
	SteamVR_Controller.Device head = null;
	Color color;

	void Start() {

		color = gameObject.GetComponent<Renderer> ().material.color;

		// カメラにSteamVR_Fadeスクリプトをアタッチ
		CameraRig = GameObject.Find ("[CameraRig]");
		CameraEye = CameraRig.transform.FindChild( "Camera (eye)" ).gameObject;
		CameraEye.AddComponent<SteamVR_Fade> ();

		// HMDデバイスの参照を取得
		head = SteamVR_Controller.Input( (int) SteamVR_TrackedObject.EIndex.Hmd );
	}

	...

次に実際のワープ処理を実装します。SteamVR_Fade.Start()によってシーンを暗転し、一定時間後にHMDの位置を変更し、再びシーンをフェードインさせます。

今回のワープ処理では、足元の境界線(シャペロン)も一緒に移動させるために、 HMDオブジェクトの位置を直接変更するのではなく、親オブジェクトである[CameraRig]オブジェクトを移動させることで、ルーム全体を移動するようにします。

またこの移動先のY座標は、前章で作成したPhysicsTeleporterのように、足元へレイキャストして一番最初にヒットしたオブジェクトに合わせています。

void StartTeleport() {

	// 画面をフェードアウトしてカメラの位置を変更した後にフェードイン
	SteamVR_Fade.Start( new Color( 0, 0, 0 ), 0.3f, false );
	Invoke ( "EndTeleport", 0.3f );
}

void EndTeleport() {

	// 自身の真下にレイを飛ばして足元を探る
	RaycastHit hitInfo;
	if ( Physics.Raycast ( transform.position, Vector3.down, out hitInfo, 100 ) ) {

		// ルームごと移動させるために、ターゲット座標とHMD座標との差分をCameraRigのポジションに適用
		CameraRig.transform.position = new Vector3( hitInfo.point.x - head.transform.pos.x,  hitInfo.point.y, hitInfo.point.z - head.transform.pos.z );
		SteamVR_Fade.Start( Color.clear, 1.0f, false );
	}
}

最後にこのStartTeleport()を、ヒットしたときに呼ばれるインタフェースメソッド内で呼び出します。

public void OnPointTeleporterHit( bool isOn ) {
	gameObject.GetComponent<Renderer> ().material.color = isOn ? new Color ( 1, 1, 0 ) : color;
}

public void OnPointTeleporterClick() {
	StartTeleport ();
}

シーンの実行

シーンを実行し、レーザーポインターをワープポイントに合わせてトリガーを引くと、その箇所へ移動できたでしょうか? 興味があればフェードアウトフェードインの処理を外して、暗転の効果がないとどれぐらい酔いやすいのか確認してみてください。

また、以下のサンプルシーンでは、前章で作成したPhysicsTeleporter.csを[CameraRig]にアタッチして、 高いところから踏み外した場合は自然落下する処理を入れています。

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