using System; using System.Collections.Generic; using System.IO; using System.Reflection; using UnityEditor; using UnityEngine; using UnityEngine.UIElements; namespace Fantasy { public class FantasySettingsProvider : SettingsProvider { private SerializedObject _serializedObject; private SerializedProperty _autoCopyAssembly; private SerializedProperty _hotUpdatePath; private SerializedProperty _hotUpdateAssemblyDefinitions; private SerializedProperty _linkAssemblyDefinitions; private SerializedProperty _includeAssembly; private bool _showLinkXmlConfig = true; // 控制是否显示 Link.xml 配置 public FantasySettingsProvider() : base("Project/Fantasy Settings", SettingsScope.Project) { } public override void OnActivate(string searchContext, VisualElement rootElement) { Init(); base.OnActivate(searchContext, rootElement); } public override void OnDeactivate() { base.OnDeactivate(); FantasySettingsScriptableObject.Save(); } private void Init() { _serializedObject?.Dispose(); _serializedObject = new SerializedObject(FantasySettingsScriptableObject.Instance); _autoCopyAssembly = _serializedObject.FindProperty("autoCopyAssembly"); _hotUpdatePath = _serializedObject.FindProperty("hotUpdatePath"); _hotUpdateAssemblyDefinitions = _serializedObject.FindProperty("hotUpdateAssemblyDefinitions"); _linkAssemblyDefinitions = _serializedObject.FindProperty("linkAssemblyDefinitions"); _includeAssembly = _serializedObject.FindProperty("includeAssembly"); } public override void OnGUI(string searchContext) { if (_serializedObject == null || !_serializedObject.targetObject) { Init(); } using (CreateSettingsWindowGUIScope()) { _serializedObject!.Update(); // ============ Fantasy 框架集成检测 ============ DrawCscRspInstallationSection(); DrawSectionDivider(); EditorGUILayout.Space(10); // ============ 程序集自动拷贝设置 ============ EditorGUI.BeginChangeCheck(); DrawAssemblyCopySection(); DrawSectionDivider(); // ============ Link.xml 生成设置 ============ DrawLinkXmlSection(); if (EditorGUI.EndChangeCheck()) { _serializedObject.ApplyModifiedProperties(); FantasySettingsScriptableObject.Save(); EditorApplication.RepaintHierarchyWindow(); } base.OnGUI(searchContext); } } private IDisposable CreateSettingsWindowGUIScope() { var unityEditorAssembly = System.Reflection.Assembly.GetAssembly(typeof(EditorWindow)); var type = unityEditorAssembly.GetType("UnityEditor.SettingsWindow+GUIScope"); return Activator.CreateInstance(type) as IDisposable; } /// /// 绘制 csc.rsp 安装状态区域 /// private void DrawCscRspInstallationSection() { bool isInstalled = CheckCscRspStatus(out bool fileExists, out bool hasDefine); // 状态框 if (isInstalled) { // 已安装 - 绿色背景框 GUIStyle boxStyle = new GUIStyle(GUI.skin.box) { padding = new RectOffset(10, 10, 10, 10), margin = new RectOffset(0, 0, 5, 5) }; Color originalColor = GUI.color; GUI.color = new Color(0.7f, 1f, 0.7f); // 绿色背景 EditorGUILayout.BeginVertical(boxStyle); GUI.color = originalColor; GUIStyle installedButtonStyle = new GUIStyle(GUI.skin.button) { fontSize = 13, fontStyle = FontStyle.Bold, fixedHeight = 35 }; Color originalBgColor = GUI.backgroundColor; GUI.backgroundColor = new Color(0.3f, 0.8f, 0.3f); // 绿色 GUI.enabled = false; // 禁用按钮,只显示状态 GUILayout.Button("✓ FANTASY_UNITY 已安装", installedButtonStyle); GUI.enabled = true; GUI.backgroundColor = originalBgColor; EditorGUILayout.Space(3); EditorGUILayout.HelpBox("编译器配置正确,框架功能已启用", MessageType.Info); EditorGUILayout.EndVertical(); } else { // 未安装 - 橙黄色背景框 GUIStyle boxStyle = new GUIStyle(GUI.skin.box) { padding = new RectOffset(10, 10, 10, 10), margin = new RectOffset(0, 0, 5, 5) }; Color originalColor = GUI.color; GUI.color = Color.red; // 橙黄色背景 EditorGUILayout.BeginVertical(boxStyle); GUI.color = originalColor; EditorGUILayout.Space(8); // 醒目的大按钮 GUIStyle buttonStyle = new GUIStyle(GUI.skin.button) { fontSize = 13, fontStyle = FontStyle.Bold, fixedHeight = 35 }; Color originalBgColor = GUI.backgroundColor; GUI.backgroundColor = new Color(0.3f, 0.8f, 1f); if (GUILayout.Button("点击安装 FANTASY_UNITY", buttonStyle)) { InstallFantasyUnityDefine(); } GUI.backgroundColor = originalBgColor; EditorGUILayout.Space(3); EditorGUILayout.HelpBox("安装后可能需要重新编译项目才能生效", MessageType.Info); EditorGUILayout.EndVertical(); } } /// /// 检测 csc.rsp 文件状态 /// /// 文件是否存在 /// 是否包含 FANTASY_UNITY 定义 /// 是否已正确安装 private bool CheckCscRspStatus(out bool fileExists, out bool hasDefine) { string cscRspPath = Path.Combine(Application.dataPath, "csc.rsp"); fileExists = File.Exists(cscRspPath); hasDefine = false; if (fileExists) { string content = File.ReadAllText(cscRspPath); // 使用正则表达式精确匹配 FANTASY_UNITY(确保是完整的单词,不是其他定义的一部分) // 匹配条件:FANTASY_UNITY 后面是分号、空白字符、换行或文件结束 hasDefine = System.Text.RegularExpressions.Regex.IsMatch( content, @"\bFANTASY_UNITY\b" ); } return fileExists && hasDefine; } /// /// 安装 FANTASY_UNITY 定义到 csc.rsp 文件 /// private void InstallFantasyUnityDefine() { string cscRspPath = Path.Combine(Application.dataPath, "csc.rsp"); try { if (!File.Exists(cscRspPath)) { // 创建新文件 File.WriteAllText(cscRspPath, "-define:FANTASY_UNITY\n"); } else { // 读取现有内容 string content = File.ReadAllText(cscRspPath); // 使用正则表达式精确检测,避免误判(例如 FANTASY_UNITY123) bool hasFantasyUnity = System.Text.RegularExpressions.Regex.IsMatch( content, @"\bFANTASY_UNITY\b" ); if (!hasFantasyUnity) { // 查找是否已有 -define: 行 string[] lines = content.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); bool defineLineFound = false; for (int i = 0; i < lines.Length; i++) { if (lines[i].TrimStart().StartsWith("-define:")) { // 添加到现有的 define 行 if (lines[i].EndsWith(";")) { lines[i] = lines[i] + "FANTASY_UNITY"; } else { lines[i] = lines[i] + ";FANTASY_UNITY"; } defineLineFound = true; break; } } if (defineLineFound) { content = string.Join("\n", lines) + "\n"; } else { // 添加新的 define 行到文件开头 content = "-define:FANTASY_UNITY\n" + content; } File.WriteAllText(cscRspPath, content); } } AssetDatabase.Refresh(); EditorUtility.DisplayDialog("成功", "FANTASY_UNITY 已经安装成功。\n\n重新编译后生效。", "确定"); } catch (Exception ex) { Debug.LogError($"安装 FANTASY_UNITY 失败: {ex.Message}"); EditorUtility.DisplayDialog("错误", $"安装失败:\n{ex.Message}", "确定"); } } /// /// 绘制分隔线 /// private void DrawSectionDivider() { EditorGUILayout.Space(5); Rect rect = EditorGUILayout.GetControlRect(false, 1); rect.height = 1; // 绘制细线 EditorGUI.DrawRect(rect, new Color(0.5f, 0.5f, 0.5f, 0.3f)); EditorGUILayout.Space(5); } /// /// 绘制程序集自动拷贝设置区域 /// private void DrawAssemblyCopySection() { // 标题 GUIStyle titleStyle = new GUIStyle(EditorStyles.boldLabel) { fontSize = 14, alignment = TextAnchor.MiddleLeft }; EditorGUILayout.LabelField("程序集自动拷贝", titleStyle); EditorGUILayout.Space(5); // 功能说明 GUIStyle descStyle = new GUIStyle(EditorStyles.wordWrappedLabel) { fontSize = 11, normal = { textColor = Color.gray } }; EditorGUILayout.LabelField("Unity 编译后自动将指定的程序集文件(DLL + PDB)复制到目标目录,用于热更新或其他用途", descStyle); EditorGUILayout.Space(8); // 主开关区域 GUIStyle boxStyle = new GUIStyle(GUI.skin.box) { padding = new RectOffset(12, 12, 12, 12), margin = new RectOffset(0, 0, 0, 8) }; EditorGUILayout.BeginVertical(boxStyle); // 开关 - 更大更明显 EditorGUILayout.BeginHorizontal(); GUIStyle bigToggleStyle = new GUIStyle(EditorStyles.boldLabel) { fontSize = 13 }; bool newValue = EditorGUILayout.ToggleLeft("启用自动拷贝", _autoCopyAssembly.boolValue, bigToggleStyle); if (newValue != _autoCopyAssembly.boolValue) { _autoCopyAssembly.boolValue = newValue; } GUILayout.FlexibleSpace(); if (_autoCopyAssembly.boolValue) { GUIStyle statusLabel = new GUIStyle(EditorStyles.miniLabel) { fontSize = 11, normal = { textColor = new Color(0f, 0.6f, 0f) } }; EditorGUILayout.LabelField("● 已启用", statusLabel, GUILayout.Width(60)); } else { GUIStyle statusLabel = new GUIStyle(EditorStyles.miniLabel) { fontSize = 11, normal = { textColor = Color.gray } }; EditorGUILayout.LabelField("○ 已禁用", statusLabel, GUILayout.Width(60)); } EditorGUILayout.EndHorizontal(); // 如果启用了自动拷贝,显示详细配置 if (_autoCopyAssembly.boolValue) { EditorGUILayout.Space(15); // 步骤 1:设置输出目录 DrawStepHeader("1", "设置输出目录"); EditorGUILayout.BeginHorizontal(); EditorGUILayout.PropertyField(_hotUpdatePath, GUIContent.none); if (GUILayout.Button("选择文件夹", GUILayout.Width(80))) { string path = EditorUtility.OpenFolderPanel("选择程序集输出目录", _hotUpdatePath.stringValue, ""); if (!string.IsNullOrEmpty(path)) { _hotUpdatePath.stringValue = path; } } EditorGUILayout.EndHorizontal(); // 路径状态提示 if (string.IsNullOrEmpty(_hotUpdatePath.stringValue)) { EditorGUILayout.Space(3); GUIStyle warningStyle = new GUIStyle(EditorStyles.miniLabel) { normal = { textColor = new Color(0.8f, 0.4f, 0f) } }; EditorGUILayout.LabelField("⚠ 请先设置输出目录", warningStyle); } else if (!Directory.Exists(_hotUpdatePath.stringValue)) { EditorGUILayout.Space(3); GUIStyle warningStyle = new GUIStyle(EditorStyles.miniLabel) { normal = { textColor = new Color(0.6f, 0.6f, 0f) } }; EditorGUILayout.LabelField("ℹ 目录不存在,编译时将自动创建", warningStyle); } else { EditorGUILayout.Space(3); GUIStyle successStyle = new GUIStyle(EditorStyles.miniLabel) { normal = { textColor = new Color(0f, 0.6f, 0f) } }; EditorGUILayout.LabelField("✓ 目录已配置", successStyle); } EditorGUILayout.Space(15); // 步骤 2:选择要拷贝的程序集 DrawStepHeader("2", "程序集列表"); GUIStyle hintStyle = new GUIStyle(EditorStyles.miniLabel) { fontSize = 10, normal = { textColor = Color.gray } }; EditorGUILayout.LabelField("选择需要自动拷贝的热更新程序集", hintStyle); EditorGUILayout.Space(10); // 程序集列表区域 if (_hotUpdateAssemblyDefinitions.arraySize == 0) { // 空状态提示框 Color originalColor = GUI.color; GUI.color = new Color(0.95f, 0.95f, 0.95f); EditorGUILayout.BeginVertical(GUI.skin.box); GUI.color = originalColor; EditorGUILayout.Space(10); GUIStyle emptyIconStyle = new GUIStyle(EditorStyles.boldLabel) { fontSize = 20, alignment = TextAnchor.MiddleCenter, normal = { textColor = new Color(0.6f, 0.6f, 0.6f) } }; EditorGUILayout.LabelField("📦", emptyIconStyle); GUIStyle emptyTextStyle = new GUIStyle(EditorStyles.label) { fontSize = 11, alignment = TextAnchor.MiddleCenter, normal = { textColor = Color.gray } }; EditorGUILayout.LabelField("暂未添加任何程序集", emptyTextStyle); EditorGUILayout.Space(5); GUIStyle addHintStyle = new GUIStyle(EditorStyles.miniLabel) { fontSize = 10, alignment = TextAnchor.MiddleCenter, normal = { textColor = new Color(0.3f, 0.6f, 1f) } }; EditorGUILayout.LabelField("点击下方的 + 按钮添加程序集", addHintStyle); EditorGUILayout.Space(10); EditorGUILayout.EndVertical(); EditorGUILayout.Space(5); } // 程序集列表 EditorGUILayout.PropertyField(_hotUpdateAssemblyDefinitions, new GUIContent(""), true); if (_hotUpdateAssemblyDefinitions.arraySize > 0) { EditorGUILayout.Space(5); GUIStyle successStyle = new GUIStyle(EditorStyles.miniLabel) { normal = { textColor = new Color(0f, 0.6f, 0f) } }; EditorGUILayout.LabelField($"✓ 已添加 {_hotUpdateAssemblyDefinitions.arraySize} 个程序集", successStyle); } // 配置完成状态总结 bool isConfigured = !string.IsNullOrEmpty(_hotUpdatePath.stringValue) && _hotUpdateAssemblyDefinitions.arraySize > 0; if (isConfigured) { Color originalColor = GUI.color; GUI.color = new Color(0.8f, 1f, 0.8f); EditorGUILayout.BeginVertical(GUI.skin.box); GUI.color = originalColor; GUIStyle summaryStyle = new GUIStyle(EditorStyles.miniLabel) { fontSize = 11, normal = { textColor = new Color(0f, 0.5f, 0f) } }; EditorGUILayout.LabelField($"✓ 配置完成!编译后将自动拷贝 {_hotUpdateAssemblyDefinitions.arraySize} 个程序集到目标目录", summaryStyle); EditorGUILayout.EndVertical(); } } else { EditorGUILayout.Space(8); EditorGUILayout.HelpBox("启用后可在 Unity 编译完成时自动拷贝程序集文件", MessageType.Info); } EditorGUILayout.EndVertical(); } /// /// 绘制步骤标题 /// private void DrawStepHeader(string stepNumber, string title) { EditorGUILayout.BeginHorizontal(); // 步骤编号 GUIStyle stepStyle = new GUIStyle(EditorStyles.boldLabel) { fontSize = 12, normal = { textColor = new Color(0.3f, 0.6f, 1f) } }; EditorGUILayout.LabelField($"步骤 {stepNumber}:", stepStyle, GUILayout.Width(60)); // 步骤标题 GUIStyle titleStyle = new GUIStyle(EditorStyles.boldLabel) { fontSize = 12 }; EditorGUILayout.LabelField(title, titleStyle); EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(5); } /// /// 绘制 Link.xml 生成设置区域 /// private void DrawLinkXmlSection() { // 标题 GUIStyle titleStyle = new GUIStyle(EditorStyles.boldLabel) { fontSize = 14, alignment = TextAnchor.MiddleLeft }; EditorGUILayout.LabelField("Link.xml 代码裁剪配置", titleStyle); EditorGUILayout.Space(5); // 功能说明 GUIStyle descStyle = new GUIStyle(EditorStyles.wordWrappedLabel) { fontSize = 11, normal = { textColor = Color.gray } }; EditorGUILayout.LabelField("配置 Link.xml 文件以防止 IL2CPP 编译时过度裁剪反射或动态调用的代码,确保运行时正常", descStyle); EditorGUILayout.Space(8); GUIStyle boxStyle = new GUIStyle(GUI.skin.box) { padding = new RectOffset(12, 12, 12, 12), margin = new RectOffset(0, 0, 0, 8) }; EditorGUILayout.BeginVertical(boxStyle); // 显示配置开关 EditorGUILayout.BeginHorizontal(); GUIStyle bigToggleStyle = new GUIStyle(EditorStyles.boldLabel) { fontSize = 13 }; _showLinkXmlConfig = EditorGUILayout.ToggleLeft("显示配置", _showLinkXmlConfig, bigToggleStyle); GUILayout.FlexibleSpace(); if (_showLinkXmlConfig) { GUIStyle statusLabel = new GUIStyle(EditorStyles.miniLabel) { fontSize = 11, normal = { textColor = new Color(0.3f, 0.6f, 1f) } }; EditorGUILayout.LabelField("● 已展开", statusLabel, GUILayout.Width(60)); } else { GUIStyle statusLabel = new GUIStyle(EditorStyles.miniLabel) { fontSize = 11, normal = { textColor = Color.gray } }; EditorGUILayout.LabelField("○ 已折叠", statusLabel, GUILayout.Width(60)); } EditorGUILayout.EndHorizontal(); // 如果显示配置,则展示详细内容 if (_showLinkXmlConfig) { EditorGUILayout.Space(15); // 程序集定义配置标题 GUIStyle sectionTitleStyle = new GUIStyle(EditorStyles.boldLabel) { fontSize = 13 }; EditorGUILayout.LabelField("需要保护的程序集", sectionTitleStyle); EditorGUILayout.Space(3); GUIStyle hintStyle = new GUIStyle(EditorStyles.miniLabel) { fontSize = 10, normal = { textColor = Color.gray } }; EditorGUILayout.LabelField("选择需要在 IL2CPP 编译时防止代码裁剪的程序集", hintStyle); EditorGUILayout.Space(3); // 默认包含程序集说明 EditorGUILayout.BeginHorizontal(); GUILayout.Space(5); GUIStyle defaultInfoStyle = new GUIStyle(EditorStyles.miniLabel) { fontSize = 10, normal = { textColor = new Color(0.4f, 0.6f, 0.8f) } }; EditorGUILayout.LabelField("ℹ 默认包含:Assembly-CSharp、Fantasy.Unity", defaultInfoStyle); EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(10); // 程序集列表区域 if (_linkAssemblyDefinitions.arraySize == 0) { // 空状态提示框 Color originalColor = GUI.color; GUI.color = new Color(0.95f, 0.95f, 0.95f); EditorGUILayout.BeginVertical(GUI.skin.box); GUI.color = originalColor; EditorGUILayout.Space(10); GUIStyle emptyIconStyle = new GUIStyle(EditorStyles.boldLabel) { fontSize = 20, alignment = TextAnchor.MiddleCenter, normal = { textColor = new Color(0.6f, 0.6f, 0.6f) } }; EditorGUILayout.LabelField("📋", emptyIconStyle); GUIStyle emptyTextStyle = new GUIStyle(EditorStyles.label) { fontSize = 11, alignment = TextAnchor.MiddleCenter, normal = { textColor = Color.gray } }; EditorGUILayout.LabelField("暂未添加任何程序集", emptyTextStyle); EditorGUILayout.Space(5); GUIStyle addHintStyle = new GUIStyle(EditorStyles.miniLabel) { fontSize = 10, alignment = TextAnchor.MiddleCenter, normal = { textColor = new Color(0.3f, 0.6f, 1f) } }; EditorGUILayout.LabelField("点击下方的 + 按钮添加程序集", addHintStyle); EditorGUILayout.Space(10); EditorGUILayout.EndVertical(); EditorGUILayout.Space(5); } // 程序集列表 EditorGUILayout.PropertyField(_linkAssemblyDefinitions, new GUIContent(""), true); if (_linkAssemblyDefinitions.arraySize > 0) { EditorGUILayout.Space(5); GUIStyle successStyle = new GUIStyle(EditorStyles.miniLabel) { normal = { textColor = new Color(0f, 0.6f, 0f) } }; EditorGUILayout.LabelField($"✓ 已添加 {_linkAssemblyDefinitions.arraySize} 个程序集", successStyle); } EditorGUILayout.Space(15); // 生成按钮区域 EditorGUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); GUIStyle buttonStyle = new GUIStyle(GUI.skin.button) { fontSize = 13, fontStyle = FontStyle.Bold, fixedHeight = 35, fixedWidth = 200 }; Color originalBgColor = GUI.backgroundColor; GUI.backgroundColor = new Color(0.4f, 0.7f, 1f); if (GUILayout.Button("生成 Link.xml 文件", buttonStyle)) { LinkXmlGenerator.GenerateLinkXml(); EditorUtility.DisplayDialog("操作成功", "Link.xml 文件已生成", "确定"); } GUI.backgroundColor = originalBgColor; GUILayout.FlexibleSpace(); EditorGUILayout.EndHorizontal(); } else { EditorGUILayout.Space(8); EditorGUILayout.HelpBox("点击上方开关展开配置", MessageType.Info); } EditorGUILayout.EndVertical(); } static FantasySettingsProvider _provider; [SettingsProvider] public static SettingsProvider CreateMyCustomSettingsProvider() { if (FantasySettingsScriptableObject.Instance && _provider == null) { _provider = new FantasySettingsProvider(); using (var so = new SerializedObject(FantasySettingsScriptableObject.Instance)) { _provider.keywords = GetSearchKeywordsFromSerializedObject(so); } } return _provider; } } }