🏭

Unityエディタ拡張でAndroidアプリのカスタムビルド

に公開

UnityでAndroidアプリの開発ビルド本番ビルドを簡単にスイッチできるエディタ拡張を実装したので、記事にしておきます。設定の変更を自動化することで、手間とミスが減ります!

以下のようなことをドロップダウンでまとめて切り替えてビルドできるようにしています:
✅ 開発ビルドと本番ビルドでバンドルIDや出力形式(APK/AAB+PAD)を分けたい
✅ 開発ビルドと本番ビルドでアイコン設定を別にしたい
✅ 本番ビルドでのみ、Keystore設定を有効にしたい

おまけで以下にも対応しています:
✅ ビルド番号を自動で加算
✅ ビルド対象のシーンリストを表示

色々積むとこんな感じ:

そもそもエディタ拡張とは?

Unityでは、EditorクラスやMenuItemを使って、独自のエディタ機能を追加することができます。
今回のようにウィンドウを作成する場合は EditorWindow を継承し、OnGUI() メソッドでUIを描画していきます。

最低限の構成は以下のようになります:

using UnityEditor;
using UnityEngine;

public class SampleEditorWindow : EditorWindow
{
    [MenuItem("Window/SampleWindow")]
    public static void ShowWindow()
    {
        GetWindow<SampleEditorWindow>("SampleWindow");
    }

    private void OnGUI()
    {
        GUILayout.Label("Hello!", EditorStyles.boldLabel);
    }
}

エディタメニューから作成したウィンドウが開けるようになります:

開発ビルドと本番ビルドの切り替え

選択用のドロップダウンUI表示

OnGUI()内で、EditorGUILayout.Popup を使うことで実現できます。ビルドタイプを enum BuildType で定義し、各種設定を切り替えます。

private void OnGUI()
{
    string[] labels = { "Development APK", "Production AAB+PAD" };
    var selectedIndex = (int)_buildType;
    var newIndex = EditorGUILayout.Popup("Build Type", selectedIndex, labels);
    if (selectedIndex != newIndex)
    {
        _buildType = (BuildType)newIndex;
    }
}

バンドルID、出力形式、アプリ名、キーストア設定の変更

private void SetBuildSettings()
{
    switch (_buildType)
    {
        case BuildType.DevelopmentApk:
            PlayerSettings.applicationIdentifier = "com.***.***.dev";
            PlayerSettings.productName = "AppName";
            EditorUserBuildSettings.buildAppBundle = false; //.apkで書き出し
            EditorUserBuildSettings.development = true;
            PlayerSettings.Android.splitApplicationBinary = false; //obbファイル分割なし
            PlayerSettings.Android.useCustomKeystore = false;
            break;

        case BuildType.ProductionAab:
            PlayerSettings.applicationIdentifier = "com.***.***";
            PlayerSettings.productName = "AppName";
            EditorUserBuildSettings.buildAppBundle = true; //.abbで書き出し
            EditorUserBuildSettings.development = false;
            PlayerSettings.Android.splitApplicationBinary = true; //padファイル分割あり
            PlayerSettings.Android.useCustomKeystore = true;
            PlayerSettings.Android.keystorePass = EditorPrefs.GetString(KeystorePasswordKey, "");
            PlayerSettings.Android.keyaliasPass = EditorPrefs.GetString(KeyAliasPasswordKey, "");
            break;
    }
}
  • APK形式の場合:splitApplicationBinary = true にすると、APKとは別に .obb 拡張ファイル(expansion file)が出力されます。
  • AAB形式の場合:splitApplicationBinary = true にすると、AABがBaseモジュールとAsset Packに分割され、Play Asset Delivery(PAD)に対応した構成になります。

参考:PlayerSettings.Android.splitApplicationBinary

アイコン設定を別にする

private void SetAdaptiveIcons()
{
    var basePath = "Assets/Resources/Images/Common/";
    var fgPath = _buildType == BuildType.DevelopmentApk
        ? basePath + "icon_dev_foreground.png"
        : basePath + "icon_prod_foreground.png";
    var bgPath = _buildType == BuildType.DevelopmentApk
        ? basePath + "icon_dev_background.png"
        : basePath + "icon_prod_background.png";

    var fgTex = AssetDatabase.LoadAssetAtPath<Texture2D>(fgPath);
    var bgTex = AssetDatabase.LoadAssetAtPath<Texture2D>(bgPath);

    if (!fgTex || !bgTex)
    {
        Debug.LogWarning("Adaptive icon textures not found. Please check the file paths.");
        return;
    }

    var icons = PlayerSettings.GetPlatformIcons(NamedBuildTarget.Android, AndroidPlatformIconKind.Adaptive);

    foreach (var icon in icons)
    {
        icon.SetTextures(bgTex, fgTex);
    }

    PlayerSettings.SetPlatformIcons(NamedBuildTarget.Android, AndroidPlatformIconKind.Adaptive, icons);
}

ビルドボタン表示

private void OnGUI()
{
    //[省略]
    if (GUILayout.Button("Build"))
    {
        Build()
    }
}
private void Build()
{
    SetBuildSettings();
    SetAdaptiveIcons();
    var outputPath = GetOutputPath();

    AddressableAssetSettings.BuildPlayerContent();

    var options = new BuildPlayerOptions
    {
        scenes = GetEnabledScenes(),
        locationPathName = outputPath,
        target = BuildTarget.Android,
        options = BuildOptions.None
    };

    var report = BuildPipeline.BuildPlayer(options);

    if (report.summary.result == BuildResult.Succeeded)
    {
        Debug.Log($"✅ Build Success! Version: {BundleVersionString}");
        EditorUtility.RevealInFinder(outputPath);
    }
    else
    {
        Debug.LogError("❌ Build Failed.");
    }
}

おまけ

ビルド番号を加算

PlayerSettings.Android.bundleVersionCode++;

ビルド対象のシーンリストをGUI上に表示

var scenes = GetEnabledScenes();
foreach (var scene in scenes)
{
    GUILayout.Label($"• {scene}", EditorStyles.miniLabel);
}
private string[] GetEnabledScenes()
{
    return EditorBuildSettings.scenes.Where(s => s.enabled).Select(s => s.path).ToArray();
}

Discussion