【Unity】AppleVisionPro 向けにUnityのAddressables使ってDLC配信できるのかやってみた【AppleVisionPro】

Graffity のUnityエンジニアのcovaです。

今回は AppleVisionPro でサポートされているらしいと噂の Addressables が使えるのか検証してみました。

# 対象読者
- Unity 開発経験がある人(中級者〜)
- AppleVisionPro 開発に興味がある
- AppleVisionPro で運用系コンテンツを作ってみたい方

# TL;DR
- <font color="#FFFF00"><b> いけるで!!</b></font>
    - Unity公式のLocalizeパッケージも利用可能です


# (※Swiftエンジニア/Unity開発初心者向け) 「Addressables 」とは？

Addressables とはUnity が提供するDownload Contents (通称: DLC) を可能にするためのシステムのことを指します。

昔は `AssetBundle` と呼ばれていましたが、紆余曲折あって2022年以降は `Addressables / Addressable Asset System (AAS)` などと呼ばれます。

（ただ、中身についてはAssetBundle を扱いやすくするためのラッパー&Asset管理システムなので実質AssetBundle といっても過言）

# 検証してみた

## 動作環境

| 項目 | version |
| --- | ---|
| Unity | 6000.0.23f1 |
| PolySpatial | 2.0.4 |
| Addressables | 2.2.2 |
| visionOS | 2.0 |

## 準備
大まかな準備の流れは以下の通りです

1. Unity6 をInstall 
1. UnityPRO ライセンス以上を契約
1. Switch Platform でPolySpatial を選択
1. PackageManager を開く
1. Addressable を追加
1. (推奨・必須ではない) UniTask を導入
1. Cloudサーバーのストレージ (GoogleCloud.CloudStorage, AWS S3, Cloud Storage for Firebase etc.)を用意

### 検証環境作り
VisionOS で検証するので `Bounded` と `Unbounded` それぞれの環境でテストします。

Scene はPolySpatial のサンプルをそのまま流用して使いましょう。

### 素材準備
今回は、画像を2種類用意しました。

1. Built-in Resource としてアプリに内包したAssetをAddressables として読み込む用
1. Download Resorces としてGCP上に配置したAddressables をDownloadして読み込む用

わかりやすく前者は準備中という意味で屈伸してる男の子。
![ready.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65257/ffa57229-6184-c1da-338d-ff7d8e484101.png)

後者は「完了」で検索して出てきた怪しいアカウントの「登録完了」画像を使いたいと思います。

![complete.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65257/206121ac-c99b-4a18-84b5-980b8c02b7af.png)

（いらすとやさん、いつもありがとうございます。

### Addressables の設定

Addressables のGroup を追加して、そちらをDL用とします。
先ほどの画像をそれぞれのGroupに登録します。以下のスクショのようなイメージです。
![スクリーンショット 2024-11-30 17.25.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65257/d4f9a77e-5650-f83a-9b15-d5a984c6140a.png)

その後、Addressables Profile ウィンドウでRemoteのLoadPath を用意したクラウドストレージのPathに設定します。

![addressables_profile.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65257/ddf5f16c-0114-ecfc-74c4-0e4c8dce09b3.png)


### Load Script 

簡単なAssetLoadScriptを用意しました。

```SpriteAssetLoader.cs
using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.AddressableAssets;
using UnityEngine.UI;
public class SpriteAssetLoader : MonoBehaviour
{
    [SerializeField] private Image icon;
    [SerializeField] private float waitTime;

    [Header("Addressables")]
    [SerializeField] private AssetReference builtinAsset;
    [SerializeField] private AssetReference downloadAsset;

    private readonly CancellationTokenSource cts = new();
    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        LoadAssetAsync(cts.Token).Forget(e=>Debug.LogError(e));
    }

    private async UniTask LoadAssetAsync(CancellationToken token)
    {
        try
        {
            var sprite = await Addressables.LoadAssetAsync<Sprite>(builtinAsset).WithCancellation(token);
            Assert.IsNotNull(sprite, "BuiltInAsset is null");
            if(sprite != null)icon.sprite = sprite;
            
            // Wait 
            await UniTask.Delay(TimeSpan.FromSeconds(waitTime), cancellationToken: token);
            
            sprite = await Addressables.LoadAssetAsync<Sprite>(downloadAsset).WithCancellation(token);
            Assert.IsNotNull(sprite, "Download Asset is null");
            if(sprite != null)icon.sprite = sprite;
        }
        catch (Exception e)
        {
            Debug.LogError(e);
            throw;
        }
    }


    private void OnDestroy()
    {
        cts.Cancel();
    }
}
```

## いざ実験

### Volume (Bounded) App
<iframe width="560" height="315" src="https://www.youtube.com/embed/g-Wd_0BaE-U?si=BBbquXp3wBNaXUfz" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

### MR (Unbounded) App

<iframe width="560" height="315" src="https://www.youtube.com/embed/Xl00G97iTh0?si=R_xGF0dMD9culfpt" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>


# まとめ

- AppleVisionPro + Addressables は問題なく利用可能!!
    - コンテンツの動的な差し替えが可能
    - 多言語対応のようなローカライズも問題無し