【Unity道場10】アセット運用ベストプラクティス に参加しました
参加しました!私的メモです(ˊ꒳ˋ*)
公開された資料
まとめ
- Resourcesには動的に差し替えるコンテンツのみを入れる
- Resources以下のファイル全てに対し、初期処理が発生する
- Resources以下に多量のアセットを配置することで膨大な初期化処理が発生
- Asset以下にある全てのResourcesが対象なので、外部アセットにも注意
- UnityはInstanceIDを元にアセットの参照を行っている
- InstanceIDが参照出来ないケースではMissingになるので注意
- 特にAssetBundleアンロード時等で注意
- AssetBundleの取得はUnityWebRequest + DownloadHandlerがオススメ
- UnityWebRequestはUnity5.4から正式版になった
- AssetBundleの圧縮形式はUnity5.3から採用されたLZ4がオススメ
- ただし使用環境によって最適解は異なるので特徴見極める必要がある
- ベストプラクティスを信じない
- 環境やゲームの内容によって最適解は異なる
メモ
unityでアセットを利用する
アセットをインポートしてそのまま使用する
Resourcesフォルダを使用する
// Resources直下に置いたファイルを取得する var spriteRenderer = GetComponent<SpriteRenderer>(); spriteRenderer.sprite = Resources.Load<Sprite>("FilePath");
AssetBundleを使用する
- リリース済みのアプリにアセットを追加できる
- asset群を束ねた(bundke)もの
- Unityが使用可能なアセットを1つ以上格納するアーカイブ
アセットの依存関係
- 依存するアセットも同時にロードされる
- e.g) Prefab => Sprote
AssetBundleにおいて生成されるもの
- Manifest
- 依存関係を表示する
- Asset AssetBundle
- アセットを格納する
- StremedSceneAssetBundle
- シーンを格納できる
- SeneManagerから呼び出せる
- シーン情報とシーンが参照するアセット群が含まれる
AssetBundleManager
- AssetStoreからダウンロード出来る
- 機能
- シミュレーター機能
- AsserBundleをビルドせずにAssetBundleからロードした事にする
- LocalServer機能
- Build機能
- シミュレーター機能
アセットとオブジェクトのシリアライズについて
- オブジェクトの状態を保存し、復元する
- Unityはシリアライズに依存している
- e.g) PrefabとSceneの関係
- e.g) オブジェクトとアセットの関係
- 実行時にでシリアライズしてオブジェクト構造を再現する
- アセットインポート時(D&D時)
- TextureやAudio, Model等のネイティブデータを参照するUnityEngine.Objectをシリアライズ
- ネイティブデータ: ハードウェアによってフォーマットが決定しているもの
- プラットフォームによってTextureの参照する可能な形式が異なる
- UnityEngine.Objectが中間に入ることで統一されたIFで各PF向けのネイティブデータが読み込める
- UnityEngine.Object : データは1:1の関係
- prefab/scene => GUID/LocalIDでUnityEngine.Objectにアクセス => .meta => ネイティブデータにアクセス
GUID
- 重複しないIDでアセットを識別
- GUIDハインポート時に背呈され .metaデータが失われない限り維持される
- renameやパスの変更でも維持される
- 以下にも割り当てられる
.meta
- アセットをインポートする祭に作成される
- metaを残したままファイルを上書きする事で参照/設定を維持したまま更新出来る
- 初期設定は隠しファイルになっている
LocalID
- GUID : .meta は1:1の関係
- ただし1つのアセットに複数のアセットを持つ事がある
- e.g) Atalas化したSprite
- GUIDとは別にLocalIDにて識別する
- .metaに記載されている&の後の数字がLocalID
アセットのライフサイクル
- UnityEngine,Objectを経由したロードされる
- 実行時にはGUID/LocalIDを変換したInstanceIDでアセットを参照する(高速化のため)
- ゲーム起動時に全てのアセットに対して生成が行われる
- 不要になったらアンロードするが、InstanceIDは残る
Resourcesについて
- テキストベースでInstanceIDにアクセスし、任意のアセットを引き出す
- Resourceフォルダ以下に配置したアセットにInstanceIDを登録する
内部動作的
- アプリ起動時にInstanceIDとファイル名とフォルダパスをキャッシュする
- Resources/Prefabに紐づいているアセットはキャッシュされない
- アプリ起動時にLookup用のテーブルを作成する
- 単一のファイルにシリアライズされる
デメリット
- (Resourcesを不適切に使う事によって) アプリの起動時間やビルド時間が延びる
- Resources以下に全てのアセットを配置することで膨大な初期化処理が発生
- ルックアップテーブルも広がる
- Resources以下に全てのアセットを配置することで膨大な初期化処理が発生
- カスタムコンテンツの配信に向いていない
- 差分更新が出来ない
- アセットを圧縮できない
- Androidはアクセスに追加コストが発生する
AssetBundle
- AssetBundleのロード時にAssetBundle内に含まれるアセットのInstanceIDが読み込まれる
- AssetBundleを跨いだ依存関係を設定することができる
- AssetBundleの持つネイティブデータへInstanceIDを経由してアクセス
- AssetBundleをアンロードするとInstanceIDが失われる
- 参照先をUnloadした場合、参照もともUnloadし、再度Loadする必要がある
- 文字列アクセス用のLookupテーブルが生成される
- AssetBundle1会に月10kb=40kbのメモリ消費と僅かながらの展開のオーバーヘッドがある
AssetBundleの依存関係
- 他のAssetBundleに依存する場合、InstanceIDを元に参照を解決する
- 見つからない場合、Missingに設定される
AssetBundle.Unload
- AssetBundle.Unload(false):AssetBundleから提供したInstanceIDを削除する
- AssetBundle.Unload(true): 全てのInstanceIDを持つアセットを強制的に解放し、InstanceIDも削除する
AssetBundleの分割数
- LZMAの場合、サイズが大きいと非常に長いローディング時間が発生する
- CacheOrDownloadのキャッシュを利用すると高速ロード出来るが数が多すぎると起動時に負荷
- LoadAllAssetsは2/3のアセットを一括で伃邑な、逐次ロードよりも早いかも
- AssetBundleの分割数が少ない
- (= アセットのサイズが大きい)
- メモリ使用量が増える (memory等で読むと)
- 読み込み時間が延びる (lzmaで読むと)
- AssetBundleのビルド時、再構築の機会が増える
- AssetBundleの分割数が多い
- ビルドに時間が掛かる
- リソースマネジメントが非常に複雑
- ダウンロードの時間が延びる
暗黙の参照/重複アセット
- AssetBundleは直接assetbundle nameを指定しないアセットもAssetBundleに含める
- 複数のAssetBundleに含まれる場合がある
- 例えばシーンをAssetBundle化する場合、そのシーンに含まれるアセットについて
- 既にInstanceIDが割り振られていた場合、新しいInstanceIDを割り振られる
- 特にShaderやTextureが危険
- 対策
- resource AssetBunde を用意し、シーンはresourceに対する参照を持たせる
- 複数のAssetBundleに含まれる場合がある
AssetBundleの圧縮について
- サイズ: LZMA <<<< LZ4 < 非圧縮
- ロード速度: LZMA > LZ4 >>>>> 非圧縮
- LZ4はUniy5.3以降に使用可能
- LZMAを解答するには一旦すべてをメモリに展開する必要がある
AssetBundleの読み込み
AssetBundle.LoadfFomFiles
- ヘッダを読み、ローカルストレージにアクセスするため最速
AssetBundle.LoadFromMemory (WWW.assetBundle)
- メモリからAssetBUndleを構築してロードする
- LZ4の場合、メモリにそのままコピーする
- LZMAの場合、全て解答し、LZ4へ再圧縮する(5.3以降)
- 使用するのはAssetBundleを暗号化したい場合くらい
WWW.CacheOrDownLoad
- アセットをダウンロードし、Unityのキャッシュシステムでキャッシュする
- LZ4/無圧縮の場合、メモリにそのまま保存する
- LZMAの場合、全て解答し、LZ4へ再圧縮する(5.3以降)
- 起動時にキャッシュの有無や削除判定を行う
- 2回目以降はキャッシュから取得される
UnityWebRequest & DownloadHandler
- DownloadHandlerでのデータ処理を指定する事でヒープを抑えられる
AssetBundleのアセットの読み込み
LoadAsset
LoadAllAssets
LoadAssetWithSubAssets
- Asyncを設定するとアセットの読み込みをワーカースレッドで行う
- 1フレームの処理時間は設定可能 (ThredPriority)
- Unity5.3からUnityEngine.Objectのロードもワーカースレッドで実行する
- 並列で一括で処理し、ロード完了後にAwakeが呼ばれる
内部キャッシュシステム
- 指定のAssetBundleがあればキャッシュから取得し、無ければダウンロードする
- キャッシュには保存可能容量や有効期限が存在する
Cacheing.maximumAvailableDiskSpace
Cacheing.expirationDelay
- バージョンを上げても旧バージョンを削除しない
- 現状はcrcが異なる場合、削除してDLし直す挙動を利用する感じに…
- (私的メモ) これってUnity5.3以降でも効く? 動かない的な記事を見たことがあるので検証したい
- 現状はcrcが異なる場合、削除してDLし直す挙動を利用する感じに…
- キャッシュの処理はワーカースレッドで行う
- 複数を同時に走らせるとOSの同時アクセス限界
キャッシュシステムを自作
- WWWクラスはbytesアクセス用にメモリを確保していため、メモリを余計に使う
ベストプラクティス
同一の要素を大量に含んだPrefab
- 単一のPrefabをInstantiateで複製した方が若干効率的
Resources
- Resources/AssetBundleには直接参照しないアセットは含めない
- Resourcesのアクセス数を減らす
- Resourcesはプロトタイプ用と割り切る (unity社内のオススメ)
- アプリにAssetBundleを含めてしまう
- StreamingAsset等
- ただしAndroidはapk(zip)内にStremingAssetsがあり、LoadFromCacheでアクセス出来ない
- CacheOrDownloadや何らかの手で取り出す
AssetBundleの分割
- 論理エンティティによる分割
- DLC等に使いやすいフォーマット
- レイアウトやUI,キャラクター
- 共有する背景のモデルやテクスチャ
- タイプのグループによる分割
- 同タイプのアセット(Texture/Audio)を1つにまとめる
- 数が少なく、ローカルから接続するなら基本繫ぎっぱなしでOK
- 同時コンテンツのグルーピングによる分割
- SceneをAssetBundle化しておく使い方
- 何度も使用するアセットは分割する
- 多少のアセットの重複は気にしない
- 1Asset1AssetBundle
- 1つに拘らず、ケースバイケースで使い分ける