(:3[kanのメモ帳]

個人ゲーム開発者kan.kikuchiのメモ的技術ブログ。月木更新でUnity関連がメイン。

(:3[kanのメモ帳]

ResourcesのシンプルさとAssetBundleの自由度を実現したAddressable Assetsとは(脱Preview記念リライト)【Unity】【Addressable Assets】


このエントリーをはてなブックマークに追加

この記事でのバージョン
Unity 2018.4.8f1
Addressables 1.2.3


はじめに

Unityの鬼門の一つに「画像や音源等のリソースをどう管理するか」というものがあります。

「Resources使えば良いじゃないの?」と思う方もいるかもしれませんが、

実は公式で「Resourcesフォルダのベストプラクティスは使用しない事である」

なんて言われるぐらい推奨されていません。



じゃあ何を使えばいいのかと言えばAssetBundleなのですが、

「AssetBundleって難しいし、面倒くさいし、出来れば使いたくない!!」

というのが大方の意見かと思います。


それでもなくなくAssetBundleを使っていましたが、そんな現状を打破するために

なんとAddressable Assets System(通称AAS)というリソース管理の新システム

Unity2018.2から使えるようになりました!

そしてそのAddressable Assetsを使ってみようという感じの記事を以前書きました。



この時から基本的な使い方は変わっていないのですが、

メソッド名が変わったり、Previewが外れて正式版(?)になったりしたので、新しく書き直して見ました!


なお、記事中の画像は以下のアセットを使っております。





Addressable Assetsとは

まずはAddressable Assetsとはなんぞやという話ですが、簡潔に言うと

ResourcesのシンプルさとAssetBundleの自由度を実現したシステムです。


ResourcesやAssetBundleと比較しての利点を列挙してみると以下の通りです。

  • 全体的に変更に強く、気軽に使える
  • 使うのに複雑なコードが必要なく、簡単に使える。
  • アプリのサイズを小さく出来る(Resourcesはサイズが大きくなる)
  • アプリの起動が早くなる(Resourcesは起動に時間がかかる)
  • アプリ配信後に更新しやすい、パッチを当てやすい(AssetBundleと同じ)
  • (AssetBundleと違い)プレイモード開始前に自動で更新が行われる(Resourcesと同じ)
  • (AssetBundleと違い)プラットフォームごとにアセットを作る必要がない
  • アセットの実際の配置を自由に変更できる (出来なくなったっぽい?)
  • ロードにはAdress(文字列)を使うが、そのAdressは自由に変更可能
  • Adressは手打ちでも使えるが、手打ちしなくて良い機能が付いてる(AssetReference)
  • 簡単に非同期でのロードや事前ロードができる
  • 開発時とリリース時の設定を簡単に切り替えられる
  • エディタ上での動作の変更可能(早いけど実機と動作が違う OR 遅いけど実機と同じ みたいな)
  • 補助用のツール類が色々


なお、色々と項目があって難しそうに見えるかもしれませんが、

やってる事は単純な事ばかりで、実際に使ってみるとさらに簡単に感じると思います。


導入

では、Addressable Assetsを実際に使っていきましょう。まずは導入です。

導入にはPackage Managerを使います。

f:id:kan_kikuchi:20190917045001j:plain
f:id:kan_kikuchi:20190917045141j:plain


インストールが完了すると、

Window -> Asset Management -> Addressablesでウィンドウが開けるようになります。

f:id:kan_kikuchi:20190917045300j:plain


最初に開いた時は設定を作成するかインポートするかのボタンが出るので、

Create Addressables Settingで設定を作成します。

f:id:kan_kikuchi:20180717092308j:plain


なお、Addressable Assets関連のファイルはAssets/AddressableAssetsDataにあるので、

間違って消さないようにしましょう。

なお、このディレクトリを移動しても大丈夫っぽいです。

f:id:kan_kikuchi:20190917045412j:plain


導入はこれだけで完了です。


設定

次にAddressable Assetsで使うアセットを設定します。

一番簡単なのはアセットやディレクトリをドラック&ドロップで追加する方法です。

f:id:kan_kikuchi:20190917050621j:plain


ウィンドウ左にあるAsset Addressがロードする時に使う文字列任意なのですが、

Simplify Entry Namesでシンプルな名前(Assets/Textures/Alchemist_Man.png→Alchemist_Man)

Renameで好きな名前に変更可能です。

f:id:kan_kikuchi:20190917054202g:plain


ただし、ディレクトリで追加したものはそのディレクトリの名前しか変更出来ません。

f:id:kan_kikuchi:20190917054152j:plain


また、Project上で選択し、InspectorのAddressableから設定する事も可能です。

チェックを有効にすればAddressable Assetsに含めて、Adrressもそのまま入力出来ます。

f:id:kan_kikuchi:20190918053515j:plain


なお、各アセットにLabelを付ける事も出来ます。(複数付ける事も可能)

(設定したLabelの利用方法については後述)

f:id:kan_kikuchi:20190918053919j:plain


新しいラベルはAddressableAssetSettingsというアセットのLabelsから追加出来ます。

f:id:kan_kikuchi:20190918053935j:plain


ちなみに、Pathはそのファイルがどこにあるかを表しているもので、実際のプログラムでは使いません。

前確認した時は自動で更新されていたので、後からどこへ移動しても大丈夫だったのですが、


f:id:kan_kikuchi:20180717102507g:plain


仕様が変わったのか、バグなのか分かりませんが、今は移動しても自動更新されないので

Addressable Assetsに設定後、アセットを移動してしまったら、再度設定が必要になるっぽいです。

f:id:kan_kikuchi:20190918053425g:plain



単体ロード

Addressable Assetsからアセットを1つロードする時はAddressables.LoadAssetsAsyncを使います。

ちなみにLoadAssetはobsolete(非推奨)になったので、使わないようにしましょう。


ResourcesのLoadと似ていますが、ファイルの指定にはパスではなく先程設定したAddressを使います。

また直接受け取るのではなく、受け取り用のデリゲートを登録して受け取る感じです。

さらにロードは非同期で行われます。(同期でのロードは出来ないっぽい?)

using UnityEngine;
using UnityEngine.AddressableAssets;//Addressablesを使うのに必要

public class NewBehaviourScript : MonoBehaviour {

  private void Start () {
    //Alchemist_ManというAddressのSpriteを非同期でロード
    Addressables.LoadAssetAsync<Sprite>("Alchemist_Man").Completed +=  op => { 
      //op.ResultがロードしたSprite
      Debug.Log(op.Result.name);
    };
  }
	
}
f:id:kan_kikuchi:20190918054744j:plain


なお、アセットが存在しない場合、nullが返るわけではなくエラーが出ます。


またコルーチンを使って、ロード完了まで待つことも可能です。

using System.Collections;
using UnityEngine;
using UnityEngine.AddressableAssets;//Addressablesを使うのに必要

public class NewBehaviourScript : MonoBehaviour {

  private void Start () {
    //コルーチンを使ってロード開始
    StartCoroutine (LoadSprite());
  }

  private IEnumerator LoadSprite() {
    //Alchemist_ManというAddressのSpriteを非同期でロードの開始
    var spriteHandle = Addressables.LoadAssetAsync<Sprite>("Alchemist_Man");
    
    //ロードが完了するまで待機
    yield return new WaitUntil (() => spriteHandle.IsDone);
    
    // エラーがなければロードしたSpriteの名前表示
    if (spriteHandle.Status == UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded) {
      Debug.Log(spriteHandle.Result.name);
    }
    //エラー表示
    else {
      Debug.LogError(spriteHandle.Status);
    }
  }

}



複数同時ロード

Addressable Assetsからアセットを複数同時にロードする時は

Addressables.LoadAssetsAsyncを使います。

ちなみにLoadAssetsはobsolete(非推奨)になったので、使わないようにしましょう。


ResourcesのLoadAllと似ていますが、ファイルの指定にはパスではなく先程設定したLabelを使い、

指定したLabelが付いてるアセットを全て取得します。

f:id:kan_kikuchi:20190923051516j:plain


また直接受け取るのではなく、受け取り用のコールバックを登録して受け取る感じです。

さらにロードは非同期で行われます。(同期でのロードは出来ないっぽい?)

using UnityEngine;
using UnityEngine.AddressableAssets;//Addressablesを使うのに必要

public class NewBehaviourScript : MonoBehaviour {

  private void Start () {
    //PrefabというLabelが付いたGameObjectを全て取得
    Addressables.LoadAssetsAsync<GameObject>("Prefab", OnLoadPrefab);
  }

  //Prefabのロードが完了する度に(1つのPrefabづつ)呼ばれる
  private void OnLoadPrefab(GameObject prefab) {
    Debug.Log(prefab.name);
  }
	
}
f:id:kan_kikuchi:20190923050858j:plain


なお、コールバックをnullにして、Completedに処理を登録する形でも実装可能です。

using UnityEngine;
using UnityEngine.AddressableAssets;//Addressablesを使うのに必要

public class NewBehaviourScript : MonoBehaviour {

  private void Start () {
    //PrefabというLabelが付いたGameObjectを全て取得
    Addressables.LoadAssetsAsync<GameObject>("Prefab", null).Completed +=  op => {
      foreach (var result in op.Result) {
        Debug.Log(result.name);
      }
    };

  }

}


ちなみに、該当するアセットが1つも存在しない場合、エラーが出ます。


インスタンスの生成と破棄

Addressables.InstantiateAsyncを使えば、インスタンスをそのまま生成する事も可能です。

使い方はAddressables.LoadAssetAsyncと同じ感じです。

using UnityEngine;
using UnityEngine.AddressableAssets;

public class NewBehaviourScript : MonoBehaviour {

  private void Start() {
    //PlayerというPrefabをロードし、インスタンスを作成
     Addressables.InstantiateAsync("Player").Completed +=  op => { 
      Debug.Log(op.Result.name);
    };
  }
	
}
f:id:kan_kikuchi:20190923070014j:plain


ただし、Addressables.InstantiateAsyncで生成したインスタンスを削除する時には

DestroyでなくAddressables.ReleaseInstanceで削除する必要があります。

一応Destroyでも削除は出来てしまいますが、メモリリークが発生するっぽいです。(未確認)

//Destroyで削除しちゃダメ!
//Destroy(_player);

//削除する時はReleaseInstance
Addressables.ReleaseInstance(_player);



事前ロード

前はAddressables.PreloadDependenciesを使ってあらかじめAssetをロードしておいて、

後からAddressables.LoadAssetを実行した時の処理時間を短くする事が出来たのですが、

PreloadDependenciesが使えなくなり、それに変わるようなメソッドも見当たらないので、

事前ロードは出来なくなったっぽいです。


文字列(AddressやLabel)の直接入力を避ける

AddressやLabelを指定する際、

文字列で直接入力してしまうとバグの温床になるので避けたい所です。

そんな悩みを解決する機能がReferenceになります。


以下のようにAssetReferenceというクラスをInspectorで設定出来るようにすると、

プルダウンのGUIでAddressable Assetsに登録したアセットを指定出来るようになります。(検索も可能)

そしてこのAssetReference、なんとAddressの文字列の代わりになるのです!

using UnityEngine;
using UnityEngine.AddressableAssets;

public class NewBehaviourScript : MonoBehaviour {

  //Inspectorで設定出来るように
  [SerializeField]
  private AssetReference _prefabReference = null;

  private void Start () {
    //Addressの文字列の代わりにAssetReferenceを使ってロード
    Addressables.LoadAssetAsync<GameObject>(_prefabReference).Completed += op => {
      Debug.Log(op.Result.name);
    };
  }
	
}
f:id:kan_kikuchi:20190918063523j:plain


しかもAssetReferenceで設定すると、Addressを後から変更してもそのまま使えます。

(前はすぐに変わっていたが、今は再生等をしないと表記が変わらない)

f:id:kan_kikuchi:20190918064232g:plain


なお、Addressable Assetsに登録してないアセットはプルダウンから選べませんが、

AssetReferenceの所にドラック&ドロップする事と自動で登録され、選択できます。

f:id:kan_kikuchi:20190918064324g:plain


同様にAssetLabelReferenceを使う事で、Labelも文字列を使わずに設定出来るようになります。

//Inspectorで設定出来るように
[SerializeField]
private AssetLabelReference _labelReference;
f:id:kan_kikuchi:20190923071619j:plain



AssetReferenceに設定出来るタイプを制限

AssetReferenceをそのまま使うと、何でも設定出来てしまうのでちょっと危ないです。

そんな時に使えるAssetReferenceTypeRestrictionという属性

がありましたが、残念ながら廃止になりました



ただし、AssetReferenceGameObjectというGameObjectだけを設定出来るものはあります。

//Inspectorで設定出来るように
[SerializeField]
private AssetReferenceGameObject _prefabReference = null;
f:id:kan_kikuchi:20190922050640j:plain


また、AssetReferenceUILabelRestrictionを使う事でラベルを指定することも可能です。

//PlayerというLabelが付いたアセットだけ設定出来るように
[AssetReferenceUILabelRestriction("Player")]
public AssetReference _prefabReference;
f:id:kan_kikuchi:20190918064757j:plain


ちなみにAssetReferenceLabelRestrictionも、前は制限してるラベルが表示されていたのですが、

今はなくなったようです。

f:id:kan_kikuchi:20190918064848j:plain



グループ(アセットのパッキング設定)

Addressable Assetsはグループごとにアセットがまとめられて(パッキングされて)ビルドされます。

なので別でビルドしたいアセットはグループを分ける必要があります。

(AssetBundleを分けるのと同じ感じ)


ちなみに新しいグループは右クリックのCreate New Groupから作成出来ます。

(以前はグループの種類が3つあったが、今はPacked Assetsのみになった)

f:id:kan_kikuchi:20190922052228j:plain


また、右クリックのメニューでグループ名を変えたり、削除も出来ます。

f:id:kan_kikuchi:20190922052300j:plain


なお、アセットが所属するグループを変えても、
 コードの変更は一切必要ありません。


プロファイル(開発中とリリース時で設定を変える)

開発中とリリース時で、ロード場所やビルド場所等を違う設定にしたい!

という時のためにプロファイルという機能も用意されています。


プロファイルはウィンドウの左上から変更可能で、

Inspect Profile Settingsを選択する事で、設定ファイルをInspectorで開く事も出来ます。

なお、新しいプロファイルを作成したい場合は設定ファイルのInspector上で+ボタンを押します。

f:id:kan_kikuchi:20190922052554j:plain


いくつかの変数は最初から設定されてますが、自分で追加する事も可能です。


ResourcesやAssetBundleからの移行

ResourcesやAssetBundleを使っているけど、Addressable Assetsに移行したい!

という場合の話です。


Resourcesから移動は簡単で、ウィンドウ上でResourcesを選択し、

Move All Resources to groupを実行するだけ。ただし、コードの修正は必要です。

f:id:kan_kikuchi:20190922071240j:plain


AssetBundleからの移行は以下のような手順で行います。

  • 自動的に設定済みAssetBundleをグループに変換(ダイアログあり)
  • AssetBundleにしているアセットをすべてAddressable Assetsにする
  • 現在の設定に合わせて、グループとプロファイルを設定する


こちらも、コードの修正は別途必要です。


Play Mode(エディター上でのAddressable Assetsの動作を変更)

ウィンドウの上部にはPlay Mode Scriptという項目があり、

ここからエディター上でのAddressable Assetsの動作を変更できます。

f:id:kan_kikuchi:20190922072439j:plain


なお、各項目の概要は以下の通りです。

  • Fast Mode : パッキングしない。速いがProfilerで得られる情報が少ない。
  • Virtual Mode : パッキングしないが、パッキングした時の動作をシミュレートする。
  • Packed Play Mode : パッキングする。遅いが実機での動作と同じ。



RM Profiler(アセットのロード状態の可視化)

Addressable Assetsの機能ではありませんが、ついでに

RM(Resource Manager) Profilerも紹介しておきます。


RM Profilerはどのアセットがいつロード、アンロードされているのか可視化出来たり、

参照カウント数や、どのような方式でロードされたのかの確認が可能です。


f:id:kan_kikuchi:20180721112046g:plain
【Unite Tokyo 2018】そろそろ楽がしたい!新アセットバンドルワークフロー&リソースマネージャー詳細解説 - YouTube



なお、Window -> Asset Management -> Addressable Profilerから開けます。

f:id:kan_kikuchi:20190922072640j:plain


おわりに

Resourcesと比べたら手間は増えますが、AssetBundleと比べたらかなり楽ですし、

何よりPreviewも外れたので、そろそろ実用してもいい頃合いかもしれません。


Resourcesはアセットが増えると起動時のロード時間が増えますし、ファイルサイズも増えるので、

サーバーを使わない場合でもAddressable Assetsを使う価値はあると思います。



また、最初からAddressable Assetsを使っておけば、

後々サーバーにアセットを置きたくなった時に移行が楽ですし、

別プロジェクトでサーバーを使いたいとなった時のために慣れておくという考え方も出来ます。