Design_SubModule/Scripts/Editor/Design_Tools/GuideConfigEditor.cs
2026-05-29 18:11:28 +08:00

841 lines
30 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
/// <summary>
/// GuideConfig.txt 可视化编辑器
/// 菜单路径: 程序工具/策划工具/引导配置编辑器
/// </summary>
public class GuideConfigEditor : EditorWindow
{
#region
[Serializable]
public class GuideConditions
{
public List<string> Satisfy = new List<string>();
public int CheckAgain;
public List<string> Trigger = new List<string>();
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<GuideEntry> _entries = new List<GuideEntry>();
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<string> _satisfyPresets = new List<string>();
private List<string> _triggerPresets = new List<string>();
private List<string> _endPresets = new List<string>();
private List<string> _roleExpressionPresets = new List<string>();
private List<string> _dialogPosPresets = new List<string>();
private List<string> _clickPosPresets = new List<string>();
private List<string> _focusPosPresets = new List<string>();
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<GuideConfigEditor>("引导配置编辑器");
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} 条引导配置");
}
/// <summary>
/// 从所有现有条目中收集去重的预设值,用于快速选择
/// </summary>
private void RebuildPresets()
{
if (!_presetsDirty) return;
_presetsDirty = false;
var satisfySet = new HashSet<string>();
var triggerSet = new HashSet<string>();
var endSet = new HashSet<string>();
var roleExprSet = new HashSet<string>();
var dialogPosSet = new HashSet<string>();
var clickPosSet = new HashSet<string>();
var focusPosSet = new HashSet<string>();
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 = $"<color=#888888>[{entry.Id}]</color> {entry.Name}";
if (entry.Priority > 0)
label += $" <color=#cc8800>P{entry.Priority}</color>";
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; }
}
/// <summary>
/// 带预设下拉的文本字段:点击▼按钮弹出已有值列表快速选择
/// </summary>
private void DrawFieldWithPresets(string label, ref string value, List<string> 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();
}
/// <summary>
/// 预设回调:根据字段标签将选中值写回对应字段
/// </summary>
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();
}
/// <summary>
/// 带预设快速选择的字符串列表Satisfy / Trigger
/// 点击 "+" 新增空行,点击 "▼" 从已有值中选择并新增
/// </summary>
private void DrawStringListWithPresets(string label, ref List<string> list, ref bool foldout, List<string> 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--;
}
/// <summary>
/// 显示预设值的下拉菜单GenericMenu按前缀分组
/// </summary>
private void ShowPresetMenu(List<string> presets, Action<string> onSelect)
{
var menu = new GenericMenu();
// 按前缀分组显示
var groups = new Dictionary<string, List<string>>();
foreach (var p in presets)
{
string prefix = ExtractPrefix(p);
if (!groups.ContainsKey(prefix))
groups[prefix] = new List<string>();
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();
}
/// <summary>
/// 提取条件字符串的前缀(如 "LevelEqual_1" → "LevelEqual""UIPanel_MainHomeUI_1" → "UIPanel"
/// </summary>
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<string> 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<GuideEntry> GetFilteredAndSorted()
{
IEnumerable<GuideEntry> 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();
}
/// <summary>
/// 全字段匹配:搜索 Name、ConditionsSatisfy/Trigger/End、ShowUI 所有文本字段
/// </summary>
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<GuideEntry>(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 / ()
/// <summary>
/// 使用 Unity 内置 JsonUtility 无法直接解析顶层数组,这里包装一下。
/// 原文件存在尾逗号等不规范 JSON需要预处理。
/// </summary>
private List<GuideEntry> ParseJsonArray(string json)
{
// 预处理:移除尾逗号 (trailing commas before ] or })
json = Regex.Replace(json, @",(\s*[\]\}])", "$1");
// 包装成对象让 JsonUtility 能解析
string wrapped = "{\"items\":" + json + "}";
var wrapper = JsonUtility.FromJson<GuideEntryArrayWrapper>(wrapped);
return wrapper != null && wrapper.items != null ? wrapper.items : new List<GuideEntry>();
}
[Serializable]
private class GuideEntryArrayWrapper
{
public List<GuideEntry> items;
}
/// <summary>
/// 序列化回原始格式(带缩进的 JSON 数组,保持和原文件一致的风格)
/// </summary>
private string SerializeToJson(List<GuideEntry> 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
}