はじめに
この記事では題名の通りAddressables版の IPreprocessBuild
と IPostprocessBuild
、
つまりビルド前後処理を組み込むための手順を解説します。
それに合わせてAddressablesのビルドスクリプトに関して軽い説明を添えているので、
実装をなぞりつつAddressablesのカスタマイズに関して少し詳しくなれます。
この記事の対象者
- Addressablesをすでに使ったがある人
- Addressables自体に関する解説はすでにたくさんあるのでそっちを見てください
環境
- Unity 2022.3.16.f1
- Addressables 1.21.21
実装概要
Addressablesでビルド前/ビルド後に処理を組み込むにはそのタイミングで処理を行うよう実装を追加する必要があります。
これを実現するためにAddressablesのビルドスクリプトを自前で用意します。
といってもAddressablesを実際にビルドする部分の処理は触らず、最小限の実装で済むように進めていきます。
Addressablesのビルドスクリプトを作成
まずは最小単位のAddressablesビルドスクリプトを用意します。
UnityEditor.AddressableAssets.Build.DataBuilders.BuildScriptPackedMode
を継承したクラスを作成します。
BuildScriptPackedMode
はEditor用アセンブリに含まれるクラスなので、
継承クラスの定義場所はEditor
ディレクトリ以下に置くなどしてEditor用アセンブリに含まれるように配置しましょう。
using UnityEngine;
namespace UnityEditor.AddressableAssets.Build.DataBuilders
{
[CreateAssetMenu(fileName = "BuildScriptPacked_Postprocessable.asset",
menuName = "Addressables/Content Builders/Postprocessable Build Script")]
public class AddressablesBuildScript_Postprocessable : BuildScriptPackedMode
{
public override string Name
{
get { return "Postprocessable Build Script"; }
}
}
}
BuildScriptPackedMode
はDefault Build Script
を定義したclassで、
初期状態のAddressablesに設定されているAddssablesビルドスクリプトです。
AddssablesビルドスクリプトはUnityEditor.AddressableAssets.Build.DataBuilders.BuildScriptBase
を継承していれば作成できるのですが、
ビルドプロセスを大きく変えるようなことがない限りは基本的なビルドプロセスが入っているBuildScriptPackedMode
を継承元に選ぶ方が楽に実装が済みます。
今回用意したAddressablesBuildScript_Postprocessable
も、
このまま実行すればDefault Build Script
と同じ動きをします。
Postprocess interfaceを準備
ビルドスクリプトから呼び出されるInterfaceを用意しておきます。
IPreprocessBuild
IPostprocessBuild
に該当するinterfaceです。
using UnityEditor.AddressableAssets.Build;
using UnityEditor.Build;
namespace UnityEditor.AddressableAssets
{
public interface IPreprocessAddressablesBuild: IOrderedCallback
{
public void OnPreprocessAddressablesBuild(AddressablesDataBuilderInput builderInput);
}
public interface IPostprocessAddressablesBuild : IOrderedCallback
{
public void OnPostprocessAddressablesBuild(AddressablesDataBuilderInput builderInput);
}
}
IOrderedCallback
は処理の呼び出し順を制御するためのinterfaceです。
実装することで呼び出し順を明確に制御できるようにするのが目的です。
AddressablesDataBuilderInput
はAddressablesビルドに使用されるデータです。
この内容に沿ってAddressablesはビルドされます。
内容を変更することでAddressablesのビルド内容をコード上で制御したりできます。
AddssablesビルドスクリプトからProcess Interfaceを呼び出す
Addressablesのビルドスクリプトを作成 で作成したclass内にProcess呼び出し処理を追加します。
長いので小分けに記載します。
Postprocess Interfaceを呼び出す関数を実装
Postprocess interfaceのついた型を収集し、呼び出す関数を実装します。
...
public class AddressablesBuildScript_Postprocessable : BuildScriptPackedMode
{
...
private static Exception _OnPreprocessAddressablesBuild(AddressablesDataBuilderInput builderInput)
{
var processTypes = TypeCache.GetTypesDerivedFrom<IPreprocessAddressablesBuild>();
List<IPreprocessAddressablesBuild> instances = new List<IPreprocessAddressablesBuild>();
try
{
foreach (var processType in processTypes)
{
instances.Add((IPreprocessAddressablesBuild)Activator.CreateInstance(processType));
}
instances = instances
.OrderBy(m => m.callbackOrder)
.ToList();
foreach (var instance in instances)
{
instance.OnPreprocessAddressablesBuild(builderInput);
}
}
catch (Exception exception)
{
return exception;
}
finally
{
foreach (var instance in instances
.Where(m => m != null)
.OfType<IDisposable>())
{
instance.Dispose();
}
}
return null;
}
private static Exception _OnPostprocessAddressablesBuild(AddressablesDataBuilderInput builderInput)
{
var processTypes = TypeCache.GetTypesDerivedFrom<IPostprocessAddressablesBuild>();
List<IPostprocessAddressablesBuild> instances = new List<IPostprocessAddressablesBuild>();
try
{
foreach (var processType in processTypes)
{
instances.Add((IPostprocessAddressablesBuild)Activator.CreateInstance(processType));
}
instances = instances
.OrderBy(m => m.callbackOrder)
.ToList();
foreach (var instance in instances)
{
instance.OnPostprocessAddressablesBuild(builderInput);
}
}
catch (Exception exception)
{
return exception;
}
finally
{
foreach (var instance in instances
.Where(m => m != null)
.OfType<IDisposable>())
{
instance.Dispose();
}
}
return null;
}
...
}
}
TypeCache.GetTypesDerivedFrom<T>
を使用してPostprocess interfaceを実装した型を収集、
収集したType
をActivator.CreateInstance
でインスタンス化し、
IOrderedCallback.callbackOrder
で処理順を整理してPostprocessを呼び出しています。
また、インスタンス化したPostprocess classが何かしらの廃棄処理が必要な場合を考えてIDisposable.Dispose
が実装されている場合は終了時に呼び出しています。
Addressablesのビルドフローを崩さずにエラーを出すには専用のリザルトデータを返す必要があるため、
Exceptionが出た場合は止めずにその内容を返すようにしています。
Addressablesのビルド前/ビルド後にPostprocessが呼ばれるようにする
Addressablesのビルド前/ビルド後にここまでに記述した関数を呼ぶように実装します。
...
public class AddressablesBuildScript_Postprocessable : BuildScriptPackedMode
{
...
protected override TResult BuildDataImplementation<TResult>(AddressablesDataBuilderInput builderInput)
{
var setting = builderInput.AddressableSettings;
{
var exception = _OnPreprocessAddressablesBuild(builderInput);
if (exception != null)
{
return AddressableAssetBuildResult.CreateResult<TResult>(null, 0, exception.Message);
}
}
var result = base.BuildDataImplementation<TResult>(builderInput);
{
var exception = _OnPostprocessAddressablesBuild(builderInput);
if (exception != null)
{
UnityEngine.Debug.LogException(exception);
}
}
return result;
}
...
}
}
BuildDataImplementation
はAddressablesのビルド処理が実装される関数です。
base.BuildDataImplementation
にはDefault Build Script
のビルド処理が入っているので、
これの呼び出し前と呼び出し後にPostprocessの処理を置いています。
_OnPreprocessAddressablesBuild
から発行されるExceptionに対してはAddressablesのビルドフローに沿って例外を返すために
AddressableAssetBuildResult.CreateResult<T>
を使用してビルドを中断するように実装しています。
Addressablesはこのリザルトデータをもってビルドフローを停止し、ログを出します。
逆に_OnPostprocessAddressablesBuild
から発行されるExceptionに対してはビルド自体は正常に終了したことを評価して、ビルドフローを中断させずにログを出すのみにとどめています。
以上でPostprocess呼び出し処理の実装完了です。
Addressablesビルドスクリプトとして登録する
Addressablesのビルドスクリプトとして使用するにはデータを作成する必要があります。
AddressablesビルドスクリプトのScriptableObjectを作成する
Unityのプロジェクトビュー内で右クリックし、コンテキスト内からAddressables/Content Builders/Postprocessable Build Script
を押下して
ここまでで作ったビルドスクリプトのScriptableObjectを作成します。
AddressablesSettingにビルドスクリプトを設定する
AddressablesGroups Window内の Build/New Build
内に表示させるにはAddressablesSettingに設定する必要があります。
AddressablesビルドスクリプトのScriptableObjectを作成する で作成したScriptableObjectを使用し、
Default Build Script
に対して上書きしてしまいましょう。
(今回はDefault Build Script
が不要になるので上書きしています。
Default Build Script
も併用したい場合は下部にある+ボタンから追加しましょう。)
以上でAddressablesGroups Window内の Build/New Build
内に新しく定義したビルドスクリプトが表示され、使用できるようになります。
完成!
以上で定義したIPreprocessAddressablesBuild
とIPostprocessAddressablesBuild
を使用してAddressablesのビルド前/ビルド後に処理を組み込めるようになりました。
試しに使ってみる
適当にログを出すPostprocessを追加します。
using System.Linq;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Build;
using UnityEngine;
namespace Samples
{
public class Preprocess_Sample : IPreprocessAddressablesBuild
{
public int callbackOrder => 0;
public void OnPreprocessAddressablesBuild(AddressablesDataBuilderInput builderInput)
{
var groups = builderInput.AddressableSettings.groups;
Debug.Log($"preprocess {builderInput.Target.ToString()}: {string.Join(',', groups.Select(m=>m.name))}");
}
}
public class Postprocess_Sample : IPostprocessAddressablesBuild
{
public int callbackOrder => 0;
public void OnPostprocessAddressablesBuild(AddressablesDataBuilderInput builderInput)
{
Debug.Log("done build");
}
}
}
そしたらAddressablesGroups Window内のBuild/New Build/Postprocessable Build Script
を使用してビルドしましょう。
ビルドが終了すると正常に設定した処理が実行され、ログが出ます。
で、どう活用すればいいの?
例えばEditorにしか存在せず、ランタイムに載らない情報をビルドのタイミングで確定させて載せたり、
AddressablesDataBuilderInput
の内容を編集してロムに乗せるAddressablesGroupを制限したりするのに使用できます。
筆者はDLC対応をするときに使用しました。
DLCを処理できるかどうかの判定の一部にファイルの存在検証があったのですが、
Addressablesでビルドされるデータが格納されるディレクトリパスには[BuildTarget]
(=ビルド先のプラットフォーム名)が入ります。
信頼性のために、これはAddressablesのビルド時に評価された値で取得したいです。
下の画像ではその解決のために、DLCのアセットバンドルのファイルパス情報をIPreprocessAddressablesBuild
を使用してScriptableObjectに格納しています。
おしまい
今回はAddressables版の IPreprocessBuild IPostprocessBuild を構築しました。
Addressablesを積極的にカスタムしよう!みたいな記事はあんまり見かけないので、
自身で対応する時にその情報収集や調査に苦労しました。
ので、そんな同族がこの記事で救われればいいなと思います。