【Unity笔记】Unity 编辑器扩展:打造一个可切换 Config.assets 的顶部菜单插件
Unity 编辑器扩展:打造一个可切换 Config(ScriptableObject)的顶部菜单插件
关键词:Unity 编辑器扩展、ScriptableObject、EditorWindow、自定义菜单、配置文件管理
文章目录
- Unity 编辑器扩展:打造一个可切换 Config(ScriptableObject)的顶部菜单插件
- 1、前言:从需求到想法的萌芽
- 2、技术选型:Unity 编辑器扩展的武器库
- 3、原理解析:从输入到持久化
- 4、代码实现:完整流程
- 5、使用体验:效果演示
- 6、总结与拓展
1、前言:从需求到想法的萌芽
在 Unity 的项目开发中,我们经常会遇到各种「配置文件」:
- 游戏参数配置
- 战斗数值配置
- UI 样式配置
- 网络服务端地址配置
这些配置往往存放在 ScriptableObject
的 .asset
文件中,以方便可视化编辑。
但是,随着项目的迭代,我们会发现一个问题:
👉 配置文件数量变多,每次要手动在 Project 窗口搜索、点击、打开,非常低效。
特别是团队协作时,测试、策划、程序员都可能需要修改配置。光是「找到文件」这一步,就要多点几次。
于是,萌生了一个想法:
能不能在 Unity 编辑器顶部菜单栏添加一个「配置管理」菜单?点击它就能打开一个面板,里面有下拉列表,直接切换不同的 Config 文件,并在面板中修改它的内容。
进一步优化:
- 第一次可以通过文件管理器选择一个配置文件。
- 之后自动保存「上一次选择的配置」,下次打开时就能直接使用。
这就是本文要实现的功能。
2、技术选型:Unity 编辑器扩展的武器库
需求流程:
为了实现上述需求,我们需要掌握以下 Unity 编辑器扩展相关技术:
-
EditorWindow
- Unity 提供的编辑器自定义窗口基类,可以在菜单栏打开一个专属面板。
-
MenuItem
- 可以在 Unity 顶部菜单栏注册自定义菜单。
-
ScriptableObject
- 作为配置文件的载体,可以序列化保存为
.asset
文件,天然适合做 Config。
- 作为配置文件的载体,可以序列化保存为
-
EditorGUILayout
- 用于在编辑器面板中绘制 UI,例如下拉框、按钮、对象选择器等。
-
EditorPrefs
- Unity 提供的本地偏好设置存储,可以保存简单的 key-value 数据(比如用户选择的上次 Config 文件路径)。
-
AssetDatabase
- 用于加载、查找
.asset
文件资源。
- 用于加载、查找
类图:
3、原理解析:从输入到持久化
在动手写代码前,我们先理一下「实现原理」:
-
入口
- 在 Unity 顶部菜单栏添加一个菜单项,例如:
Tools/Config Manager
。
- 在 Unity 顶部菜单栏添加一个菜单项,例如:
-
面板 UI
-
使用
EditorWindow
打开一个窗口。 -
窗口中有:
- 一个 下拉框,显示所有已知 Config 文件的名字。
- 一个 按钮,点击后可通过文件管理器选择新的 Config 文件。
- 一个 配置内容编辑区,直接显示并可修改 Config 的字段。
-
-
配置文件识别
- Config 文件是
ScriptableObject
,我们通过AssetDatabase.FindAssets("t:Config")
找到所有同类型的.asset
文件。
- Config 文件是
-
默认配置记忆
- 使用
EditorPrefs.SetString("LastConfigPath", path)
保存上次选择的路径。 - 下次打开窗口时,从
EditorPrefs
读取该路径,并尝试加载对应的配置文件。
- 使用
-
编辑内容保存
- Unity 的
SerializedObject
+EditorGUILayout.PropertyField
可以动态绘制 ScriptableObject 的字段,并保证修改后能保存。
- Unity 的
4、代码实现:完整流程
下面给出一个完整的实现案例。
假设我们的配置类是这样的:
using UnityEngine;[CreateAssetMenu(fileName = "GameConfig", menuName = "Config/GameConfig")]
public class GameConfig : ScriptableObject
{public string gameName;public int maxPlayerCount;public float gravityScale;
}
然后我们写一个编辑器插件 ConfigManagerWindow.cs
:
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.IO;public class ConfigManagerWindow : EditorWindow
{private const string PREF_KEY = "LastConfigPath";private List<GameConfig> configs = new List<GameConfig>();private string[] configNames;private int selectedIndex = -1;private SerializedObject serializedConfig;private Vector2 scrollPos;[MenuItem("Tools/Config Manager")]public static void ShowWindow(){GetWindow<ConfigManagerWindow>("Config Manager");}private void OnEnable(){LoadConfigs();// 尝试读取上一次选择的配置string lastPath = EditorPrefs.GetString(PREF_KEY, "");if (!string.IsNullOrEmpty(lastPath)){GameConfig lastConfig = AssetDatabase.LoadAssetAtPath<GameConfig>(lastPath);if (lastConfig != null){selectedIndex = configs.IndexOf(lastConfig);if (selectedIndex >= 0){SetCurrentConfig(configs[selectedIndex]);}}}}private void OnGUI(){if (configs.Count == 0){EditorGUILayout.HelpBox("未找到任何 Config 文件,请先创建 ScriptableObject。", MessageType.Info);if (GUILayout.Button("选择 Config 文件")){SelectConfigFromFile();}return;}EditorGUILayout.LabelField("选择配置文件:", EditorStyles.boldLabel);int newIndex = EditorGUILayout.Popup(selectedIndex, configNames);if (newIndex != selectedIndex){selectedIndex = newIndex;SetCurrentConfig(configs[selectedIndex]);}if (GUILayout.Button("通过文件管理器选择 Config")){SelectConfigFromFile();}if (serializedConfig != null){EditorGUILayout.Space();EditorGUILayout.LabelField("配置内容:", EditorStyles.boldLabel);scrollPos = EditorGUILayout.BeginScrollView(scrollPos);serializedConfig.Update();SerializedProperty prop = serializedConfig.GetIterator();prop.NextVisible(true);while (prop.NextVisible(false)){EditorGUILayout.PropertyField(prop, true);}serializedConfig.ApplyModifiedProperties();EditorGUILayout.EndScrollView();}}private void LoadConfigs(){configs.Clear();string[] guids = AssetDatabase.FindAssets("t:GameConfig");foreach (string guid in guids){string path = AssetDatabase.GUIDToAssetPath(guid);GameConfig config = AssetDatabase.LoadAssetAtPath<GameConfig>(path);if (config != null){configs.Add(config);}}configNames = new string[configs.Count];for (int i = 0; i < configs.Count; i++){configNames[i] = configs[i].name;}}private void SetCurrentConfig(GameConfig config){serializedConfig = new SerializedObject(config);string path = AssetDatabase.GetAssetPath(config);EditorPrefs.SetString(PREF_KEY, path);}private void SelectConfigFromFile(){string path = EditorUtility.OpenFilePanel("选择 Config 文件", Application.dataPath, "asset");if (!string.IsNullOrEmpty(path)){path = "Assets" + path.Substring(Application.dataPath.Length);GameConfig config = AssetDatabase.LoadAssetAtPath<GameConfig>(path);if (config != null){if (!configs.Contains(config)){configs.Add(config);List<string> names = new List<string>(configNames);names.Add(config.name);configNames = names.ToArray();}selectedIndex = configs.IndexOf(config);SetCurrentConfig(config);}}}
}
5、使用体验:效果演示
交互流程:
-
在菜单栏点击
Tools/Config Manager
-
弹出一个面板:
- 上方下拉框显示已有的配置文件
- 点击可切换不同 Config
- 右侧按钮可打开文件管理器,手动选择新的 Config
-
下方面板显示所选 Config 的所有字段,可以直接修改
-
修改后 Unity 自动保存到对应的
.asset
文件
6、总结与拓展
通过这次实战,我们实现了一个 Unity 编辑器扩展插件,它解决了以下问题:
- 配置文件散落在 Project 中 → 统一入口,集中管理
- 每次要手动搜索配置 → 一键下拉切换
- 修改不方便 → 在面板中直接可视化编辑
核心技术包括:
EditorWindow
自定义窗口MenuItem
注册菜单SerializedObject
保证 Unity 序列化EditorPrefs
保存用户默认配置AssetDatabase
搜索和加载.asset
文件
可能的扩展方向:
- 支持多类型 Config(不同的 ScriptableObject 类型)
- 添加「搜索框」快速过滤 Config
- 添加「收藏夹」功能,常用 Config 放到置顶位置
- 结合
EditorGUILayout.Toolbar
做更美观的 UI