UnityのARFoundationでAR空間に豆腐を召喚する

# はじめに
TechTrain Advent Calendar 2019の4日目は、UnityでARアプリを開発する際に使用できるSDKの一つであるARFoundationを使って「AR空間に豆腐を召喚するまで」の記事を書いてみました！

# ARFoundationとは
今回紹介するARFoundationとは、Unityが開発しているマルチプラットフォームのAR開発フレームワークです。[Unity’s Handheld AR Ecosystem: AR Foundation, ARCore and ARKit](https://blogs.unity3d.com/jp/2018/12/18/unitys-handheld-ar-ecosystem-ar-foundation-arcore-and-arkit/?_ga=2.117997769.1981158098.1566289349-2040198668.1552376445)で紹介されています。原文は英語なのですが、要点だけ翻訳しておくと、


> ARFoundationを使用することで、ARCoreとARKit両方のプラットフォームで共通した実装ができます。これは、一度アプリを開発すれば両方のデバイスに一切の修正なしでデプロイできるということです。しかし、ARFoundationはARKitとARCore全ての機能を実装し終えたわけではありません。なので、ARFoundationがまだ実装していない任意の機能を実装する場合は、対応するSDKそれぞれを利用して開発する必要があります。これからも機能を追加していくので、ARCoreとARKit全ての機能をARFoundationから扱えるように慣れればいいよね(￣▽￣)

って感じのことをBlogの一部で書いていました。

# 開発環境
- Unity2019.1.10
- ARFoundation2.1.4
- ARKit XR Plugin2.1.2(iPhone実機で試すため)

[AR Foundation Samples](https://github.com/Unity-Technologies/arfoundation-samples)のREADMEに記載があるのですが、ARFoundationのmasterブランチ(最新版)を使う場合はUnity2019.2かそれ以降のバージョンが必要です。

# 豆腐を召喚する
## 1. カメラの用意
まずは、カメラを用意します。ARFoundationには専用のカメラがあるのでそちらを利用するため、まずはMain Cameraを削除します。

そして、HierarchyViewから、XR>AR Session Originを作成します。
<img width="378" alt="スクリーンショット 2019-12-04 1.41.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185248/9c12ffd1-506d-aa01-7722-93169186ee35.png">

すると、AR Session Originの子要素にAR Cameraが新しく作成されていると思います。それがAR空間でのカメラとなります。

XR>AR Sessionを作成します。

<img width="375" alt="スクリーンショット 2019-12-04 1.41.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185248/b3a25400-3607-2ba6-bd4e-fad611c96e09.png">


ARSessionとは何か、まずはUnity公式の説明を原文ママ紹介します。
> Class ARSession. Controls the lifecycle and configuration options for an AR session. There is only one active session. If you have multiple ARSession components, they all talk to the same session and will conflict with each other. Enabling or disabling the ARSession will start or stop the session, respectively.

翻訳すると、
> ARSessionクラスはAR Sessionのライフサイクルなどを制御します。これは、アクティブなシーンに対してただ一つのみ存在します。もし、複数のARSessionコンポーネントがある場合はコンフリクトを起こします。Enable/Disableを切り替えることで、セッションを再開、停止することができます。

となります。AR機能を管理してくれていて、シーンに一つおけばいいんだよって感じですね。

## 2. 平面の検出
次に、ARアプリなどでは、カメラに移った特徴点から平面を検知します。今回も平面を検知した上で、Rayを飛ばし豆腐を召喚したいので、まずは平面を検知しましょう。

平面を検知するには、先ほど作成したARSessionOriginに、AR Plane Managerというコンポーネントを追加します。

<img width="610" alt="スクリーンショット 2019-12-04 1.50.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185248/5e0414a9-42cf-e265-5c09-edd949fb877d.png">

平面検知は、水平方向と、垂直方向の両方を検知することが可能であり、床に豆腐を置いたり、壁にカレンダーをかけたりなどができます。

今回は床に豆腐を召喚したいので、ModeをHorizontalのみにしておきましょう。

*デバッグ時などに、どこを平面として検知しているのかなどを確認したい時があると思います。その際には、Plane Prefabにオブジェクトをセットすることで、検知した平面にそのオブジェクトを描画してくれます。*

## 3. Rayを飛ばして豆腐召喚！
さぁ、いよいよ豆腐を召喚していきましょう💪

AR空間でRayを飛ばして衝突を判定するには、ARRaycastManagerコンポーネントを使用します。そのため、まずはARSessionOriginオブジェクトにAR Raycast Managerコンポーネントを追加しましょう。

<img width="607" alt="スクリーンショット 2019-12-04 2.19.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185248/032f7b91-e586-db54-ea35-cafd16f76c61.png">


次に、実際にRayを飛ばして豆腐を召喚するコードを書いていきます。複雑なコードなどは出てこないので、なんとなく読めると思いますが、RaycastはPhysicsクラスではなく、ARRaycastManagerクラスのものを使うところなどは、ARFoundation独自の実装方法になっていると思います。

```csharp
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;

[RequireComponent(typeof(ARRaycastManager))]
public class PlaceOnPlane : MonoBehaviour
{
    [SerializeField, Tooltip("AR空間に召喚する豆腐")] GameObject tohu;

    private GameObject spawnedObject;
    private ARRaycastManager raycastManager;
    private static List<ARRaycastHit> hits = new List<ARRaycastHit>();

    private void Awake()
    {
        raycastManager = GetComponent<ARRaycastManager>();
    }

    void Update()
    {
        if (Input.touchCount > 0)
        {
            Vector2 touchPosition = Input.GetTouch(0).position;
            if (raycastManager.Raycast(touchPosition, hits, TrackableType.Planes))
            {
                // Raycastの衝突情報は距離によってソートされるため、0番目が最も近い場所でヒットした情報となります
                var hitPose = hits[0].pose;

                if (spawnedObject)
                {
                    spawnedObject.transform.position = hitPose.position;
                }
                else
                {
                    spawnedObject = Instantiate(tohu, hitPose.position, Quaternion.identity);
                }
            }
        }
    }
}
```

ちょっと補足ですが、今回は検知した平面に豆腐を召喚したかったため、TrackableTypeのPlanesを指定しましたが、enumのTrackableTypeは他にも種類があります。TrackableTypeの実装を記載しておきます。

```csharp
[Flags]
public enum TrackableType
{
	None = 0x0,
	PlaneWithinPolygon = 0x1,
	PlaneWithinBounds = 0x2,
	PlaneWithinInfinity = 0x4,
	PlaneEstimated = 0x8,
	Planes = 0xF,
	FeaturePoint = 0x10,
	Image = 0x20,
	Face = 0x40,
	All = 0x7F
}
```

## ビルドして実行
よし！！出来たぞ、ビルドして豆腐召喚やああぁぁ！！！

って感じでやってしまうと、アプリ起動時にいきなりエラーで落ちてしまいます。案外盲点かもしれないので最後に対処法を追記しておきます。

原因としては、ARアプリなので、カメラを使います。カメラを使う際にはプライバシーの関係上ユーザからの使用許可を得る必要があります。位置情報とかもそうですね。その際に表示する「なぜ、カメラを使うのか」の説明文を記入しておかないと、実行時エラーとなりアプリが終了してしまいます。

なので、Project Settings>Player>Other Settings>Camera Usage Descriptionの欄になんでもいいので記入しておいてください。(なんでもいいと言いましたが、テキトーなこと書いてリリース申請出すとappleさんに弾かれます…)
もしくは、ビルド後にXCodeからInfo.plistというファイルを探し、Privacy - Camera Usage Descriptionを追加して解決することもできます。お好きな方で。

では、そこを記入したらいよいよビルドです！！

![tohugif-compressor.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185248/2d7a89e9-b6ed-ae34-593e-666a68209ba9.gif)

ファイルサイズの都合上ほんの一瞬しかうつせてないのですが、豆腐を召喚する前に、平面を検知するためにスマホを上下左右に動かしています。ある程度動かしてから画面をタップすると豆腐が召喚できると思います！！👍

# 最後に
ARFoundationには、他にもFace TrackingやLight Estimationなど様々機能があるので、是非色々使ってみてください！！
この記事がAR開発を始めるきっかけになれば幸いです！(￣▽￣)