
マイケル
みなさんこんばんは!
マイケルです!!
マイケルです!!

エレキベア
おひさクマ〜〜〜〜

マイケル
今日は久しぶりのUnity記事!
AssetBundle を使ってみるぞ!
AssetBundle を使ってみるぞ!

エレキベア
AssetBundleって何クマ?

マイケル
説明しよう!
AssetBundleとは!!
AssetBundleとは!!
AssetBundleとは
- Unityで読み込める形にアセットを事前にビルドしておくことで
アプリ外部からアセットをロードできる仕組みのこと!

エレキベア
アプリ本体とアセットを分けることができるということクマ?

マイケル
その通り!
アプリを直接更新せずにアセット更新の運用をすることができるんだ!
アプリを直接更新せずにアセット更新の運用をすることができるんだ!

↑AssetBundleファイルの作成処理

↑AssetBundleファイルのロード処理

エレキベア
これは使うのが楽しみクマ〜〜〜
参考書籍

マイケル
AssetBundleの使い方を勉強するにあたり、Unity公式マニュアルの他に、
下記の書籍を参考にさせていただきました!
下記の書籍を参考にさせていただきました!
[Unity公式マニュアル]
[参考書籍]

マイケル
こちらの「AssetBundle」の章を参考にしています!
実践的なサンプルがたくさん載っているので、
今回の記事を読んで 詳細を知りたくなった方 や
他のサンプルが気になる方 は是非読んでみてください!
実践的なサンプルがたくさん載っているので、
今回の記事を読んで 詳細を知りたくなった方 や
他のサンプルが気になる方 は是非読んでみてください!

エレキベア
内容が盛りだくさんだから
1冊持っていて損はないクマ〜〜〜
1冊持っていて損はないクマ〜〜〜
基本的な使い方

マイケル
それじゃ早速使っていこう!
AssetBundleファイルのビルド処理


マイケル
まずはAssetBundleファイルのビルド処理から!
AssetBundleにしたいAssetを選択して、Inspector最下部よりAssetBundle名を入力します!
AssetBundleにしたいAssetを選択して、Inspector最下部よりAssetBundle名を入力します!

↑AssetBundle名の入力

マイケル
AssetBundleを指定したら、ビルド処理を行います!
ビルドは下記メソッドより行うことができます!
ビルドは下記メソッドより行うことができます!
// ビルド処理
BuildPipeline.BuildAssetBundles(
"./AssetBundle01", // ビルドパス
BuildAssetBundleOptions.None, // オプション(圧縮、梱包等)
BuildTarget.iOS // プラットフォーム
);

マイケル
第一引数にAssetBundleファイルを作成するパス、
第二引数に圧縮方式、第三引数にビルドするプラットフォーム
を指定します!
第二引数に圧縮方式、第三引数にビルドするプラットフォーム
を指定します!

マイケル
圧縮方式とプラットフォームは下記を指定できます!
用途に応じて設定しましょう!
用途に応じて設定しましょう!
・圧縮方式
圧縮方式 | 実装 |
LZMA形式 | BuildAssetBundleOptions.None |
非圧縮 | BuildAssetBundleOptions.UncompressedAssetBundle |
LZ4形式 | BuildAssetBundleOptions.ChunkBasedCompression |
・プラットフォーム(※一部抜粋)
圧縮方式 | 実装 |
iOS | BuildTarget.iOS |
Android | BuildTarget.Android |
Windows | BuildTarget.StandaloneWindows |
Mac | BuildTarget.StandaloneOSX |
※詳細は下記参照
アセットバンドルのビルド – Unity スクリプトリファレンス
BuildTarget – Unity スクリプトリファレンス

エレキベア
いろんなオプションがあるのクマね

マイケル
このメソッドを使って、実際にビルドしてみましょう!
下記スクリプトをEditorフォルダに作成します!
下記スクリプトをEditorフォルダに作成します!
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEditor;
/// <summary>
/// AssetBundleビルド用Editorクラス
/// </summary>
public class AssetBundleBuild
{
/// <summary>
/// AssetBundleビルド処理(AssetBundle01)
/// </summary>
[MenuItem("Assets/AssetBundle01 Build")]
public static void Build01()
{
Build("./AssetBundle01");
}
/// <summary>
/// AssetBundleビルド処理(共通)
/// </summary>
/// <param name="assetBundleDirectory">AssetBundleファイル作成パス</param>
private static void Build(string assetBundleDirectory)
{
// ディレクトリ作成
if (!Directory.Exists(assetBundleDirectory))
{
Directory.CreateDirectory(assetBundleDirectory);
}
// ビルド処理
BuildPipeline.BuildAssetBundles(
assetBundleDirectory, // ビルドパス
BuildAssetBundleOptions.None, // オプション(圧縮、梱包等)
BuildTarget.iOS // プラットフォーム
);
// 終了メッセージ表示
EditorUtility.DisplayDialog("AssetBundleビルド完了", "AssetBundleのビルドが終わったでやんす。", "承知!");
}
}

マイケル
するとAssetウィンドウに「AssetBundle01 Build」というメニューが追加されるので実行しましょう!

↑ビルド処理の実行

マイケル
実行すると下記のようにAssetBundleファイルとmanifestファイルが作成されます!

↑完了メッセージ

↑AssetBundleファイルとmanifestファイルが作成される

マイケル
manifestファイルには、ハッシュ値や依存関係といった情報が記載されています!
手動で処理を行う場合、使うことも多いと思うため覚えておきましょう!
手動で処理を行う場合、使うことも多いと思うため覚えておきましょう!

エレキベア
これで準備完了クマ〜〜〜
AssetBundleファイルのロード処理


マイケル
AssetBundleファイルを作成したら、次はロード処理!
ローカルからのロード処理

マイケル
ネットワーク経由でロードするのが一般的だけど、まずはローカルからのロード処理を実装します!
AssetBundle.LoadFromFile()メソッドでファイルのロード、LoadAsset()メソッドでインスタンスの生成を行うことができます!
AssetBundle.LoadFromFile()メソッドでファイルのロード、LoadAsset()メソッドでインスタンスの生成を行うことができます!
// AssetBundleファイルをロード
AssetBundle assetBundle = AssetBundle.LoadFromFile("【AssetBundle格納パス】");
// ロードしたAssetBundleファイルからインスタンス生成
GameObject prefab = assetBundle.LoadAsset<GameObject>("【Asset名】");

エレキベア
ローカルからも読み込めるのクマね

マイケル
上記メソッドを使って、下記のようなスクリプトを作成します!
Sceneの管理クラスとしてアタッチしてみましょう!
Sceneの管理クラスとしてアタッチしてみましょう!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// Test01Scene管理クラス
/// </summary>
public class Test01SceneManager : MonoBehaviour
{
/// <summary>
/// 開始処理
/// </summary>
void Start()
{
// AssetBundleロード処理
StartCoroutine("LoadAssetBundle");
}
/// <summary>
/// AssetBundleロード処理
/// (ローカルからの取得)
/// </summary>
IEnumerator LoadAssetBundle()
{
// AssetBundleファイルをロード
AssetBundle assetBundle = AssetBundle.LoadFromFile("AssetBundle01/char_prefab");
if (assetBundle == null)
yield break;
// ロードしたAssetBundleファイルからインスタンス生成
GameObject prefab = assetBundle.LoadAsset<GameObject>("Kani_red");
Instantiate(prefab);
Debug.Log("カニをロード!!");
// マウスクリックでアンロード
yield return new WaitUntil(() => Input.GetMouseButton(0));
assetBundle.Unload(true);
Debug.Log("カニをアンロード!!");
}
}

マイケル
するとこのようにしっかりとロードできていることが分かります!
使い終わったら、assetBundle.Unload()メソッドでアンロードしましょう!
(※上記処理ではマウスクリックでアンロードしています。)
使い終わったら、assetBundle.Unload()メソッドでアンロードしましょう!
(※上記処理ではマウスクリックでアンロードしています。)


エレキベア
(シュールクマ・・・。)
ネットワーク経由でのロード処理

マイケル
そして次はネットワーク経由でのロード処理!
下記のようにUnityWebRequestAssetBundle.GetAssetBundle()メソッドでリクエストを取得して、サーバからダウンロードします!
下記のようにUnityWebRequestAssetBundle.GetAssetBundle()メソッドでリクエストを取得して、サーバからダウンロードします!
// Webサーバーからアセットバンドルをダウンロード
using (UnityWebRequest req = UnityWebRequestAssetBundle.GetAssetBundle("【AssetBundle格納URL】"))
{
// Webサーバーからのダウンロード待機
yield return req.SendWebRequest();
if (req.result == UnityWebRequest.Result.ConnectionError
|| req.result == UnityWebRequest.Result.ProtocolError)
{
Debug.LogError("エラー:" + req.error);
yield break;
}
// AssetBundleファイルをロード後、インスタンス生成
AssetBundle assetBundle = DownloadHandlerAssetBundle.GetContent(req);
GameObject prefab = assetBundle.LoadAsset<GameObject>("【Asset名】");
}

マイケル
このメソッドを使って実装してみましょう!
サーバを準備する必要がありますが、Python3がインストールされている環境であれば下記コマンドで立ち上げることができます!
サーバを準備する必要がありますが、Python3がインストールされている環境であれば下記コマンドで立ち上げることができます!
python -m http.server 8080

マイケル
コマンドをプロジェクト配下で実行することで「http://localhost:8080/【AssetBundle格納パス】」でアクセスできるようになります!

マイケル
続けてスクリプトを下記に修正しましょう!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
/// <summary>
/// Test01Scene管理クラス
/// </summary>
public class Test01SceneManager : MonoBehaviour
{
/// <summary>
/// 開始処理
/// </summary>
void Start()
{
// AssetBundleロード処理
StartCoroutine("LoadAssetBundleNetwork");
}
・・・略・・・
/// <summary>
/// AssetBundleロード処理
/// (Webサーバからの取得)
/// </summary>
IEnumerator LoadAssetBundleNetwork()
{
// Webサーバーからアセットバンドルをダウンロード
using (UnityWebRequest req = UnityWebRequestAssetBundle.GetAssetBundle("http://localhost:8080/AssetBundle01/char_prefab"))
{
// Webサーバーからのダウンロード待機
yield return req.SendWebRequest();
Debug.Log("http response:" + req.responseCode);
if (req.result == UnityWebRequest.Result.ConnectionError
|| req.result == UnityWebRequest.Result.ProtocolError)
{
Debug.LogError("エラー:" + req.error);
yield break;
}
Debug.Log("サーバからAssetBundleを取得!!");
// AssetBundleファイルをロード後、インスタンス生成
AssetBundle assetBundle = DownloadHandlerAssetBundle.GetContent(req);
GameObject prefab = assetBundle.LoadAsset<GameObject>("Kani_red");
Instantiate(prefab);
Debug.Log("カニをロード!!");
// マウスクリックでアンロード
yield return new WaitUntil(() => Input.GetMouseButton(0));
assetBundle.Unload(true);
Debug.Log("カニをアンロード!!");
}
}
}

マイケル
こちらも問題なく読み込めていることがわかります!


エレキベア
(さっきと変わらないクマ・・・。)
ロード時のテクニック

マイケル
以上が基本的な処理となりますが、実用上使うにはまだまだ問題があります!
その中でもよく使いそうなロード時のテクニックとして2点紹介します!
その中でもよく使いそうなロード時のテクニックとして2点紹介します!
複数ファイルのロード処理(依存関係の考慮)

マイケル
まずは複数のAssetBundleファイルが存在する場合!
各Assetにはそれぞれ依存性があり、ロードする順番には気をつけなければならないです!
各Assetにはそれぞれ依存性があり、ロードする順番には気をつけなければならないです!

マイケル
manifestファイルより、GetAllDependencies()メソッドで依存性のあるAssetを全て取得することができるので、下記のように事前にロードしておきます!
// Manifestファイル
AssetBundleManifest manifest;
// ロード済AssetBundleリスト
Dictionary<string, AssetBundle> loadAssetBundleList = new Dictionary<string, AssetBundle>();
/// <summary>
/// AssetBundleロード処理
/// (Webサーバから複数ファイル取得)
/// </summary>
IEnumerator LoadAssetBundle()
{
// AssetBudnle格納URL
string url = "【AssetBundle格納URL】";
// Manifestファイルをロード
yield return loadAssetBundle(url, "【AssetBundleフォルダ名】");
manifest = loadAssetBundleList["【AssetBundleフォルダ名】"].LoadAsset<AssetBundleManifest>("Assetbundlemanifest");
// AssetBundleファイルのロード
// (Manifestファイルから依存関係のファイルもロードする)
foreach (string depAssetBundleName in manifest.GetAllDependencies("【AssetBundle名】"))
yield return loadAssetBundle(url, depAssetBundleName);
yield return loadAssetBundle(url, "【AssetBundle名】");
// ロードしたAssetBundleファイルからインスタンス生成
AssetBundle prefabAssetBundle = loadAssetBundleList["【AssetBundle名】"];
GameObject prefab_kani_red = prefabAssetBundle.LoadAsset<GameObject>("【Asset名】");
}
/// <summary>
/// AssetBundleファイルロード処理
/// </summary>
/// <param name="url">AssetBundle格納URL</param>
/// <param name="assetBundleName">AssetBundle名</param>
private IEnumerator loadAssetBundle(string url, string assetBundleName)
{
// ロード済の場合、処理しない
if (loadAssetBundleList.ContainsKey(assetBundleName))
yield break;
・・・ロード処理・・・
// ロード済AssetBundleリストに追加
loadAssetBundleList[assetBundleName] = DownloadHandlerAssetBundle.GetContent(req);
}
}

マイケル
一点気をつけないといけないのは、同じAssetを何度もロードしないことです!
上記処理では、loadAssetBundleList変数にロード済のAssetを保持するようにしています!
上記処理では、loadAssetBundleList変数にロード済のAssetを保持するようにしています!

エレキベア
複数のAssetBundleファイルがある場合は、
・依存性のあるAssetを事前にロードする。
・一度ロードしたAssetは保持しておく。
ことが大事クマね
・依存性のあるAssetを事前にロードする。
・一度ロードしたAssetは保持しておく。
ことが大事クマね

マイケル
そのとおり!
それじゃこの処理を使って実装してみよう!
今回は下記のようにカニ3種類をchar_prefab、それぞれのテクスチャをchar_textureに設定してAssetBundleファイルを作成してみます!
それじゃこの処理を使って実装してみよう!
今回は下記のようにカニ3種類をchar_prefab、それぞれのテクスチャをchar_textureに設定してAssetBundleファイルを作成してみます!
・AssetBundke名の設定
AssetBundle名 | Asset名 |
char_prefab | Kani_red |
char_prefab | Kani_blue |
char_prefab | Kani_yellow |
char_texture | kani_red_col |
char_texture | kani_blue_col |
char_texture | kani_yellow_col |



マイケル
さっき作ったフォルダと分けるため、
今回はAssetBundle02フォルダに作成しました!
今回はAssetBundle02フォルダに作成しました!


マイケル
作成されたmanifestファイルの中身を見てみると、
char_prefabが、char_textureに依存していることが分かります!
char_prefabが、char_textureに依存していることが分かります!

↑char_prefabはchar_textureに依存している

エレキベア
char_textureから先にロードしないといけないわけクマね

マイケル
その通り!
新たにSceneを作成して、下記スクリプトをアタッチしてみよう!
新たにSceneを作成して、下記スクリプトをアタッチしてみよう!
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;
/// <summary>
/// Test02Scene管理クラス
/// </summary>
public class Test02SceneManager : MonoBehaviour
{
// Manifestファイル
AssetBundleManifest manifest;
// ロード済AssetBundleリスト
Dictionary<string, AssetBundle> loadAssetBundleList = new Dictionary<string, AssetBundle>();
/// <summary>
/// 開始処理
/// </summary>
void Start()
{
// AssetBundleロード処理
StartCoroutine("LoadAssetBundle");
}
/// <summary>
/// AssetBundleロード処理
/// (Webサーバから複数ファイル取得)
/// </summary>
IEnumerator LoadAssetBundle()
{
// AssetBudnle格納URL
string url = "http://localhost:8080/AssetBundle02/";
// Manifestファイルをロード
yield return loadAssetBundle(url, "AssetBundle02");
manifest = loadAssetBundleList["AssetBundle02"].LoadAsset<AssetBundleManifest>("Assetbundlemanifest");
// AssetBundleファイルのロード
// (Manifestファイルから依存関係のファイルもロードする)
foreach (string depAssetBundleName in manifest.GetAllDependencies("char_prefab"))
yield return loadAssetBundle(url, depAssetBundleName);
yield return loadAssetBundle(url, "char_prefab");
// ロードしたAssetBundleファイルからインスタンス生成
AssetBundle prefabAssetBundle = loadAssetBundleList["char_prefab"];
GameObject prefab_kani_red = prefabAssetBundle.LoadAsset<GameObject>("Kani_red");
Instantiate(prefab_kani_red);
GameObject prefab_kani_blue = prefabAssetBundle.LoadAsset<GameObject>("Kani_blue");
Instantiate(prefab_kani_blue);
GameObject prefab_kani_yellow = prefabAssetBundle.LoadAsset<GameObject>("Kani_yellow");
Instantiate(prefab_kani_yellow);
Debug.Log("カニ軍団をロード!!");
// マウスクリックでアンロード
yield return new WaitUntil(() => Input.GetMouseButton(0));
prefabAssetBundle.Unload(true);
Debug.Log("カニ軍団をアンロード!!");
}
/// <summary>
/// AssetBundleファイルロード処理
/// </summary>
/// <param name="url">AssetBundle格納URL</param>
/// <param name="assetBundleName">AssetBundle名</param>
private IEnumerator loadAssetBundle(string url, string assetBundleName)
{
// ロード済の場合、処理しない
if (loadAssetBundleList.ContainsKey(assetBundleName))
yield break;
// AssetBundleのロード処理
string loadUrl = Path.Combine(url, assetBundleName);
using (UnityWebRequest req = UnityWebRequestAssetBundle.GetAssetBundle(loadUrl))
{
// Webサーバーからのダウンロード待機
yield return req.SendWebRequest();
Debug.Log("http response:" + req.responseCode);
if (req.result == UnityWebRequest.Result.ConnectionError
|| req.result == UnityWebRequest.Result.ProtocolError)
{
Debug.LogError("エラー:" + req.error);
yield break;
}
Debug.Log("サーバからAssetBundleを取得!! -> " + assetBundleName);
// ロード済AssetBundleリストに追加
loadAssetBundleList[assetBundleName] = DownloadHandlerAssetBundle.GetContent(req);
}
}
}

マイケル
これで無事にカニ達を召喚することができたね!


エレキベア
(増えたクマ・・・。)
差分がある場合のみロードする

マイケル
次は差分が発生した時だけロードする方法です!
これは大きく、ロード時にバージョンを指定する方法と、ハッシュ値を指定する方法の2つがあります。
これは大きく、ロード時にバージョンを指定する方法と、ハッシュ値を指定する方法の2つがあります。
バージョンを指定する

マイケル
バージョンを指定する方法については、下記のようにロード時に引数でバージョンを渡します!
private string url = "【AssetBundle格納URL】";
private uint version = 1;
req = UnityWebRequestAssetBundle.GetAssetBundle(url, version, 0);
↑バージョン指定によるロード処理
マイケル
バージョンを指定することでキャッシュされて、違うバージョンを渡した時のみ再ロードするようになります!

エレキベア
これだけでキャッシュしてくれるのは便利クマね〜〜〜

マイケル
ちなみに第3引数は、CRCチェックをする場合に指定します!
今回は使わないので、チェックを行わないという意味の0を指定しています。
今回は使わないので、チェックを行わないという意味の0を指定しています。
ハッシュ値を指定する

マイケル
バージョンによる管理ができれば便利だけど、全てのAssetのバージョンを管理するのは大規模なシステムだと難しい!
そこでもう一つ、ハッシュ値を指定する方法があります!
そこでもう一つ、ハッシュ値を指定する方法があります!

マイケル
ハッシュ値はManifestファイルに記載されていて、下記のように取得して引数に指定してあげると、バージョン指定と同じようにキャッシュされます!
private string url = "【AssetBundle格納URL】";
private Hash128 hash = manifest.GetAssetBundleHash(【AssetBundle名】);
req = UnityWebRequestAssetBundle.GetAssetBundle(url, hash, 0);
↑ハッシュ値指定によるロード処理
マイケル
バージョン指定やハッシュ値指定のロード処理は、
下記のように組み込んで使いましょう!
下記のように組み込んで使いましょう!
/// <summary>
/// AssetBundleロード処理
/// (AssetBundleの変更をhash値から検知する)
/// </summary>
IEnumerator LoadAssetBundle()
{
・・・略・・・
// キャッシュシステムの準備を待つ
yield return new WaitUntil(() => Caching.ready);
// Manifestファイルをロード
// (ハッシュ未使用、常にロードする)
yield return loadAssetBundle(url, "【AssetBundle格納パス】", false);
manifest = loadAssetBundleList["【AssetBundle格納パス】"].LoadAsset<AssetBundleManifest>("Assetbundlemanifest");
・・・略・・・
// AssetBundleファイルのロード
// (ハッシュ使用、Manifestファイルから依存関係のファイルもロードする)
foreach (string depAssetBundleName in manifest.GetAllDependencies("【AssetBundle名】"))
yield return loadAssetBundle(url, depAssetBundleName, true);
yield return loadAssetBundle(url, "【AssetBundle名】", true);
・・・略・・・
}
/// <summary>
/// AssetBundleファイルロード処理
/// </summary>
/// <param name="url">AssetBundle格納URL</param>
/// <param name="assetBundleName">AssetBundleファイル名</param>
/// <param name="useHash">ロードにハッシュを使用するか</param>
private IEnumerator loadAssetBundle(string url, string assetBundleName, bool useHash)
{
// ロード済の場合、処理しない
if (loadAssetBundleList.ContainsKey(assetBundleName))
yield break;
// hashの有無によりロード方法を変更
string loadUrl = Path.Combine(url, assetBundleName);
UnityWebRequest req;
if (useHash)
{
// hashを使用する場合、変更分のみロードする
Hash128 hash = manifest.GetAssetBundleHash(assetBundleName);
req = UnityWebRequestAssetBundle.GetAssetBundle(loadUrl, hash, 0);
}
else
{
// hashを使用しない場合、常にロードする
req = UnityWebRequestAssetBundle.GetAssetBundle(loadUrl);
}
// AssetBundleのロード処理
using (req)
{
// Webサーバーからのダウンロード待機
yield return req.SendWebRequest();
Debug.Log("http response:" + req.responseCode);
if (req.result == UnityWebRequest.Result.ConnectionError
|| req.result == UnityWebRequest.Result.ProtocolError)
{
Debug.LogError("エラー:" + req.error);
yield break;
}
Debug.Log("サーバからAssetBundleを取得!! -> " + assetBundleName);
// ロード済AssetBundleリストに追加
loadAssetBundleList[assetBundleName] = DownloadHandlerAssetBundle.GetContent(req);
}
}

マイケル
キャッシュされているかどうかは、req.responseCodeで確認することができます!
2回目以降の実行で、下記のようにレスポンスが0で返ってきていれば上手くキャッシュ出来ています!
2回目以降の実行で、下記のようにレスポンスが0で返ってきていれば上手くキャッシュ出来ています!

↑キャッシュ後のロード処理

マイケル
キャッシュされた後、位置を変えるなど変更を加えた後に再ビルドすると、レスポンスが200で返ってきて再ロードされていることも確認できます!

↑再ロード処理

エレキベア
うまく効いてるクマね〜〜
おわりに

マイケル
というわけで今回はAssetBundleを触ってみました!
どうだったかな?
どうだったかな?

エレキベア
なかなか慣れるまでが大変そうクマね〜〜
でもアプリと分離化できるのは便利クマ!
でもアプリと分離化できるのは便利クマ!

マイケル
大規模なゲーム開発となると、
使うのはほぼ必須になると思うから、がんばって慣れていこう!!
使うのはほぼ必須になると思うから、がんばって慣れていこう!!

マイケル
それでは今日はこの辺で!!
アデューー!!
アデューー!!

エレキベア
クマ〜〜〜〜〜〜
【Unity】AssetBundleを使ってリソースを管理する 〜完〜
コメント