【Unity道場 2016】アセット運用ベストプラクティス

>100 Views

July 15, 17

スライド概要

2016/8/14に開催されたUnity道場の資料です。
講師:池和田有輔(ユニティ・テクノロジーズ・ジャパン合同会社)

profile-image

リアルタイム3Dコンテンツを制作・運用するための世界的にリードするプラットフォームである「Unity」の日本国内における販売、サポート、コミュニティ活動、研究開発、教育支援を行っています。ゲーム開発者からアーティスト、建築家、自動車デザイナー、映画製作者など、さまざまなクリエイターがUnityを使い想像力を発揮しています。

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

ダウンロード

関連スライド

各ページのテキスト
1.

Unity道場 #10 Asset運用ベスト・プラクティス

2.

自己紹介 山村達彦 ユニティ・テクノロジーズ・ジャパン フィールドエンジニア兼 エヴァンジェリスト

3.

#Unity道場

4.

Unityでアセットを利用する! (基礎)

5.

アセット(Asset) = 資産 オーディオ、テクスチャ、モデル、アニメーション 質感、スクリプト、その他諸々

6.

とりあえず絵を出す、Unity初心者向け説明。 ・UnityエディタのProjectビューに アセットをドラッグ&ドロップ ・〇〇コンポーネントの△△に ドラッグ&ドロップ ・表示された!ヽ(`▽´)ノ

7.

描画する機能 登録する 突っ込む 描画する

8.

Unityの動作の「初心者向けの説明」 ・UnityエディタのProjectビューに アセットをドラッグ&ドロップ ・〇〇コンポーネントの△△に ドラッグ&ドロップ ・表示された!ヽ(`▽´)ノ ・アセットをインポートして 使える形に変換 ・シーンにアセットを利用する コンポーネント(機能)を用意 ・エディタでコンポーネントに アセットを設定 ・表示されたヽ(`▽´)ノ

9.

Unityの動作の「初心者向けの説明」 ・UnityエディタのProjectビューに アセットをドラッグ&ドロップ ・〇〇コンポーネントの△△に ドラッグ&ドロップ ・表示された!ヽ(`▽´)ノ ・アセットをインポートして 使える形に変換 ・シーンにアセットを利用する コンポーネント(機能)を用意 ・エディタでコンポーネントに アセットを設定 ・表示されたヽ(`▽´)ノ

10.

動的に差し替えられるResources ・Resourcesフォルダ以下に配置した アセットを、スクリプトから取得する ・ファイル名(拡張子は無し)で取得 ・リリース後に変更出来ない

11.

リリース後に変更出来るAssetBundle ・リリース済のアプリにアセットを追加 ・asset群を幾つか束ねた(bundle)もの ・Resourcesと同じようにアセットを読める

12.

その他。 ・JPEGやPNG、BMP等の画像、 WAVやMP3等のアセットとして登録して いないリソースを動的にロードする。 ・テキストを直接ロードして、 変換して云々する。

13.

もう少し詳しく。

14.

アセットのインポート ・Assetsフォルダ以下にファイルを配置し アセットをインポートする。 ・インポートしたアセットは、 各プラットフォームで読めるデータに 変換、ゲームで使えるようになる。 ・インポートしたアセットはSceneや Prefabに登録出来るようになる。

15.

シーンビューにアセットを設定する、とは ・Sceneファイルにコンポーネントを 設定し、アセットへの参照を登録する。 ・情報はSceneに保存され、Sceneを ロードした際にアセットも自動的に ロードされる。 ・ProjectビューからD&Dでオブジェクトも作成 されるアセットもある(Sprite等) ・エディタにて事前にシーンに登録する。

16.

Resourcesでロードする、とは ・実行時にResourcesフォルダへ名前を用いてアクセスし、アセットを取得する。 ・取得した内容をスクリプトにて コンポーネントに設定する。 ・取得出来るのはResourcesフォルダ以下に 含めたアセットのみ。 ・リリース後に差し替える事ができない

19.
[beta]
using UnityEngine;
using System.Collections;

public class LoadRes : MonoBehaviour
{
    void Start ()
    {
        var image = GetComponent<UnityEngine.UI.Image>();
        image.sprite = Resources.Load<Sprite> ("Reset");
    }
}
21.

AssetBundleから取得する、とは ・Unityが使用可能なアセットを 一つ以上格納するアーカイブ ・リリース後にアセットの追加・差替が可能で、実行時にアセットをロードして使う。 ・AssetBundle同士の依存関係を表示するManifest AssetBundleにアセットを格納するAsset Assetbundle AssetBundleにシーンを格納するStreamedSceneAssetBundleがある。

23.

Building Player Postprocessing Player

24.

asset bundle

25.
[beta]
using UnityEngine;
using System.Collections;

public class LoadAB : MonoBehaviour
{
    void Start ()
    {
        var path = "AssetBundleを保存してるフォルダ";
        var abIcon = AssetBundle.LoadFromFile(path + "/icon");

        var image = GetComponent<UnityEngine.UI.Image>();
        image.sprite = abIcon.LoadAsset<Sprite>("Reset");
    }
}
27.

StreamedSceneAssetBundle ・StreamedSceneAssetBundle AssetBundleにシーンを格納し、 SceneManagerから呼び出せる。 ・AssetBundleにはシーン情報と、 シーンが参照するアセット群が含まれる。

29.

Building Player Postprocessing Player

30.
[beta]
using UnityEngine;
using System.Collections;
using UnityEngine.SceneManagement;
using UnityEngine.Assertions;

public class LoadScene : MonoBehaviour
{
    void Start ()
    {
        var path = Application.streamingAssetsPath;
        var abSceneSample = AssetBundle.LoadFromFile (path + "/sample-scene");
        Assert.IsTrue (abSceneSample.isStreamedSceneAssetBundle);

        SceneManager.LoadScene ("Sample");
    }
}
31.

Assetの依存関係 AssetBundleからアセットを取得する際、 参照先のAssetBundleがロードされていれば 他のAssetBundleが持つアセットをロードしてくれる。

32.

AssetBundle Manager ・AssetBundleの上位レイヤー(予定) ・AssetBundleから ダウンロード出来る

33.

AssetBundle Manager ・AssetBundleをビルドせずAssetBundleから アセットを取得するシミュレーター機能 ・ローカルサーバーを構築するLocal Server機能 ・コードを書かずAssetBundleをビルドするBuild機能

34.

アセット運用ベスト・プラクティス

35.

の前に…

36.

ベスト・プラクティス = 最適解

37.

最適解は状況によって異なる

38.

全てはトレードオフ

39.

最適解を知る為には、 ルールと選択肢を把握する。

40.

正しい選択肢を得る為に

41.

もっともっと詳しく。

42.

アセットとオブジェクトの シリアライズについて

43.

シリアライズ、とは ・オブジェクトの状態を保存、 逆にオブジェクトに復元可能にする。 ・Unityはアセットの運用の大部分を このシリアライズ技術に依存している。

44.

例えばPrefabとSceneの関係 構造を保存

45.

例えばオブジェクトとアセットの関係 prefabやscene 参照情報を保存

46.

Prefabをシリアライズ 単一の シリアライズしたデータ アセットへの参照 アセンブリへの参照 子prefab

47.

Prefabをデシリアライズ 複雑なオブジェクト構造 アセットへの参照 アセンブリへの参照 子prefab

48.

アセットインポート処理も。 ・TextureやAudio、Model等ネイティブデータを参照する UnityEngine.Objectをシリアライズ(=インポート)

49.

ネイティブデータ? ハードウェアによってフォーマットが 決定している物。 指定のフォーマットでないと、 利用する事ができない 例えばpvrtc、etc2等のテクスチャデータ ogg、mp3等のオーディオデータ。 フォーマットはプラットフォームに依存するので、 ワンクッション置い。

50.

アセットインポート処理も。 ・TextureやAudio、Model等ネイティブデータを参照する UnityEngine.Objectをシリアライズ(=インポート) ・UnityEngine.Objectを経由して、SceneやPrefabから ネイティブデータへ間接的にアクセス可能にする。

51.

UnityEngine.Object texture (アセット) アセットとデータは1:1

52.

prefabやscene UnityEngine.Object (アセット) texture シリアライズ (状態を保存/再現可能に) ネイティブ データ

53.

参照の流れ(エディタ) prefabやscene UnityEngine.Object texture GUID &Local ID 経由でアクセス .meta

54.

GUID? Unityはアセットを識別する際、 重複しないID(GUID)でアセットを 識別している。 GUIDはインポート時に設定され、 .metaデータが失われない限り、維持される パスやファイル名が変化しても 参照が外れない為の施策

55.

.meta? アセットをインポート時に作成される アセット名+.metaのファイル。 アセットと同パスに作成される。 metaを残したままファイルを 上書きすることで、参照や設定を維持したまま アセットを更新出来る。 初期設定は隠しファイルになっている。

56.

Local ID? SpriteやModel等、アセットは内部に 複数のアセットを持つ事がある。 そういったアセット群は、GUIDとは別に Local IDにて識別する。 .metaに記述されている &の後の数字がLocal ID

57.

その他、GUIDを割り当てられるアセット ・ScriptableObject (独自のデータ型を定義するためのフォーマット。 Unityエンジンがシリアライズ/デシリアライズする) ・Monobehaviour (特定のアセンブリの名前空間.クラスへの参照を保持する)

58.

つまり、

59.

つまり、UnityEngine.Objectでラッピングし、 参照関係を丸ごと保存している。

60.

ネイティブデータ アセット プレハブ は何時ロードされるの?

61.

アセットのライフサイクル ・ネイティブデータはUnityEngine.Objectを 経由してロードされる。 ・実行時は、GUIDとLocal IDを変換したInstance IDで アセットを参照する(高速化の為) ・不要になったらアンロードする。Instance IDは残る

62.

アセットのライフサイクル prefabやscene UnityEngine.Object Instance IDをエンジンに登録

63.

アセットのライフサイクル prefabやscene UnityEngine.Object Instance ID経由で アクセス

64.

アセットのライフサイクル prefabやscene UnityEngine.Object texture テクスチャをロード

65.

アセットのライフサイクル prefabやscene UnityEngine.Object 参照元を無くす

66.

アセットのライフサイクル UnityEngine.Object texture テクスチャをアンロード (Instance IDは残る)

67.

アセットをロードするシナリオ ・Instance IDが参照される(Scene・Prefab等から) ・AssetBundle.LoadやResources.Load等からのアクセス

68.

アセットをアンロードするシナリオ ・Resources.UnloadAssets ・Resources.UnloadUnusedAssets ・AssetBundle.Unload(true) ・SceneManager.LoadScene(LoadSceneMode.Single)

69.

アンロード出来ないケース 細かい範囲でアセットを削除出来るUnloadAssetsや AssetBundle.Unload(true)はInstance(複製)された アセットやオブジェクトを消せない。 Destroyで消すか、LoadSceneで丸ごと シーンをやり直す。

70.

Resourcesについて

71.

Resourcesの特徴 ・テキストベースでInstance IDにアクセス ・任意のアセットを引き出せる ・Resourcesフォルダ以下に配置したアセットを登録

72.

Resourcesの内部動作 ・アプリ起動時に初期化処理が走り、 Instance IDとファイル名とフォルダパス をキャッシュする。 ・アプリ起動時、文字列アクセスの為の ルックアップテーブルを作る。 ・単一のファイルにシリアライズされる。

73.

Resources - Resources/prefab1 - Resources/prefab2 Asset1 (material) Asset2 (texture) Asset3 (animation) Asset4 (material) Asset5 (texture)

74.

Resourcesのデメリット ・Resourcesフォルダを不適切に使うと、 アプリの起動時間やビルド時間が伸びる ・アセットを追加出来ない為、カスタムコンテンツを配信出来ない ・差分更新が出来ない ・アセットは圧縮されない ・Androidはアクセスに追加コストが発生する

75.

Resourcesフォルダを不適切に使う Resourcesフォルダ以下に全てのアセットを 置くと、膨大な初期化処理が発生する。 アセットの数に応じて ルックアップテーブルも広がる (N log N)で

76.

AssetBundleについて

77.

AssetBundleの動作 ・AssetBundleをロードすると、 AssetBundle内に含まれるアセットの Instance IDが読み込まれる。 ・AssetBundleを跨いだ依存関係を 設定する事が出来る。 ・AssetBundleの持つネイティブデータへ Instance IDを経由してアクセス

78.

AssetBundleの動作 ・AssetBundleをロードすると、 AssetBundle内に含まれるアセットの Instance IDが読み込まれる。 ・AssetBundleを跨いだ依存関係を 設定する事が出来る。 ・AssetBundleをUnloadすると、 Instance IDが失われる。 ・Resourcesと同様、直接参照しているア セットの数により、文字アクセス用の ルックアップテーブルが広がる。 ・AssetBundle1回につき10~40kbの メモリ消費と僅かながら展開の オーバーヘッドがある

79.

AssetBundleの動作 ・AssetBundleをロードすると、 AssetBundle内に含まれるアセットの Instance IDが読み込まれる。 ・AssetBundleを跨いだ依存関係を 設定する事が出来る。 ・AssetBundleをUnloadすると、 Instance IDが失われる。 ・Resourcesと同様、直接参照しているア セットの数により、文字アクセス用の ルックアップテーブルが広がる。 ・AssetBundle1回につき10~40kbの メモリ消費と僅かながら展開の オーバーヘッドがある

80.

AssetBundleの依存関係 他のAssetBundleに依存している場合、 Instance IDを元に参照を解決する。 依存先のInstance IDが見つからない場合、 Missingに設定される。 missing後でもInstance IDが解決されれば 依存関係は復活する。

84.

AssetBundleの依存関係 AssetBundleをUnloadすると、Instance ID をPersistentManagerから取り下げる。 一度取り下げたInstance IDは復活出来ず、 再ロード時に新しいInstance IDが割り振ら れる。 参照先をUnloadした場合、参照元もUnload し再度Loadする必要がある。

89.

AssetBundle.Unload AssetBundle.Unload(true) AssetBundleから提供した全ての Instance IDを持つアセットを強制的に 開放し、Instance IDも削除する。 AssetBundle.Unload(false) AssetBundleから提供したInstance ID を削除する。

90.

AssetBundleの動作 ・AssetBundleをロードすると、 AssetBundle内に含まれるアセットの Instance IDが読み込まれる。 ・AssetBundleを跨いだ依存関係を 設定する事が出来る。 ・AssetBundleをUnloadすると、 Instance IDが失われる。 ・Resourcesと同様、直接参照しているア セットの数により、文字アクセス用の ルックアップテーブルが広がる。 ・AssetBundle1回につき10~40kbの メモリ消費と僅かながら展開の オーバーヘッドがある

91.

AssetBundleの分割数 LZMAの場合、サイズが大きいと非常に 長いローディング時間が発生する。 CacheOrDownloadのキャッシュを利用する事 で高速でロードが可能になるが、 数が多すぎると起動時に負荷になる。 LoadAllAssetsは2/3のアセットを一括で読むな らば、逐次ロードよりも早いかもしれない。

92.

AssetBundleの分割数 AssetBundleの数が少ない ・メモリ使用量が増える (memory等で読むと) ・読込時間が伸びる (lzmaで読むと) ・AssetBundleのビルド時、 再構築の機会が増える AssetBundleの数が多い ・ビルドに時間がかかる ・リソースマネジメントが非常に複雑 ・ダウンロード時間が伸びる (複数のファイルを纏めていない場合)

93.

暗黙の参照と重複アセット ・AssetBundleは直接assetbundle nameを指定しない アセットもAssetBundleに含める。 ・既にInstance IDが割り振られていた場合、 新しくInstance IDを割り振られる。

95.

AssetBundle1 AssetBundle2 別のInstance IDが 割り振られる

96.

AssetBundle 1 AssetBundle 2 resource AssetBundle

97.

AssetBundleの圧縮 非圧縮 LZMA LZ4

98.

AssetBundleの圧縮 ・非圧縮:圧縮しない為、ファイルサイズは大きい。 ダウンロード出来ればアクセスは最速。 ・LZ4:LZMA並の高い圧縮率と、非圧縮並の解凍速度。 Unity 5.3以降に使用可能。 ・LZMA:最も高い圧縮率。最も遅い解凍速度。 解凍するには、一旦全てメモリに展開する必要がある。

99.

AssetBundleの読込 ・AssetBundle.LoadFromFile ・AssetBundle.LoadFromMemory (WWW.assetBundle) ・WWW.CacheOrDownload

100.

AssetBundleの読込 ・AssetBundle.LoadFromFile ・旧名CreateFromFile ・ヘッダを読み、ローカルストレージへ直接アクセスする。 ・※エディタはLoadFrommemoryAsyncのような動作を行う為、 若干のオーバーヘッドがある。 ・LZMAを読む場合、LoadFromMemoryと同様の動作。

101.

LoadFromFiles ヘッダー AssetBundle アセット

102.

AssetBundleの読込 ・AssetBundle.LoadFromMemory ・旧名CreateFromMemory ・メモリからAssetBundleを構築し、ロードする ・LZ4の場合、メモリにそのままコピーする。 LZMAの場合、全て解凍しLZ4へ再圧縮する(5.3以降) ・AssetBundleを暗号化する場合にのみお勧め

103.

LoadFromMemory ヘッダー AssetBundle エンジンメモリ アセット

104.

WWW.assetBundle ヘッダー WWW AssetBundle エンジンメモリ アセット

105.

AssetBundleの読込 ・WWW.CacheOrDownload ・アセットをダウンロードし、Unityのキャッシュシステムでキャッシュする ・LZ4及び無圧縮の場合、メモリにそのまま保存する LZMAの場合、解凍しLZ4へ再圧縮する(5.3以降) ・起動時にキャッシュの有無や削除判定を行う ・2回目以降はキャッシュから取得する

106.

CacheOrDownload(初回) ヘッダー AssetBundle エンジンメモリ アセット

107.

CacheOrDownload(キャッシュより取得) ヘッダー エンジンメモリ アセット

108.

AssetBundleの読込 ・UnityWebRequest & DownloadHandler ・AssetBundleをローカルストレージにダウンロードし、キャッシュする。 ・LZ4の場合、メモリにそのままコピーする。 LZMAの場合、全て解凍しLZ4へ再圧縮する(5.3以降) ・WWW.CacheOrDownloadと比較し、データの処理を指定することでヒープを抑えられる

109.

LoadFromMemory ヘッダー WebRequest アセット

110.

AssetBundleのアセットの読み込み ・LoadAsset ・LoadAllAssets ・LoadAssetWithSubAssets ・Asyncを付けると非同期

111.

AssetBundleのアセットの読み込み ・LoadAsset 単一のアセットを取り出す ・LoadAllAssets AssetBundleが内包する、直接参照可能なアセットを全て取り出す 全体の2/3を使用する場合、LoadAssetより効率的 ・LoadAssetWithSubAssets サブアセット(LocalIDの関わるアセット)を取り出す

112.

AssetBundleのアセットの読み込み(非同期) ・Asyncを設定すると、アセットの読込を ワーカースレッドで行う。 ・1フレームの処理時間はThreadPriorityで設定 High- 最大50ミリ秒、Normal- 10ミリ秒 BelowNormal- 4ミリ秒、Low- 2ミリ秒

113.

AssetBundleのアセットの読み込み(非同期) ・5.1では、1フレーム1アセットという謎動作をしていた。 (その為、ロードするアセットによって停止時間にバラつきがあった) ・5.2までは、LoadAllAssetsAsyncとLoadAssetWithSubAssetAsyncが大幅 に遅い不具合があった。 ・5.3からUnityEngine.Objectのロードもワーカースレッドで実行する。 並列一括で処理し、ロード完了後にAwakeが呼ばれる。

114.

内蔵キャッシュシステム ・LoadFromCacheOrDownloadもしくは DownloadHandlerAssetBundleを使用すると、 内蔵キャッシュシステムを使用する。 ・LZMAをキャッシュする際、LZ4へ再圧縮する事ができる。 ・キャッシュしたAssetBundleは、LoadFromFilesで 低コストでアクセス出来る。

115.

内蔵キャッシュシステムの動作 ・指定のAssetBundleがあればキャッシュから取得し、無ければダウンロードする。 但し、バージョンを上げても旧バージョンを削除しない。 ・キャッシュの処理はワーカースレッドで行う。 ・複数を同時に走らせるとOSの同時アクセス限界数に達する(特にモバイル)

116.

内蔵キャッシュシステムの動作 ・起動時にキャッシュの最終使用日を確認して、 未使用期間が指定期間を過ぎていれば削除する。 ・ストレージが一杯になると、未使用のアセットから削除する。 ・AssetBundleの識別はファイル名 ・指定のAssetBundleを明示的に削除するAPIが無い (誤ったcrcを指定すると削除される)

117.

内蔵キャッシュシステム メリット - LZMAをダウンロードした場合でも、 LZ4を使用出来る。 - とりあえず使える デメリット - 細かいアセット管理ができない。 - アセットが増えすぎると、アセットの有無 チェック等で時間がかかる。

118.

キャッシュシステムを自作する場合 ・WWWクラスはbytesアクセス用に(エンジン側が) メモリを確保している為、メモリを余計に使う。 ・推奨 ・HttpWebRequest (PCは現在HTTPSを使えない) ・ネイティブプラグイン (オンデマンドリソース(iOS)・DownloadManager(android) NSURLConnection(iOS)・java.net.HttpURLConnection(Android))

119.

アセット運用ベスト・プラクティス (仮)

120.

同一の要素を大量に含んだPrefabは避ける ・オブジェクトは全てそのままシリアライズされる為。 ・例えば同一のPrefabを30個コピーしてリストビューを 作った場合、同じデータを30回デシリアライズする。 ・単一のPrefabをInstantiateを使用して 複製した方が、若干効率的。

121.

Resources ・Resources/AssetBundleには、 直接参照しないアセットは含めない。 ・Resourcesのアセットは出来れば減らす。

122.

Resourcesを使わない (Unity社内のお勧め) ・Resourcesはプロトタイプ用と割り切る ・常にアクセスする必要がある、アセットにのみ使う

123.

Resourcesを使わない…(どうすんねん…) アプリにAssetBundleを含めてしまう。 (StreamingAssets等) Androidはapk内(zip)内にStreamingAssetsがあり LoadFromFileでアクセス出来ないため、 CacheOrDownloadや何らかの手で取り出す。 徹底することで、開発を面倒に出来る。 (Resourcesを使用するのと比較して)

124.

AssetBundleの分割 ・論理エンティティ ・タイプのグループ ・同時コンテンツのグルーピング ・1Asset 1AssetBundle ・一つに拘らず、ケースバイケースで使い分ける

125.

AssetBundleの分割 ・論理エンティティ ・DLC等に使いやすいフォーマット ・レイアウトやUI ・キャラクター ・共有する背景のモデルやテクスチャ

126.

AssetBundleの分割 ・タイプのグループ ・同タイプのアセット(TextureやAudio)を一つにまとめる ・数が少なく、ローカルから接続するなら基本繋ぎっぱなしでOK

127.

AssetBundleの分割 ・同時に使用するコンテンツでグルーピング ・必要になるアセットを、必要な分だけロードする。 ・SceneをAssetBundle化しておく使い方 ・何度も使用するアセットは分割する ・多少のアセットの重複は気にしない

128.

AssetBundleの分割 ・1アセット1アセットバンドル ・個別にアセットを取得する ・カードゲーム等で有利 ・依存関係の解決はスクリプトで行う ・シェーダー等の暗黙のロードを見落とすと余計なコストが出る事がある ・5.3未満でAssetBundleを使用する場合は最適解かもしれない (LZMAが大きなサイズで極端に遅くなる為)

129.

依存先AssetBundleはUnloadしない ・アセットの開放のみを行い、AssetBundleは維持する。 ・アセットの複製によるメモリ・ロードコストを防ぐ ・Load維持にもコストがあるので、ケースバイケースで。 ・分割数にも依る

130.

AssetBundleはどの圧縮を使うべきか ・ロード速度を上げたい場合:非圧縮(最速) LZ4(バランス) ・ビルド時間が問題な場合:非圧縮(圧縮処理が無い) ・アプリサイズが問題な場合:LZMA(圧縮最高) ・使用メモリが問題な場合:LZ4 or 非圧縮 ・通信時間が問題な場合:LZMA(要CacheOrDownload系)

131.

AssetBundleのダウンロードはどうやる? ・5.3以降はWebRequestがお勧め ・WWWは避ける ・CacheOrDownloadを使うならば、 ヒープが膨らみ過ぎないようにする。

132.

「ベストプラクティス」を信じない ・環境やゲームによって最適解は異なる。

133.

「ベストプラクティス」を信じない ・環境やゲームによって最適解は異なる。 ・Stay alert! Trust no one! Keep your laser handy!

134.

Thank you!