【Unity】アセットバンドルのすゝめ【AssetBundles】
はじめに
Unityのレガシーなアセット管理には Resources クラスがあります。テストや検証プロジェクトではお手軽なファイルシステムですが、大規模プロジェクトになってくると非常に厄介な存在になってきます。
本家のマニュアルでも利用を控えるよう訴えています。
ファイル管理とアセットの管理はアセットバンドルを利用してメモリやデバイスの存在を意識しながら開発していくことをお薦めます。
Resourcesクラスの欠点
- ビルド時間の増大(特にプロジェクト終盤)
- メモリ消費の増大
- デバイスへの過度なアクセス増加
- 初期起動の速度低下
- パッチ作成時の差分の増加
- 利用制限がないため無秩序に呼ばれる
などなど、”お手軽”くらいしか利点はないようです。
アセットバンドルの存在
アセットバンドルはファイルアーカイバみたいなもので、ある程度のアセットをまとめて一つのファイルにします。必要に応じてアセットバンドルを読み込みアセットを取り出します。不必要になったらアンロードしてメモリを空けます。
アセットバンドルの置き場所は自由で、web だったり ファイルだったりメモリ上かもしれません。開発者が明確に把握できます。(Resourcesは不明瞭)
これでピーキーな開発環境でもなんとかやりくりできるというものです。
構想
あくまで一例であり、プラットフォーム依存や運営・開発方針によりベストな方法を模索するのをおすすめします。
- Assets/Resources は一切使わない
- Assetsの外にアセットバンドル群を置いて、Editorの速度低下を軽減
- プラットフォームごとのアセットバンドルを生成する
- Editorからのプレイは直でアセットバンドルを読み込むようにする
- プロジェクトのビルド時は Assets/StreamingAssets にコピーして内包する
- Release 後は StreamingAssets からアセットバンドルをロードしてアセットをインスタンスする
- シーンもアセットバンドル化してScenes In Build には追加しない
- プロジェクトを分けて肥大化を防ぐ
アセットバンドルの作り方
UnityEditor.BuildPipeline クラスがアセットバンドルビルドクラスになります。ここにいろいろなメソッドがあり、目的に応じて利用していきます。
アセットにアセットバンドルを登録する
アセットバンドルに突っ込めるアセットの Inspector には AssetBundle なる項目があります。
この赤い部分に 固めるためのアセットバンドル名をいれます。スラッシュをつけでディレクトリを含めることもできるので、管理がしやすくなると思います。
右の項目はローカライズに便利な variant name を設定します。ローカライズする予定がないならNone のままでいいんじゃないかな?
このように maps/map00 というアセットバンドルに含めたいアセットにすべて設定していきます。依存関係があるアセットは自動的にビルド時に含まれますので、便利ですけど注意も必要です。
この時点ではアセットバンドル化はされてません。
ビルドスクリプトを作成する。
UnityEditor.BuildPipeline.BuildAssetBundles を利用すると一度にすべてアセットバンドル化できるのでこれを使っていきます。
Assets/Editor フォルダーを作成しエディッタを拡張していきます。
こちら Windows 64bit用のアセットバンドルビルドコードになります。
複数のプラットフォームを選択可能なコードにしました。
using System;
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
public class AssetBundlesMenuItems
{
const string RootPath = "AssetBundles"; // 書き出すフォルダ
const string AssetBundleVariant = "assetbundle"; // AssetBundle のバリアント
//----------------------------------------------------------------------------
// アセットバンドル名のつけられた物をアセットバンドルフォルダに出力する
static private void FullBuildAssetBundles( UnityEditor.BuildTarget targetPlatform)
{
string platformName = targetPlatform.ToString();
// AssetBundles/{targetPlatform} フォルダがなければ作成する
var outputPath = System.IO.Path.Combine( RootPath, platformName );
if( !System.IO.Directory.Exists( outputPath))
{
System.IO.Directory.CreateDirectory( outputPath);
}
// アセットデータベースにある、全アセットバンドルの名称からビルドマップを作成する
var buildList = new List<UnityEditor.AssetBundleBuild>();
foreach( var name in UnityEditor.AssetDatabase.GetAllAssetBundleNames())
{
var builder = new AssetBundleBuild();
builder.assetBundleName = name; // アセットバンドル名
builder.assetNames = UnityEditor.AssetDatabase.GetAssetPathsFromAssetBundle( builder.assetBundleName); // AssetBundle に含まれるアセット名
builder.assetBundleVariant = AssetBundleVariant; // AssetBundle のバリアント
buildList.Add( builder);
}
if( buildList.Count > 0)
{
// 全アセットバンドルをビルドする
UnityEditor.BuildPipeline.BuildAssetBundles(
outputPath, // アセットバンドルの出力パス
buildList.ToArray(), // AssetBundleのリスト
UnityEditor.BuildAssetBundleOptions.ChunkBasedCompression, // アセットバンドル作成時にチャンクベースの LZ4 圧縮を使用します。
targetPlatform // ビルド時の対象プラットフォーム
);
}
UnityEngine.Debug.Log( $"Build {platformName} AssetBundles Completed");
}
//----------------------------------------------------------------------------
// Windows
[MenuItem ("AssetBundle/フルアセットビルド/Windows", false)]
static public void BuildWindowsAssetBundles() => FullBuildAssetBundles( UnityEditor.BuildTarget.StandaloneWindows64);
//----------------------------------------------------------------------------
// Mac
[MenuItem ("AssetBundle/フルアセットビルド/Mac", false)]
static public void BuildMacAssetBundles() => FullBuildAssetBundles( UnityEditor.BuildTarget.StandaloneOSX);
//----------------------------------------------------------------------------
// iOS
[MenuItem ("AssetBundle/フルアセットビルド/iOS", false)]
static public void BuildiOSAssetBundles() => FullBuildAssetBundles( UnityEditor.BuildTarget.iOS);
//----------------------------------------------------------------------------
// Android
[MenuItem ("AssetBundle/フルアセットビルド/Android", false)]
static public void BuildAndroidAssetBundles() => FullBuildAssetBundles( UnityEditor.BuildTarget.Android);
} // class AssetBundlesMenuItems
コードの解説
ファルダの作成
// AssetBundles/{targetPlatform} フォルダがなければ作成する
var outputPath = System.IO.Path.Combine( RootPath, platformName );
if( !System.IO.Directory.Exists( outputPath))
{
System.IO.Directory.CreateDirectory( outputPath);
}
Assetsフォルダーの外 プロジェクトの直下に AssetBundles というフォルダを作り 対象のプラットフォームのフォルダを作成ます。(macOS / iOS / Windowsなど)
Assetsの外に作ることで Editor から参照されなくなり、Editorの速度低下軽減を狙ってます。
アセットバンドルの列挙
// アセットデータベースにある、全アセットバンドルの名称からビルドマップを作成する
var buildList = new List<UnityEditor.AssetBundleBuild>();
foreach( var name in UnityEditor.AssetDatabase.GetAllAssetBundleNames())
{
var builder = new AssetBundleBuild();
builder.assetBundleName = name; // アセットバンドル名
builder.assetNames = UnityEditor.AssetDatabase.GetAssetPathsFromAssetBundle( builder.assetBundleName); // AssetBundle に含まれるアセット名
builder.assetBundleVariant = AssetBundleVariant; // AssetBundle のバリアント
buildList.Add( builder);
}
次に現在のProject内の全アセットからアセットバンドルを列挙し、ビルド用の情報をリスト化します。
ビルド
// 全アセットバンドルをビルドする
UnityEditor.BuildPipeline.BuildAssetBundles(
outputPath, // アセットバンドルの出力パス
buildList.ToArray(), // AssetBundleのリスト
UnityEditor.BuildAssetBundleOptions.ChunkBasedCompression, // アセットバンドル作成時にチャンクベースの LZ4 圧縮を使用します。
targetPlatform // ビルド時の対象プラットフォーム
);
最後に作成したビルドマップ(buildList) からアセットバンドルをフルビルドします。
BuildAssetBundleOptions は複数あるので色々試してみると便利です。
ビルドする
Editorフォルダにスクリプトを入れることで、メニューが拡張されビルド可能になります。
ビルドすると {Project}/AssetBundles が作成され ファイルが存在することをUnityEditor以外( Explorer / Finder )から確認してください。
.manifest ファイルはアセットバンドルの構成要素が書かれたファイルです。確認用で実際に製品に入れることは無いです。
ビルドはタイムスタンプで比較しているのか、変更がない場合はビルドされません。
もし強制的にビルドしたい場合は ビルドオプションで BuildAssetBundleOptions.ForceRebuildAssetBundle を設定するとフルビルドされます。まぁ自分はめんどいので AssetBundlesフォルダごと削除してリビルドしてますが:p
長くなったので 利用方法は次回に続く…
AddressableAssetsネタも書きました。