引导配置可视化

This commit is contained in:
zhang hongbo 2026-05-29 18:11:28 +08:00
parent d6bc6f1fb6
commit 8701c43bc5

View File

@ -71,6 +71,16 @@ public class GuideConfigEditor : EditorWindow
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;
@ -116,9 +126,61 @@ public class GuideConfigEditor : EditorWindow
_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);
@ -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; }
}
/// <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();
@ -373,15 +623,15 @@ public class GuideConfigEditor : EditorWindow
{
IEnumerable<GuideEntry> 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();
}
/// <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;