【ARFoundation5.0】Unityで複数の画像を認識しそれぞれにARオブジェクトを表示させる　後編

# はじめに
この記事は後編となります！
前編でのCube表示までが前提となりますのでよろしくお願いいたします！

https://qiita.com/DI_198/private/0b2518c3c6593331d187


## デモ
※今回の記事でここまでいきます！
<img width="700" alt="Videotogif.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584542/3a9c120f-f795-65ec-1993-c92ad0bab68d.gif">

## 目次
[1.環境](#環境)
[2.画像認識準備](#画像認識準備)
[3.対応した画像にオブジェクトを表示させる](#対応した画像にオブジェクトを表示させる)
[4.オブジェクトのプレハブ化](#オブジェクトのプレハブ化)
[5.参考](#参考)
## 環境

## 環境
* Windows11
* Unity 2022.3.10f1
・ARFoundation5.0.7
・ARCore XR Plugin 
・ARKit XR Plugin 
* Test Device
・Galaxy S22 SC-51C

## ARFoundation5.0
「ARFoundation」 とはiOSやAndoidプラットフォーム共通のARアプリを開発するためのUnity公式パッケージです。

https://docs.unity3d.com/Packages/com.unity.xr.arfoundation@5.0/manual/index.html



## 画像認識準備
初めに`Project`ウィンドウ内で右クリック→`Create`→`XR`→`Reference Image Library`を作成します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584542/7817a0a7-d880-9fcc-d51c-e0bc51d66402.png)

`Reference Image Library`を選択すると`Add Image`をクリックします（今回は２つ）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584542/d25f859a-3649-74b5-7267-e7822d2d1cdd.png)


Texture2Dの場所に好きな画像を入れ、Specity Sizeにチェックを入れます。
（今回はだるまと狸にします）
Physical Sizeを実際に読み込みたい画像のサイズに変更します

:::note warn
メートル単位なので注意！
:::
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584542/a078ae79-8631-1007-88d9-dc83e5e043f5.png)


続いて`XR Origin`を選択し`Add Component`→ `AR Tracked Image Manager`を設定します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584542/07192c1c-1ba0-ec50-20be-465f171408e5.png)

`Serialized Library`に先程の`Reference image Library`を設定します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584542/96520027-0562-6d45-dba3-2292cbcc16f2.png)

以上で画像認識までのまとめになります！
## 対応した画像にオブジェクトを表示させる
ここからはそれぞれの画像を区別し、対応したオブジェクトを表示させていきます！
`Project`ウィンドウで右クリック→`Create`→`C#Scripts`を作成します



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584542/f7defd14-ac5b-084a-f4f2-e82d9753c935.png)
名前を`ImageTrackingObjectManager`とします

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584542/4ada41ce-eda9-eced-7dc4-2b0a20f21690.png)

作成した`ImageTrackingObjectManager`に以下のコードを追加します

:::note warn
ファイル名とクラス名を一致させないとエラーが出てしまうので注意！
:::

```ImageTrackingObjectManager.cs
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
#if UNITY_EDITOR
using UnityEditor;
#endif

[Serializable]
public class NameToPrefab
{
    public string name;
    public GameObject prefab;
}

public class ImageTrackingObjectManager : MonoBehaviour
{
    [HideInInspector]
    public ARTrackedImageManager arTrackedImageManager;

    // マーカー名とプレハブのマッピング
    [HideInInspector, SerializeField]
    public List<NameToPrefab> markerNameToPrefab = new List<NameToPrefab>();

    void OnEnable()
    {
        // トラッキングされた画像が変更された際のイベントを登録
        arTrackedImageManager.trackedImagesChanged += OnTrackedImagesChanged;
    }

    void OnDisable()
    {
        // トラッキングされた画像が変更された際のイベントを解除
        arTrackedImageManager.trackedImagesChanged -= OnTrackedImagesChanged;
    }

    // トラッキングされた画像が変更された際の処理
    void OnTrackedImagesChanged(ARTrackedImagesChangedEventArgs eventArgs)
    {
        // 追加された画像
        foreach (var trackedImage in eventArgs.added)
        {
            // マーカー名を取得
            var name = trackedImage.referenceImage.name;
            // マーカー名とプレハブのマッピングからプレハブを取得
            var prefab = markerNameToPrefab.Find(x => x.name == name)?.prefab;
            if (prefab != null)
            {
                // ARTrackedImageのTransformの位置を少し上に調整
                var pos = trackedImage.transform.position;
                pos.y += 0.02f;
                // 180度回転し調整
                var rote = trackedImage.transform.eulerAngles;
                rote.y += 180;

                var instance = Instantiate(
                    prefab,
                    pos,
                    Quaternion.Euler(rote),
                    trackedImage.transform
                );
            }
        }
        // 更新された画像
        foreach (var trackedImage in eventArgs.updated)
        {
            if (trackedImage.trackingState == TrackingState.Tracking)
            {
                trackedImage.gameObject.SetActive(true);
            }
            else if (trackedImage.trackingState == TrackingState.Limited)
            {
                trackedImage.gameObject.SetActive(false);
            }
        }
        // 削除された画像
        foreach (var trackedImage in eventArgs.removed)
        {
            trackedImage.gameObject.SetActive(false);
        }
    }

    // リファレンスライブラリに名前が存在するかチェック
    bool HasNameInReferenceLibrary(IReferenceImageLibrary library, string name)
    {
        for (int i = 0; i < library.count; i++)
        {
            if (library[i].name == name)
            {
                return true;
            }
        }
        return false;
    }
    // マーカー名とプレハブのマッピングを更新
    public void UpdateNameToPrefabMappings()
    {
        if (arTrackedImageManager == null || arTrackedImageManager.referenceLibrary == null)
        {
            return;
        }
        // マーカー名とプレハブのマッピングから存在しないマーカー名を削除
        foreach (var pair in markerNameToPrefab)
        {
            if (!HasNameInReferenceLibrary(arTrackedImageManager.referenceLibrary, pair.name))
            {
                markerNameToPrefab.Remove(pair);
            }
        }
        // ReferenceImageLibraryに登録されているすべてのマーカー名に対して、
        // マーカー名とプレハブのマッピングにマーカー名が登録されていない場合は名前を登録する
        for (int i = 0; i < arTrackedImageManager.referenceLibrary.count; i++)
        {
            var name = arTrackedImageManager.referenceLibrary[i].name;
            if (!markerNameToPrefab.Exists(x => x.name == name))
            {
                markerNameToPrefab.Add(new NameToPrefab { name = name });
            }
        }
    }
}
// カスタムエディタ
#if UNITY_EDITOR
[CustomEditor(typeof(ImageTrackingObjectManager))]
public class ImageTrackingObjectManagerEditor : Editor
{
    bool showNameToPrefabMappings = true;

    public override void OnInspectorGUI()
    {
        DrawDefaultInspector();

        var manager = (ImageTrackingObjectManager)target;
        ARTrackedImageManager newManager = (ARTrackedImageManager)
            EditorGUILayout.ObjectField(
                "AR Tracked Image Manager",
                manager.arTrackedImageManager,
                typeof(ARTrackedImageManager),
                true
            );
        if (newManager != manager.arTrackedImageManager)
        {
            manager.arTrackedImageManager = newManager;
        }
        if (manager.arTrackedImageManager == null)
        {
            EditorGUILayout.HelpBox("Tracked Image Manager is required.", MessageType.Error);
        }
        else
        {
            manager.UpdateNameToPrefabMappings();
            if (manager.markerNameToPrefab.Count == 0)
            {
                EditorGUILayout.HelpBox(
                    "There are no reference images in the Reference Image Library.",
                    MessageType.Warning
                );
            }
            else
            {
                showNameToPrefabMappings = EditorGUILayout.Foldout(
                    showNameToPrefabMappings,
                    new GUIContent("Marker To Prefab", "The mapping from marker name to prefab."),
                    true
                );
                if (showNameToPrefabMappings)
                {
                    foreach (var pair in manager.markerNameToPrefab)
                    {
                        EditorGUILayout.BeginHorizontal();
                        EditorGUILayout.Space();
                        EditorGUILayout.LabelField(pair.name);
                        var newPrefab = (GameObject)
                            EditorGUILayout.ObjectField(pair.prefab, typeof(GameObject), true);
                        if (newPrefab != pair.prefab)
                        {
                            pair.prefab = newPrefab;
                            EditorUtility.SetDirty(manager);
                        }
                        EditorGUILayout.EndHorizontal();
                    }
                }
            }
        }
    }
}
#endif

```

作成したを`ImageTrackingObjectManager`を`XR Origin`にAdd Componentします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584542/a578c135-cb43-e6a3-38b2-3ab7a2ff2c93.png)

## オブジェクトのプレハブ化

適当なオブジェクトを作成しプレハブ化します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584542/41f0a5d8-c200-f517-5f90-1c3b922f07c9.png)

プレハブ化したオブジェクトのスケールを0.05にします（適宜調整）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584542/78db4468-83e9-4fa7-2d9d-2950a24226b2.png)

先程の`ImageTrackingObjectManager`の
AR Tracked Image ManagerにXR Originを設定し、それぞれのマーカーに対応したプレハブを設定します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584542/db3cc0c7-7342-5e06-99e1-f1b654a2ed20.png)

ではBuild And Runをし画像を読み取ってみましょう！
![Videotogif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584542/b4f950a9-982c-9442-312f-73cc181cc331.gif)
画像の上にオブジェクトが表示できれば完成です！

オブジェクトやアニメーションにこだわれば作品の幅が広がりそうです！

![Videotogif (3).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584542/0acbfdd7-a89b-d2d0-af1e-dcbf375f9ce9.gif)


## まとめ
ARFoundation5.0で複数の画像を認識し、それぞれに対応したオブジェクトを表示させることができました！今回はAndoroidにビルドしましたが、iOS でビルドするときは ARKit を用いれば動作するようにしてくれます。
少年時代３DSのARカードで遊んでいましたが、実際に作れるようになるとは...！

## 参考
https://www.wwwmaplesyrup-cs6.work/entry/2019/11/12/193016

https://qiita.com/OKsaiyowa/items/29504242ec74cb5dfb04

https://zenn.dev/futahei/articles/07b048a5ba6cf7