Android
Unity
VR
ハコスコ
GoogleCardboard

UnityでVRアプリを作ってみよう!

メリークリスマス!! 株式会社ゆめみでVR/AIリサーチフェローをやっている三浦テツンドです。この記事ではクリスマスにふさわしい、サンタになりきれるVRアプリをハンズオン形式で作ってみようと思います。Unity自体が初めての方でも、VRゴーグルとAndroid携帯で実際に体験できるアプリが作れます。

この記事は、2017年8月に株式会社ゆめみの勉強会で行われたVRSummerLessonをクリスマス仕様に改修してお送りします。

またゆめみでは、VR-StudiesというVRプログラミング教材の配信を行っています。こちらもよろしくお願いします。https://www.yumemi.co.jp/vrstudies/


///// スーパー・ジャンプ・サンタ /////

今回のハンズオンでは、空から降ってくるクリスマスプレゼントを踏むと、サンタがスーパージャンプするVRアプリを作ります。高いところにあるプレゼントを踏むことで、さらに高くジャンプできます。VRゴーグルを通して見ることで本当に空を飛ぶような体験ができます。また特別なコントローラがなくても遊べるように作るので、ハコスコ等のGoogleCardboardに対応している安価なVRゴーグルだけで体験できます。

開発・動作環境

  • Unity 2017-2.0f
  • GoolgleCardBoard対応のVRゴーグル
  • Android携帯 - Android4.0以上、メモリ1GB以上、ジャイロ機能あり
    (ビルド対象をiOSにすることでiPhoneでも動作します)

ハンズオン用プロジェクトの取得

ハンズオン用プロジェクト : https://github.com/miuccie-miurror/vr-winterlesson

上記のURLからプロジェクトをダウンロード後、下記のシーンをUnityで開いてください。
VR-winterlesson/Assets/VR-winterlesson/Lesson/VR-winterlesson.unity

シーンを開いたら、ヒエラルキーパネル上に下記のオブジェクトが存在することを確認してください。

なお、今回のレッスンの完成版となるシーンは以下にあります。参考にしてください。
VR-winterlesson/Assets/VR-winterlesson/Complete/VR-winterlesson-complete.unity

ビルドターゲットの変更

ダウンロードしたばかりのプロジェクトはビルドターゲットがPCになっています。今回は最終的にAndroid実機でビルドしますので、ビルドターゲットを変更する必要があります。

  • Unity上部のメニューの、File/Build Settingsで開くパネルの下部にあるSwitch Platformを押してください


プレイヤーの作成

まずはVRゴーグルで覗いた時にプレイヤー自身の分身を表現するオブジェクトを作成します。

Playerオブジェクトの作成

  • ヒエラルキーパネル上で右クリック後、Create Emptyを選択
  • ゲームオブジェクト名をPlayerに変更してくだい
  • TransformコンポーネントのPositionをすべて0にしてください

Cameraの移動

今回はこのPlayerオブジェクトの背後に常時カメラが追随するTPS視点のアプリにしたいため、CameraオブジェクトをD&DしてPlayerオブジェクトの子にします。

Avatarの追加

プレイヤーの実際のアバターとなるオブジェクトをPlayerの子に追加します。事前に作成したPrefabがあるのでこれを使用します。

  • プロジェクトパネルの、VR-winterlesson/Resources/Prefabs/Avatarを、Playerオブジェクトの直下にD&D

  • 床に埋もれてしまうので、Playerオブジェクトのy座標を少し上にあげます。

物理演算の適用

プレイヤーはソリに乗って常にジャンプします。このアクションの実装には、今回は物理演算を使用したいと思います。

Playerオブジェクトに物理演算を適用するため、下記の作業をおこなってください。

  • 衝突検知を行うため、Playerオブジェクトのインスペクタパネルで、Add Componentを押し、Box Colliderコンポーネントを追加

  • 細い緑の線が衝突検知範囲になるので、Avatarオブジェクト全体がちょうど収まるように、Box ColliderコンポーネントのCenterとSizeを調整してください

  • さらに運動エネルギーを反映させるため、Rigid Bodyコンポーネントを追加してください。この際、Collision DetectionにContinuosを指定し、ConstraintsのFreeze Rotationの全座標にチェックを入れてください。

バウンスマテリアルの適用

ソリが床などのオブジェクトと衝突した際に、飛び跳ねるように設定したいと思います。Physics Matrialをコライダーに適用することで自動的にこれが可能になります。

  • プロジェクトパネルの、VR-winterlesson/Resources/Materials/BounceをPlayerオブジェクトにD&Dしてください

  • Bounceマテリアルの値を確認してください。摩擦係数を0にして、バウンスを最大の1にしています

Playerスクリプトの実装

Playerオブジェクトの挙動を動的にコントロールするためにスクリプトを追加します。

  • プロジェクトパネルの、VR-winterlesson/Lesson/Script/Player.csを、Playerオブジェクトにアタッチ

  • Player.csをMonoDevelopで開き、下記の内容を記述してください。

using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;

public class Player : MonoBehaviour {

    //------------------------------------------------------------------------------------------------------------------------------//
    void Update () {

        // アバターをカメラと同じ方向へ向かせる
        var avatar = this.transform.Find("Avatar");
        var camera = GameObject.Find("Main Camera");
        avatar.transform.forward = camera.transform.forward;
    }

    //オブジェクトと衝突したとき
    void OnCollisionEnter(Collision collision) {

        // ジャンプが止まらないように常に上方向に力を加える
        var rigid = this.gameObject.GetComponent<Rigidbody>();
        rigid.AddForce( 0, 3f, 0, ForceMode.Impulse);

        // 一瞬だけ縦方向に縮める
        StartCoroutine( "animateHopping" );
    }

    // ホッピングの簡易アニメーション
    IEnumerator animateHopping() {

        // 縦方向のスケールを縮める
        transform.Find("Avatar").localScale = new Vector3 ( 1, 0.9f, 1 );

        //コルーチンで一定フレーム待つ
        yield return new WaitForSeconds (0.08f);

        // 縦方向のスケールを戻す
        transform.Find("Avatar").localScale = new Vector3 ( 1, 1, 1 );
    }
}


プレイヤーの移動

次にプレイヤーを移動させる処理を実装しますが、今回はVRアプリですので、キーボードなどの汎用入力を利用しないで移動する方法を考えます。

ここでは、VRゴーグルをつけているときの視線ベクトルを利用して移動する方法を採用したいと思います。

Google VR SDKの設定

Google VR SDK : https://developers.google.com/vr/unity/

まずはカメラの状態を、Unityエディタ上で実行したときと、VRゴーグル上で実行したときで同じになるように、Google VR SDKを導入します。本来は上記URLよりSDKをダウンロードしてインポートが必要ですが、今回は事前にプロジェクトに組み込んであるものを利用します。

  • プロジェクトパネルの、VR-winterlesson/Resources/Prefabs/GVRをヒエラルキーパネル上の一番上にD&Dしてください

  • さらにUnityメニュー/File/Build Settings/Player Settings/Other Settingsの中の、Virtual Reality SupportがONになっているか、Virtual Reality SDKs にCardboardが表示されているかを確認してください

この状態でUnity上で実行してみてください。Altキー + マウスでカメラを回転させることが出来たでしょうか。Android/iOSに向けてビルドすると、自動でステレオレンダリングされ、さらにスマホのジャイロによってカメラを回転してくれるようになります。

プレゼントの作成

今回はカメラの視線の先にプレゼントがあった場合、その方向に向かってPlayerオブジェクトに力を加えることで、Playerの移動を実現することにします。

まずは、その移動先の判定に必要となるプレゼントを作成します。

  • プロジェクトパネルの、VR-winterlesson/Resources/Prefabs/Presentをヒエラルキーパネル上にD&Dしてください。位置はPlayerオブジェクトとかぶらないように適当に配置してください

プレゼントに対するレイキャスト

プレイヤーの視線がプレゼントに向いていることを検知するにはどうしたら良いでしょうか?ここではレイキャストを使用して検知することにします。

レイキャストとは、特定の座標から仮想の光線を飛ばして、光線と衝突したオブジェクトを拾うテクニックです。

Unityでレイキャストを行うには、Physics.Raycast()を使用します。これに始点座標と光線の方向ベクトルを与えることで、Colliderを持つオブジェクトと衝突した際に、その座標とオブジェクトを拾うことが可能になります。

PresentオブジェクトにはすでにColliderがアタッチされていますので、あとはカメラの中央座標から光線を投げるだけです。

Player.csのUpdate()を下記のように修正してください。

void Update () {

  // アバターをカメラと同じ方向へ向かせる
  var avatar = this.transform.Find("Avatar");
  var camera = GameObject.Find("Main Camera");
  avatar.transform.forward = camera.transform.forward;

  ////////////////////////////////////////////////////

  // 視線ポインターを非アクティブにする
  camera.transform.Find("Pointer/Pointer").gameObject.SetActive(false);

  // カメラ中央からプレイヤーの視線方向へレイキャスト
  Ray ray = new Ray( camera.transform.position, camera.transform.forward );
  RaycastHit hit;
  if (Physics.Raycast (ray, out hit, 300.0f, 1)) {

    // プレゼントと視線が衝突したとき
    if (hit.collider.gameObject.name == "Present") {

      // 視線ポインターをアクティブにする
      camera.transform.Find("Pointer/Pointer").gameObject.SetActive(true);

      // Rayの衝突地点と現在のプレイヤーの位置から速度ベクトルを作成する
      var direction = hit.point - this.transform.position;
      Vector3 velocity = direction * 1f;

      // プレイヤーに力を加えて移動させる
      var rigid = this.gameObject.GetComponent<Rigidbody> ();
      rigid.AddForce (velocity.x, 0, velocity.z, ForceMode.Force);
    }
  }
}

注意: このままだとPlayerオブジェクトが視線中央にきた場合にレイが先にそちらに当たってしまい、正面にあるプレゼントを検知しにくくなってしまいます。これを避けるには、Playerオブジェクトを選択し、インスペクタ上部にあるLayerにIgnore Raycastを指定します

プレゼントとプレイヤーの衝突

プレゼントとプレイヤーが衝突した際、つまりプレイヤーがプレゼントを踏んだ際にアクションを追加してみたいと思います。

Player.csのOnCollisionEnter()を下記のように変更してください。

//オブジェクトと衝突したとき
void OnCollisionEnter(Collision collision) {

  // ホッピングが止まらないように常に上方向に力を加える
  var rigid = this.gameObject.GetComponent<Rigidbody>();
  rigid.AddForce( 0, 3f, 0, ForceMode.Impulse);

  // 一瞬だけ縦方向に縮める
  StartCoroutine( "animateHopping" );

  ////////////////////////////////////////////////////

  // プレゼントを踏んだ場合
  if (collision.gameObject.name == "Present") {

      // モーションアニメを再生する
      Animator animator = transform.Find ("Avatar/Model").GetComponent<Animator> ();
      animator.Play ("Yeah", 0, 0.0f);
  }
}

Presentスクリプトの実装

同様にPlayerとぶつかったPresent側にもアクションを追加します。プレゼントを踏むとスーパージャンプするようにします。

  • プロジェクトパネルの、VR-winterlesson/Lesson/Script/Present.csを、Presentオブジェクトにアタッチ

  • Present.csをMonoDevelopで開き、下記の内容を記述してください。

using System.Collections;
using UnityEngine;

public class Present : MonoBehaviour {

    void OnCollisionEnter(Collision collision) {

        //プレイヤーと衝突したとき
        if( collision.gameObject.name == "Player" ){

            // ランダムな方向へ爆発させる
            var present = this.gameObject.GetComponent<Rigidbody>();
            present.AddForceAtPosition (
               new Vector3( Random.Range( -300, 300 ), 600, Random.Range( -300, 300 ) ),
                new Vector3( Random.Range( -5, 5 ), Random.Range( -5, 5 ), Random.Range( -5, 5 ) )
            );

            // プレイヤーにも力を加えて、スーパージャンプさせる
            var player = collision.rigidbody;
            player.AddForce( 0, 30f, 0, ForceMode.Impulse );
        }
    }
}

プレゼントの大量生成

PresentMakerオブジェクトの作成

プレゼントがひとつだけでは寂しいので、スクリプトから大量生成したいと思います。
そのためのゲームオブジェクトを作成しましょう。

  • PresentMakerという名前で空のGameObjectを作成します
  • PresentMakerの子にPresentオブジェクトを移動してください

PresentMakerスクリプトの実装

  • プロジェクトパネルの、VR-winterlesson/Lesson/Script/PresentMaker.csを、PresentMakerオブジェクトにアタッチ

  • PresentMaker.csをMonoDevelopで開き、下記の内容を記述してください

using System.Collections;
using UnityEngine;

public class PresentMaker : MonoBehaviour {


    void Start() {

        // 初回のプレゼントを生成する
        GeneratePresents ( 300 );
    }

    public void GeneratePresents( int num ) {

        //プレゼントを大量複製して空から降らす
        for( int i = 0; i < num; i++ ){

            //ランダムな初期位置へ移動
            var present = transform.Find("Present").gameObject;
            GameObject copy = Object.Instantiate( present ) as GameObject;
            copy.name = "Present";
            copy.transform.parent = this.transform;
            copy.transform.position = new Vector3( Random.Range( -150, 150 ), Random.Range( 30, 300 ), Random.Range( -150, 150 ) );
        }
    }
}

さらに、各Presentオブジェクトが踏まれた際に、追加で空から降ってくるようにします。
Present.csのOnCollisionEnter()の最後に下記の処理を追記してください。

OnCollisionEnter(){

--- 省略 ---

  // 追加でプレゼントを生成する
  transform.parent.GetComponent<PresentMaker>().GeneratePresents( 30 );
}

ステージの作り込み

AssetStoreの利用

最後にこのスーパージャンパーが活躍する舞台を作成します。PlayerオブジェクトとPresentオブジェクトは物理演算で動いているので、それらしい3Dモデルをシーンに置いてColliderをアタッチするだけで、そのままインタラクションが可能です。

ステージを作り込むために必要な3Dモデルは、AssetStoreから取得したいと思います。
大量のポリゴン数を消費するようなモデルでなければ、なんでも使用可能ですが、ここではAssetStoreで無料で配布されている、WhiteCityというアセットを使用することにします。

WhiteCity
https://www.assetstore.unity3d.com/jp/#!/content/76766

すでにハンズオンプロジェクト内にインポートしてあるので、ここではそれを使用します。またこのアセットには、個別のビルのモデルだけでなく、サンプルとして各モデルを配置済みのステージが入っていますので、それを利用することにします。

  • プロジェクトパネルの、WhiteCity/Scenes/Sample.unityを開きます
  • ヒエラルキーパネル上にあるGroupオブジェクトをコピーします

  • プロジェクトパネルの、VR-winterlesson/Lesson/VR-winterlesson.unityを開き直します

  • ヒエラルキーパネル上に先ほどコピーしたGroupオブジェクトを貼り付け、名前をCityに変更します

  • Cityオブジェクトの座標をFloorオブジェクトに収まるように調整します
    この際、ビル群の底がしっかりとFloorより下に来るように気をつけてください

コライダーの設定

このCityオブジェクトのビル群に当たり判定をつけるため、Colliderコンポーネントをアタッチします。

  • ヒエラルキーパネル上のCity/Buildings以下の全てのオブジェクトを選択します
  • インスペクタパネルのAdd Componentを押して、Mesh Colliderを追加します

これでビルの上にPlayerが乗れるようになったので、PlayerオブジェクトとPresentMakerオブジェクトの初期位置を調整します。ここではステージ中央のビルの屋上から始まるようにしてみました。

ステージ外へ落ちたときのリスタート

調子に乗ってFloorオブジェクトより外にジャンプしてしまった場合、そのまま永久に落下してしまいます。ステージ外となったときはシーンをリスタートするようにしたいと思います。

Player.csのUpdate()メソッドの先頭に下記の処理を追加してください。

// y座標がマイナスになったらシーンをリセットする
if( this.transform.position.y < -100 ){
    SceneManager.LoadScene (0);
}


VRゴーグルでの確認

ここまでで実装は完了です。次は実際にAndroid上にビルドして、VRゴーグルを通して覗いてみましょう。

なお、ここまでの実装を行った完成版となるシーンが以下にあります。レッスン内ではやらなかった効果音やBGMなども実装してありますので参考にしてください。
VR-winterlesson/Assets/VR-winterlesson/Complete/VR-winterlesson-complete.unity

External Toolsの設定

  • Unity上部メニューの、Unity/Preference/Andoid/SDKに、AndroidSDKへのパスを指定します

Macでの例:/Users/fl_miura/Library/Android/sdk

  • Unity上部メニューの、Unity/Preference/Andoid/JDKに、JavaDevelopmentKitへのパスを指定します

Macでの例:/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/

Build Settingの確認とビルド

  • Unity上部メニューの、File/Build Settingsを開きます

  • Player Settings/Other Settingsの中の、Virtual Reality SupportがONになっているか、Virtual Reality SDKs にCardboardが表示されているかを確認します

  • Player Settings/Minimum API LevelがAndoird4.4になっていることを確認します

  • Android実機をUSBで接続したら、Build And Run を押してビルドします



GoogleCardBoardアプリの設定

はじめてハコスコとCardboardアプリを利用する場合、レンズの焦点距離等があっていない可能性があります。

下記の一覧より、自分の携帯端末と利用しているVRゴーグルの組み合わせを見つけて、Cardboardアプリの設定からQRコードを読み込ませることで、自動で調節することができます。

http://www.hypergridbusiness.com/faq/vr-headset-qr-codes/

ハコスコの場合は下記のQRコードで大抵のスマホ端末を調整できます