Synamon’s Engineer blog

Synamonはリアルとデジタルの融合を加速させるため、メタバース領域で法人向けにサービス提供を行うテックカンパニーです。現在開発を進めている「メタバース総合プラットフォーム」をはじめ、メタバース市場の発展に向けた事業展開を行っています。このブログでは、メタバース技術とその周辺の技術、開発全般に関してエンジニアがお話しします。

Unityの設定を一括で切り替える為に作ったEditor拡張の紹介

エンジニアの岡村です。

Unityで開発をしていると、ビルドや実行を行う時に設定を一括で切り替えたいという事がよくあると思います。例えばデバッグビルドとリリースビルドで接続先のエンドポイントやAPIキー、アプリの名前を変えるといったものです。

その場合の対応方法としては、設定リストをメモに書いておく、UnityのPreset機能を使う、Editor拡張を自作するなど色々ありますが、弊社ではEditor拡張で専用のメニューを作成しています。これが実装されてから数年程経ち、使い勝手も良くなってきたため、今回この記事で紹介させて頂きます。

EnvironmentContextSwitcher

各設定を纏めたオブジェクト(コンテキスト)を複数用意し、簡単に切り替えられるよう、以下の画像のようなEditor拡張を作成しました。

図の左のリストがコンテキスト一覧、右が現在選択しているコンテキストの中身です。左下にはロック機能のアイコン、その右のチェックボックスはコンテキストの自動適用機能のON/OFFを切り替えます。

主な機能

コンテキストの追加、削除

左側のリストはReorderableListになっていて、ボタンでコンテキストの追加や並べ替えが出来るようになっています。

 

コンテキストの適用

コンテキストをダブルクリックすることで、コンテキストの内容を現在のプロジェクトに適用することが出来ます。コンテキストが持っている適用処理によって、選択されたコンテキストの内容がコピーされて書き込まれます。

コンテキストの定義の編集

コンテキスト定義用のC#クラスを用意しており、その内容を編集することで定義を増やすことが出来ます。SerializeFieldに対応した型のフィールドであれば何でも定義可能です。

public sealed class Context : ContextBase
{
    // コンテキストで管理する変数をSerializeFieldで記述する
    [Header("Package Information")]
    [SerializeField] private string displayName = default;
    [SerializeField] private string packageName = default;
    [SerializeField] private string packageVersionName = default;
    [SerializeField] private int packageVersionCode = default;

    [Header("Build Option")]
    [SerializeField] private bool generateAAB = false;

    // Applyメソッドの中に、コンテキストを適用したときの挙動を記述する
    public override void Apply()
    {
        PlayerSettings.productName = displayName;
        PlayerSettings.SetApplicationIdentifier(BuildTargetGroup.Standalone, packageName);
        PlayerSettings.SetApplicationIdentifier(BuildTargetGroup.Android, packageName);
        PlayerSettings.SetApplicationIdentifier(BuildTargetGroup.iOS, packageName);
        PlayerSettings.bundleVersion = packageVersionName;
        PlayerSettings.macOS.buildNumber = packageVersionCode.ToString();
        PlayerSettings.iOS.buildNumber = packageVersionCode.ToString();
        PlayerSettings.Android.bundleVersionCode = packageVersionCode;

        EditorUserBuildSettings.buildAppBundle = generateAAB;
    }
}

ビルド、再生時の自動適用

ウィンドウ左下、ロックアイコンの右にあるAuto Applyトグルにチェックを入れることで、PlayボタンでのEditor上での再生時や、ビルド時に自動で選択されている(Unityマークが付いている)コンテキストを適用することが出来ます。この機能が有効になっている時は、タブに●アイコンが表示されます。

C#スクリプトからのコンテキストの適用

CI等のワークフローからもアクセスできるように、C#コードでコンテキストの適用ができるよう、文字列でコンテキストを検索して適用することが可能です。この機能を使い、開発ビルドとリリースビルドを出し分けています。

// EnvironmentContextの自動適用を抑制する(ビルド後に元に戻す)
AutoContextApplier.SetAutoApply(false);

var contextName = GetCommandLineArgument("-environmentContext");
var context = ContextStore.Instance.FromName(contextName);
context.Apply();

実装

開発での使い易さを優先したため、このツールはプロジェクトに埋め込まれており、アセットとして汎用的なものにはなっていません。その代わり、コアロジック以外の頻繁に触る必要がある部分は前述の通りContextBaseを継承したContextクラスだけを弄ればよく、シンプルになっています。

public sealed class Context : ContextBase
{
    // コンテキストで管理する変数をSerializeFieldで記述する

    public override void Apply()
    {
        // コンテキストを適用したときの挙動を記述する
    }
}

ContextSwitcherは、このContextクラスをインスタンス生成したScriptableObjectをContextStoreというScriptableObjectで管理しています。それをContextSwitherWindowから参照して、適用するインスタンスのApply()を呼び出しています。

また、コンテキストはEditorOnlyのScriptableObjectとして実装しています。その為、コンテキストに開発用のWeb APIのエンドポイントや、ダミーのプレイヤー情報などを入れていたとしても、リリースビルドには含まれません。

その他細かい実装の話

ContextSwitcherWindowからContextStoreを参照する

ContextSwitherWindowは生成された時やスクリプトがリロードされた際にContextStoreの参照を見つける必要がありますが、Assets以下のファイルをstringのパスで参照すると万一フォルダ構造が変わったときにエラーになってしまいます。

毎回手動でパスを書き換えてもいいのですが、出来れば余計なメンテナンスはしたくないので、フォルダにラベルを付けて、ラベル検索を行うことでContextStoreの配置されているフォルダを探しています。

var guids = AssetDatabase.FindAssets("l:EnvironmentContext");
if (guids.Length == 0)
{
    return null;
}
foreach (var id in guids)
{
    var dir = AssetDatabase.GUIDToAssetPath(id);
    if (Directory.Exists(dir))
    {
        dataPath = dir + Path.DirectorySeparatorChar + "ContextStore.asset";
        return dataPath;
    }
}

コンテキストを自動的に適用する

EditorがPlayModeに入ったタイミングはEditorApplication.playModeStateChangedで取得できるので、そのタイミングでコンテキストを適用します。

[InitializeOnLoadMethod]
public static void Register()
{
    EditorApplication.playModeStateChanged += EditorApplication_playModeStateChanged;
}

private static void EditorApplication_playModeStateChanged(PlayModeStateChange state)
{
    if (AutoApply && state == PlayModeStateChange.ExitingEditMode)
    {
        ContextStore.Instance.Current.Apply();
    }
}

また、ビルド時はIPreprocessBuildWithReportインターフェースを実装したクラスを用意すると、ビルド直前に自動でインスタンス生成してメソッドを呼び出してくれるので、このタイミングでコンテキストを適用しています。

public sealed class AutoContextApplier : IPreprocessBuildWithReport
{
    public void OnPreprocessBuild(BuildReport report)
    {
        if (AutoApply)
        {
            ContextStore.Instance.Current.Apply();
        }
    }
}

以上

個人的には開発とリリースでどういうパラメータが変化するのかをUnity GUI上で一覧で見られるのと、コンテキストそのものや、コンテキストで管理する値の追加/削除が簡単にできるのが気に入っています。

仕組みを1回作っておくとだいたいどんなプロジェクトでも使いまわしが利くので便利です。もしあなたのプロジェクトが大規模化して、設定の管理が大変になってきたら、こういったものを作ってみては如何でしょうか?