2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

UnityAdvent Calendar 2024

Day 13

Addressables版 IPreprocessBuild IPostprocessBuild を実装する

Last updated at Posted at 2024-12-12

はじめに

この記事では題名の通りAddressables版の IPreprocessBuildIPostprocessBuild
つまりビルド前後処理を組み込むための手順を解説します。

それに合わせて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用アセンブリに含まれるように配置しましょう。

AddressablesBuildScript_Postprocessable.cs
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"; }
        }
    }
}

BuildScriptPackedModeDefault Build Scriptを定義したclassで、
初期状態のAddressablesに設定されているAddssablesビルドスクリプトです。

image.png
image.png

AddssablesビルドスクリプトはUnityEditor.AddressableAssets.Build.DataBuilders.BuildScriptBaseを継承していれば作成できるのですが、
ビルドプロセスを大きく変えるようなことがない限りは基本的なビルドプロセスが入っているBuildScriptPackedModeを継承元に選ぶ方が楽に実装が済みます。

今回用意したAddressablesBuildScript_Postprocessableも、
このまま実行すればDefault Build Scriptと同じ動きをします。

Postprocess interfaceを準備

ビルドスクリプトから呼び出されるInterfaceを用意しておきます。
IPreprocessBuild IPostprocessBuild に該当するinterfaceです。

IAddressablesEditor.cs
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のついた型を収集し、呼び出す関数を実装します。

AddressablesBuildScript_Postprocessable.cs
    ...
    
    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を実装した型を収集、
収集したTypeActivator.CreateInstanceでインスタンス化し、
IOrderedCallback.callbackOrderで処理順を整理してPostprocessを呼び出しています。

また、インスタンス化したPostprocess classが何かしらの廃棄処理が必要な場合を考えてIDisposable.Disposeが実装されている場合は終了時に呼び出しています。

Addressablesのビルドフローを崩さずにエラーを出すには専用のリザルトデータを返す必要があるため、
Exceptionが出た場合は止めずにその内容を返すようにしています。

Addressablesのビルド前/ビルド後にPostprocessが呼ばれるようにする

Addressablesのビルド前/ビルド後にここまでに記述した関数を呼ぶように実装します。

AddressablesBuildScript_Postprocessable.cs
    ...
    
    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を作成します。

image.png

AddressablesSettingにビルドスクリプトを設定する

AddressablesGroups Window内の Build/New Build 内に表示させるにはAddressablesSettingに設定する必要があります。

AddressablesビルドスクリプトのScriptableObjectを作成する で作成したScriptableObjectを使用し、
Default Build Scriptに対して上書きしてしまいましょう。
(今回はDefault Build Scriptが不要になるので上書きしています。
Default Build Scriptも併用したい場合は下部にある+ボタンから追加しましょう。)

image.png
image.png

以上でAddressablesGroups Window内の Build/New Build 内に新しく定義したビルドスクリプトが表示され、使用できるようになります。

image.png

完成!

以上で定義したIPreprocessAddressablesBuildIPostprocessAddressablesBuildを使用してAddressablesのビルド前/ビルド後に処理を組み込めるようになりました。

試しに使ってみる

適当にログを出すPostprocessを追加します。

Postprocess_Sample.cs
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を使用してビルドしましょう。
ビルドが終了すると正常に設定した処理が実行され、ログが出ます。

image.png

で、どう活用すればいいの?

例えばEditorにしか存在せず、ランタイムに載らない情報をビルドのタイミングで確定させて載せたり、
AddressablesDataBuilderInputの内容を編集してロムに乗せるAddressablesGroupを制限したりするのに使用できます。

筆者はDLC対応をするときに使用しました。

DLCを処理できるかどうかの判定の一部にファイルの存在検証があったのですが、
Addressablesでビルドされるデータが格納されるディレクトリパスには[BuildTarget](=ビルド先のプラットフォーム名)が入ります。
信頼性のために、これはAddressablesのビルド時に評価された値で取得したいです。

下の画像ではその解決のために、DLCのアセットバンドルのファイルパス情報をIPreprocessAddressablesBuildを使用してScriptableObjectに格納しています。

image.png

おしまい

今回はAddressables版の IPreprocessBuild IPostprocessBuild を構築しました。

Addressablesを積極的にカスタムしよう!みたいな記事はあんまり見かけないので、
自身で対応する時にその情報収集や調査に苦労しました。

ので、そんな同族がこの記事で救われればいいなと思います。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?