【Unity笔记】Unity编辑器扩展:打造一个可切换Config.assets的顶部菜单插件

引言

在Unity项目中,Config.assets文件通常存储游戏配置(如难度参数、关卡数据、UI设置),通过编辑器扩展可以快速切换不同环境(开发/测试/生产)的配置,提升开发效率。顶部菜单插件使用MenuItem创建主菜单项,结合EditorWindow提供可视化界面,支持一键切换Config文件、自动保存/加载和版本管理。本教程基于Unity 2022.3 LTS+,使用SerializedObjectAssetDatabase实现完整功能,支持多Config文件管理和环境预设。

核心功能

  • 顶部菜单:Tools > Config Manager 快速访问
  • 可视化切换:按钮/下拉选择不同Config.assets
  • 自动保存:修改后一键Apply/Save
  • 环境预设:Dev/Test/Prod配置模板
  • 版本控制:Config历史记录和回滚

适用场景:多人协作项目、不同平台配置、多环境部署测试。

完整实现代码

1. ConfigManager主菜单(顶部菜单入口)

using UnityEngine;
using UnityEditor;

public static class ConfigManagerMenu
{
    [MenuItem("Tools/Config Manager/打开配置管理器", false, 10)]
    static void OpenConfigManager()
    {
        ConfigManagerWindow.ShowWindow();
    }

    [MenuItem("Tools/Config Manager/切换到开发配置", false, 11)]
    static void SwitchToDevConfig()
    {
        ConfigManager.Instance.SwitchConfig("Assets/Configs/DevConfig.asset");
    }

    [MenuItem("Tools/Config Manager/切换到测试配置", false, 12)]
    static void SwitchToTestConfig()
    {
        ConfigManager.Instance.SwitchConfig("Assets/Configs/TestConfig.asset");
    }

    [MenuItem("Tools/Config Manager/切换到生产配置", false, 13)]
    static void SwitchToProdConfig()
    {
        ConfigManager.Instance.SwitchConfig("Assets/Configs/ProdConfig.asset");
    }

    [MenuItem("Tools/Config Manager/保存当前配置", false, 20)]
    static void SaveCurrentConfig()
    {
        ConfigManager.Instance.SaveCurrentConfig();
    }

    [MenuItem("Tools/Config Manager/创建新配置", false, 30)]
    static void CreateNewConfig()
    {
        ConfigManager.Instance.CreateNewConfig();
    }
}

2. Config数据结构定义

首先定义Config的ScriptableObject基类,支持序列化字段。

using UnityEngine;
using System;

[CreateAssetMenu(fileName = "NewConfig", menuName = "Config/Base Config")]
public class BaseConfig : ScriptableObject
{
    [Header("基础设置")]
    public string configName = "Default Config";
    public string version = "1.0.0";
    public ConfigEnvironment environment = ConfigEnvironment.Development;

    [Header("游戏参数")]
    public float gameSpeed = 1f;
    public int maxPlayers = 4;
    public bool enableDebugMode = true;

    [Header("UI设置")]
    public Color primaryColor = Color.white;
    public float uiScale = 1f;

    [Header("性能设置")]
    public int targetFrameRate = 60;
    public bool useHighQualityGraphics = true;

    public enum ConfigEnvironment
    {
        Development,
        Testing,
        Production
    }

    // 标记配置已修改
    [HideInInspector] public bool isDirty;

    public virtual void OnConfigChanged()
    {
        isDirty = true;
        Debug.Log($"配置[{configName}]已修改,环境:{environment}");
    }

    // 环境特定逻辑
    public virtual void ApplyEnvironmentSettings()
    {
        switch (environment)
        {
            case ConfigEnvironment.Development:
                enableDebugMode = true;
                targetFrameRate = 30;  // 开发时降低帧率
                break;
            case ConfigEnvironment.Production:
                enableDebugMode = false;
                targetFrameRate = 60;
                useHighQualityGraphics = true;
                break;
        }
    }
}

3. Config管理单例(核心控制器)

using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.IO;

public class ConfigManager : ScriptableObject
{
    private static ConfigManager _instance;
    public static ConfigManager Instance
    {
        get
        {
            if (_instance == null)
            {
                string[] guids = AssetDatabase.FindAssets("t:ConfigManager");
                if (guids.Length > 0)
                {
                    _instance = AssetDatabase.LoadAssetAtPath<BaseConfig>(AssetDatabase.GUIDToAssetPath(guids[0])) as ConfigManager;
                }
                else
                {
                    _instance = CreateInstance<ConfigManager>();
                }
            }
            return _instance;
        }
    }

    [Header("配置管理")]
    public BaseConfig currentConfig;
    public List<BaseConfig> availableConfigs = new List<BaseConfig>();
    public string configPath = "Assets/Configs/";

    [Header("备份设置")]
    public bool autoBackup = true;
    public int maxBackupCount = 5;

    void OnEnable()
    {
        RefreshAvailableConfigs();
    }

    public void RefreshAvailableConfigs()
    {
        availableConfigs.Clear();
        string[] guids = AssetDatabase.FindAssets("t:BaseConfig");
        foreach (string guid in guids)
        {
            string path = AssetDatabase.GUIDToAssetPath(guid);
            BaseConfig config = AssetDatabase.LoadAssetAtPath<BaseConfig>(path);
            if (config != null)
            {
                availableConfigs.Add(config);
            }
        }
    }

    public void SwitchConfig(string configPath)
    {
        if (currentConfig != null)
        {
            SaveCurrentConfig();  // 保存当前配置
        }

        currentConfig = AssetDatabase.LoadAssetAtPath<BaseConfig>(configPath);
        if (currentConfig != null)
        {
            currentConfig.ApplyEnvironmentSettings();
            EditorUtility.SetDirty(currentConfig);
            AssetDatabase.SaveAssets();

            // 通知场景中依赖配置的对象
            NotifyConfigChanged();

            Debug.Log($"已切换到配置:{currentConfig.configName}");
        }
        else
        {
            Debug.LogError($"配置文件不存在:{configPath}");
        }
    }

    public void SaveCurrentConfig()
    {
        if (currentConfig != null)
        {
            if (autoBackup)
            {
                CreateBackup(currentConfig);
            }

            EditorUtility.SetDirty(currentConfig);
            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();

            Debug.Log($"已保存配置:{currentConfig.configName}");
        }
    }

    private void CreateBackup(BaseConfig config)
    {
        string backupDir = "Assets/Configs/Backups/";
        if (!Directory.Exists(backupDir))
        {
            Directory.CreateDirectory(backupDir);
        }

        string timestamp = System.DateTime.Now.ToString("yyyyMMdd_HHmmss");
        string backupPath = $"{backupDir}{config.name}_backup_{timestamp}.asset";

        BaseConfig backup = Instantiate(config);
        backup.name = $"{config.name}_backup_{timestamp}";
        AssetDatabase.CreateAsset(backup, backupPath);

        // 清理旧备份
        CleanupOldBackups(backupDir);
    }

    private void CleanupOldBackups(string backupDir)
    {
        string[] backups = Directory.GetFiles(backupDir, "*.asset");
        if (backups.Length > maxBackupCount)
        {
            System.Array.Sort(backups, (a, b) => File.GetCreationTime(b).CompareTo(File.GetCreationTime(a)));
            for (int i = maxBackupCount; i < backups.Length; i++)
            {
                AssetDatabase.DeleteAsset(backups[i]);
            }
        }
    }

    public void CreateNewConfig()
    {
        string newPath = EditorUtility.SaveFilePanelInProject(
            "创建新配置", "NewConfig", "asset", "选择保存位置");

        if (!string.IsNullOrEmpty(newPath))
        {
            BaseConfig newConfig = CreateInstance<BaseConfig>();
            newConfig.name = Path.GetFileNameWithoutExtension(newPath);
            newConfig.ApplyEnvironmentSettings();
            AssetDatabase.CreateAsset(newConfig, newPath);
            AssetDatabase.SaveAssets();

            RefreshAvailableConfigs();
            SwitchConfig(newPath);
        }
    }

    private void NotifyConfigChanged()
    {
        // 通知所有监听配置的对象
        var allConfigs = FindObjectsOfType<MonoBehaviour>();
        foreach (var obj in allConfigs)
        {
            if (obj is IConfigListener listener)
            {
                listener.OnConfigChanged(currentConfig);
            }
        }
    }
}

4. 可视化编辑器窗口

using UnityEngine;
using UnityEditor;
using System.Linq;

public class ConfigManagerWindow : EditorWindow
{
    private BaseConfig selectedConfig;
    private Vector2 scrollPos;
    private bool showAdvanced = false;

    [MenuItem("Tools/Config Manager/打开配置管理器")]
    public static void ShowWindow()
    {
        GetWindow<ConfigManagerWindow>("Config Manager");
    }

    void OnEnable()
    {
        selectedConfig = ConfigManager.Instance?.currentConfig;
        RefreshConfigs();
    }

    void OnGUI()
    {
        GUILayout.Label("配置管理器", EditorStyles.boldLabel);

        // 配置列表
        EditorGUILayout.BeginVertical("box");
        GUILayout.Label("可用配置", EditorStyles.miniBoldLabel);

        ConfigManager.Instance.RefreshAvailableConfigs();
        foreach (var config in ConfigManager.Instance.availableConfigs)
        {
            bool isSelected = config == selectedConfig;
            EditorGUILayout.BeginHorizontal();

            if (GUILayout.Toggle(isSelected, config.configName, EditorStyles.radioButton))
            {
                if (!isSelected)
                {
                    SelectConfig(config);
                }
            }

            if (GUILayout.Button("切换", GUILayout.Width(60)))
            {
                SelectConfig(config);
            }

            if (GUILayout.Button("备份", GUILayout.Width(60)))
            {
                CreateBackup(config);
            }

            EditorGUILayout.EndHorizontal();
        }
        EditorGUILayout.EndVertical();

        // 当前配置编辑
        if (selectedConfig != null)
        {
            EditorGUILayout.Space();
            EditorGUILayout.LabelField("当前配置", EditorStyles.boldLabel);

            SerializedObject serializedConfig = new SerializedObject(selectedConfig);
            SerializedProperty iterator = serializedConfig.GetIterator();
            bool enterChildren = true;

            scrollPos = EditorGUILayout.BeginScrollView(scrollPos);
            while (iterator.NextVisible(enterChildren))
            {
                if (iterator.propertyType == SerializedPropertyType.ManagedReference)
                {
                    EditorGUILayout.PropertyField(iterator, true);
                }
                else
                {
                    EditorGUILayout.PropertyField(iterator, true);
                }
            }
            EditorGUILayout.EndScrollView();

            serializedConfig.ApplyModifiedProperties();

            EditorGUILayout.Space();
            EditorGUILayout.BeginHorizontal();
            if (GUILayout.Button("应用更改"))
            {
                serializedConfig.ApplyModifiedProperties();
                EditorUtility.SetDirty(selectedConfig);
                AssetDatabase.SaveAssets();
            }
            if (GUILayout.Button("保存配置"))
            {
                ConfigManager.Instance.SaveCurrentConfig();
            }
            EditorGUILayout.EndHorizontal();
        }

        // 高级选项
        showAdvanced = EditorGUILayout.Foldout(showAdvanced, "高级选项");
        if (showAdvanced)
        {
            ConfigManager.Instance.autoBackup = EditorGUILayout.Toggle("自动备份", ConfigManager.Instance.autoBackup);
            ConfigManager.Instance.maxBackupCount = EditorGUILayout.IntSlider("最大备份数", ConfigManager.Instance.maxBackupCount, 1, 10);
        }
    }

    void SelectConfig(BaseConfig config)
    {
        ConfigManager.Instance.SwitchConfig(AssetDatabase.GetAssetPath(config));
        selectedConfig = config;
        Repaint();
    }

    void CreateBackup(BaseConfig config)
    {
        ConfigManager.Instance.CreateBackup(config);
        RefreshConfigs();
    }

    void RefreshConfigs()
    {
        ConfigManager.Instance.RefreshAvailableConfigs();
    }
}

5. 配置监听接口(游戏逻辑集成)

public interface IConfigListener
{
    void OnConfigChanged(BaseConfig newConfig);
}

public class GameSettings : MonoBehaviour, IConfigListener
{
    private BaseConfig currentConfig;

    void Start()
    {
        ApplyConfig(ConfigManager.Instance.currentConfig);
    }

    public void OnConfigChanged(BaseConfig newConfig)
    {
        ApplyConfig(newConfig);
    }

    void ApplyConfig(BaseConfig config)
    {
        if (config == null) return;

        currentConfig = config;
        Application.targetFrameRate = config.targetFrameRate;
        Time.timeScale = config.gameSpeed;
        Debug.unityLogger.logEnabled = config.enableDebugMode;

        Debug.Log($"应用配置:{config.configName}");
    }
}

6. 自定义编辑器样式(可选增强)

#if UNITY_EDITOR
using UnityEditor;

[CustomEditor(typeof(BaseConfig))]
public class BaseConfigEditor : Editor
{
    public override void OnInspectorGUI()
    {
        BaseConfig config = (BaseConfig)target;

        // 自定义GUI样式
        GUILayout.Label("配置编辑器", EditorStyles.boldLabel);
        EditorGUILayout.HelpBox($"环境: {config.environment}\n版本: {config.version}", MessageType.Info);

        DrawDefaultInspector();

        EditorGUILayout.Space();
        EditorGUILayout.BeginHorizontal();

        if (GUILayout.Button("应用环境设置"))
        {
            config.ApplyEnvironmentSettings();
            EditorUtility.SetDirty(config);
        }

        if (GUILayout.Button("保存配置"))
        {
            EditorUtility.SetDirty(config);
            AssetDatabase.SaveAssets();
        }

        EditorGUILayout.EndHorizontal();
    }
}
#endif

使用流程

1. 安装与初始化

  1. 创建文件夹Assets/Editor/(编辑器脚本)和Assets/Configs/(配置存储)。
  2. 导入脚本:将所有代码放入对应文件夹。
  3. 创建ConfigManager:右键 > Create > Config > Config Manager(单例)。
  4. 生成默认配置
  • Tools > Config Manager > 创建新配置
  • 生成 DevConfig/TestConfig/ProdConfig

2. 日常使用

  • 切换配置:Tools > Config Manager > [环境名称]
  • 可视化编辑:Tools > Config Manager > 打开配置管理器
  • 游戏集成:在MonoBehaviour实现IConfigListener监听配置变更
  • 备份管理:自动备份到Assets/Configs/Backups/

3. 高级功能

  • 环境预设:不同Config设置不同参数(如开发模式开启Debug)
  • 版本控制:自动备份 + 手动回滚
  • 团队协作:Config文件可提交到版本控制,快速同步

扩展与优化

  • 多项目支持:通过GUID管理不同项目的配置
  • 加密配置:敏感数据加密存储
  • 云同步:集成Unity Cloud Build自动部署不同环境配置
  • 验证系统:配置参数合法性检查和自动修复

通过此插件,可实现高效的配置管理,大幅提升开发和测试效率!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注