[Unity] Addressable Asset を扱う自前のリソース管理機構を作る [ゲーム開発ログ 2020-12-13]

2020-12-13

Unity の動的リソース

Unity では静的に読み込まれるリソース(事前に何を使うかが決まっているリソース)は、 エディタのインスペクタ上でアセットをポチポチ割り当てれば、 それが Scene にシリアライズされるので Scene を開くだけで一緒に読み込まれて動いてくれる。

が、ゲームを作るときは静的ではなく動的に読み込まれるリソースというのがよく出てくる。 例としては

  • ステージに応じて、そのステージに必要なデータだけを読み込んで使う
  • キャラクター詳細画面で、指定した ID のキャラクターモデルをその場で読み込んで表示する

みたいなものがわかりやすい。 要は(メモリは有限なので)都度必要なデータだけをメモリにロードして使うわけだが、それが動的なリソースということだ。

Unity ではこうした動的リソース読み込みを行う方法として、2020 年現在では

  • Resources/ というフォルダにアセットを置いて Resources.Load("パス") で読み込む
  • Addressable アセットにして Addressables.LoadAssetAsync<型>("アドレス") で読み込む

という 2 つの方法がある。

Resources フォルダ

Resources フォルダはクラシカルなやり方で、単純でわかりやすく、即時読み込みもできるので手軽だ。 プロトタイプ開発のときはとりあえず Resources でやるし、 実際、中規模程度までのゲームであればこれで作ってもそんなに不都合はない。

ただ、内部的に参照テーブルを作る関係でアセット数が増えると起動が遅くなるとか、 メモリの確保・解放が微妙にアンコントローラブルだったりする関係で、 規模の大きい開発、メモリ使用量をしっかり制御したいゲームには向いていない。

実際、Unity 公式がベストプラクティスとして “Don’t Use it.” と言っていたりもする。

Addressable Asset System

Addressable アセットというのは ツラい AssetBundle の時代 を経て Unity 公式が整えたアセット管理の解答で、 一個アドレスをかまして (ファイルパスを直接指定するのではなく) アセットを参照できたり、 サーバからオンデマンドでダウンロードするような使い方も考慮したものになっている。 2019.3 で検証済みとなった比較的新しい機能だが、今後は基本的にはこれを使えばよいだろう。

自前フレームワーク上で Addressable アセットの読み込みを管理する

個人開発では自前のフレームワークを書いて、 シーンに必要なアセットを動的ロードしてからシーンを開始する処理を書けるようにしている。 イメージとしては以下のような感じでシーンごとの Context クラスを書く:

// タイトル画面の Scene Context
public class TitleSceneContext : DefaultSceneContext
{
    // シーン名の定義
    public override string SceneName() { return MyGame.SceneName.TitleScene; }

    // シーンロード後、処理開始前に呼ばれる
    public override async UniTask InitAfterLoadScene()
    {
        // BGM と SE のアセットを読み込み
        await Alto.resources.LoadAudio(
            AssetAddress.BGM_Title,
            AssetAddress.SE_Click
        );
        // BGM 再生
        Alto.bgm.Play(AssetAddress.BGM_Title);
    }
}

趣味のコードなので詳細な説明は省くが、リソースをロード / 保持 / 解放するクラスの実装はこんな感じ:

こだわりポイントとして、自前フレームワークが管理しているリソースをモニターできるウインドウを用意している。 これで、現在どれくらいのアセットをメモリに確保しているかが目視確認できる:

ちなみにこういうマルチカラムのウィンドウは TreeView というのを使って実装している。コードはこの辺:

Unity にはよくできたプロファイラがあってメモリ使用量も確認できるが、 要素が多いのと、エディタでは実機と挙動が異なるらしく参照カウントとかがなんかよく分からない感じになっていたりしたので、 とりあえず自分が管理したものは自分で一覧できるようにしておくか、という思いで実装した。 コードから動的に読み込んだものだけ確認できるのでこれはこれで便利。