追記 - 2018.05.21
Unite2018の講演を受けて、ちゃんとした記事を書き直しました!
→ Addressable Assets Systemを完全に理解する
発端
AssetBundleをいい感じにする奴を作ってくれと頼まれた(辛い)ので、色々と調べているとこんな記事が。
なんかいい感じそう?~~できれば自前で実装したくないし、全部これで解決してほしいな!~~と思って色々調べてみることに。
注意事項
2018/05/02現在、Resource Managerは開発中の機能であり、ここに書かれている内容は正式リリースにおいて変更されている可能性があります。
また、もうすぐ開催のUnite 2018において、Resource Managerの開発者である大前 広樹さんによる直々の解説セッションが予定されているため、正直それを聞いたほうが120%良いと思います!!(私も聞きに行きます)
→ そろそろ楽がしたい!新アセットバンドルワークフロー&リソースマネージャー詳細解説 - Unite 2018
この記事を書いているのはただの自己満足なので、少しでも内容を先取りしたい!という奇特な方以外にはオススメしません。
さらに言うと、結構頑張って読んだものの雰囲気が掴める程度だったので、時間かけて読むほどの記事ではないです!
今までのAssetBundle管理の問題点
Resource Managerは何ができるの?という話の前に、まずは今までのAssetBundleの問題点について整理しておきます。
@neon-izm さんのこの記事がふんわりまとまっていて、雰囲気を知るには良さげです!
→ AssetBundleまったく分からない人向けのAssetBundleのメンタルモデル
また、以前私が社内で発表に使った資料も置いておきます。こちらは継続運用を見据えた内容になっています。
AssetBundleの問題点 - SpeekerDeck
ざっくりまとめると、継続運用を行うアプリでAssetBundleを使うにあたり、大体以下のような機能を持ったものを各自で頑張って実装していました。
- ロード、アンロード(依存関係解決、参照管理)
- テスト機能(AssetBundleのロード元(本番、ステージング、テスト、ローカルなど)の差し替え、ビルド不要のシミュレーションモード)
- 世代管理(キャッシュ管理、更新判定)
- ビルド〜デプロイ(自動化)
ロード、アンロード周りとシミュレーションモードはAssetBundleManagerで解決できるのですが、AssetBundleManagerはシミュレーションモードがAssetDatabaseに依存しており、AssetBundle名をAssetDatabase経由で管理しないといけなくて、Inspector上からいちいちAssetBundle名を付けるのなんかやってられないので自動でAssetBundle名を付けるスクリプトを書いたりするのですが、AssetDatabaseはなんか割と遅くて(少なくともUnity5.x系まではAssetBundle数が増えてくるとめっちゃ重かった…)クソ!だったり、AssetBundleGraphToolを使う場合はAssetDatabaseの管理外でAssetBundle名が決定されるのでシミュレーションモードが使えなかったりという、まあとにかく継続運用を見据えたケースにおいては若干微妙なのでした。
また、世代管理やデプロイの部分はUnity内部で完結が不可能な問題であり、AssetBundleの配信方法やゲームサーバーの開発方法は各社で異なるため、なかなか共通化されない要因となっていました。クライアント側はUnityでC#で済みますが、Webサーバーは各社PHPだったりRubyだったりでそもそも言語が違う上、使用しているフレームワークもCake PHPだったりLaravelだったりRuby on Railsだったり色々あるので、恐らく今後も共通化するのは難しいでしょう…。
Resource Managerでできること
Resource Managerに関する話は実はいままでも何回かされていて、Addressable Asset Systemとして紹介されてきたのがそれです。GDC 2018ではもう少し踏み込んだ解説があった模様。
→ [GCC2018] Unity 2018は「カスタマイズ」で小さく速く多機能に - GamesIndustry.biz Japan Edition
アセットに対してAddressableというユニークなIDを割り振って管理し,バーチャルファイルシステムを介して,外部ファイルかどうかを気にすることなくアクセス可能になるそうだ。新しい仕組みであるが,これらの実装はオープンソースで提供されるとのことである。
ということでなんとなく
- ロード、アンロード(依存関係解決、参照管理)
- テスト機能(AssetBundleのロード元の差し替え、ビルド不要のシミュレーションモード)
このあたりができそうな感じですね!
では、ここからは実際に実装を見ていきましょう。
導入してみる
ResourceやAsset Bundleをいい感じに使えそうなResource Managerという公式の機能を見つけたけど、調べてる途中でギブアップしたという話【Unity】 - (:3[kanのメモ帳]
上記によると、Package Manager経由で導入できるというふうに書かれていますが、2018/05/02現在は見当たりません。
が、実はmanifest.json
を直接編集することで導入が可能です!(?)
{
"dependencies": {
"com.unity.resourcemanager": "2.0.15-preview"
}
}
プレビュー版なので、表向きには公開されていないようですね。
Package Managerについては別途紹介記事を書いたので、詳しくはそちらを。
→ Unity Package Managerについて
設計を読み込んでみる
一応APIリファレンス的なのはあるのですが、
https://docs.unity3d.com/Packages/com.unity.resourcemanager@2.0-preview/api/index.html
ちょっとこれだけ見て全体像を把握するのは厳しいですね!
頑張ってサンプルコードとか読みつつなんとなく雰囲気を把握したので、かいつまんで説明していきます。
とりあえず、多分実際ロードする時に使うであろうResourceManagerクラスのAPIを見ていきましょう。
→ Class ResourceManager
ProvideInstance, ProvideResourceなど、Provideほげほげでロード。
ReleaseInstance, ReleaseResourceなど、Releaseほげほげでアンロード。
で、全てのAPIはIResourceLocationを引数に取っています。
Contains enough information to load an asset (what/where/how/dependencies)
とのことで、このインターフェースは
- 自分が何のアセットか(what)
- アセットの実体がどこにあるか(where)
- どうやってロードするか(how)
- 依存関係(dependencies)
を知っているようです。これがAddressableと呼ばれるものの正体っぽいですね。全てのアセットについてこのIResourceLocationを割り当て、それを持ってアクセスできる=アドレスで呼び出せるというのがAddressable Asset Systemの真相のようです。(想像ですけど!)
依存関係はそのままプロパティにDependenciesがあって特にツッコミ所はないですが、その他の部分はどうやって解決するのでしょうか。
InternalID: Internal name used by the provider to load this location
ProviderId: Matches the provider used to provide/load this location
InternalIDが場所、ProviderIdがどうやってロードするかに対応していそうです。
なにやらproviderに使われる的な事が書かれています。providerってなんだ?
ここで、ResourceManagerの実装を見てみます。IResourceLocationを引数に取っているProvideほげほげの中身はどうなっているのでしょう。関係ありそうな所を抜粋してみます。
public static IList<IResourceProvider> ResourceProviders
{
get
{
Debug.Assert(s_resourceProviders != null);
return s_resourceProviders;
}
}
public static IAsyncOperation<TObject> ProvideResource<TObject>(IResourceLocation location)
where TObject : class
{
if (location == null)
throw new ArgumentNullException("IResourceLocation location");
var provider = GetResourceProvider<TObject>(location);
if (provider == null)
throw new UnknownResourceProviderException(location);
return provider.Provide<TObject>(location, LoadDependencies(location)).Retain();
}
public static IResourceProvider GetResourceProvider<TObject>(IResourceLocation location)
where TObject : class
{
if (location == null)
throw new ArgumentNullException("IResourceLocation location");
for (int i = 0; i < ResourceProviders.Count; i++)
{
var p = ResourceProviders[i];
if (p.CanProvide<TObject>(location))
return p;
}
return null;
}
- ResourceManagerがIResourceProviderのリストを持っている
- ロードする時、渡されたResourceLocationに対応するResourceProviderをリストから引っ張ってくる
- ロード処理はResourceProviderが行う
という事が分かりますね。
- IResourceLocation
- アセットの場所、依存関係などを知っている
- どのResourceProviderによって読み込まれるか知っている
- IResourceProvider
- アセットのロード、アンロード方法について知っている
- どのIResourceLocationに対応しているか知っている
- ResourceManager
- IResourceLocationとIResourceProviderの橋渡しを行う
- IResourceProviderのリストを持っており、ロード時にはそのリストから使えそうなProviderを使う
そして、インターフェースになっているという事は、この部分を色々差し替えする事が前提の設計になっているのだと想像できます。
リファレンスからこのインターフェースを実装しているクラスを持ってくると、以下のような感じでした。
- IResourceLocation
- ResourceLocationBase
- LegacyResourcesLocation
- IResourceProvider
- ResourceProviderBase
- AssetBundleProviderRemoteWebRequest
- BundledAssetProvider
- LegacyResourcesProvider
- LocalAssetBundleProvider
- RawDataProvider
- JsonAssetProvider
- TextDataProvider
- RemoteAssetBundleProvider
- RemoteTextureProvider
- ResourceProviderBase
ここまで読んで…まだ使い方がいまいちよく分かりませんね!実際にIResourceLocationはどうやってアセットに割り当てられるの?とか。
ちょっとこの辺りは2.0.15-previewで色々APIが変わってソース読んでもよくわからない状態なのですが、なんとなく以下のようなイメージでした(雑)
- class ResourceManagerRuntimeData
- アドレスマップ Dictionary<string, IResourceLocation<string>> を持つ
- ビルド時に生成されシリアライズされ保存される
- 起動時にデシリアライズして読み込む
- stringを渡すとIResourceLocationを返すAPIを持つ
要はAssetBundleManifestのような物がビルド時に書き出されるので、それを読み込んで使うみたいなイメージのようですね。
まとめ
頑張って読み込んでみましたが、まだ開発中なところもあって、なんとなくの雰囲気しか掴めませんでした…!Unite 2018での解説に期待ですね。
個人的にはAssetBundleGraphToolとの連携についても気になります。(今のところ2018に対応していないので、同時に対応してくれたら嬉しい…)
なんにせよ、AssetBundleについてUnityがちゃんと取り組んでくれているのは嬉しい事ですね。大前さん、応援しています!!