【Unity】Releaseビルドのときだけシンボル(#define)を定義したい!!

はじめに

ログ出力などのデバッグ機能は、デバッグビルドでは定義したいけどReleaseビルドでは消したい、など条件によって処理実装有無を切り替えたい場合が開発していると出てきます。

ProjectSetting > Player > OtherSettings > Scripting Define Symbolsで全体に適応可能なSymbolを設定することもできますが、前述のようにデバッグとリリースで切り替える時に毎回ここを書き換えるのは面倒です。

下記のように条件文を書けると便利なのですが、現在のところこのような書き方は不可のようです。

#if DEVELOPMENT_BUILD || UNITY_EDITOR
    #define DEFINE_VALUE
#endif

これがC++であれば、ヘッダファイルに上記を書いておいて、使用するところでincludeしてDEFINE_VALUEの定義有無を参照するということができるのですが、C#にはそのように全体的に条件でディレクティブを変更する方法がなさそうでした。(ある場合、教えていただけると大変助かります)

じゃあどうする?

今回の目的はリリースビルドが否かでdefine定義(ディレクティブ)の有無を切り替えたいというものなので、Claudeに教えてもらいつつUnityの機能を用いて実装してみました。

指定したシンボルを下記の条件ごとに定義するか、否かをtrue、falseで切り替えることができます。

フィールド説明
enableInEditorエディタ上での実行時に有効
enableInDevelopmentBuildDevelopmentビルド時に有効
enableInReleaseBuildリリースビルド時に有効
(Development BuildのチェックOFF時)

下記に実装例を示します。
この例では「USE_CATHUT_DEBUG」というシンボルが、エディタ上及び、DevelopmentBuild後のEXEでは定義され、Releaseビルドでは定義されないという挙動になります。


#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEngine;
using System.Collections.Generic;
using System.Linq;


/// <summary>
/// Unity Editor起動時、スクリプトのコンパイル時、ビルド時に自動的にスクリプティング定義シンボルを管理するクラス
/// </summary>
[InitializeOnLoad]
public class DefineSymbolsManager : IPreprocessBuildWithReport, IPostprocessBuildWithReport
{
    /// <summary>
    /// ビルドパイプラインでの実行順序を指定します
    /// </summary>
    public int callbackOrder => 0;

    /// <summary>
    /// 管理する定義シンボルのリスト
    /// </summary>
    private static readonly List<SymbolDefinition> Symbols = new()
    {
        new SymbolDefinition(
            name: "USE_CATHUT_DEBUG",
            enableInEditor: true,
            enableInDevelopmentBuild: true,
            enableInReleaseBuild: false
        ),
        // 例:リリースビルドでも有効にしたい機能
        // new SymbolDefinition(
        //     name: "ENABLE_ANALYTICS",
        //     enableInEditor: false,
        //     enableInDevelopmentBuild: true,
        //     enableInReleaseBuild: true
        // ),
    };

    /// <summary>
    /// 現在ビルド中かどうかを示すフラグ
    /// </summary>
    private static bool isBuilding = false;

    /// <summary>
    /// 現在のビルドがDevelopmentビルドかどうかを示すフラグ
    /// </summary>
    private static bool isBuildDevelopment = false;

    static DefineSymbolsManager()
    {
        UpdateDefineSymbols();
    }

    /// <summary>
    /// ビルド開始前に呼ばれ、スクリプティング定義シンボルを更新します
    /// </summary>
    public void OnPreprocessBuild(BuildReport report)
    {
        isBuilding = true;
        isBuildDevelopment = (report.summary.options & BuildOptions.Development) != 0;
        UpdateDefineSymbols();
    }

    /// <summary>
    /// ビルド完了後に呼ばれ、エディタの状態に応じてスクリプティング定義シンボルを再設定します
    /// </summary>
    public void OnPostprocessBuild(BuildReport report)
    {
        isBuilding = false;
        UpdateDefineSymbols();
    }

    /// <summary>
    /// スクリプティング定義シンボルを更新する
    /// </summary>
    private static void UpdateDefineSymbols()
    {
        var buildTarget = NamedBuildTarget.FromBuildTargetGroup(
            EditorUserBuildSettings.selectedBuildTargetGroup);
        var currentDefines = PlayerSettings.GetScriptingDefineSymbols(buildTarget)
            .Split(new[] { ';' }, System.StringSplitOptions.RemoveEmptyEntries)
            .ToList();

        var newDefines = new List<string>();

        // 既存の管理対象外のシンボルを保持
        newDefines.AddRange(currentDefines.Where(symbol =>
            !Symbols.Any(def => def.Name == symbol)));

        // 各シンボルの状態を評価
        foreach (var symbol in Symbols)
        {
            bool shouldDefine;
            if (isBuilding)
            {
                shouldDefine = isBuildDevelopment
                    ? symbol.EnableInDevelopmentBuild  // Development Build
                    : symbol.EnableInReleaseBuild;     // Release Build
            }
            else
            {
                // エディタ上での実行
                shouldDefine = symbol.EnableInEditor;
            }

            if (shouldDefine)
            {
                newDefines.Add(symbol.Name);
            }
        }

        // 重複を除去してソート
        var finalDefines = newDefines.Distinct().OrderBy(s => s).ToList();
        var newDefinesString = string.Join(";", finalDefines);

        var currentDefinesString = PlayerSettings.GetScriptingDefineSymbols(buildTarget);
        if (newDefinesString != currentDefinesString)
        {
            PlayerSettings.SetScriptingDefineSymbols(buildTarget, newDefinesString);
            Debug.Log($"Scripting Define Symbols updated. Building: {isBuilding}, Development: {isBuildDevelopment}");
            Debug.Log($"Current symbols: {newDefinesString}");
        }
    }

    /// <summary>
    /// 定義シンボルの設定情報
    /// </summary>
    private class SymbolDefinition
    {
        /// <summary>シンボル名</summary>
        public string Name { get; }

        /// <summary>エディタでの有効状態</summary>
        public bool EnableInEditor { get; }

        /// <summary>開発ビルドでの有効状態</summary>
        public bool EnableInDevelopmentBuild { get; }

        /// <summary>リリースビルドでの有効状態</summary>
        public bool EnableInReleaseBuild { get; }

        public SymbolDefinition(
            string name,
            bool enableInEditor,
            bool enableInDevelopmentBuild,
            bool enableInReleaseBuild)
        {
            Name = name;
            EnableInEditor = enableInEditor;
            EnableInDevelopmentBuild = enableInDevelopmentBuild;
            EnableInReleaseBuild = enableInReleaseBuild;
        }
    }

}

#endif

なにが便利?

個人的にはDebug用のラッパークラスのConditionalに使いたくて実装方法を調べていました。

namespaceを切って下記のようなデバッグクラスを定義しておくと、エディタ上、およびDevelopmentBuild時のみログ出力メソッドが実行され、Releaseビルドでは実行されないようにするということができるようになります。
(※LogErrorは出力したほうが・・・という場合にはConditional属性を外せばよいです)

namespace CatHut
{
    public static class Debug
    {
        [Conditional("USE_CATHUT_DEBUG")]
        public static void Log(object message)
        {
            UnityEngine.Debug.Log(message);
        }

        [Conditional("USE_CATHUT_DEBUG")]
        public static void Log(object message, Object context)
        {
            UnityEngine.Debug.Log(message, context);
        }

        [Conditional("USE_CATHUT_DEBUG")]
        public static void LogWarning(object message)
        {
            UnityEngine.Debug.LogWarning(message);
        }

        [Conditional("USE_CATHUT_DEBUG")]
        public static void LogWarning(object message, Object context)
        {
            UnityEngine.Debug.LogWarning(message, context);
        }

        [Conditional("USE_CATHUT_DEBUG")]
        public static void LogError(object message)
        {
            UnityEngine.Debug.LogError(message);
        }

        [Conditional("USE_CATHUT_DEBUG")]
        public static void LogError(object message, Object context)
        {
            UnityEngine.Debug.LogError(message, context);
        }
    }
}

さいごに

C++のヘッダファイルでやっていたようなことだったので、簡単にできるかと思ったら以外に苦戦しましたね。

最後までお読みいただき、ありがとうございます

誰かの役に立てば幸いです。

コメント

タイトルとURLをコピーしました