Unityでインスタ映え？するAR作ってみた。

## はじめに

前回書いた[iPhoneのARアプリの記事](https://qiita.com/UpAllNight/items/35f8d611e8f8d48fec62)からグレードアップしたものを作ろうということで、
好きな文字や飾りを出すiPhoneのARアプリを作成しました。

流行に乗ってインスタ映え狙ってみました！(インスタやってないですがww)
<img width=30% alt="shortcut.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378268/31a23de1-4a58-ce5b-4c84-4ea67d68f090.jpeg">


## 作ったものは？？

これです！！
<img alt="originalAR.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378268/2939469a-70f0-b41e-75f2-450fcc149f04.gif">

こだわった点は、以下３点です。
①オリジナルのモデルを作成をし、Unityで使う。
②オープニングとタイトル、メインの３シーン構成。
③メニュー画面を作り、好きな飾りや入力した文字を空間に出せる。

①のモデル作成については[過去の記事](https://qiita.com/UpAllNight/items/317708069d1ed3ff0486)で詳細に解説していますのでご覧下さい。
この記事では②と③の仕組み・プロセスを解説していきます。


## 事前準備

前回書いた[iPhoneのARアプリの記事](https://qiita.com/UpAllNight/items/35f8d611e8f8d48fec62)の「1.事前準備」と同じです！
(1) Unityをインストール →バージョンは「2019.1.10f1」を利用
(2) Unity-ARKit-Plugin をダウンロード
(3) Xcodeをインストール
(4) iOSを12にアップデート

## いざ、実装へ！

このARアプリは、以下の３シーンから成っています。
1. オープニング
2. タイトル
3. メイン

全てのシーンの大まかな作成手順は、「新規シーン作成 → UI作成 → スクリプト作成 」と単純です。
ではそれぞれのシーン作成に入っていきます。


###◆オープニング作成


####・手順
①まずはメニュータブの「File > New Scene」で新規シーンを作成。
　「Game」の画面サイズを"Free Aspect"から"iPhone 1334×750 Portrait(750×1334)[^1]"に変更。
　そして「Hierarchy」のCreateから「UI > Text」をクリック。
![tejun1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378268/aff735c6-f791-704f-498d-c1da5926d84c.png)

②Canvas内にTextオブジェクトとEventSystemオブジェクト[^2]が生成される。
　Textオブジェクトの「Inspector」のRect Transformからオープニング位置を設定。
　またText(Script)からオープニング名や文字サイズ、色などを設定。
![tejun2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378268/3d587aef-e817-cf52-0da0-72f22503cd5e.png)

③ProjectのCreateから「C#Script」をクリックし、スクリプトを生成。
![tejun3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378268/477dc7e5-6799-e264-ee87-a22527511a99.png)
　ソースは以下の通り。

```C#:OpeningController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class OpeningController : MonoBehaviour
{
  public Text text;
  private float textColor;

    // Start is called before the first frame update
    void Start()
    {
      textColor = 0.0f;
      //DelayMethodを5秒後に呼び出す
      Invoke("DelayMethod", 5.0f);
    }

    // Update is called once per frame
    void Update()
    {
      //徐々にテキストの文字色を浮かび上がらせる
      if(textColor <= 1){
        textColor += 0.01f;
        text.color = new Color(255, 255, 255, textColor);
      }
    }
    //タイトルシーンをロードする。
    void DelayMethod(){
      SceneManager.LoadScene("Title");
    }
}
```


④作成したスクリプトをGameObjectにドラッグ&ドロップし、オープニングシーン完成！！
![全画面_2019_09_08_15_10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378268/cc00b325-6b30-4fa8-9c70-57e36043978b.png)

####・完成イメージ
「Up All Night Project Inc.」という会社は架空ですww
![イメージ 2 2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378268/3ef7211f-3c91-ac6e-edcb-0d717024701a.gif)

###◆タイトルシーン作成

####・オープニングシーンとの違い
流れはオープニングシーンと同様です！
主な違いは、スカイボックスの変更とスクリプトだけ。

スカイボックスとは簡単に言うと、**シーンの背景を設定するもの**(デフォルトは青空の背景)
・[参考リンク](http://inter-high-blog.unity3d.jp/2017/07/06/skybox/)

メニュータブの「Window > Rendering > Lighting Settings」で変更可能。
今回はオシャレに宇宙の背景で！Asset Storeからインポートしました。

・Skybox Series Free
https://assetstore.unity.com/packages/2d/textures-materials/sky/skybox-series-free-103633
![全画面_2019_09_08_16_22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378268/1686d064-f8fb-6262-bdda-031ebbda4a6a.png)
↓ソースは以下の通り。スカイボックス(背景)が動くようにしています。

```C#:TitleContoroller.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class TitleContoroller : MonoBehaviour
{
  //　回転スピード
  [SerializeField]
  private float rotateSpeed = 5.0f;
  //　スカイボックスのマテリアル
  private Material skyboxMaterial;

  // Use this for initialization
  void Start () {
      //　Lighting Settingsで指定したスカイボックスのマテリアルを取得
      skyboxMaterial = RenderSettings.skybox;
  }

  // Update is called once per frame
  void Update () {
      //　スカイボックスマテリアルのRotationを操作して角度を変化させる
      skyboxMaterial.SetFloat("_Rotation", Mathf.Repeat(skyboxMaterial.GetFloat("_Rotation") + rotateSpeed * Time.deltaTime, 360f));
      
      //画面をタッチしたらメインシーンをロード
      if (Input.touchCount > 0 ){
        SceneManager.LoadScene("Main");
      }
    }
}
```

####・完成イメージ
![イメージ 4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378268/0a7b06e2-2702-6db0-86bd-22d89dbae313.gif)


###◆メインシーン作成

さて、いよいよメインシーンに入っていきます！

####・仕様
メインシーンの仕様としては、初期画面では左下にボタンが１つだけあるだけで、ボタン以外の画面部分をタップしても何も反応しません。
ボタンを押すことでUIのCanvasが活性化し、メニュー画面を表示します。
<img width=90% alt="mainScene1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378268/8c828732-97f5-a26b-c4f5-fe6861aaaa25.png">



そして、オブジェクトを選択し、再度ボタンを押下することでCanvasを非活性にし、メニュー画面を非表示にします。あとは画面でタップした位置に選択したオブジェクトが表示されるという仕組みです！
<img width=90% alt="mainScene2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378268/2f6bed10-b958-2d3d-edf0-af57963cf355.png">


ちなみに作成したモデル以外に利用したオブジェクト(フルーツやプレゼント箱など)は、
前回の[iPhoneのARアプリの記事](https://qiita.com/UpAllNight/items/35f8d611e8f8d48fec62)で紹介したAsset Storeのものです。

####・ARkitから利用するものをピックアップ

メインシーンは、ARkitの「UnityARKitScene」というサンプルを元に作成してきます。
その中で必要なオブジェクトは以下の４つ[^3]なので、他は削除します。シーン名もMainに変えてます。
![全画面_2019_09_08_23_45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378268/354132ab-c8cf-04f4-7293-a3f16163cd76.png)

####・初期画面のボタンの作り方

「Hierarchy」のCreateから「UI > Button」をクリックし、ボタンを作成。
またボタン名を表示するために「UI > Text」を生成し、Button配下にドラッグ&ドロップする。
そしてCanvasの「Inspector」内のCanvas Scalerのプロパティ[^4]を修正する。
![Untitled-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378268/320d5ece-c8db-6131-64db-b7867a934de0.png)


ボタン押下時のスクリプトを生成する。
ソースは以下。次に実装するメニュー画面のCanvasの活性・非活性の制御を実装。

```C#:ButtonScript.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ButtonScript : MonoBehaviour
{
  public GameObject canvas;
  public bool viewCanvas = false;

  //ボタン押下時に、メニュー画面のCanvasの活性・非活性の制御を行う。
  public void OnClick() {
    Debug.Log("click!");
    if(viewCanvas){
      canvas.SetActive (false);
      viewCanvas = false;
    }else{
      canvas.SetActive (true);
      viewCanvas = true;
    }
  }
}
```

作成したスクリプトをButtonにドラッグ&ドロップし、
Button「Inspector」でボタン押下時にスクリプトを呼び出すように設定する。
![全画面_2019_09_09_0_56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378268/40cf0927-1ef6-209f-6d5f-8fda00062a37.png)
↓この時点ではまだボタンがあるだけ。
![onlyButton.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378268/c1c39d05-f5ba-84b2-cab8-935fadd40d2b.png)

####・メニュー画面の作り方

「Hierarchy」のCreateから「UI > Panel」をクリックし、パネルを作成。
その上にInputFieldとButton(Imageの上に並べた)を配置。今回選択肢は７種類なのでボタンを７個用意。
ボタンには文字ではなく画像を貼り付けるので、Button配下のTextは削除して下さい。
また、ここもCanvasの「Inspector」内のCanvas Scalerのプロパティ[^4]を修正しておきましょう。

ボタンへの画像貼り付けは、[この記事](https://tech.motoki-watanabe.net/entry/2018/07/24/113457)をご参考ください。
今回、画像はモデルに合ったフリー素材をネットから拾ってきました。
![全画面_2019_09_09_9_53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378268/231952a2-205d-2635-ce3a-98b6d236863c.png)
そしてCanvasの「Inspector」内のチェックボックスを外しておく。
![全画面_2019_09_09_11_13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378268/13a752a6-e3cc-3bc9-df7a-2752ccdea17f.png)

####・メニュー画面のボタン実装

今回メニュー画面で選べるオブジェクトは"モデル"と"テキスト"の２種類があります。

メニュー画面ボタンのソースは以下。
簡単に処理を説明すると、メニュー画面で押下されたオブジェクトを画面でタップされた時に表示させるためにセットする処理です。

```C#:CreateObjMerge.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.iOS;
using UnityEngine.EventSystems;

public class CreateObjMerge : MonoBehaviour
{
    //メニュー画面のボタンは７個あるが、他のボタンも同じ処理のため他は省略。
    //ボタン分の数、プロパティとメソッドを、frame2,frame3....で用意する。
    public GameObject frame1;

    GameObject TargetObj;
    TouchCreateObjMerge obj;

    public void Frame1()
    {
        //"Game_yasuko"というゲームオブジェクトの"TouchCreateObjMerge"コンポーネント(スクリプト)のdecoratePrefabプロパティにオブジェクトをセット。
        TargetObj = GameObject.Find("Game_yasuko");
        obj = TargetObj.GetComponent<TouchCreateObjMerge>();
        obj.decoratePrefab = frame1;
        obj.textFlug = false;
    }
}
```

「Hierarchy」のCreateからCreate Emptyをクリックし、オブジェクトを作成。
上記で作成したスクリプトをドラッグ&ドロップし、７種類のモデルをframeプロパティにセット。
![全画面_2019_09_09_21_00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378268/4cbae76f-a2c9-8e85-b825-a817e75ee567.png)
そしてメニュー画面の各ボタンに上記のソースをメソッド出すようにそれぞれ設定する。
![全画面_2019_09_09_20_51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378268/8ae8f6dd-abca-1ad1-9df4-2556ea8b349a.png)

####・InputFieldの実装

好きな文字を空間に出すために、InputFieldに文字が入力される毎に文字をセットします。
ソースは以下です。

```C#:ChangeText.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class ChangeText : MonoBehaviour
{
  public string changeText = "";
  public InputField inputField;

  public void InputText() { // 必ず public にする。
    //テキストにinputFieldの内容を反映
    changeText = inputField.text;
  }
}
```

InputFieldで上記のスクリプトを呼び出すように設定します。
![Untitled-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378268/a6cac46b-1c53-5636-4620-84c9d4d8261b.png)

####・画面タップ時の実装

さて、いよいよメインシーン最後の工程です！

メニュー画面で選択されたオブジェクトを画面でタップされた位置に表示させる実装をします。
ソースは以下です。

```C#:TouchCreateObjMerge.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.iOS;
using UnityEngine.UI;
using UnityEngine.XR.iOS;

public class TouchCreateObjMerge : MonoBehaviour
{
    public GameObject decoratePrefab;
    public GameObject inputField;
    public GameObject button;
    private bool viewCanvas = false;
    private ChangeText textScript;
    private ButtonScript buttonScript;
    public bool textFlug = false;

    // オブジェクト生成メソッド
    void CreateObj(Vector3 atPosition)
    {
        // メニュー画面で選択されたオブジェクトをWorld座標の位置に生成する。
        GameObject decorateObj = Instantiate(decoratePrefab, atPosition, Quaternion.identity);
        if (textFlug){
            //テキストの場合は正面を向くので向きを調整は不要。
            decorateObj.transform.Rotate(0.0f, 0.0f, 0.0f, Space.World);
            //InputFieldで入力された文字をセット。
            string text = textScript.changeText;
            TextMesh textMesh = decorateObj.GetComponent<TextMesh>();
            textMesh.text = text;
        }
        else
        {
            //モデルが正面を向くように向きを調整。
            decorateObj.transform.Rotate(0.0f, 180.0f, 0.0f, Space.World);
        }
    }

    // Use this for initialization
    void Start()
    {
        textScript = inputField.GetComponent<ChangeText>();
        buttonScript = button.GetComponent<ButtonScript>();
    }

    void Update()
    {
        //画面にタッチ、かつメニュー画面が活性化していない時
        if (Input.touchCount > 0 && !buttonScript.viewCanvas)
        {
            var touch = Input.GetTouch(0);
            if (touch.phase == TouchPhase.Began)
            {
                // タッチされたスクリーンの座標をセット
                var screenPosition = Camera.main.ScreenToViewportPoint(touch.position);
                ARPoint point = new ARPoint
                {
                    x = screenPosition.x,
                    y = screenPosition.y
                };

                // スクリーンの座標をWorld座標に変換
                List<ARHitTestResult> hitResults = UnityARSessionNativeInterface.GetARSessionNativeInterface().HitTest(point, ARHitTestResultType.ARHitTestResultTypeFeaturePoint);
                if (hitResults.Count > 0)
                {
                    foreach (var hitResult in hitResults)
                    {
                        Vector3 position = UnityARMatrixOps.GetPosition(hitResult.worldTransform);
                        // オブジェクト生成メソッドにWorld座標を渡す。
                        CreateObj(new Vector3(position.x, position.y, position.z));
                        break;
                    }
                }
            }
        }
    }
}
```

「Hierarchy」のCreateからCreate Emptyをクリックし、オブジェクトを作成。
上記で作成したスクリプトをドラッグ&ドロップし、「メニュー画面のボタン実装」で作成したオブジェクトとInputField、初期画面のボタンをセット。
![全画面_2019_09_09_22_11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378268/cfe547b2-a3db-613e-f532-ee26e6ef77a9.png)


以上で完成です！！


###◆ビルド

あとは、３シーンをまとめてビルドします！
各シーンを開いて、メニューバーの「File > Build Settings」から以下の画面を開く。
"Add Open Scenes"を押下するとそのシーンがScenes In Buildに表示されるので、あとは順番を揃えて"Build And Run"を押下。
自分のiPhoneを接続しておくと、Unityでビルド完了後にXcodeが開くのでiPhoneにビルドする。
![Build_Settings_と_Unity_2019_1_10f1_Personal_-_Main_unity_-_try_AR_picture_-_iOS__Personal___Metal_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378268/e5f7a199-490c-bbac-1fc7-44275e70db46.png)



## 苦労したこと

####・平面にしかオブジェクトを出せない？
最初は「UnityARKitScene」サンプルで平面上にしかオブジェクトを出せませんでしたが、ARkitのHitTestメソッドでオプションを変えれば空間にオブジェクトを出現できました！！
使ったオプションは「ARHitTestResultTypeFeaturePoint」で、ARKitで検出された特徴点を対象に当たり判定を行うものです。

↓以下の記事、参考にさせていただきました。
http://nn-hokuson.hatenablog.com/entry/2018/09/20/202442

####・初めてのC＃
Unityは実質選択肢はC＃のみということで最初は不安でした。。
が同じオブジェクト志向のJavaを業務で経験していたので、サンプルを読んだり、実装していくうちに慣れてきました。

####・シーンをセーブせずに次のシーン作成をし、設定が消えた。
初歩的ミスですww 皆様お気をつけて。

Unityは日本語ドキュメントがあり、情報も出回っているので調べれば大体出てきます。
サンプルも豊富で、参考にできる機能も多いので比較的苦労せずにできました。


## さいごに
インスタ映えするかどうかは皆様のご判断に任せます。。。汗
ただ作りたかったものは実現できました！
まだ細かい点などブラッシュアップできるところはありますが、あわよくばApple Storeに出そうかなとも:rolling_eyes:(しかし年間参加費約1.2万円がネック。)

UnityはARだけでなくVRや２D・３Dゲームなど幅広くあるので、少しでも興味があればぜひトライしてみて下さい！
そして当記事がご参考になれば幸いです。ありがとうございました！


[^1]: 今回はiPhone8用に画面サイズを指定しましたので、機種によって画面サイズを変更して下さい。
[^2]: UIオブジェクトを作ると、必ずこのEventSystemが作られる。このEventSystemはプレイヤーのUIへの入力を制御していていて、UIを使う時は必ず1つ必要になる。基本デフォルトのままで大丈夫で、今回手はつけません。
[^3]: PointCloudParticleExampleはカメラで得られた特徴点(物と物の境界など)を表示するコンポーネント。アプリで小さな黄色い点で表示されているやつ。
[^4]: Scale With Screen Sizeは、画面サイズによってGUIを拡大、縮小してくれる。