From 8701c43bc5293840de9488ea0d213752a406bd8f Mon Sep 17 00:00:00 2001 From: zhang hongbo Date: Fri, 29 May 2026 18:11:28 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BC=95=E5=AF=BC=E9=85=8D=E7=BD=AE=E5=8F=AF?= =?UTF-8?q?=E8=A7=86=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Editor/Design_Tools/GuideConfigEditor.cs | 318 +++++++++++++++++- 1 file changed, 307 insertions(+), 11 deletions(-) diff --git a/Scripts/Editor/Design_Tools/GuideConfigEditor.cs b/Scripts/Editor/Design_Tools/GuideConfigEditor.cs index 39e03f7..c9441fe 100644 --- a/Scripts/Editor/Design_Tools/GuideConfigEditor.cs +++ b/Scripts/Editor/Design_Tools/GuideConfigEditor.cs @@ -71,6 +71,16 @@ public class GuideConfigEditor : EditorWindow private enum SortMode { Id, Priority, Name } private SortMode _sortMode = SortMode.Id; + // 预设值缓存(从现有数据中收集) + private List _satisfyPresets = new List(); + private List _triggerPresets = new List(); + private List _endPresets = new List(); + private List _roleExpressionPresets = new List(); + private List _dialogPosPresets = new List(); + private List _clickPosPresets = new List(); + private List _focusPosPresets = new List(); + private bool _presetsDirty = true; + // 样式缓存 private GUIStyle _headerStyle; private GUIStyle _selectedStyle; @@ -116,9 +126,61 @@ public class GuideConfigEditor : EditorWindow _entries = ParseJsonArray(json); _isDirty = false; _selectedIndex = -1; + _presetsDirty = true; Debug.Log($"[GuideConfigEditor] 已加载 {_entries.Count} 条引导配置"); } + /// + /// 从所有现有条目中收集去重的预设值,用于快速选择 + /// + private void RebuildPresets() + { + if (!_presetsDirty) return; + _presetsDirty = false; + + var satisfySet = new HashSet(); + var triggerSet = new HashSet(); + var endSet = new HashSet(); + var roleExprSet = new HashSet(); + var dialogPosSet = new HashSet(); + var clickPosSet = new HashSet(); + var focusPosSet = new HashSet(); + + foreach (var e in _entries) + { + if (e.Conditions.Satisfy != null) + foreach (var s in e.Conditions.Satisfy) + if (!string.IsNullOrEmpty(s)) satisfySet.Add(s); + + if (e.Conditions.Trigger != null) + foreach (var t in e.Conditions.Trigger) + if (!string.IsNullOrEmpty(t)) triggerSet.Add(t); + + if (!string.IsNullOrEmpty(e.Conditions.End)) + endSet.Add(e.Conditions.End); + + if (!string.IsNullOrEmpty(e.ShowUI.RoleExpression)) + roleExprSet.Add(e.ShowUI.RoleExpression); + + if (!string.IsNullOrEmpty(e.ShowUI.DialogPos)) + dialogPosSet.Add(e.ShowUI.DialogPos); + + if (!string.IsNullOrEmpty(e.ShowUI.ClickPos)) + clickPosSet.Add(e.ShowUI.ClickPos); + + if (!string.IsNullOrEmpty(e.ShowUI.FocusPos)) + focusPosSet.Add(e.ShowUI.FocusPos); + } + + _satisfyPresets = satisfySet.OrderBy(s => s).ToList(); + _triggerPresets = triggerSet.OrderBy(s => s).ToList(); + _endPresets = endSet.OrderBy(s => s).ToList(); + _roleExpressionPresets = roleExprSet.OrderBy(s => s).ToList(); + _dialogPosPresets = dialogPosSet.OrderBy(s => s).ToList(); + _clickPosPresets = clickPosSet.OrderBy(s => s).ToList(); + _focusPosPresets = focusPosSet.OrderBy(s => s).ToList(); + } + private void SaveConfig() { string fullPath = Path.Combine(Application.dataPath, "..", ConfigPath); @@ -136,6 +198,7 @@ public class GuideConfigEditor : EditorWindow private void OnGUI() { InitStyles(); + RebuildPresets(); DrawToolbar(); EditorGUILayout.BeginHorizontal(); @@ -201,7 +264,7 @@ public class GuideConfigEditor : EditorWindow } GUILayout.Space(5); - GUILayout.Label("搜索:", EditorStyles.toolbarButton, GUILayout.Width(30)); + GUILayout.Label("全局搜索:", EditorStyles.toolbarButton, GUILayout.Width(50)); _searchText = EditorGUILayout.TextField(_searchText, EditorStyles.toolbarSearchField, GUILayout.Width(120)); GUILayout.Label("ID:", EditorStyles.toolbarButton, GUILayout.Width(20)); @@ -271,10 +334,10 @@ public class GuideConfigEditor : EditorWindow if (_showConditions) { EditorGUI.indentLevel++; - DrawStringList("Satisfy (满足条件)", ref entry.Conditions.Satisfy, ref _showSatisfy); + DrawStringListWithPresets("Satisfy (满足条件)", ref entry.Conditions.Satisfy, ref _showSatisfy, _satisfyPresets); DrawField("CheckAgain", ref entry.Conditions.CheckAgain); - DrawStringList("Trigger (触发器)", ref entry.Conditions.Trigger, ref _showTrigger); - DrawField("End (结束条件)", ref entry.Conditions.End); + DrawStringListWithPresets("Trigger (触发器)", ref entry.Conditions.Trigger, ref _showTrigger, _triggerPresets); + DrawFieldWithPresets("End (结束条件)", ref entry.Conditions.End, _endPresets); DrawField("Delay (延迟秒)", ref entry.Conditions.Delay); DrawField("Limit (限制次数)", ref entry.Conditions.Limit); EditorGUI.indentLevel--; @@ -288,12 +351,12 @@ public class GuideConfigEditor : EditorWindow { EditorGUI.indentLevel++; DrawField("LangText (多语言Key)", ref entry.ShowUI.LangText); - DrawField("RoleExpression (角色表情)", ref entry.ShowUI.RoleExpression); - DrawField("DialogPos (对话位置)", ref entry.ShowUI.DialogPos); + DrawFieldWithPresets("RoleExpression (角色表情)", ref entry.ShowUI.RoleExpression, _roleExpressionPresets); + DrawFieldWithPresets("DialogPos (对话位置)", ref entry.ShowUI.DialogPos, _dialogPosPresets); DrawField("OkBtn (确认按钮)", ref entry.ShowUI.OkBtn); DrawField("ClickSg (点击信号)", ref entry.ShowUI.ClickSg); - DrawField("ClickPos (点击位置)", ref entry.ShowUI.ClickPos); - DrawField("FocusPos (聚焦位置)", ref entry.ShowUI.FocusPos); + DrawFieldWithPresets("ClickPos (点击位置)", ref entry.ShowUI.ClickPos, _clickPosPresets); + DrawFieldWithPresets("FocusPos (聚焦位置)", ref entry.ShowUI.FocusPos, _focusPosPresets); DrawField("Phone (手机引导)", ref entry.ShowUI.Phone); DrawField("ClickToSkip (点击跳过)", ref entry.ShowUI.ClickToSkip); EditorGUI.indentLevel--; @@ -324,6 +387,193 @@ public class GuideConfigEditor : EditorWindow if (newVal != (value ?? "")) { value = newVal; _isDirty = true; } } + /// + /// 带预设下拉的文本字段:点击▼按钮弹出已有值列表快速选择 + /// + private void DrawFieldWithPresets(string label, ref string value, List presets) + { + EditorGUILayout.BeginHorizontal(); + string newVal = EditorGUILayout.TextField(label, value ?? ""); + if (newVal != (value ?? "")) { value = newVal; _isDirty = true; } + + if (presets != null && presets.Count > 0) + { + if (GUILayout.Button("▼", GUILayout.Width(20))) + { + // 记录当前选中条目和字段信息,在回调中通过索引赋值 + int entryIdx = _selectedIndex; + string fieldLabel = label; + ShowPresetMenu(presets, selected => + { + ApplyPresetToField(entryIdx, fieldLabel, selected); + }); + } + } + EditorGUILayout.EndHorizontal(); + } + + /// + /// 预设回调:根据字段标签将选中值写回对应字段 + /// + private void ApplyPresetToField(int entryIdx, string fieldLabel, string selected) + { + if (entryIdx < 0 || entryIdx >= _entries.Count) return; + var entry = _entries[entryIdx]; + + if (fieldLabel.StartsWith("End")) + entry.Conditions.End = selected; + else if (fieldLabel.StartsWith("RoleExpression")) + entry.ShowUI.RoleExpression = selected; + else if (fieldLabel.StartsWith("DialogPos")) + entry.ShowUI.DialogPos = selected; + else if (fieldLabel.StartsWith("ClickPos")) + entry.ShowUI.ClickPos = selected; + else if (fieldLabel.StartsWith("FocusPos")) + entry.ShowUI.FocusPos = selected; + + _isDirty = true; + _presetsDirty = true; + Repaint(); + } + + /// + /// 带预设快速选择的字符串列表(Satisfy / Trigger) + /// 点击 "+" 新增空行,点击 "▼" 从已有值中选择并新增 + /// + private void DrawStringListWithPresets(string label, ref List list, ref bool foldout, List presets) + { + EditorGUILayout.BeginHorizontal(); + foldout = EditorGUILayout.Foldout(foldout, $"{label} ({list.Count})", true); + if (GUILayout.Button("+", GUILayout.Width(20))) + { + list.Add(""); + _isDirty = true; + } + // 预设快速添加按钮 + if (presets != null && presets.Count > 0) + { + if (GUILayout.Button("▼选择", GUILayout.Width(45))) + { + var listRef = list; + ShowPresetMenu(presets, selected => + { + listRef.Add(selected); + _isDirty = true; + _presetsDirty = true; + Repaint(); + }); + } + } + EditorGUILayout.EndHorizontal(); + + if (!foldout) return; + + EditorGUI.indentLevel++; + for (int i = 0; i < list.Count; i++) + { + EditorGUILayout.BeginHorizontal(); + string newVal = EditorGUILayout.TextField($"[{i}]", list[i] ?? ""); + if (newVal != (list[i] ?? "")) { list[i] = newVal; _isDirty = true; } + + // 每行也有预设选择按钮,可以替换当前值 + if (presets != null && presets.Count > 0) + { + int idx = i; + if (GUILayout.Button("▼", GUILayout.Width(20))) + { + var listRef = list; + ShowPresetMenu(presets, selected => + { + if (idx < listRef.Count) + { + listRef[idx] = selected; + _isDirty = true; + _presetsDirty = true; + Repaint(); + } + }); + } + } + + if (GUILayout.Button("↑", GUILayout.Width(20)) && i > 0) + { + (list[i - 1], list[i]) = (list[i], list[i - 1]); + _isDirty = true; + } + if (GUILayout.Button("↓", GUILayout.Width(20)) && i < list.Count - 1) + { + (list[i], list[i + 1]) = (list[i + 1], list[i]); + _isDirty = true; + } + if (GUILayout.Button("×", GUILayout.Width(20))) + { + list.RemoveAt(i); + _isDirty = true; + break; + } + EditorGUILayout.EndHorizontal(); + } + EditorGUI.indentLevel--; + } + + /// + /// 显示预设值的下拉菜单(GenericMenu),按前缀分组 + /// + private void ShowPresetMenu(List presets, Action onSelect) + { + var menu = new GenericMenu(); + // 按前缀分组显示 + var groups = new Dictionary>(); + foreach (var p in presets) + { + string prefix = ExtractPrefix(p); + if (!groups.ContainsKey(prefix)) + groups[prefix] = new List(); + groups[prefix].Add(p); + } + + // 如果分组数 > 1 且有明显前缀,按分组显示 + if (groups.Count > 1 && groups.Keys.Any(k => k != "")) + { + foreach (var kvp in groups.OrderBy(g => g.Key)) + { + string groupLabel = string.IsNullOrEmpty(kvp.Key) ? "其他" : kvp.Key; + foreach (var item in kvp.Value) + { + // 用 \u2215 (除号斜杠) 替换 / 避免被 GenericMenu 当作子菜单分隔符 + string safeItem = item.Replace("/", "\u2215"); + string menuPath = $"{groupLabel}/{safeItem}"; + string captured = item; + menu.AddItem(new GUIContent(menuPath), false, () => onSelect(captured)); + } + } + } + else + { + // 数量少或无明显前缀,平铺显示 + foreach (var item in presets) + { + string safeItem = item.Replace("/", "\u2215"); + string captured = item; + menu.AddItem(new GUIContent(safeItem), false, () => onSelect(captured)); + } + } + + menu.ShowAsContext(); + } + + /// + /// 提取条件字符串的前缀(如 "LevelEqual_1" → "LevelEqual","UIPanel_MainHomeUI_1" → "UIPanel") + /// + private string ExtractPrefix(string value) + { + if (string.IsNullOrEmpty(value)) return ""; + int idx = value.IndexOf('_'); + if (idx > 0 && idx < value.Length - 1) + return value.Substring(0, idx); + return ""; + } + private void DrawStringList(string label, ref List list, ref bool foldout) { EditorGUILayout.BeginHorizontal(); @@ -373,15 +623,15 @@ public class GuideConfigEditor : EditorWindow { IEnumerable result = _entries; - // 按名称搜索 + // 全字段搜索(Name、Conditions、ShowUI 所有文本字段) if (!string.IsNullOrEmpty(_searchText)) { string lower = _searchText.ToLower(); - result = result.Where(e => (e.Name ?? "").ToLower().Contains(lower)); + result = result.Where(e => EntryMatchesSearch(e, lower)); } // 按ID搜索 - if (!string.IsNullOrEmpty(_searchId) && int.TryParse(_searchId, out int searchIdVal)) + if (!string.IsNullOrEmpty(_searchId)) { result = result.Where(e => e.Id.ToString().Contains(_searchId)); } @@ -403,6 +653,52 @@ public class GuideConfigEditor : EditorWindow return result.ToList(); } + /// + /// 全字段匹配:搜索 Name、Conditions(Satisfy/Trigger/End)、ShowUI 所有文本字段 + /// + private bool EntryMatchesSearch(GuideEntry e, string lower) + { + // 基本信息 + if ((e.Name ?? "").ToLower().Contains(lower)) + return true; + + // Conditions + if (e.Conditions.Satisfy != null && e.Conditions.Satisfy.Any(s => (s ?? "").ToLower().Contains(lower))) + return true; + if (e.Conditions.Trigger != null && e.Conditions.Trigger.Any(s => (s ?? "").ToLower().Contains(lower))) + return true; + if ((e.Conditions.End ?? "").ToLower().Contains(lower)) + return true; + if (e.Conditions.CheckAgain.ToString().Contains(lower)) + return true; + if (e.Conditions.Delay.ToString("G").ToLower().Contains(lower)) + return true; + if (e.Conditions.Limit.ToString().Contains(lower)) + return true; + + // ShowUI + if ((e.ShowUI.LangText ?? "").ToLower().Contains(lower)) + return true; + if ((e.ShowUI.RoleExpression ?? "").ToLower().Contains(lower)) + return true; + if ((e.ShowUI.DialogPos ?? "").ToLower().Contains(lower)) + return true; + if ((e.ShowUI.ClickPos ?? "").ToLower().Contains(lower)) + return true; + if ((e.ShowUI.FocusPos ?? "").ToLower().Contains(lower)) + return true; + if (e.ShowUI.OkBtn.ToString().Contains(lower)) + return true; + if (e.ShowUI.ClickSg.ToString().Contains(lower)) + return true; + if (e.ShowUI.Phone.ToString().Contains(lower)) + return true; + if (e.ShowUI.ClickToSkip.ToString().Contains(lower)) + return true; + + return false; + } + private void AddNewEntry() { int maxId = _entries.Count > 0 ? _entries.Max(e => e.Id) : 0;