Unity·Unreal 실전

Unity BuildPlayerWindow 커스텀 빌드 버튼으로 실수 없는 팀 빌드 파이프라인 만들기

kr-gamedev 2026. 7. 2. 09:02

Unity 프로젝트가 커질수록 빌드는 단순히 Build 버튼을 누르는 일이 아니게 됩니다. 개발 빌드인지, 릴리즈 빌드인지, 서버 주소는 맞는지, 버전 번호는 올렸는지, 테스트용 씬이 섞이지 않았는지까지 확인해야 합니다.

문제는 이런 체크가 사람 손에 맡겨지면 언젠가는 빠진다는 점입니다. 특히 팀 프로젝트에서는 한 명은 Android, 한 명은 Windows, 한 명은 QA 빌드를 만들면서 설정이 조금씩 달라지기 쉽습니다.

이럴 때 Unity의 BuildPlayerWindow.RegisterBuildPlayerHandler를 쓰면 기본 Build 버튼을 우리 팀 전용 빌드 파이프라인으로 바꿀 수 있습니다. 오늘은 에디터에서 버튼을 누르는 순간 검증, 경로 생성, 빌드 옵션 적용까지 한 번에 처리하는 구조를 만들어 보겠습니다.

기본 Build 버튼 가로채기

RegisterBuildPlayerHandler는 Unity의 Build Settings 창에서 Build 버튼을 눌렀을 때 실행될 함수를 등록합니다. Editor 전용 코드이므로 반드시 Editor 폴더 아래에 둡니다.

using UnityEditor;
using UnityEditor.Build.Reporting;
using UnityEngine;

[InitializeOnLoad]
public static class TeamBuildHandler
{
    static TeamBuildHandler()
    {
        BuildPlayerWindow.RegisterBuildPlayerHandler(OnBuildRequested);
    }

    private static void OnBuildRequested(BuildPlayerOptions options)
    {
        if (!BuildGuard.Validate(options))
        {
            Debug.LogError("빌드 검증 실패. 설정을 확인하세요.");
            return;
        }

        options.locationPathName = BuildPath.Make(options.target);
        options.options |= BuildOptions.StrictMode;

        BuildReport report = BuildPipeline.BuildPlayer(options);
        Debug.Log($"빌드 결과: {report.summary.result}, 크기: {report.summary.totalSize} bytes");
    }
}

핵심은 개발자가 누르는 버튼은 그대로 두되, 실제 동작만 팀 규칙으로 감싸는 것입니다. 메뉴를 새로 만들면 사람들이 기존 버튼을 누르는 실수를 하지만, 기본 버튼 자체를 바꾸면 습관을 바꿀 필요가 없습니다.

빌드 전 검증을 코드로 고정하기

가장 먼저 막아야 할 것은 잘못된 씬, 비어 있는 경로, 릴리즈 빌드의 Development Build 옵션 같은 실수입니다. 이런 규칙은 문서보다 코드가 낫습니다.

using System.Linq;
using UnityEditor;
using UnityEditor.Build;

public static class BuildGuard
{
    public static bool Validate(BuildPlayerOptions options)
    {
        if (options.scenes.Length == 0)
            return Fail("빌드 씬이 비어 있습니다.");

        bool hasEditorOnlyScene = options.scenes.Any(s => s.Contains("/Test/"));
        if (hasEditorOnlyScene)
            return Fail("Test 폴더의 씬은 빌드에 포함할 수 없습니다.");

        bool isRelease = EditorUserBuildSettings.development == false;
        if (isRelease && EditorUserBuildSettings.connectProfiler)
            return Fail("릴리즈 빌드에서 Profiler 연결 옵션이 켜져 있습니다.");

        return true;
    }

    private static bool Fail(string message)
    {
        EditorUtility.DisplayDialog("Build Guard", message, "확인");
        return false;
    }
}

이 검증은 CI에서도 재사용할 수 있게 작게 유지하는 편이 좋습니다. UI에 묶인 복잡한 EditorWindow보다, 정적 함수로 나누면 커맨드라인 빌드에서도 같은 규칙을 호출할 수 있습니다.

날짜와 플랫폼이 들어간 출력 경로 만들기

빌드 결과물이 Build, NewBuild, Final, Final_Final처럼 쌓이면 QA 전달 시점에 혼란이 생깁니다. 출력 경로도 규칙화해야 합니다.

using System;
using UnityEditor;
using UnityEngine;

public static class BuildPath
{
    public static string Make(BuildTarget target)
    {
        string version = PlayerSettings.bundleVersion;
        string stamp = DateTime.Now.ToString("yyyyMMdd_HHmm");
        string product = Application.productName.Replace(" ", "");

        return $"Builds/{target}/{product}_{version}_{stamp}/{product}.exe";
    }
}

실무에서는 플랫폼별 확장자를 나누는 처리가 더 필요합니다. Android는 .apk 또는 .aab, macOS는 .app, Windows는 .exe처럼 분기하면 됩니다. 중요한 것은 빌드 담당자가 매번 폴더명을 고민하지 않게 만드는 것입니다.

팀 설정은 ScriptableObject로 빼기

빌드 서버 주소, 스토어 타입, 로깅 레벨 같은 값은 코드에 박지 않는 것이 좋습니다. 대신 ScriptableObject로 빌드 프로파일을 만들면 기획자나 QA도 Inspector에서 확인할 수 있습니다.

using UnityEngine;

[CreateAssetMenu(menuName = "Build/Team Build Profile")]
public sealed class TeamBuildProfile : ScriptableObject
{
    public string apiBaseUrl;
    public bool enableVerboseLog;
    public string[] scriptingDefines;
}

여기서 읽은 값을 PlayerSettings.SetScriptingDefineSymbols에 반영하면 DEV_SERVER, QA_BUILD, RELEASE_STORE 같은 분기 처리를 빌드 시점에 고정할 수 있습니다. 단, 빌드 후에는 기존 define을 복구해야 에디터 상태가 오염되지 않습니다.

결론

Unity 빌드 자동화는 거창한 CI부터 시작할 필요가 없습니다. 먼저 팀원이 매일 누르는 Build 버튼을 가로채고, 반복되는 실수를 코드로 막는 것만으로도 효과가 큽니다.

RegisterBuildPlayerHandler는 작은 기능처럼 보이지만, 검증, 경로 규칙, 빌드 옵션, 프로파일 적용을 한곳에 모으는 출발점이 됩니다. 빌드가 사람의 기억이 아니라 코드의 규칙으로 움직이면, QA 전달과 릴리즈 준비가 훨씬 안정적으로 바뀝니다.