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 }