Androidスマホで自由に動き回れるVRアプリを作る方法（Unity・ARCore・Google VR(Cardboard)）

こんな感じに部屋の中を動き回れるものを作ることができます。

![ARCoreVR_drop16.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427156/c2a4f659-cdc5-9071-de67-1461a51de69e.gif)
© Unity Technologies Japan/UCL

いわゆる、6DoFっていうやつです。
ARCoreに対応しているスマホと、スマホ装着型のVRゴーグルを用意すれば誰でも試せます。
スマホ内蔵のカメラを使いますが、マーカーなどは必要ありません。

# 補足
- 古い機種やiPhoneといったARCore非対応の端末には使えません。（iPhoneの場合は、ARKitを使えばいけるかもしれないけど、試したことはありません）
- ARCoreを使っていますが、ARアプリではありません。（カメラ画像とモデル表示の位置がずれてしまうため、そのままではARでは使えないです）
- 自分の足で動きまわることができますが、なるべく広い部屋で行い、壁などにぶつからないように注意してください。
- 起動時にはカメラ画像が表示されますが、その時は床を映してください。床を認識次第、VRの画面に移行します。

# 用意するもの
- Unity 2018.3.8f1
- arcore-unity-sdk-v1.9.0.unitypackage（[ダウンロード先](https://github.com/google-ar/arcore-unity-sdk/releases)）
- GoogleVRForUnity_1.200.0.unitypackage（[ダウンロード先](https://github.com/googlevr/gvr-unity-sdk/releases)）

# 手順
1) 新しいプロジェクトを作成
2) File→Build Settings...から設定画面を開き、Androidをクリック、その後Switch Platformボタンを押して、設定を切り替える
3) arcore-unity-sdk-v1.9.0.unitypackageをUnityエディタ上のAssetsフォルダにドラッグアンドドロップし、インポートする
4) GoogleVRForUnity_1.200.0.unitypackageも同様にインポートする
5) シーン上にあるカメラを削除する
6) Assets/GoogleARCore/Prefabsにある、「ARCore Device」プレハブをHierarchyにドラッグアンドドロップし、シーンに追加
7) 空のゲームオブジェクトを追加
8) 以下のスクリプトを作成

```csharp:TrackingObjects.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using GoogleARCore;

public class TrackingObjects : MonoBehaviour
{
    // 非表示のままにしておくオブジェクト
    List<GameObject> hideObjs_ = new List<GameObject>();
    // 認識した床
    DetectedPlane plane_ = null;
    // カメラ画像のRenderer
    ARCoreBackgroundRenderer backgroundRenderer_;

    void Start()
    {
        // 子オブジェクトを全て非表示に
        foreach (Transform trans in transform)
        {
            if (trans.gameObject.activeSelf)
            {
                trans.gameObject.SetActive(false);
            }
            else
            {
                // すでに非表示にしているものは除外
                hideObjs_.Add(trans.gameObject);
            }
        }

        backgroundRenderer_ = FindObjectOfType<ARCoreBackgroundRenderer>();
    }

    void Update()
    {
        // タッチされていたら位置をリセット
        if (Input.touchCount > 0)
        {
            ResetPosition();
        }

        if (Session.Status != SessionStatus.Tracking)
        {
            return;
        }

        if (plane_ == null || plane_.TrackingState == TrackingState.Stopped)
        {
            // 新しい床の取得
            plane_ = null;
            List<DetectedPlane> planes = new List<DetectedPlane>();
            Session.GetTrackables<DetectedPlane>(planes, TrackableQueryFilter.All);
            foreach (DetectedPlane plane in planes)
            {
                if (plane.PlaneType != DetectedPlaneType.Vertical)
                {
                    plane_ = plane;

                    transform.position = new Vector3(transform.position.x, plane_.CenterPose.position.y, transform.position.z);

                    // 子オブジェクトを表示
                    foreach (Transform trans in transform)
                    {
                        if (!hideObjs_.Contains(trans.gameObject))
                        {
                            trans.gameObject.SetActive(true);
                        }
                    }

                    // カメラ画像を非表示
                    backgroundRenderer_.enabled = false;

                    break;
                }
            }
            
            if (plane_ == null)
            {
                // 子オブジェクトを非表示
                foreach (Transform trans in transform)
                {
                    if (!hideObjs_.Contains(trans.gameObject))
                    {
                        trans.gameObject.SetActive(false);
                    }
                }

                // カメラ画像を表示
                backgroundRenderer_.enabled = true;
            }
        }
    }

    // 位置のリセット
    public void ResetPosition()
    {
        Transform transCamera = Camera.main.transform;
        if (plane_ != null)
        {
            transform.position = new Vector3(transCamera.position.x, plane_.CenterPose.position.y, transCamera.position.z);
        }
        else
        {
            transform.position = transCamera.position;
        }
    }
}
```

9) 7)のオブジェクトに8)のスクリプトをアタッチ
10) VR画面上に表示させたいゲームオブジェクトを、7)のオブジェクトの子オブジェクトに設定
11) シーンを保存
12) File→Build Settings...→Player Settings...から、以下の設定を変更

- Other Settings→Identification→Package Name：適当に変更
- Other Settings→Identification→Minimum API Level：Android 7.0 'Nougat' (API Level 24)
- XR Settings→Virtual Reality Supported：オン
- XR Settings→Virtual Reality SDKs：Cardboardを追加
- XR Settings→ARCore Supported：オン

# あとがき
この内容はすでにやった人がいるようでしたが、私でも試してみたところ、スクリプト1枚で済んでしまったので、それを記事として作ってみました。
これだけだと、いろいろ不都合な点がありますが、等身大のキャラクターが見れるだけでも結構面白いですので、試してみてください。