UnityのScriptable Build Pipelineによるビルドフロー構築方法について簡単にまとめました。
- Scriptable Build Pipelineとは?
- インストール
- IBuildTaskを理解する
- IContextObjectを理解する
- 実際にIBuildTaskが使用されている部分を見る
- 実際にIContextObjectが使用されている部分を見る
- おまけ: 軽くカスタムしたいだけの場合
- 参考
Unity2020.1.10
Scriptable Build Pipeline 1.7.3
Scriptable Build Pipelineとは?
Scriptable Build Pipelineを使うとアセットバンドルなどのビルドフローを自由に構築することができます。
例えばアセットバンドル同士の依存関係を構築したり、依存関係を書いたファイルを出力したりする処理をカスタムすることができます。
またこれを使ったデフォルトのビルドパイプラインがあらかじめUnityで定義されており、これはAddressableアセットシステムで使用されています。
従来のカスタムできなかったパイプラインに比べ、パフォーマンスが向上したのとインクリメンタルビルドが改善されたようです。
なおScriptable Build PipelineはUnity 2018.3以降で動作します。
インストール
Scriptable Build PipelineのインストールはPackage Managerから行います。
Package Managerによるインストール方法は以下の記事にまとめていますので、必要に応じて参照してください。
IBuildTaskを理解する
それではScriptable Build Pipelineの使い方を見ていきます。
まずはIBuildTask
というインタフェースを理解する必要があります。
アセットバンドルのビルドは例えば以下のようにいくつかのフェーズに分けられます(実際にはもっと複雑です)。
Scriptable Build Pipelineでは、これらのフェーズごとにIBuildTask
を実装したクラスを作ります。
そして以下のようにIBuildTask
のリストをBuildTasksRunner.Run
に渡すことで各フェーズの処理を実行します。
using System.Collections.Generic; using UnityEditor; using UnityEngine; using UnityEditor.Build.Pipeline.Interfaces; using UnityEditor.Build.Pipeline; public static class Example { [MenuItem("Example/Build")] private static void Build() { // 実行する順にIBuildTaskを作成・登録する var tasks = new List<IBuildTask> { new LogPlatform() }; // (これは次節で説明します) var contexts = new BuildContext(); // IBuildTaskを実行する var returnCode = BuildTasksRunner.Run(tasks, contexts); Debug.Log(returnCode); } } // 今のプラットフォームを出力するだけのBuildTask public class LogPlatform : IBuildTask { public int Version => 1; public ReturnCode Run() { Debug.Log(EditorUserBuildSettings.activeBuildTarget); return ReturnCode.Success; } }
これを実行するといまのプラットフォームがログ出力されることが確認できます。
IContextObjectを理解する
さて次に前節のようなIBuildTask
にパラメータを渡すことを考えます。
この場合にはScriptable Build Pipelineで用意されているDIの仕組みを使います。
具体的には以下のようにIContextObject
を実装したクラスにパラメータを格納して、IBuildTask
にDIします。
using System.Collections.Generic; using UnityEditor; using UnityEngine; using UnityEditor.Build.Pipeline.Interfaces; using UnityEditor.Build.Pipeline; using UnityEditor.Build.Pipeline.Injector; public static class Example { [MenuItem("Example/Build")] private static void Build() { var tasks = new List<IBuildTask> { // プラットフォームを切り替える new SwitchPlatform(), // 今のプラットフォームをログ出力する new LogPlatform() }; var contexts = new BuildContext(); // SwitchPlatform用のContextを追加する var switchPlatformContext = new SwitchPlatformContext(BuildTargetGroup.Android, BuildTarget.Android); contexts.SetContextObject(switchPlatformContext); var returnCode = BuildTasksRunner.Run(tasks, contexts); Debug.Log(returnCode); } } public class LogPlatform : IBuildTask { public int Version => 1; public ReturnCode Run() { Debug.Log(EditorUserBuildSettings.activeBuildTarget); return ReturnCode.Success; } } // プラットフォームを切り替えるBuildTask public class SwitchPlatform : IBuildTask { // DI対象のフィールドにはInjectContextを付ける // DIが任意の場合は第二引数をtrueに [InjectContext(ContextUsage.In)] private readonly ISwitchPlatformContext _context = null; public int Version => 1; public ReturnCode Run() { return EditorUserBuildSettings.SwitchActiveBuildTarget(_context.Group, _context.Target) ? ReturnCode.Success : ReturnCode.Error; } } // SwitchPlatform用のContextのインタフェース public interface ISwitchPlatformContext : IContextObject { BuildTargetGroup Group { get; } BuildTarget Target { get; } } // SwitchPlatform用のContextの実装クラス public class SwitchPlatformContext : ISwitchPlatformContext { public BuildTargetGroup Group { get; } public BuildTarget Target { get; } public SwitchPlatformContext(BuildTargetGroup group, BuildTarget target) { Group = group; Target = target; } }
これを実行するとプラットフォームの切り替え処理が走ってからログ出力が行われることを確認できます。
実際にIBuildTaskが使用されている部分を見る
ここまで理解できたところで、IBuildTask
が実際に作られている部分を見てみます。
AddressablesではUnityEditor.Build.PipelineのDefaultBuildTasks.Create
あたりでBuildTaskが作られています。
アセットバンドル用のBuildTaskを作成しているところだけ抜き出すと以下のような感じです。
static IList<IBuildTask> AssetBundleCompatible() { var buildTasks = new List<IBuildTask>(); // Setup buildTasks.Add(new SwitchToBuildPlatform()); buildTasks.Add(new RebuildSpriteAtlasCache()); // Player Scripts buildTasks.Add(new BuildPlayerScripts()); buildTasks.Add(new PostScriptsCallback()); // Dependency buildTasks.Add(new CalculateSceneDependencyData()); #if UNITY_2019_3_OR_NEWER buildTasks.Add(new CalculateCustomDependencyData()); #endif buildTasks.Add(new CalculateAssetDependencyData()); buildTasks.Add(new StripUnusedSpriteSources()); buildTasks.Add(new PostDependencyCallback()); // Packing buildTasks.Add(new GenerateBundlePacking()); buildTasks.Add(new GenerateBundleCommands()); buildTasks.Add(new GenerateSubAssetPathMaps()); buildTasks.Add(new GenerateBundleMaps()); buildTasks.Add(new PostPackingCallback()); // Writing buildTasks.Add(new WriteSerializedFiles()); buildTasks.Add(new ArchiveAndCompressBundles()); buildTasks.Add(new AppendBundleHash()); buildTasks.Add(new PostWritingCallback()); // Generate manifest files // TODO: IMPL manifest generation return buildTasks; }
大量のBuildTaskが実行順に登録されていることが確認できます。
自前でビルドフローを作るにはこのあたりの流れを自作していくことになります。
実際にIContextObjectが使用されている部分を見る
次に実際にIContextObjectが使用されている部分を見ていきます。
これはUnityEditor.Build.PipelineのContentPipeline.BuildAssetBundles()
あたりを見るのがよさそうです。
このメソッドの一部を抜粋すると以下のようになります。
buildContext = new BuildContext(contextObjects); buildContext.SetContextObject(parameters); buildContext.SetContextObject(content); buildContext.SetContextObject(result); buildContext.SetContextObject(interfacesWrapper); buildContext.SetContextObject(progressTracker); buildContext.SetContextObject(buildCache); // If IDeterministicIdentifiers was passed in with contextObjects, don't add the default if (!buildContext.ContainsContextObject(typeof(IDeterministicIdentifiers))) buildContext.SetContextObject(new Unity5PackedIdentifiers()); buildContext.SetContextObject(new BuildDependencyData()); buildContext.SetContextObject(new BundleWriteData()); buildContext.SetContextObject(BuildCallbacks);
こちらも多数のIContextObjectがDIするために登録されていることを確認できました。
おまけ: 軽くカスタムしたいだけの場合
以上、Scriptable Build Pipelineを使ってビルドフローを構築する方法について簡単にまとめました。
ただ実際には、ビルドフローをイチから構築するというよりは一部だけカスタムできれば十分というケースもあるかと思います。
そのような場合には以下のようにCompatibilityBuildPipeline.BuildAssetBundles
に与える引数を調整することでオプショナルな設定を行うことができます。
using System.IO; using System.Linq; using UnityEditor; using UnityEditor.Build.Content; using UnityEditor.Build.Pipeline; public class Example { public static bool BuildAssetBundles(string outputPath, bool forceRebuild, bool useChunkBasedCompression, BuildTarget buildTarget) { // オプションを設定 var options = BuildAssetBundleOptions.None; if (useChunkBasedCompression) { options |= BuildAssetBundleOptions.ChunkBasedCompression; } if (forceRebuild) { options |= BuildAssetBundleOptions.ForceRebuildAssetBundle; } // アセットバンドルビルド情報を取得 // ※これはAddressablesじゃなく旧来の方式でAssetBundle名をつけたものを集めるAPIなので必要に応じて変える var bundles = ContentBuildInterface.GenerateAssetBundleBuilds(); // Addressableの名前だけフルパスからファイル名に変更する for (var i = 0; i < bundles.Length; i++) { bundles[i].addressableNames = bundles[i].assetNames.Select(Path.GetFileNameWithoutExtension).ToArray(); } var manifest = CompatibilityBuildPipeline.BuildAssetBundles(outputPath, bundles, options, buildTarget); return manifest != null; } }
この辺りにはマニュアルに書いてあるのでそちらを参照してください(上記のコードもほぼこのマニュアルのものです)。