Unity Scriptable Build Pipeline — 커스텀 빌드 자동화
Scriptable Build Pipeline(SBP)은 Unity의 빌드 파이프라인을 C# 코드로 완전히 제어할 수 있는 패키지입니다. Addressables가 내부적으로 SBP를 사용하며, 커스텀 빌드 단계 삽입, 증분 빌드(incremental build), 빌드 캐시 등을 지원합니다.
1. 설치
섹션 제목: “1. 설치”Package Manager → Unity Registry →Scriptable Build Pipeline → Install2. 기본 AssetBundle 빌드
섹션 제목: “2. 기본 AssetBundle 빌드”using UnityEditor;using UnityEditor.Build.Pipeline;using UnityEngine;
public static class BuildScript{ [MenuItem("Build/Build AssetBundles (SBP)")] public static void BuildBundles() { var bundleBuilds = ContentBuildInterface.GenerateAssetBundleBuilds();
var buildParams = new BundleBuildParameters( EditorUserBuildSettings.activeBuildTarget, EditorUserBuildSettings.selectedBuildTargetGroup, "Assets/StreamingAssets/Bundles");
// 증분 빌드 캐시 활성화 buildParams.UseCache = true; buildParams.CacheServerHost = "cache.example.com"; buildParams.CacheServerPort = 8126;
var content = new BundleBuildContent(bundleBuilds); var result = ContentPipeline.BuildAssetBundles( buildParams, content, out var manifest);
if (result >= ReturnCode.Success) Debug.Log($"빌드 성공: {manifest.GetAllAssetBundles().Length}개 번들"); else Debug.LogError($"빌드 실패: {result}"); }}3. 커스텀 빌드 태스크 삽입
섹션 제목: “3. 커스텀 빌드 태스크 삽입”using UnityEditor.Build.Pipeline;using UnityEditor.Build.Pipeline.Interfaces;using UnityEditor.Build.Pipeline.Utilities;
// 커스텀 빌드 태스크 정의public class LogBuildTask : IBuildTask{ public int Version => 1;
public ReturnCode Run() { var context = BuildContext.GetContextObject<IBundleBuildContent>(); Debug.Log($"[Build] 빌드 시작: {context.BundleLayout.Count}개 번들"); return ReturnCode.Success; }}
// 빌드 태스크 파이프라인 커스터마이징public static class CustomBuildPipeline{ public static ReturnCode BuildWithCustomTasks() { // 기본 태스크 목록 가져오기 var tasks = DefaultBuildTasks.Create( DefaultBuildTasks.Preset.AssetBundleBuiltInShaderExtraction);
// 시작 전 커스텀 태스크 삽입 tasks.Insert(0, new LogBuildTask());
var buildParams = new BundleBuildParameters( BuildTarget.StandaloneWindows64, BuildTargetGroup.Standalone, "Assets/StreamingAssets/Bundles");
var content = new BundleBuildContent( ContentBuildInterface.GenerateAssetBundleBuilds());
return ContentPipeline.BuildAssetBundles( buildParams, content, out _, tasks); }}4. 플레이어 빌드 자동화
섹션 제목: “4. 플레이어 빌드 자동화”using UnityEditor;using UnityEditor.Build.Reporting;using System;
public static class PlayerBuildScript{ // CI/CD에서 호출 public static void BuildFromCommandLine() { var args = Environment.GetCommandLineArgs(); string outputPath = GetArg(args, "-outputPath") ?? "Builds/Game"; string version = GetArg(args, "-version") ?? "1.0.0";
PlayerSettings.bundleVersion = version;
var options = new BuildPlayerOptions { scenes = GetEnabledScenes(), locationPathName = outputPath, target = BuildTarget.StandaloneWindows64, options = BuildOptions.None, };
var report = BuildPipeline.BuildPlayer(options);
if (report.summary.result == BuildResult.Succeeded) { Console.WriteLine($"[BUILD] 성공: {report.summary.totalSize / 1024 / 1024} MB"); EditorApplication.Exit(0); } else { Console.WriteLine($"[BUILD] 실패: {report.summary.totalErrors} 오류"); EditorApplication.Exit(1); } }
static string[] GetEnabledScenes() { return Array.ConvertAll( EditorBuildSettings.scenes, s => s.path); }
static string? GetArg(string[] args, string name) { for (int i = 0; i < args.Length - 1; i++) if (args[i] == name) return args[i + 1]; return null; }}5. BuildReport 분석
섹션 제목: “5. BuildReport 분석”using UnityEditor.Build.Reporting;
public static class BuildAnalyzer{ [MenuItem("Build/Analyze Last Build")] static void Analyze() { var report = BuildReport.GetLatestReport(); if (report == null) { Debug.LogError("빌드 리포트 없음"); return; }
Debug.Log($"빌드 시간: {report.summary.totalTime.TotalSeconds:F1}초"); Debug.Log($"빌드 크기: {report.summary.totalSize / 1024 / 1024} MB");
// 가장 큰 에셋 Top 10 var files = report.GetFiles(); Array.Sort(files, (a, b) => b.size.CompareTo(a.size));
Debug.Log("=== Top 10 큰 에셋 ==="); for (int i = 0; i < Math.Min(10, files.Length); i++) Debug.Log($"{files[i].size / 1024} KB {files[i].path}"); }}6. CI/CD 커맨드라인 실행
섹션 제목: “6. CI/CD 커맨드라인 실행”# GitHub Actions / Jenkins에서 Unity 빌드Unity \ -batchmode \ -nographics \ -projectPath /path/to/project \ -executeMethod PlayerBuildScript.BuildFromCommandLine \ -outputPath Builds/Game \ -version 1.2.3 \ -logFile build.log \ -quit7. 증분 빌드 캐시 설정
섹션 제목: “7. 증분 빌드 캐시 설정”// Unity Accelerator 또는 로컬 캐시 서버 연동var buildParams = new BundleBuildParameters(...);buildParams.UseCache = true;buildParams.CacheServerHost = Environment.GetEnvironmentVariable("CACHE_SERVER") ?? "localhost";buildParams.CacheServerPort = 8126;8. IPreprocessBuildWithReport — 빌드 전처리 훅
섹션 제목: “8. IPreprocessBuildWithReport — 빌드 전처리 훅”using UnityEditor;using UnityEditor.Build;using UnityEditor.Build.Reporting;
// 빌드 시작 전 자동 실행 — callbackOrder로 실행 우선순위 제어public class BuildPreprocessor : IPreprocessBuildWithReport{ public int callbackOrder => 0;
public void OnPreprocessBuild(BuildReport report) { // 빌드 전 버전 정보 자동 갱신 PlayerSettings.bundleVersion = GetVersionFromGit();
// 디버그 심볼 검증 if (report.summary.options.HasFlag(BuildOptions.Development)) { Debug.Log("[Build] Development 빌드: 디버그 로그 활성화"); } else { // 프로덕션 빌드에서 개발용 코드 제거 확인 ValidateNoDebugCode(); } }
private string GetVersionFromGit() { var process = new System.Diagnostics.Process { StartInfo = new System.Diagnostics.ProcessStartInfo { FileName = "git", Arguments = "describe --tags --always", RedirectStandardOutput = true, UseShellExecute = false } }; process.Start(); return process.StandardOutput.ReadToEnd().Trim(); }
private void ValidateNoDebugCode() { /* ... */ }}9. BuildReport로 Slack 알림
섹션 제목: “9. BuildReport로 Slack 알림”using UnityEditor;using UnityEditor.Build;using UnityEditor.Build.Reporting;using UnityEngine.Networking;
public class BuildNotifier : IPostprocessBuildWithReport{ public int callbackOrder => 0;
public void OnPostprocessBuild(BuildReport report) { string status = report.summary.result == BuildResult.Succeeded ? ":white_check_mark: 빌드 성공" : ":x: 빌드 실패";
string message = $"{status}\n" + $"버전: {PlayerSettings.bundleVersion}\n" + $"크기: {report.summary.totalSize / 1024 / 1024} MB\n" + $"시간: {report.summary.totalTime.TotalSeconds:F0}초";
SendSlackMessage(message); }
private static void SendSlackMessage(string text) { string webhook = System.Environment.GetEnvironmentVariable("SLACK_WEBHOOK"); if (string.IsNullOrEmpty(webhook)) return;
string json = $"{{\"text\":\"{text}\"}}"; var req = UnityWebRequest.Post(webhook, json, "application/json"); req.SendWebRequest(); }}Scriptable Build Pipeline은 빌드를 코드로 제어하고, 태스크를 삽입하며, 증분 빌드로 시간을 줄이는 데 핵심입니다. CI/CD에서 -executeMethod로 빌드 스크립트를 호출하고 BuildReport로 크기와 시간을 모니터링하면 빌드 파이프라인의 완전한 자동화가 가능합니다.