アセットバンドルはUnityのリソースを外部ファイル化することが出来る機能です。それをクラウドストレージであるFirebaseStorageで扱えるようになれば、新しいステージの追加や更新などが簡単に行うことが出来るようになります。
ここでやりたいこと
ここでは、ゲームのシーンをアセットバンドルで更新することを目的とします。ゲームのコンテンツをアプリ更新以外の方法で変更できるのでとても楽です。もちろん個別のアセットごとの更新も出来たほうが良いですが、ステージ更新や新しいシーン追加も可能なので、ぜひ覚えていってください。
実現するまでの流れは以下
- シーンのアセットバンドルを作る
- シーンのアセットバンドルのロードを行う
- シーンのアセットバンドルをFirebaseStorageからロードする
- 重複ロードを行わないようにキャッシュを利用する
- ちゃんと更新出来ている様子を確認する
シーンのアセットバンドルを作る
ものが無いとお話にならないので、まずはシーンのアセットバンドルを作るところから始めましょう。プラットフォームはStandaloneで行いますが、他のプラットフォームで行いたい場合は一部置き換えながらプログラムを読んで下さい。
シーンファイルをアセットバンドルとして設定する
プロジェクトビューからアセットバンドルにしたいシーンファイルを選択します。インスペクター下部にあるアセットバンドルタブからNewを選択してアセットバンドルの名前を決定します。渡しの場合は「assetbundle_scene01」とシーンファイル名とアセットバンドルの名前を異なるように設定しました。
確定後は下の様に表示されます。決定した名前が表示されたらOKです。
アセットバンドルを作るエディタースクリプト作成
続いてアセットバンドルを生成するスクリプトを作成します。注意が必要なのは、必ずEditorというフォルダの中に下記スクリプトを作成してください。
using UnityEditor;
using System.IO;
public class CreateAssetBundles
{
[MenuItem("Tools/Create AssetBundles")]
private static void CreateAllAssetBundles()
{
string assetBundleDirectory = $"AssetBundles/{EditorUserBuildSettings.activeBuildTarget}";
if (!Directory.Exists(assetBundleDirectory))
{
Directory.CreateDirectory(assetBundleDirectory);
}
BuildPipeline.BuildAssetBundles(assetBundleDirectory,
BuildAssetBundleOptions.None,
EditorUserBuildSettings.activeBuildTarget);
}
}
using Editorはゲームのスクリプトとは別扱いになるため、必ずEditorフォルダに隔離してください。
アセットバンドルを作成
Tools/Create AssetBundlesというメニューが追加されています。ボタンを押して実行すると、プロジェクトのルートフォルダにAssetBundlesというフォルダが生成されます。さらにその中にプラットフォームの名前フォルダが作られ、その中にアセットバンドルが生成されています。
アセットバンドルは同じアセットでも、プラットフォームごとにデータが必要になります。
シーンのアセットバンドルロード
上記でアセットバンドルを作ったとします。作ったものは利用してなんぼ!ということで別のシーンから作成したアセットバンドルのシーンを読み込んでみたいと思います。
アセットバンドル化されたシーンをロードするスクリプトを作成
かなり割り切ったスクリプトになっています。というのもアセットバンドルの開放も行っていなかったりパスも固定だったり、実践ではあまり使わないですが出来ることを確認するにはこれで十分。
using UnityEngine;
using UnityEngine.SceneManagement;
public class LoadAssetBundleScene : MonoBehaviour
{
void Start()
{
string assetbundlePath = Application.dataPath
+ "/../AssetBundles/StandaloneWindows64/assetbundle_scene01";
AssetBundle.LoadFromFile(assetbundlePath);
SceneManager.LoadScene("Scene01", LoadSceneMode.Single);
}
}
シーン名やアセットバンドルの名前、ローカルでのパスなどは
各自異なる場合はあわせてください。
動かして確認
今回の例だと、Scene01以外のシーンに空のゲームオブジェクトを作成し、スクリプトを貼り付けてください。
起動直後にScene01がロードされたら成功です!
エラーパターンとしては、パスが間違えているなどがあげられます。「/../」はひとつ上のフォルダという意味になります。Windowsではパスを調整してアセットバンドルのファイルがある位置を名指ししましょう。
シーンのアセットバンドルをFirebaseStorageからロードする
ここからは離れ技、オンラインストレージ上からアセットバンドルを利用します。
FirebaseStorageにアセットバンドルをアップロードする
可能であればAssetBundlesフォルダ以下の構成を保ったままアップロードしましょう。といってもコンソールメニューからフォルダごとアップロードする方法が無いので、フォルダを作ったら複数選択などでアップロードしてください。
FirebaseStorageからアセットバンドルを利用するスクリプト
スクリプトは先と同じファイルを利用します。ここではFirebaseStorageのパッケージを導入済みとして扱っています。FirebaseStorageを導入していない方はこちらのリンクをご確認ください。
using Firebase.Extensions;
using Firebase.Storage;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;
public class LoadAssetBundleScene : MonoBehaviour
{
void Start()
{
LoadScene();
}
private void LoadScene()
{
string assetBundlePath = "AssetBundles/StandaloneWindows64/assetbundle_scene01";
FirebaseStorage storage = FirebaseStorage.DefaultInstance;
StorageReference reference = storage.GetReference(assetBundlePath);
reference.GetDownloadUrlAsync().ContinueWithOnMainThread(task =>
{
if (!task.IsFaulted && !task.IsCanceled)
{
// ここのログはダウンロードするURLが確認出来ます。
//Debug.Log(task.Result.AbsoluteUri);
UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(task.Result.AbsoluteUri);
var operation = request.SendWebRequest();
operation.completed += _ =>
{
if (operation.webRequest.result == UnityWebRequest.Result.Success)
{
AssetBundle loadAssetBundle = DownloadHandlerAssetBundle.GetContent(operation.webRequest);
SceneManager.LoadScene("Scene01", LoadSceneMode.Single);
}
};
}
});
}
}
上記スクリプトは、プラットフォームを限定していたり、アセットバンドルのUnloadを行っていません。実際の運用時はメモリに無理がないように開放を行う処理も追加してください。
重複ロードを行わないようにキャッシュを利用する
Usage見ると分かるんですが、上記のプログラムのままだとアプリを起動するたびにダウンロードの容量を使ってしまいます。キャッシュを利用する方法として、メタデータの更新時間を利用してハッシュデータを作成します。ハッシュデータとは、暗号みたいなものと思ってください。
スクリプト修正
ファイル自体は先程のものを利用します。長い気もしましたが、全部のせておきます。
using Firebase.Extensions;
using Firebase.Storage;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;
public class LoadAssetBundleScene : MonoBehaviour
{
void Start()
{
LoadScene();
}
private void LoadScene()
{
string assetBundlePath = "AssetBundles/StandaloneWindows64/assetbundle_scene01";
FirebaseStorage storage = FirebaseStorage.DefaultInstance;
StorageReference reference = storage.GetReference(assetBundlePath);
// メタデータを取得
reference.GetMetadataAsync().ContinueWithOnMainThread(taskMeta =>
{
if (!taskMeta.IsFaulted && !taskMeta.IsCanceled)
{
reference.GetDownloadUrlAsync().ContinueWithOnMainThread(task =>
{
if (!task.IsFaulted && !task.IsCanceled)
{
Debug.Log(taskMeta.Result.UpdatedTimeMillis.ToString());
CachedAssetBundle cached = new CachedAssetBundle(
assetBundlePath,
new Hash128(
0, // アプリのバージョンとか、アプリの事情で更新したい時
(ulong)taskMeta.Result.UpdatedTimeMillis.Ticks));
UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(task.Result.AbsoluteUri, cached);
var operation = request.SendWebRequest();
operation.completed += _ =>
{
if (operation.webRequest.result == UnityWebRequest.Result.Success)
{
AssetBundle loadAssetBundle = DownloadHandlerAssetBundle.GetContent(operation.webRequest);
SceneManager.LoadScene("Scene01", LoadSceneMode.Single);
}
};
}
});
}
});
}
}
プログラムのざっくりした流れ
少しだけ補足的に説明
- GetMetadataAsync
- メタデータを取得する処理
- メタデータは取得に失敗することもあるため、チェックを行う必要があります。
- 取得できたメタデータはtaskMeta.Resultから取り出すことが出来ます。
- CachedAssetBundle
- アセットバンドルのダウンロードした履歴のようなものをキャッシュとして保存します。
- キャッシュを生成するには名前とハッシュデータの組み合わせが必要
- アセットバンドルの名前を利用
- ハッシュデータはメタデータのアップデート時間を利用することで、更新されるまで同じキャッシュとして判断させています
動かしてみて、確認
今回の更新で確認したいのは、下の数字が増えなくなることです。これで何度アクセスされてもダウンロード容量に苦労することがなくなりますね!
もちろんシーンのロード自体が行われることも確認してくださいね。
キャッシュ適応した1回目はキャッシュがないため、1回ダウンロードしますのであしからず
ちゃんと更新出来ている様子を確認する
ここまで来たら、あとは更新等に耐えられるかの確認をしておきましょう。
手順としては
- ロードしていたシーンの内容を変更する
- アセットバンドルをビルドしなおす
- ビルド後のアセットバンドルをFirebaseStorageにアップロード
- Unityを動かしてシーンが更新されたことを確認!
コメント