diff --git a/Scripts/Editor/Design_Tools/GuideConfigEditor.cs b/Scripts/Editor/Design_Tools/GuideConfigEditor.cs new file mode 100644 index 0000000..c9441fe --- /dev/null +++ b/Scripts/Editor/Design_Tools/GuideConfigEditor.cs @@ -0,0 +1,840 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using UnityEditor; +using UnityEngine; + +/// +/// GuideConfig.txt 可视化编辑器 +/// 菜单路径: 程序工具/策划工具/引导配置编辑器 +/// +public class GuideConfigEditor : EditorWindow +{ + #region 数据结构 + + [Serializable] + public class GuideConditions + { + public List Satisfy = new List(); + public int CheckAgain; + public List Trigger = new List(); + public string End = ""; + public float Delay; + public int Limit; + } + + [Serializable] + public class GuideShowUI + { + public string LangText = ""; + public string RoleExpression = ""; + public string DialogPos = ""; + public int OkBtn; + public int ClickSg; + public string ClickPos = ""; + public string FocusPos = ""; + public int Phone; + public int ClickToSkip; + } + + [Serializable] + public class GuideEntry + { + public int Id; + public string Name = ""; + public int Priority; + public GuideConditions Conditions = new GuideConditions(); + public GuideShowUI ShowUI = new GuideShowUI(); + } + + #endregion + + #region 字段 + + private static readonly string ConfigPath = "Assets/Scripts/Preload/GuideConfig.txt"; + + private List _entries = new List(); + private Vector2 _listScroll; + private Vector2 _detailScroll; + private int _selectedIndex = -1; + private string _searchText = ""; + private string _searchId = ""; + private bool _isDirty; + private bool _showConditions = true; + private bool _showShowUI = true; + private bool _showSatisfy = true; + private bool _showTrigger = true; + + // 列表排序 + 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; + private GUIStyle _entryStyle; + private bool _stylesInited; + + #endregion + + [MenuItem("策划工具/引导配置编辑器")] + public static void ShowWindow() + { + var win = GetWindow("引导配置编辑器"); + win.minSize = new Vector2(900, 500); + win.LoadConfig(); + } + + private void OnEnable() + { + LoadConfig(); + } + + private void InitStyles() + { + if (_stylesInited) return; + _headerStyle = new GUIStyle(EditorStyles.boldLabel) { fontSize = 13 }; + _selectedStyle = new GUIStyle("TV Selection"); + _entryStyle = new GUIStyle(EditorStyles.label) { richText = true }; + _stylesInited = true; + } + + #region 加载 / 保存 + + private void LoadConfig() + { + string fullPath = Path.Combine(Application.dataPath, "..", ConfigPath); + if (!File.Exists(fullPath)) + { + Debug.LogError($"[GuideConfigEditor] 文件不存在: {fullPath}"); + return; + } + + string json = File.ReadAllText(fullPath); + _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); + string json = SerializeToJson(_entries); + File.WriteAllText(fullPath, json); + _isDirty = false; + AssetDatabase.Refresh(); + Debug.Log($"[GuideConfigEditor] 已保存 {_entries.Count} 条引导配置到 {ConfigPath}"); + } + + #endregion + + #region GUI + + private void OnGUI() + { + InitStyles(); + RebuildPresets(); + DrawToolbar(); + + EditorGUILayout.BeginHorizontal(); + { + // 左侧列表 + EditorGUILayout.BeginVertical(GUILayout.Width(320)); + DrawList(); + EditorGUILayout.EndVertical(); + + // 分割线 + GUILayout.Box("", GUILayout.Width(2), GUILayout.ExpandHeight(true)); + + // 右侧详情 + EditorGUILayout.BeginVertical(); + DrawDetail(); + EditorGUILayout.EndVertical(); + } + EditorGUILayout.EndHorizontal(); + } + + private void DrawToolbar() + { + EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); + { + if (GUILayout.Button("重新加载", EditorStyles.toolbarButton, GUILayout.Width(60))) + { + if (!_isDirty || EditorUtility.DisplayDialog("确认", "有未保存的修改,确定重新加载?", "确定", "取消")) + LoadConfig(); + } + + GUI.backgroundColor = _isDirty ? Color.yellow : Color.white; + if (GUILayout.Button(_isDirty ? "保存 *" : "保存", EditorStyles.toolbarButton, GUILayout.Width(60))) + { + SaveConfig(); + } + GUI.backgroundColor = Color.white; + + GUILayout.Space(10); + + if (GUILayout.Button("+ 新建", EditorStyles.toolbarButton, GUILayout.Width(50))) + { + AddNewEntry(); + } + + if (GUILayout.Button("复制当前", EditorStyles.toolbarButton, GUILayout.Width(60))) + { + DuplicateSelected(); + } + + if (GUILayout.Button("删除当前", EditorStyles.toolbarButton, GUILayout.Width(60))) + { + DeleteSelected(); + } + + GUILayout.FlexibleSpace(); + + // 排序 + GUILayout.Label("排序:", EditorStyles.toolbarButton, GUILayout.Width(30)); + var newSort = (SortMode)EditorGUILayout.EnumPopup(_sortMode, EditorStyles.toolbarPopup, GUILayout.Width(70)); + if (newSort != _sortMode) + { + _sortMode = newSort; + } + + GUILayout.Space(5); + GUILayout.Label("全局搜索:", EditorStyles.toolbarButton, GUILayout.Width(50)); + _searchText = EditorGUILayout.TextField(_searchText, EditorStyles.toolbarSearchField, GUILayout.Width(120)); + + GUILayout.Label("ID:", EditorStyles.toolbarButton, GUILayout.Width(20)); + _searchId = EditorGUILayout.TextField(_searchId, EditorStyles.toolbarSearchField, GUILayout.Width(60)); + } + EditorGUILayout.EndHorizontal(); + } + + private void DrawList() + { + _listScroll = EditorGUILayout.BeginScrollView(_listScroll); + + var filtered = GetFilteredAndSorted(); + + for (int i = 0; i < filtered.Count; i++) + { + var entry = filtered[i]; + int realIndex = _entries.IndexOf(entry); + bool isSelected = realIndex == _selectedIndex; + + Rect rect = EditorGUILayout.BeginHorizontal(isSelected ? _selectedStyle : GUIStyle.none, GUILayout.Height(22)); + { + string label = $"[{entry.Id}] {entry.Name}"; + if (entry.Priority > 0) + label += $" P{entry.Priority}"; + + GUILayout.Label(label, _entryStyle); + } + EditorGUILayout.EndHorizontal(); + + if (Event.current.type == EventType.MouseDown && rect.Contains(Event.current.mousePosition)) + { + _selectedIndex = realIndex; + GUI.FocusControl(null); + Repaint(); + } + } + + EditorGUILayout.EndScrollView(); + + // 底部统计 + EditorGUILayout.LabelField($"共 {_entries.Count} 条 | 显示 {filtered.Count} 条", EditorStyles.centeredGreyMiniLabel); + } + + private void DrawDetail() + { + if (_selectedIndex < 0 || _selectedIndex >= _entries.Count) + { + EditorGUILayout.HelpBox("← 请在左侧列表中选择一条引导配置", MessageType.Info); + return; + } + + var entry = _entries[_selectedIndex]; + _detailScroll = EditorGUILayout.BeginScrollView(_detailScroll); + + EditorGUILayout.LabelField("基本信息", _headerStyle); + EditorGUI.indentLevel++; + DrawField("Id", ref entry.Id); + DrawField("Name (名称)", ref entry.Name); + DrawField("Priority (优先级)", ref entry.Priority); + EditorGUI.indentLevel--; + + EditorGUILayout.Space(8); + + // Conditions + _showConditions = EditorGUILayout.Foldout(_showConditions, "Conditions (触发条件)", true); + if (_showConditions) + { + EditorGUI.indentLevel++; + DrawStringListWithPresets("Satisfy (满足条件)", ref entry.Conditions.Satisfy, ref _showSatisfy, _satisfyPresets); + DrawField("CheckAgain", ref entry.Conditions.CheckAgain); + 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--; + } + + EditorGUILayout.Space(8); + + // ShowUI + _showShowUI = EditorGUILayout.Foldout(_showShowUI, "ShowUI (显示配置)", true); + if (_showShowUI) + { + EditorGUI.indentLevel++; + DrawField("LangText (多语言Key)", ref entry.ShowUI.LangText); + 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); + 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--; + } + + EditorGUILayout.EndScrollView(); + } + + #endregion + + #region 辅助绘制 + + private void DrawField(string label, ref int value) + { + int newVal = EditorGUILayout.IntField(label, value); + if (newVal != value) { value = newVal; _isDirty = true; } + } + + private void DrawField(string label, ref float value) + { + float newVal = EditorGUILayout.FloatField(label, value); + if (!Mathf.Approximately(newVal, value)) { value = newVal; _isDirty = true; } + } + + private void DrawField(string label, ref string value) + { + string newVal = EditorGUILayout.TextField(label, value ?? ""); + 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(); + foldout = EditorGUILayout.Foldout(foldout, $"{label} ({list.Count})", true); + if (GUILayout.Button("+", GUILayout.Width(20))) + { + list.Add(""); + _isDirty = true; + } + 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 (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--; + } + + #endregion + + #region 操作 + + private List GetFilteredAndSorted() + { + IEnumerable result = _entries; + + // 全字段搜索(Name、Conditions、ShowUI 所有文本字段) + if (!string.IsNullOrEmpty(_searchText)) + { + string lower = _searchText.ToLower(); + result = result.Where(e => EntryMatchesSearch(e, lower)); + } + + // 按ID搜索 + if (!string.IsNullOrEmpty(_searchId)) + { + result = result.Where(e => e.Id.ToString().Contains(_searchId)); + } + + // 排序 + switch (_sortMode) + { + case SortMode.Id: + result = result.OrderBy(e => e.Id); + break; + case SortMode.Priority: + result = result.OrderByDescending(e => e.Priority).ThenBy(e => e.Id); + break; + case SortMode.Name: + result = result.OrderBy(e => e.Name ?? ""); + break; + } + + 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; + var newEntry = new GuideEntry + { + Id = maxId + 1, + Name = "新引导步骤", + }; + _entries.Add(newEntry); + _selectedIndex = _entries.Count - 1; + _isDirty = true; + } + + private void DuplicateSelected() + { + if (_selectedIndex < 0 || _selectedIndex >= _entries.Count) return; + var src = _entries[_selectedIndex]; + string json = JsonUtility.ToJson(src); + var copy = JsonUtility.FromJson(json); + // 深拷贝列表字段 (JsonUtility 会处理) + copy.Id = _entries.Max(e => e.Id) + 1; + copy.Name = src.Name + "_副本"; + _entries.Insert(_selectedIndex + 1, copy); + _selectedIndex = _selectedIndex + 1; + _isDirty = true; + } + + private void DeleteSelected() + { + if (_selectedIndex < 0 || _selectedIndex >= _entries.Count) return; + var entry = _entries[_selectedIndex]; + if (EditorUtility.DisplayDialog("确认删除", $"确定删除引导 [{entry.Id}] {entry.Name}?", "删除", "取消")) + { + _entries.RemoveAt(_selectedIndex); + _selectedIndex = Mathf.Min(_selectedIndex, _entries.Count - 1); + _isDirty = true; + } + } + + #endregion + + #region JSON 解析/序列化 (手写,兼容原文件格式) + + /// + /// 使用 Unity 内置 JsonUtility 无法直接解析顶层数组,这里包装一下。 + /// 原文件存在尾逗号等不规范 JSON,需要预处理。 + /// + private List ParseJsonArray(string json) + { + // 预处理:移除尾逗号 (trailing commas before ] or }) + json = Regex.Replace(json, @",(\s*[\]\}])", "$1"); + + // 包装成对象让 JsonUtility 能解析 + string wrapped = "{\"items\":" + json + "}"; + var wrapper = JsonUtility.FromJson(wrapped); + return wrapper != null && wrapper.items != null ? wrapper.items : new List(); + } + + [Serializable] + private class GuideEntryArrayWrapper + { + public List items; + } + + /// + /// 序列化回原始格式(带缩进的 JSON 数组,保持和原文件一致的风格) + /// + private string SerializeToJson(List entries) + { + var sb = new System.Text.StringBuilder(); + sb.AppendLine("["); + + for (int i = 0; i < entries.Count; i++) + { + var e = entries[i]; + sb.AppendLine(" {"); + sb.AppendLine($" \"Id\": {e.Id},"); + sb.AppendLine($" \"Name\":\"{EscapeJson(e.Name)}\","); + sb.AppendLine($" \"Priority\":{e.Priority},"); + + // Conditions + sb.AppendLine(" \"Conditions\":{"); + sb.AppendLine(" \"Satisfy\":["); + for (int s = 0; s < e.Conditions.Satisfy.Count; s++) + { + string comma = s < e.Conditions.Satisfy.Count - 1 ? "," : ""; + sb.AppendLine($" \"{EscapeJson(e.Conditions.Satisfy[s])}\"{comma}"); + } + sb.AppendLine(" ],"); + sb.AppendLine($" \"CheckAgain\":{e.Conditions.CheckAgain},"); + sb.AppendLine(" \"Trigger\":["); + for (int t = 0; t < e.Conditions.Trigger.Count; t++) + { + string comma = t < e.Conditions.Trigger.Count - 1 ? "," : ""; + sb.AppendLine($" \"{EscapeJson(e.Conditions.Trigger[t])}\"{comma}"); + } + sb.AppendLine(" ],"); + sb.AppendLine($" \"End\":\"{EscapeJson(e.Conditions.End)}\","); + sb.AppendLine($" \"Delay\":{FormatFloat(e.Conditions.Delay)},"); + sb.AppendLine($" \"Limit\":{e.Conditions.Limit}"); + sb.AppendLine(" },"); + + // ShowUI + sb.AppendLine(" \"ShowUI\": {"); + sb.AppendLine($" \"LangText\":\"{EscapeJson(e.ShowUI.LangText)}\","); + sb.AppendLine($" \"RoleExpression\":\"{EscapeJson(e.ShowUI.RoleExpression)}\","); + sb.AppendLine($" \"DialogPos\":\"{EscapeJson(e.ShowUI.DialogPos)}\","); + sb.AppendLine($" \"OkBtn\":{e.ShowUI.OkBtn},"); + sb.AppendLine($" \"ClickSg\":{e.ShowUI.ClickSg},"); + sb.AppendLine($" \"ClickPos\":\"{EscapeJson(e.ShowUI.ClickPos)}\","); + sb.AppendLine($" \"FocusPos\":\"{EscapeJson(e.ShowUI.FocusPos)}\","); + sb.AppendLine($" \"Phone\":{e.ShowUI.Phone},"); + sb.AppendLine($" \"ClickToSkip\":{e.ShowUI.ClickToSkip}"); + sb.AppendLine(" }"); + + string entryComma = i < entries.Count - 1 ? "," : ""; + sb.AppendLine($" }}{entryComma}"); + } + + sb.Append("]"); + return sb.ToString(); + } + + private string FormatFloat(float val) + { + // 如果是整数值,输出不带小数点的格式(和原文件保持一致) + if (Mathf.Approximately(val, Mathf.Round(val))) + return ((int)val).ToString(); + return val.ToString("G"); + } + + private string EscapeJson(string s) + { + if (string.IsNullOrEmpty(s)) return ""; + return s.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", "\\n").Replace("\r", "\\r").Replace("\t", "\\t"); + } + + #endregion +} diff --git a/Scripts/Editor/Design_Tools/GuideConfigEditor.cs.meta b/Scripts/Editor/Design_Tools/GuideConfigEditor.cs.meta new file mode 100644 index 0000000..96827d6 --- /dev/null +++ b/Scripts/Editor/Design_Tools/GuideConfigEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 22a0284075eddb94b9d56c8e0c19b1e9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: