MetaQuest (Unity) でWebAPIにGETリクエストを送る方法

この記事はUnityを用いてMetaQuest3上でAPIにGETリクエストをするためのUnityプロジェクト設定方法について記す．シーン上にボタンとテキストがあって，ボタンを押すと，データがテキストに反映されることを目標にする．
>**実行環境**
>Unityのバージョンは2022.3.5f1
>MetaQuest3


# API作成
今回はDjangoRestFrameworkを用いてAPIを作成する．

詳しくは以下の記事を参考にデプロイまで行ってほしい．
https://qiita.com/tarakokko3233/items/0961933e2b9695cb561a

作成したDBのテーブルのモデルは以下の通りとする．
```models.py
from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()

    def __str__(self):
        return self.title

```

このPostテーブルのtitleに`1`を，contentに`test`を格納する．CLIやGUIソフトなど好きな形で格納してほしい．



# シーンの作成

Unityの公式が基本的なシーンと設定をしてくれているのでインストールする．
https://connect-prd-cdn.unity.com/20240215/d39c8bf6-4913-43da-80a7-137b06275884/Create-with-VR_2022LTS.zip

インストールしたUnityパッケージ(VRRoom)をUnityHubで開いてUnityEditorを開始する．

![スクリーンショット 2024-05-12 16.30.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/123f8091-44e5-bbe4-fd1a-f00b982c784e.png)

![スクリーンショット 2024-05-12 16.33.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/3657254f-e9c6-8817-53bc-f4707921ceb9.png)

Assets/Scenes/Create-with-VR-Starter-Sceneをhierarchyにドラッグ&ドロップする．
![スクリーンショット 2024-05-12 16.34.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/e72080f6-ca17-d2ba-7fe9-2e8915ad1473.png)
>もとあったシーンは削除して良い

ボタンとテキストを追加する．
`hierarchy`ウィンドウの+ボタンを押して，`XR` - `UI Canvas`を押してCanvasを追加する．
![スクリーンショット 2024-05-12 17.32.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/47737f9c-f95d-a11f-986b-c2b8b787ddcd.png)

:::note info
UIのコンポーネントはCanvas上に設置する．Canvasは実行時の画面に張り付くように配置されるのでSceneでは大きく配置される．InspectorビューでScaleをXYZともに0.01にすることをお勧めする．
![スクリーンショット 2024-05-12 17.35.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/3a2d39a7-7952-632a-e448-2562ae656c30.png)

:::
`UI` - `Text-TextMeshPro`を押してテキストを**先ほど作成したCanvas下**に配置する．
![スクリーンショット 2024-05-12 17.36.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/fd39db35-d6f0-0dcc-4801-ea43ab81106d.png)

>TextMeshProのインポートを促されたらインポートすること
>![スクリーンショット 2024-05-12 17.38.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/41801832-a673-2e91-6e12-8e356146ea8c.png)

`UI` - `Button-TextMeshPro`を押してボタンを追加する

![スクリーンショット 2024-05-12 17.38.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/8c82928a-8bdb-ecbe-afaf-8d65457587ee.png)

:::note warn
今のままだとカメラとボタンとテキストが同じ位置にいるのでカメラ(**XR Rig**)をZ軸-方向に，ボタンとテキストをY軸+方向に適切に移動するとカメラに映る．

![スクリーンショット 2024-05-12 17.44.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/41d59e01-ec28-46af-7470-98b8e544d29e.png)

:::


# Meta XR All-in-One SDKの導入

Unity Asset Storeで，`Meta XR All-in-One SDK`をマイアセットに追加する．
https://assetstore.unity.com/packages/tools/integration/meta-xr-all-in-one-sdk-269657

UnityEditorを開いて，`Window`-`Package Manager`を開く．
![スクリーンショット 2024-05-12 16.53.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/9b1ef446-12dd-3746-42ca-5a04fcef8ce1.png)

Packages:`My Assets`にして，`Meta XR All-in-One SDK`を選択して，右端の`Install`を押す．

![スクリーンショット 2024-05-12 16.54.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/2c3bacf0-a241-6cac-8261-1bafa361f4d7.png)

これで導入完了．

:::note info
この後editorを再起動するようにポップアップが出るはずなので再起動すること．
:::

# ビルド設定とMeta環境設定

まず，`Assets/Plugins/Android`にある`AndroidManifest.xml`に以下の内容を追記する．
```AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
```


次に，`File` - `BuildSettings...` を押す．

![スクリーンショット 2024-05-12 16.59.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/fc4d4368-02b6-c4a9-d813-f1347d93cc52.png)

`Platform`を`Android`にし，`Switch Platform`を選択する．処理が完了したら`Player Settings`を選択する．

![スクリーンショット 2024-05-12 17.19.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/e02aaf86-c198-7a35-1491-6cd3cc44916d.png)

次に，`Meta XR`を選択し，Androidマークを押し，`Fix All`,`Apply All`を押す．
![スクリーンショット 2024-05-12 17.22.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/f02fee06-7375-3d84-2487-6f41dcbd12a7.png)

次に，`XR Plug-in Management`- `OpenXR` - `Meta Quest Support`の設定(歯車)を押し，`Force Remove Internet Permission`のチェックを外す
 
![スクリーンショット 2024-05-12 17.28.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/dc45134f-916b-29f6-efa7-b9bc6de9db99.png)

その後，`Player` - `othersettings` - `Internet Access` の設定を`Require`にする
![スクリーンショット 2024-05-12 18.48.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/29bd44ff-20ff-4003-ef0f-4af7a315c90e.png)


# APIからデータを受け取る設定
APIからデータを受け取り，テキストに表示するためのC#コードを書く．
```GetTitle.cs
using System;
using UnityEngine;
using UnityEngine.Networking;
using System.Collections;
using TMPro;

public class GetTitle : MonoBehaviour
{
    public TextMeshProUGUI resultText; //TextMeshProを受け取る
    private const string url = "https://--hostname--/--endpoint--/"; //デプロイしたAPIのURLとデータを受け取りたいテーブルにGETリクエストを送るエンドポイント

    public void FetchData()
    {
        StartCoroutine(GetData());
    }

    private IEnumerator GetData()
    {
        using (UnityWebRequest webRequest = UnityWebRequest.Get(url))
        {

            webRequest.SetRequestHeader("X-Debug-Mode", "true");
            yield return webRequest.SendWebRequest();

            if (webRequest.result == UnityWebRequest.Result.ConnectionError || webRequest.result == UnityWebRequest.Result.ProtocolError)
            {
                Debug.LogError("Error: " + webRequest.error);
            }
            else
            {
                string json = webRequest.downloadHandler.text;

                Post[] PostArray = JsonHelper.FromJson<Post>(json);

                if (PostArray != null && PostArray.Length > 0)
                {
                    Post firstPostData = PostArray[0]; //Postのデータの最初のデータを格納
                    resultText.text = firstPostData.title; //title属性を得る
                }
                else
                {
                    Debug.LogWarning("No quiz difficulty found.");
                }
            }
        }
    }
}

[System.Serializable]
public class Post
{
    public String title;
    public String content;
}

public static class JsonHelper
{
    public static T[] FromJson<T>(string json)
    {
        string newJson = "{ \"array\": " + json + "}";
        Wrapper<T> wrapper = JsonUtility.FromJson<Wrapper<T>>(newJson);
        return wrapper.array;
    }

    [System.Serializable]
    private class Wrapper<T>
    {
        public T[] array;
    }
}
```

ボタンをクリックした時の挙動を管理するC#コードを書く
```ButtonClickHandler.cs
using UnityEngine;
using UnityEngine.UI;
using TMPro;

public class ButtonClickHandler : MonoBehaviour
{
    public GetTitle title;
    public TextMeshProUGUI resultText;

    private Button button;

    private void Start()
    {
        button = GetComponent<Button>();
        button.onClick.AddListener(OnButtonClicked);
    }

    private void OnButtonClicked()
    {
        title.FetchData();
        UpdateResultText();
    }

    private void UpdateResultText()
    {
        resultText.text = title.resultText.text;
    }
}
```
:::note info
C#コードはAssetsディレクトリにC#ディレクトリを作成してそこに格納すると良い．

:::

`GetTitle.cs`を格納するための空のゲームオブジェクト(TitleManager)を作成する．
`CreateEmpty`を押す
![スクリーンショット 2024-05-12 18.03.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/23bfee44-7a35-6ade-6447-f10589552075.png)

`TitleManager`のInspectorビューで`AddComponent`を押して`GetTitle`を選択する．
![スクリーンショット 2024-05-12 18.10.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/406ee29e-7538-3fa3-a614-da7235cdb801.png)

`GetTitle`コンポーネントに`Result Text`にCanvas下にある`Text(TMP)`をドラッグ&ドロップする．
![スクリーンショット 2024-05-12 18.14.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/b5087560-9447-d0e1-2bbd-f3f58fa75b90.png)

ボタンを押した時の挙動をButtonオブジェクトに追加する．
`Button`オブジェクトのInspectorビューで`Add Component`を押して`ButtonClickHandler`を追加する
![スクリーンショット 2024-05-12 18.16.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/a35c51e8-6a7e-3942-ca2f-84e8c1ba2322.png)

追加したbuttonClickHandlerコンポーネントの`Title`に`TitleManager`を，`Result Text`に`Text(TMP)`(Canvas下にある)をドラッグ&ドロップする．

![スクリーンショット 2024-05-12 18.25.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/2e122a4d-284b-a7ce-c7ce-834c4403f960.png)

# ビルドと実行
`File` - `Build and Run`で実行する．APKファイル名を聞かれるので任意の名前を書く．
![スクリーンショット 2024-05-12 18.29.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/cbe44bed-3c21-4e95-0497-5772cd24ea7f.png)

VR空間上で`Button`を押したら`1`にテキストが変わればOK
![スクリーンショット 2024-05-12 18.53.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/071ad112-a305-b02a-afcf-d19fe653c017.png)