【第2回】【Unity】【初心者向け】 AR Foundationを利用してARマーカーを認識しマーカーを基準にオブジェクトを生成する&UIの実装

# はじめに
この度インターンシップにて、株式会社GENEROSITY様でARコンテンツの開発を行いました。そこでの実習内容を第1回から第4回の全4部に渡って記事にさせていただきます。

第2回では2週間行ったAR開発の内容のモデル生成方法についてをまとめています。ARの技術として今回は、**ARマーカー**を認識しその上にオブジェクトを出現させるという事を行いました。前回の第1回の内容を踏まえて進んでいきますので第1回をご覧になっていない方はぜひ以下からご覧になってください。

https://qiita.com/Ryushin_Mano/items/8e094501f409dc955898

blenderでのモデル作成からプログラムを書いて実装するまですべて1人で進めましたのでぜひ第2回もご覧いただけると嬉しいです！



# 目次
[対象者](#対象者)
[企画概要](#企画概要)
[使用ツール](#使用ツール)
[開発環境](#開発環境)
　[OS](#os)
　[Unity](#unity)
　[テストデバイス](#テストデバイス)
[制作](#制作)
　[Illustrator](#illustrator)
 　[Blender](#blender)
    　[Unity](#unity-1)
     　 [モデルのインポート](#モデルのインポート)
    　  [ARマーカーの設定](#arマーカーの設定)
    　 [フラスコの生成スクリプト](#フラスコの生成スクリプト)
　 　[TrackedImageRotationFixer.cs](#trackedimagerotationfixercs)
  　 [UIの実装](#uiの実装)
　  　[ARマーカー読み取りスキャン枠](#arマーカー読み取りスキャン枠)
　 　[枠の非表示設定](#trackedimagerotationfixercs)
　 　 　[ScanUIManager.cs](#scanuimanagercs)
　    　[アイコンのUI](#アイコンのui)
　 　[UIの配置](#uiの配置)
　 　[アイコンの出現タイミング](#アイコンの出現タイミング)
　 　 　[IconDisplayManager](#icondisplaymanagercs)
[まとめ](#まとめ)




## 対象者
**・プログラミング初心者の方**
**・UnityでのAR開発に興味がある方**




## 企画概要
企画名は**AR授業資料** 。例えば地理の学習でARを用いて仮想の地球儀を出現させ国の位置を目で見て学んだり、化学では物質の分子構造を視覚化した疑似的なモデルをARを用いて出現させることでより理解度が深まる工夫をした、頭だけでなく**視覚的情報**を与えることで子どものさらなる学力を向上を目指したコンテンツです。

今回実習では価値の検証に必要な最低限の機能を備えたプロダクト、**MVP**(Minimum Viable Product)として、**水素と酸素の混合気体の爆発実験のシミュレーション映像**を制作することにしました。

https://monstar-lab.com/dx/about/about-mvp/


以下は私の手書きではありますが今回作るコンテンツの一連の流れになります。


1.まずARマーカーを検出する画面が開きます。

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/8c8f0028-3f06-b806-1763-43d58dbd74d1.png)

2.ARマーカーを認識するとフラスコの3Dオブジェクトがマーカーの上に出現します。
("けむり"とありますが、MVPを作る上で、出現時の演出である煙は**最低限必要な機能ではない**ため実装していません。)

![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/ec360fb5-f234-488a-cb24-597ec75d470e.png)

3.フラスコの出現後、右側にアイコンのパネルが出てきます。

![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/f9e96685-9926-ea86-c1c5-75ea0e48d66c.png)

4.上から酸素、水素、マッチ棒のアイコンです。元素のアイコンを押すとその3Dオブジェクトが出現し、フラスコの中に入るアニメーションが再生されます。

![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/2be211bc-0558-9dd7-c34b-625fad839b5d.png)
![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/d00f42a5-ff0c-af56-42e3-df9093e83efb.png)

5.マッチ棒のアイコンを押してマッチ棒を出現させます。火のついたマッチ棒がフラスコに近づきます。

![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/89291992-afdd-3a62-3e81-acb81a3e0e0e.png)

6.水素が火に反応して酸素の働きも加わることから爆発が起きます。フラスコは粉々に粉砕されます。

![7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/fcedeb98-468b-ed7b-56f0-bca4a812690b.png)

7.最後になぜその爆発が起こったのか解説が表示されます。

![8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/272c126c-2331-55f4-a423-ae58e832b3cb.png)


これが私の今回の企画の一連の流れです。この流れを頭に入れて制作を進めます。




## 使用ツール
**Unity**
**Visual Studio 2022**
**blender4.3**
**Adobe Illustrator 2025**
**ChatGPT**



## 開発環境
### OS
**Windows 11 Home**

### Unity
**Unity 2022.3.56f1**
開発開始時にUnity6がリリースされていましたが、Unity6でのAR開発について情報が少なかったため、過去バージョンで制作しました。

**AR Foundation ver. 5.1.5**
AR機能を提供するフレームワークです。

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


### テストデバイス
**Google pixel 6 (Android 15)**
実習にてお借りした端末です。Android端末で**設定**から**開発者向けオプション**を開き、**USBデバッグをオン**にしましょう。




## 制作
それではいよいよ制作の中身について入っていきます。Unityを開く前にまずは必要な素材を作ります。


### Illustrator
Adobe IllustratorでARマーカーのデザインとUIアイコンの作成を行いました。
今回はこのマーカーをAndroid端末のカメラで映し、この上にフラスコを生成させます。

フラスコの絵はIllustratorの**ベクター生成AI**を駆使して短時間でマーカーを作成しました。

マーカーは印刷しても良いですし、`png`や`jpeg`で保存してスマホなどのデバイスでマーカーの画像を表示させておくのでも良いです。

![ARicon.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/fee32c24-eb56-7b1c-acee-fe52db16f6e1.png)



## Blender
出現させる3DモデルはすべてBlenderで自作しました。

![スクリーンショット 2025-02-04 160739.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/68191933-ae24-93d7-7cc9-ad1275ce73aa.png)

![スクリーンショット 2025-02-04 160749.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/87e54cd9-7f14-8f1c-044f-d78410fdfbcc.png)

![スクリーンショット 2025-02-04 160725.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/df8e415f-9747-c7f2-8fb2-31e7d4d72f44.png)

![スクリーンショット 2025-02-04 162909.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/3cefa3c5-dbe8-e6fa-bc94-ddfe90f75290.png)


:::note
マテリアルはUnity内で設定するのでBlenderではモデル作成だけでOKです。
:::

![スクリーンショット 2025-02-03 112720.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/2115fcc7-1b67-3a47-6bc1-16179923e039.png)

後ほどプログラムで物理演算をこのフラスコにつけて粉砕を実装するのですが、その粉砕する欠片をすべてUnityに持っていく必要があるためこのように作成しています。

フラスコの破片はBlenderの**Cell Fracture**という無料のアドオンでランダムに分割します。
(ここでは詳しい説明は省かせていただきます。)

![スクリーンショット 2025-02-03 113004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/b36667aa-274c-b383-01ba-4bf6cc59a32a.png)

:::note warn
分割をしすぎてポリゴン数が多くなるとビルドした際に重くなってしまうので分割しすぎないでください。
:::

割れる前のフラスコも忘れずに用意しましょう。

![スクリーンショット 2025-02-04 161639.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/1c5a8a0e-e63c-59c5-3f5d-a4c7f603b9ad.png)

フラスコのモデリングはこちらを参考にしました。

https://youtu.be/oO_lw8rgLwg?feature=shared&t=431


作成したモデルは`Blender`で`File`→`Export`→`FBX`から**fbx形式**で書き出しをしてください。

:::note warn
書き出す際にBlenderを開いたとき、シーンに**デフォルトで存在するライトとカメラは削除して**シーンにモデルのみがある状態で書き出しをしてください。fbxで書き出す際にライトとカメラも一緒に書き出されてしまいます。
:::



## Unity
### モデルのインポート
必要な素材を作り終えたのでまずは`Unity`に3Dモデルをインポートしましょう。
制作中様々なアセットを`Unity`の`Project`内に入れていくのでフォルダを作って整理してください。

Unity画面の`Assets`のパネルの上で右クリックをし、`Create`→`Folder`を選択します。

![スクリーンショット 2025-02-04 164039.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/ce00039a-21ce-f121-f1b6-a705d74bd278.png)


するとフォルダ名を変更できるのでフォルダ名を`Prefabs`と名付けます。

![スクリーンショット 2025-02-04 164755.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/877583fe-a82d-7892-a66d-da59b86c5619.png)

`Prefabs`という名前のフォルダが作れていれば問題ありません。

次に、PCのエクスプローラーから先ほど書き出したモデルの`fbx`ファイルをドラッグ&ドロップで今作った`Prefabs`フォルダに格納します。

![スクリーンショット 2025-02-04 165423.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/28e85309-1442-8bb9-378e-e42b029008c3.png)

:::note warn
インポートしてプレハブ化されたモデルは`Position`をすべて**0**にしておきましょう。

![スクリーンショット 2025-02-05 003017.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/2b62bd88-b054-3a4c-81e9-6560c2824dac.png)
:::

マテリアルはお好みで設定してください。今回はMVP開発のため単色の簡単なマテリアル設定のみ行いました。


### ARマーカーの設定
前編をご覧になってそのプロジェクトのまま進めている方は、`Hierarchy`には`Directional Light`と`AR Session`と`XR Origin`のみがある状態にしてください。オブジェクトを消去する際は消去するオブジェクトを選択して`Deleteキー`を押すことでシーンから削除できます。

前編をご覧になっていない方は以下を参照してAR開発の準備をしてください。

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


マーカーの設定は以下の記事を参考にしましたのでその記事の中の**画像認識準備**の見出しの内容までご覧いただき設定を行ってください。

https://qiita.com/DI_198/items/351f44a570920928ab12#%E7%94%BB%E5%83%8F%E8%AA%8D%E8%AD%98%E6%BA%96%E5%82%99



### フラスコの生成スクリプト
フラスコの生成を私はスクリプトで行いました。開発中、マーカーとフラスコの位置がずれてしまうのが気になり、何とかできないかと思いスクリプトでフラスコがマーカーに追従するように工夫しました。

`Prefab`フォルダを作成したときと同様、`Assets`の上で右クリックをし、`Create`→`Folder`を選択します。

フォルダ名を`Scripts`とします。

![スクリーンショット 2025-02-04 164755_s.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/5d63598b-a75a-2940-3bcf-153e1dc6214f.png)

ではその`Scripts`フォルダの中にフラスコの生成を行うスクリプトを作りましょう。`Scripts`フォルダを選択した状態で右クリックをし、`Criate`→`C# Script`を選択します。

![スクリーンショット 2025-02-04 175018.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/47c98020-1a64-2077-96ad-413a0ad336eb.png)

スクリプト作成直後、スクリプト名が選択された状態なので**その状態のまま**スクリプト名を変更します。私は`TrackedImageRotationFixer`としました。

このように作成直後の名前に青い帯がある状態で名前を変更してください。
![スクリーンショット 2025-02-04 175915.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/d9a83865-1151-ca4d-1268-6b1f4ac1af99.png)


:::note
スクリプト名は自分が分かりやすい名前にしましょう。
:::


#### TrackedImageRotationFixer.cs
`Project`の`Assets`でスクリプトをダブルクリックするとエディタが立ち上がります。私の場合は**Visual Studio 2022が立ち上がります。**

**コードはこちらです。**

```ruby:TrackedImageRotationFixer.cs

using System.Collections;
using System.Collections.Generic;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;


public class TrackedImageRotationFixer : MonoBehaviour
{
    [SerializeField] private GameObject normalFlaskPrefab; // 通常のフラスコ
    private ARTrackedImageManager trackedImageManager;

    // 生成されたフラスコを保持するリストまたは変数
    private GameObject spawnedFlask;

    private void Awake()
    {
        trackedImageManager = FindObjectOfType<ARTrackedImageManager>(); // シーン内のARTrackedImageManagerを取得
    }

    // オブジェクトが有効になる際 = ARトラッキング画像が検出される際に呼ばれる
    private void OnEnable()
    {
        if (trackedImageManager != null)
        {
            trackedImageManager.trackedImagesChanged += OnTrackedImagesChanged;
        }
    }

    // オブジェクトが無効になる際に呼ばれる
    private void OnDisable()
    {
        if (trackedImageManager != null)
        {
            trackedImageManager.trackedImagesChanged -= OnTrackedImagesChanged;
        }
    }

    // マーカーが追加されたり、更新されたときに実行される
    private void OnTrackedImagesChanged(ARTrackedImagesChangedEventArgs eventArgs)
    {
        // foreachは配列やListの要素を順番に読み取り、要素の数だけ処理を繰り返す
        // 新しく検出されたマーカーのリスト
        foreach (var trackedimage in eventArgs.added)
        {
            // FixRotationAndSpawn関数の呼び出し
            FixRotationAndSpawn(trackedimage);
        }

        // すでに検出されているマーカーが更新された場合のリスト。
        foreach (var trackedImage in eventArgs.updated)
        {
            FixRotationAndSpawn(trackedImage);
        }
    }

    private void FixRotationAndSpawn(ARTrackedImage trackedImage)
    {

        // すでにフラスコが生成されている場合は何もしない
        if (spawnedFlask != null) return;

        //trackedImage.trackingStateがTrackingState.Tracking の場合にのみ処理を行う
        if (trackedImage.trackingState == TrackingState.Tracking)
        {
            // フラスコを生成
            spawnedFlask = Instantiate(normalFlaskPrefab, trackedImage.transform.position, Quaternion.identity);
            spawnedFlask.name = normalFlaskPrefab.name;

            // 回転補正
            Quaternion targetRotation = trackedImage.transform.rotation;
            Quaternion rotationOffset = Quaternion.Euler(-90f, 0f, 0f); // 必要に応じて調整 //モデルのローカル軸を調整
            spawnedFlask.transform.rotation = targetRotation * rotationOffset;

            // 画像の子として設定
            // マーカーに追従するようにしている
            spawnedFlask.transform.SetParent(trackedImage.transform, true);
            }
        }
    }

    // 生成されたフラスコを返すメソッド
    public GameObject GetSpawnedFlask()
    {
        return spawnedFlask;
    }
}
```
</details>

コードが書けましたら`Ctrl+S`でセーブし、`Unity`に戻ってください。

今書いたスクリプトを選択すると`Inspector`に`Normal Flask Prefab`という項目があると思います。その右側に`None`と書いてある箇所があります。
![スクリーンショット 2025-02-04 181606.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/f90bfd7c-9045-ee70-14e1-cec1a1b89456.png)


ここには最初に出現させるフラスコを入れてあげる必要があります。ここが`None`のままだと、ARマーカーを読み取っても何も出現しません。

スクリプトの`Inspector`を開いた状態で、`Prefab`フォルダの中のヒビが入っていないフラスコを`None`の場所までドラッグします。

![スクリーンショット 2025-02-04 181936.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/3abfb47f-7e2a-524a-00fc-2840f16dd1ef.png)

↑このように青くなったらマウスのボタンを離してドロップさせます。

![スクリーンショット 2025-02-04 181949.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/4b2d85ba-b55c-0a35-510e-7673d7f0aa94.png)

このスクリプトを実行させるにはシーンにある`Game Object`へアタッチする必要があります。
`Hierarchy`で右クリック→`Create Empty`を選択して空のオブジェクトを用意します。

![スクリーンショット 2025-02-05 000257.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/5c806ee8-f688-364d-a27b-3f528c7045ba.png)

オブジェクトの名前は`RotationFixer`とします。

![スクリーンショット 2025-02-05 000602.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/97776396-46ce-4684-0fad-6f5d8ef3380a.png)

この`RotationFixer`に`TrackedImageRotationFixer` スクリプトをドラッグ&ドロップをしてアタッチをします。`RotationFixer`の`Inspector`にドラッグ&ドロップでも大丈夫です。

オブジェクトの`Inspector`がこのようになっていればOKです！`Normal Flask Prefab`にも、`Project`の`Asset`から`Prefabs`フォルダを開いて、最初に出現させるフラスコをドラッグ&ドロップをしてアタッチしてください。

![スクリーンショット 2025-02-05 001133.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/33fde84b-69fa-70aa-2547-09a3b863daf8.png)


ここで一旦`File`→`Build Settings`→`Build And Run`でマーカーの上にモデルが表示するか確認してみてください。




### UIの実装
制作では先に分子やマッチ棒のモデルを出現させてからUIの操作による動作を実装しましたが、記事での説明上、先にUIの実装から説明します。



#### ARマーカー読み取りスキャン枠
ARマーカーを探している時に出すフレームを作ります。フレームはIllustratorで画像書き出しまで行って用意しました。

`Unity`で画像をシーンに配置する方法を説明します。`Hirarchy`で右クリック→`UI`→`Image`を選択します。すると`Canvas`の子に`Image`が入った状態で`Hierarchy`に追加されます。`Image`の名前を`ScanFrame`とします。

![スクリーンショット 2025-02-05 025033.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/31528f46-d4b5-eb2b-7b05-5158be48f8a2.png)

`Inspector`の`Source Image`にフレームの画像をドラッグ&ドロップで埋め込みます。その前に`Project`の`Assets`で右クリック→`Create`→`Folder`を選択して、画像を入れておくためのフォルダを用意しましょう。名前は`Images`とします。

ARマーカーの画像素材がどこのフォルダにも入っていない状態で`Assets`に存在している場合はこの段階で今作った`Image`フォルダにスキャン時のフレーム画像素材とともに入れておきましょう。

`Source Image`の部分にフレームの画像をアタッチするのを忘れないでください。

![スクリーンショット 2025-02-05 025710.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/989775b9-b7f5-4a61-4b8e-ea605349d9cd.png)

`Position`は画面の中央に来るようにし、`Scale`は起動したときに適切な大きさになるように何度か`Build And Run`をして調整してください。

![スクリーンショット 2025-02-05 025717.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/9b0bff1f-f997-ed79-25bc-6fecc3bb2bd3.png)

シーンはこのようになっていればOKです！



#### 枠の非表示設定
ARマーカーを認識してフラスコが出現したらスキャン時のフレームは必要がないので、マーカーが検出された際にこのフレームを消す処理をスクリプトで作成します。

`Project`の`Assets`に先ほど作成した`Scripts`フォルダを選択して`Create`→`C# Script`から新しいスクリプトを用意してください。名前は`ScanUIManager`としました。



#### ScanUIManager.cs
作成したスクリプトをダブルクリックしてコードを書き入れましょう。

**コードはこちらです。**

```ruby:ScanUIManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.XR.ARFoundation;

public class ScanUIManager : MonoBehaviour
{
    [SerializeField] private Image scanUI; // スキャン枠のUIオブジェクトを設定
    private ARTrackedImageManager trackedImageManager;

    private void Awake()
    {
        // ARFoundation の ARTrackedImageManager を取得しマーカーの状態を監視する
        trackedImageManager = FindObjectOfType<ARTrackedImageManager>();
    }

    private void OnEnable()
    {
        if (trackedImageManager != null)
        {
            trackedImageManager.trackedImagesChanged += OnTrackedImagesChanged;
        }
    }

    private void OnDisable()
    {
        if (trackedImageManager != null)
        {
            trackedImageManager.trackedImagesChanged -= OnTrackedImagesChanged;
        }
    }

    private void OnTrackedImagesChanged(ARTrackedImagesChangedEventArgs eventArgs)
    {
        // マーカーが検出された場合スキャン枠を非表示にする
        if (eventArgs.added.Count > 0 || eventArgs.updated.Count > 0)
        {
            HideScanUI();
        }
        else if (eventArgs.removed.Count > 0)
        {
            ShowScanUI();
        }
    }

    public void ShowScanUI()
    {
        if (scanUI != null)
        {
            scanUI.enabled = (true); // Imageを表示
        }
    }

    public void HideScanUI()
    {
        if (scanUI != null)
        {
            scanUI.enabled = (false); // Imageを非表示
        }
    }
}
```
</details>

コードがが書けたら`Unity`に戻り、このスクリプトを`Game Object`にアタッチします。
`Hierarchy`で右クリック→`Create Empty`を選択してシーンに空のゲームオブジェクトを用意します。名前を`ScanUIManagerObject`とします。

`ScanUIManagerObject`に`ScanUIManager`スクリプトをドラッグ&ドロップをしてアタッチしましょう。

以下のようになっていればOKです！

![スクリーンショット 2025-02-05 101555.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/49a9c263-d08b-6e6c-406f-c92e8744ee19.png)

`Scan UI`と書いてあるところには作ったスキャン時に表示されるフレームの画像を入れてください。

**スキャン前**

![Screenshot_2025-02-05-10-22-45-42_60acaf36eb21ddc1bae5c6992ed50ac6.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/d583c673-e391-db34-72df-0c468193d168.jpeg)


**スキャン後**

![Screenshot_2025-02-05-10-23-01-40_60acaf36eb21ddc1bae5c6992ed50ac6.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/51167ef7-b478-5f26-8d4a-3ef8a994b3e2.jpeg)

ここまで確認できればOKです！



#### アイコンのUI

ここからはマーカーを読み取ってから少しだけ間をあけてアイコンを表示させる機能を実装します。このアイコンをタップするとそれぞれの3Dモデルが出現して次に進むというものです。

**デモ画像**

![Screenshot_2025-02-05-10-28-51-09_60acaf36eb21ddc1bae5c6992ed50ac6.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/8496909e-b7c7-c15e-7d04-6ce4075689e9.jpeg)


#### UIの配置
先ほどマーカー読み込み前に表示するフレームを作成したときに`Canvas`というものが作られていたと思います。この`Canvas`の子にアイコンを入れてあげます。
以下をもう一度ご覧いただき、酸素・水素・マッチ棒・ツールボックスのUIを今回実装するので4つ`Image`を用意してそれぞれ名前分かる形でを変更してください。
[ARマーカー読み取りスキャン枠](#arマーカー読み取りスキャン枠)

`Canvas`がこのようになっていれば大丈夫です。

![スクリーンショット 2025-02-05 104216.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/097507b0-4faf-5448-9b2b-24bac9daca0e.png)

それぞれの`Image`を選択し、`Inspector`に表示されている`Source Image`に画像を入れましょう。Unityではほとんどの一般的な画像ファイルに対応しています。詳しくは以下のリンクからご覧ください。

https://docs.unity3d.com/ja/2018.4/Manual/AssetTypes.html#:~:text=%E7%94%BB%E5%83%8F%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB,%E3%81%AB%E5%AF%BE%E5%BF%9C%E3%81%97%E3%81%A6%E3%81%84%E3%81%BE%E3%81%99%E3%80%82

![スクリーンショット 2025-02-05 111538.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/a947082d-2813-d590-e088-6d02cf379853.png)



:::note warn
`toolbox`という名前のUI(私ので言うと緑のパネル)は`Hierarchy`では`Canvas`の子の中で**一番上に来るように**並べてください。`Unity`では`Hierarchy`の中で上にあるものが画面上で言うと奥のレイヤーになります。

![スクリーンショット 2025-02-05 104327.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/d6f23d60-c177-5896-0caa-9f3cb03657e3.png)

仮に、`toolbox`を下にしてみると、このように`Hierarchy`の上側にある他のアイコンが隠れてしまいました。

![スクリーンショット 2025-02-05 104847.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/5757f0e2-7752-09b8-d2e4-ef0f3d405ef3.png)
:::


**位置調整**

このUIたちは画面が回転しても右端に表示させたいので右端に固定されるように設定します。`Hierarchy`でアイコンの`Image`を選択し、`Inspector`に四角いマスのようなものがあると思います。

画像のように選択をすると右端に寄るので`Build And Run`をして確認してみてください。(できていなかったらすみません。もしその場合は`PosX`を変更したりしてみてください)

`Scale`ははみ出ないようにそして見にくくない程度でいい感じに調整してください。

![スクリーンショット 2025-02-05 105845.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/32bc22a2-0212-2a4d-5727-e978a1233245.png)



#### アイコンの出現タイミング
ではスクリプトで、**マーカーを認識した2秒後にアイコンのUIを表示させる**ようにします。

スクリプトを新規作成して名前を`IconDisplayManager`とします。(スクリプトの新規作成方法を忘れてしまった方は記事を遡ってみてください。)

#### IconDisplayManager.cs
以下コードです。

```ruby:IconDisplayManager

using System.Collections;
using System.Collections.Generic;
using System.Data;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UI;
using UnityEngine.XR.ARFoundation;


public class IconDisplayManager : MonoBehaviour
{
    [SerializeField] private GameObject[] icons; // 表示するUIアイコン(複数対応)
    private ARTrackedImageManager trackedImageManager;
    private bool[] isIconHidden; // アイコンが非表示になっているかを記録
    
    private void Awake()
    {
        trackedImageManager = FindObjectOfType<ARTrackedImageManager>();

        // 非表示状態の配列を初期化
        isIconHidden = new bool[icons.Length];

        // シーン開始時に全アイコンを非表示にする
        foreach (GameObject icon in icons)
        {
            if (icon != null)
            {
                icon.SetActive(false); // 非表示に設定
            }
        }
    }

    private void OnEnable()
    {
        if (trackedImageManager != null)
        {
            trackedImageManager.trackedImagesChanged += OnTrackedImagesChanged;
        }
    }

    private void OnDisable()
    {
        if (trackedImageManager != null)
        {
            trackedImageManager.trackedImagesChanged -= OnTrackedImagesChanged;
        }
    }

    private void OnTrackedImagesChanged(ARTrackedImagesChangedEventArgs eventArgs)
    {
        // ARマーカーが検出された場合
        if (eventArgs.added.Count > 0 || eventArgs.updated.Count > 0)
        {
            StartCoroutine(DisplayIconsWithDelay());
            SetupIconButtons();
        }
    }

    private IEnumerator DisplayIconsWithDelay()
    {
        yield return new WaitForSeconds(2f); // 2秒待機

        Debug.Log($"Icons array length: {icons.Length}"); // 配列の長さを表示

        // すべてのアイコンを表示 (ただし、非表示にされたものは除外)
        for (int i = 0; i < icons.Length; i++)
        {
            if (icons[i] != null && !isIconHidden[i]) // 非表示にされていないアイコンだけ表示
            {
                icons[i].SetActive(true);
                Debug.Log($"Icon {i} is now active: {icons[i].name}");
            }
            else
            {
                Debug.LogWarning($"Icon {i} is null or hidden.");
            }
        }
    }
}

```
</details>

これまでと同様にコードを書いたら`Unity`に戻って空のゲームオブジェクトにスクリプトをアタッチします。名前を`IconDisplayManagerObject`とします。

スクリプトがアタッチされた`IconDisplayManagerObject`の`Inspector`を見ると`Icons`という項目があるので、`+`を押して箱を増やし、それぞれの**モデルのプレハブ**をドラッグ&ドロップしてください。

![スクリーンショット 2025-02-05 113558.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/2f96ea1b-8ef4-2154-cde7-3bce0e8347ef.png)

私は水素→酸素→マッチ棒の順番にタップすることを想定して作っているので`Icons`にもこの順番に入れています。順番を誤ると押したUIと出てくるモデルが違ってしまうので注意してください。



**デモ画像**
ここまで出来たら`Build And Run`で確認してみてください。UIが表示されればOKです！

![Screenshot_2025-02-05-10-28-51-09_60acaf36eb21ddc1bae5c6992ed50ac6.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001687/f173ae9f-34eb-1ee2-9b33-598ca66940bb.jpeg)





## まとめ
　ここまでお読みいただきありがとうございました。モデルの生成はうまく出来たでしょうか。私と同じ初心者の方にも分かるような説明を心掛けましたのでお役に立てていたら嬉しいです！

 次回の第3回では生成したモデルにアニメーションをつけていきますので次回もよろしくお願いします。



**おすすめ**

**第1回はこちら**

https://qiita.com/Ryushin_Mano/items/8e094501f409dc955898


**第3回はこちら**

https://qiita.com/Ryushin_Mano/private/88f042c61f234b32c7e7#%E3%81%BE%E3%81%A8%E3%82%81


**第4回はこちら**

https://qiita.com/Ryushin_Mano/items/24461bde667d56061bed