- QuestSO: Add ValidateBranchCycles() DFS detection for branches[].nextQuest loop - QuestSO: Mark three legacy prerequisite fields with v2.0 removal warning in Tooltip - IQuestManager: Add QuestLockReason enum + QuestLockInfo struct (strongly-typed lock info) - IQuestManager: Add GetQuestLockInfo() method to interface; GetQuestLockReason() now delegates to it - IQuestEventSource: Add OnQuestStateChanged(questId, oldState, newState) unified event - QuestManager: Implement GetQuestLockInfo(); fire OnQuestStateChanged on all state transitions - DialogueManager: Add one-frame yield in HandleChoices before ShowChoices (skip-debounce fix) - DialogueManager: Increment _playbackId in ForceEnd() to invalidate residual choice callbacks - DialogueSequenceSO: Add UNITY_EDITOR debug log in TryGetActiveVariant on variant match - WorldStateRegistry: Add OnBatchStateChanged event + BatchMark() batch-write API - DialogueModule: List badge shows warning indicator for unconditional-shadowing variants - DialogueModule: BuildVariantsCard shows logic mode (AND/OR) alongside flag conditions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
250 lines
9.1 KiB
C#
250 lines
9.1 KiB
C#
using System;
|
||
using UnityEditor;
|
||
using UnityEditor.UIElements;
|
||
using UnityEngine;
|
||
using UnityEngine.UIElements;
|
||
using BaseGames.Skills;
|
||
|
||
namespace BaseGames.Editor.Modules
|
||
{
|
||
/// <summary>
|
||
/// DataHub 技能模块 —— 管理 FormSkillSO 资产。
|
||
/// </summary>
|
||
public class SkillModule : IDataModule, IDataModuleOrdered
|
||
{
|
||
private const string Folder = "Assets/_Game/Data/Skills";
|
||
private const string Prefix = "SKL_";
|
||
|
||
public string ModuleId => "skill";
|
||
public string DisplayName => "技能";
|
||
public string IconName => null;
|
||
public int DisplayOrder => 20;
|
||
|
||
private SoListPane<FormSkillSO> _listPane;
|
||
private DetailHeader _header;
|
||
private FormSkillSO _selected;
|
||
|
||
public void Initialize()
|
||
{
|
||
_listPane = new SoListPane<FormSkillSO>(
|
||
Folder, Prefix,
|
||
s => s.resourceType.ToString());
|
||
}
|
||
|
||
public void BuildListPane(VisualElement container, Action<UnityEngine.Object> onSelected)
|
||
{
|
||
_listPane.SelectionChanged = sel =>
|
||
{
|
||
_selected = sel;
|
||
onSelected?.Invoke(sel);
|
||
};
|
||
container.Add(_listPane);
|
||
_listPane.Refresh();
|
||
}
|
||
|
||
public void BuildDetailPane(VisualElement container, UnityEngine.Object selected)
|
||
{
|
||
_selected = selected as FormSkillSO;
|
||
|
||
_header = new DetailHeader();
|
||
_header.SetAsset(_selected);
|
||
_header.RenameRequested += OnRenameRequested;
|
||
container.Add(_header);
|
||
|
||
if (_selected == null) return;
|
||
|
||
// Stats Card
|
||
var card = BuildStatsCard(_selected);
|
||
container.Add(card);
|
||
|
||
// 操作按钮
|
||
container.Add(BuildActionBar(_selected));
|
||
container.Add(MakeDivider());
|
||
|
||
// Inspector
|
||
var insp = new InspectorElement(_selected); container.Add(insp);
|
||
}
|
||
|
||
public void OnActivated() => _listPane?.Refresh();
|
||
|
||
// ── 内部 ─────────────────────────────────────────────────────────────
|
||
|
||
private void OnRenameRequested(string newName)
|
||
{
|
||
if (_selected == null) return;
|
||
var (ok, err) = AssetOperations.Rename(_selected, newName);
|
||
if (!ok) EditorUtility.DisplayDialog("重命名失败", err, "确定");
|
||
else { _header.SetAsset(_selected); _listPane.Invalidate(); }
|
||
}
|
||
|
||
private static VisualElement BuildStatsCard(FormSkillSO s)
|
||
{
|
||
var card = MakeCard();
|
||
AddChip(card, "效果类型", s.effectType.ToString());
|
||
AddChip(card, "资源类型", s.resourceType.ToString());
|
||
AddChip(card, "冷却", $"{s.cooldown:F1}s");
|
||
AddChip(card, "消耗", s.baseCost.ToString());
|
||
if (!string.IsNullOrEmpty(s.skillId))
|
||
AddChip(card, "ID", s.skillId);
|
||
return card;
|
||
}
|
||
|
||
private VisualElement BuildActionBar(FormSkillSO s)
|
||
{
|
||
var bar = MakeActionBar();
|
||
|
||
new Button(() => { EditorGUIUtility.PingObject(s); Selection.activeObject = s; })
|
||
{ text = "定位" }.AlsoAddTo(bar);
|
||
|
||
new Button(() => { var c = AssetOperations.Clone(s, Folder); if (c != null) _listPane.Refresh(c); })
|
||
{ text = "克隆..." }.AlsoAddTo(bar);
|
||
|
||
var del = new Button(() => { if (AssetOperations.Delete(s)) _listPane.Refresh(null); }) { text = "删除" };
|
||
ApplyDeleteStyle(del);
|
||
del.AlsoAddTo(bar);
|
||
|
||
return bar;
|
||
}
|
||
|
||
// ── 共享构建辅助 ─────────────────────────────────────────────────────
|
||
|
||
internal static VisualElement MakeCard()
|
||
{
|
||
var c = new VisualElement();
|
||
c.style.flexDirection = FlexDirection.Row;
|
||
c.style.flexWrap = Wrap.Wrap;
|
||
c.style.paddingLeft = 12;
|
||
c.style.paddingRight = 12;
|
||
c.style.paddingTop = 8;
|
||
c.style.paddingBottom = 8;
|
||
c.style.marginBottom = 4;
|
||
c.style.backgroundColor = new StyleColor(new Color(0.5f, 0.5f, 0.5f, 0.08f));
|
||
c.style.borderBottomWidth = 1;
|
||
c.style.borderBottomColor = new StyleColor(new Color(0.5f, 0.5f, 0.5f, 0.2f));
|
||
return c;
|
||
}
|
||
|
||
internal static void AddChip(VisualElement parent, string label, string value)
|
||
{
|
||
var chip = new VisualElement();
|
||
chip.style.flexDirection = FlexDirection.Row;
|
||
chip.style.alignItems = Align.Center;
|
||
chip.style.marginRight = 14;
|
||
chip.style.marginBottom = 2;
|
||
|
||
var l = new Label(label + ":");
|
||
l.style.opacity = 0.6f;
|
||
l.style.fontSize = 11;
|
||
l.style.marginRight = 3;
|
||
chip.Add(l);
|
||
|
||
var v = new Label(value);
|
||
v.style.fontSize = 11;
|
||
v.style.unityFontStyleAndWeight = UnityEngine.FontStyle.Bold;
|
||
chip.Add(v);
|
||
parent.Add(chip);
|
||
}
|
||
|
||
internal static VisualElement MakeActionBar()
|
||
{
|
||
var b = new VisualElement();
|
||
b.style.flexDirection = FlexDirection.Row;
|
||
b.style.paddingLeft = 12;
|
||
b.style.paddingRight = 12;
|
||
b.style.paddingTop = 6;
|
||
b.style.paddingBottom = 6;
|
||
b.style.flexWrap = Wrap.Wrap;
|
||
return b;
|
||
}
|
||
|
||
internal static VisualElement MakeDivider()
|
||
{
|
||
var d = new VisualElement();
|
||
d.style.height = 1;
|
||
d.style.backgroundColor = new StyleColor(new Color(0.5f, 0.5f, 0.5f, 0.2f));
|
||
return d;
|
||
}
|
||
|
||
/// <summary>将删除按钮染成红色边框,统一各模块样式。</summary>
|
||
internal static void ApplyDeleteStyle(Button btn)
|
||
{
|
||
var red = new StyleColor(new Color(0.8f, 0.3f, 0.3f, 0.6f));
|
||
btn.style.borderLeftColor = red;
|
||
btn.style.borderRightColor = red;
|
||
btn.style.borderTopColor = red;
|
||
btn.style.borderBottomColor = red;
|
||
btn.style.borderLeftWidth = 1;
|
||
btn.style.borderRightWidth = 1;
|
||
btn.style.borderTopWidth = 1;
|
||
btn.style.borderBottomWidth = 1;
|
||
btn.style.marginLeft = 8;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 为任意 ScriptableObject 模块生成标准 ActionBar(新建 / 定位 / 克隆 / 删除)。
|
||
/// 各模块可在返回后向 bar 追加额外按钮。
|
||
/// </summary>
|
||
/// <param name="asset">当前选中资产。</param>
|
||
/// <param name="folder">资产所在文件夹(用于新建 / 克隆)。</param>
|
||
/// <param name="prefix">新建资产的文件名前缀。</param>
|
||
/// <param name="onCreated">新建完成回调(传入新资产)。</param>
|
||
/// <param name="onCloned">克隆完成回调(传入克隆资产)。</param>
|
||
/// <param name="onDeleted">删除完成回调。</param>
|
||
internal static VisualElement BuildStandardActionBar<T>(
|
||
T asset,
|
||
string folder,
|
||
string prefix,
|
||
Action<T> onCreated,
|
||
Action<T> onCloned,
|
||
Action onDeleted,
|
||
Action<Action<T>> wizardCreate = null) where T : UnityEngine.ScriptableObject
|
||
{
|
||
var bar = MakeActionBar();
|
||
|
||
new Button(() =>
|
||
{
|
||
if (wizardCreate != null)
|
||
{
|
||
wizardCreate(c => onCreated?.Invoke(c));
|
||
}
|
||
else
|
||
{
|
||
var c = AssetOperations.Create<T>(folder, prefix + "New");
|
||
if (c != null) onCreated?.Invoke(c);
|
||
}
|
||
}) { text = "新建" }.AlsoAddTo(bar);
|
||
|
||
new Button(() =>
|
||
{
|
||
EditorGUIUtility.PingObject(asset);
|
||
Selection.activeObject = asset;
|
||
}) { text = "定位" }.AlsoAddTo(bar);
|
||
|
||
new Button(() =>
|
||
{
|
||
var c = AssetOperations.Clone(asset, folder);
|
||
if (c != null) onCloned?.Invoke(c);
|
||
}) { text = "克隆..." }.AlsoAddTo(bar);
|
||
|
||
var del = new Button(() =>
|
||
{
|
||
if (AssetOperations.Delete(asset)) onDeleted?.Invoke();
|
||
}) { text = "删除" };
|
||
ApplyDeleteStyle(del);
|
||
del.AlsoAddTo(bar);
|
||
|
||
return bar;
|
||
}
|
||
}
|
||
|
||
// ── Button 扩展(模块内共用)─────────────────────────────────────────────
|
||
internal static class ButtonExtensions
|
||
{
|
||
public static Button AlsoAddTo(this Button btn, VisualElement parent)
|
||
{
|
||
parent.Add(btn);
|
||
return btn;
|
||
}
|
||
}
|
||
}
|