GearVR、CardBoard、Milbox Touch対応したゲームの開発方法

この投稿は、5/26 VR Tech Tokyo #1 でお話させていただいた内容の技術的な詳細です。

最近、VR向けゴーグルが多数出ており、とくにモバイル向けは数えきれないほどあります。

ゲームを作る場合、毎回それぞれのゴーグル向けに作っていると大変です。

そこで、私の独断で、3つのHMDを選び、それぞれ向けにゲームを作る時のTipsと、実際にゲームを作ったときの
手順を整理してみました。

発表資料はSlideshareに公開しました。(2016/9/6追記)

www.slideshare.net




まずは作ったゲームの基本の動きです。

f:id:Takyu:20160526205939j:plain


ほんとは弾の出るタイミングを限定するとか、もう少しゲーム性を持たせようと思ってるのですが、今回の発表までには間に合いませんでした。。
この辺は今後作っていこうと思ってます。

では、開発方法です。

==================================

目次

1.GearVR、MilBox Touch、CardBoardのSDKをインポート

2. tap処理を作成

3. 弾の発射処理を作成

4. 衝撃波の発生を作成

5. 吹き飛ばす処理を作成

6. 敵にダメージを与える処理を作成

7. シーンフェードを作成

8. ビルドする

感想

==================================

1.GearVR、MilBox Touch、CardBoardのSDKをインポート

■GearVR

VR Sample アセットを使います。

インポート後、Project ViewのAssets/VRSampleScenes/Prefabs/Main Camera をHierarchy Viewにドラッグします。

Hierarchy Viewに最初から付いていたMain Cameraは右クリック→deleteで削除します。

■CardBoard


このブログの「3. CardBoard SDKとの連携方法」の3-1から3-4までを参照ください。

magicbullet.hatenablog.jp

■MilBox Touch

このブログの「2.Unitye SDKを使った動作確認」の2-1手前までを参照ください。

magicbullet.hatenablog.jp

2. tap処理を作成

■GearVR

このようなスクリプトを書いて、GameObjectにくっつけると、tapを認識できます。

using UnityEngine;
using VRStandardAssets.Utils;

namespace VRStandardAssets.Examples {
    public class TestGearVRTap : MonoBehaviour {
        [SerializeField]
        private VRInput m_VRInput;

        public GameObject BombPrefab;

        private void OnEnable() {
            m_VRInput.OnDown += HandleDown;
        }


        private void OnDisable() {
            m_VRInput.OnDown -= HandleDown;
        }

        private void HandleDown() {
             /*ここにタップしたときに実行したい処理を書きます*/
             /*下記は今回のゲームで、タップで爆弾を落とすための処理です*/
            GameObject bomb = Instantiate(BombPrefab, transform.position, Quaternion.identity) as GameObject;
            bomb.GetComponent<Rigidbody>().AddForce(transform.forward * 10.0f, ForceMode.Impulse);
        }
    }
}

■CardBoard

CardBoard SDKがインポートできていれば自動的にタップ検出ができるようになっています。

■MilBox Touch

このブログの「2.Unitye SDKを使った動作確認」の2-1から2-5までを参照ください。

magicbullet.hatenablog.jp



3. 弾の発射処理を作成

これは3つのVRゴーグルで共通です。

以下のようなスクリプトを準備し、Cameraオブジェクトの子にくっつけます。

using UnityEngine;
using System.Collections;

public class ThrowBomb : MonoBehaviour {
    public GameObject BombPrefab;
    public AudioClip bomshotClip;
    AudioSource playerAudio;

    void Awake() {
        playerAudio = GetComponent<AudioSource>();
    }

	void Update () {
#if UNITY_EDITOR
        if (Input.GetKeyDown(KeyCode.J)) {
            throwingBomb(); 
        }
#endif
    }

    public void throwingBomb() {
        GameObject bomb = Instantiate(BombPrefab, transform.position, Quaternion.identity) as GameObject;
        bomb.GetComponent<Rigidbody>().AddForce(transform.forward * 10.0f, ForceMode.Impulse);
        playerAudio.Play();
    }
}

AddForceで使われている"forward"とは、現在の視線方向について"前方”という意味です。
この処理により、Playerの向いた方向に向かって弾が飛びます。

また、BombPrefabはSphereです。RigidBodyが付いています。
RigidBodyの重力があることで、弾が水平投射します。


高校物理の解説サイトより引用させていただきました。


f:id:Takyu:20160527001722p:plain

水平投射の例


ところで、弾の発射位置ですが、Cameraと同じにすると目の前から弾が出て気持ち悪くなります。
なので、このように視線よりやや上に発射位置(MilBoxTouchController)を設定しています。

f:id:Takyu:20160527001833j:plain


ちなみに、弾の発射音は下記のサイトの効果音「コミカルなダメージ音」を使わせていただきました。

4. 衝撃波の発生を作成


このブログの手法をベースにさせていただきました。ありがとうございます。

ガンズターン 公式サイト » ゲームエフェクト勉強会のまとめ 後編その3フレア(衝撃波?)っぽいエフェクト

この手法との違いは、発生方向を地面と平行にしていることです。

この衝撃波の発生方向を地面と平行にするには、下記の箇所をHorizontal Billboardに変更します。

f:id:Takyu:20160527225152p:plain


f:id:Takyu:20160527230047g:plain

Horizontal Billboardに変更した場合


f:id:Takyu:20160527230132g:plain

通常のBillboardのままの場合


また、Play On Awakeにチェックをいれることでinstantiate時に衝撃波が発生するようにしてます。

ちょっと小さくて申し訳ありませんが、Particleの設定項目は下記をご覧ください。

f:id:Takyu:20160527102538p:plain

5. 吹き飛ばす処理を作成


2段階で行います。

(1) 吹き飛ばす対象を見つける

Collider[] targets = Physics.OverlapSphere(transform.position, explosionRadius);

Physics.Overというメソッドを使うことで、指定位置(transformposition)から指定半径(explosionRadius)に含まれるGameObjectを取得します。

(2) tagがEnemyのObjectに対してAddExplosionForceを適用する

(1)で見つけたGameObjectについて、以下の処理を実行します。

 foreach (Collider obj in targets) {
         
            if (obj.tag == "Enemy") {

                attackEnemy(obj.gameObject);
                obj.GetComponent<Rigidbody>().AddExplosionForce(explosionForce, transform.position, explosionRadius);
            }
        }

6 敵にダメージを与える処理を作成



_CompeletedAssets/Scripts/EnemyにあるEnemyHealthクラスにアクセスして、ダメージ量を伝えます。

 
   private void attackEnemy(GameObject obj) {
        CompleteProject.EnemyHealth enemyHealth = obj.GetComponent<CompleteProject.EnemyHealth>();
  
  
        if (enemyHealth != null) {
            // ... the enemy should take damage.
       //     Debug.Log("EnemyDamage:" + damagePerBombShockWave);
            enemyHealth.TakeDamage(damagePerBombShockWave,obj.transform.position);
        }else {
            Debug.Log("null!");
        }
    }


なお、サンプルプロジェクトでは、

    
    namespace CompleteProject
   


を宣言しているので、その中のメンバを呼び出すには、

    CompleteProject.EnemyHealth
  


のように宣言する必要があります。

全部まとめたExplosionForce.csは下記です。BombsPrefabにくっつけてます。

using UnityEngine;
using System.Collections;
//using CompleteProject;

public class ExplosionForce : MonoBehaviour {

    public GameObject ShockWavePrefab;
    public AudioClip windClip;
    public int explosionRadius = 10;
    public int damagePerBombShockWave = 20;
    public int explosionForce = 100;

    private SphereCollider spherecollider;
    private GameObject ShockWaveObj;

    AudioSource playerAudio;

    void Awake() {
        playerAudio = GetComponent<AudioSource>();
    }

    void OnTriggerEnter(Collider col) {
     //   Debug.Log("col.name :" + col.gameObject.name);
          if(col.gameObject.name == "Floor") {
            playerAudio.Play();
            explosion();
        }

    }

    private void explosion() {
       this.gameObject.GetComponent<Renderer>().enabled = false;
        ShockWaveObj = Instantiate(ShockWavePrefab, transform.position, Quaternion.identity) as GameObject;
        Collider[] targets = Physics.OverlapSphere(transform.position, explosionRadius);
        foreach (Collider obj in targets) {
            if (obj.tag == "Enemy") {
                attackEnemy(obj.gameObject);
                obj.GetComponent<Rigidbody>().AddExplosionForce(explosionForce, transform.position, explosionRadius);
            }
        }
        StartCoroutine("DestroyBomb");
    }

    private void attackEnemy(GameObject obj) {
        CompleteProject.EnemyHealth enemyHealth = obj.GetComponent<CompleteProject.EnemyHealth>();
        // If the EnemyHealth component exist...
  
        if (enemyHealth != null) {
            // ... the enemy should take damage.
            enemyHealth.TakeDamage(damagePerBombShockWave,obj.transform.position);
        }else {
            Debug.Log("null!");
        }
    }

    IEnumerator DestroyBomb() {
        yield return new WaitForSeconds(1);
        Destroy(ShockWaveObj);
        Destroy(this.gameObject);
    }
}


7. シーンフェードを作成

■MilBox Touchの場合

ベースとしたSurvival Shooterでは、シーンをフェードさせるためにAnimationClipとAnimatorControllerを使っています。
クリア用に作ったAnimation ClipとAnimator Controllerです。

f:id:Takyu:20160527102150j:plain


Survival Shooterのチュートリアルビデオで初めて知りましたが、AnimationClipではuGUIの制御もできます。

なので、クリップ上のタイムラインで画面全体に広げたimageのα値を0から1に変更することで、フェードアウトが実現できます。

上の画像では、ScreenFader : ImageColor:α の値を時間とともに変更する設定が書かれています。

その他、テキストの大きさとαを制御すると、このような表現もできます。

f:id:Takyu:20160527102407g:plain

■GearVRの場合

GearVRでベースにしたVRSampleには、3次元空間全体のフェードアウトを実行する機能がついています。

ですので、それをカウントダウンがゼロになったときにコルーチンの形で呼びます。

      private IEnumerator FadeAndReloadGame() {
            yield return StartCoroutine(m_CameraFade.BeginFadeOut(m_IntroOutroFadeDuration, false));
        }

8. ビルドする


下記に注意してください。

■GearVR

GearVR向けにビルドするときは、ユーザ定義変数に"GEARVR"を追加する

f:id:Takyu:20160527230822p:plain

BuildSettings / Other Settings の中にあります。

VRSupportedを有効化し、MIN API Levelを使用するAndroidのバージョンに合わせる

f:id:Takyu:20160527230941p:plain

下記の記事を参考にしてDeviceIDの取得と、Assets/Plugins/Androidフォルダへのインポートが必要です。

(VR関係の開発環境構築方法が非常に丁寧にまとまっております)

https://framesynthesis.jp/tech/2015/12/gearvr/

■ Milbox Touch

ユーザ定義変数に"MILBOX"を追加すること

追加場所はGearVRと同じです。

VRSupportedを無効化し、MIN API Levelを対象デバイスに合わせる


これで今回のようなゲームが作れます。

感想

複数のモバイルVR対応を試してみましたが、デバイスごとの制約がある中で共通の処理を作っていくのは意外と楽しいものでした。
VRはモバイルに限らずまだまだ増えていくし、どれか一つだけが残るということはあまりないと思います。
なので、複数端末向け対応ができると色々便利ですよね。

あとは最近、下記のブログのようにARというかMRも気になっていて、代表デバイスのHoloLensに興味があります。

magicbullet.hatenablog.jp



今回のゲームの作り方について、VR系のデバイス対応方法は整理できたので、ゲーム性を付ける続きの開発についてはHoloLensも視野に入れようと思っています。

(プラットフォームを変えても開発ができるのはUnityの強みですね)