diff --git a/Scripts/Editor/Design_Tools/GuideConfigEditor.cs b/Scripts/Editor/Design_Tools/GuideConfigEditor.cs
new file mode 100644
index 0000000..39e03f7
--- /dev/null
+++ b/Scripts/Editor/Design_Tools/GuideConfigEditor.cs
@@ -0,0 +1,544 @@
+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 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;
+ Debug.Log($"[GuideConfigEditor] 已加载 {_entries.Count} 条引导配置");
+ }
+
+ 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();
+ 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(30));
+ _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++;
+ DrawStringList("Satisfy (满足条件)", ref entry.Conditions.Satisfy, ref _showSatisfy);
+ DrawField("CheckAgain", ref entry.Conditions.CheckAgain);
+ DrawStringList("Trigger (触发器)", ref entry.Conditions.Trigger, ref _showTrigger);
+ DrawField("End (结束条件)", ref entry.Conditions.End);
+ 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);
+ DrawField("RoleExpression (角色表情)", ref entry.ShowUI.RoleExpression);
+ DrawField("DialogPos (对话位置)", ref entry.ShowUI.DialogPos);
+ DrawField("OkBtn (确认按钮)", ref entry.ShowUI.OkBtn);
+ DrawField("ClickSg (点击信号)", ref entry.ShowUI.ClickSg);
+ DrawField("ClickPos (点击位置)", ref entry.ShowUI.ClickPos);
+ DrawField("FocusPos (聚焦位置)", ref entry.ShowUI.FocusPos);
+ 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 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;
+
+ // 按名称搜索
+ if (!string.IsNullOrEmpty(_searchText))
+ {
+ string lower = _searchText.ToLower();
+ result = result.Where(e => (e.Name ?? "").ToLower().Contains(lower));
+ }
+
+ // 按ID搜索
+ if (!string.IsNullOrEmpty(_searchId) && int.TryParse(_searchId, out int searchIdVal))
+ {
+ 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();
+ }
+
+ 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: