refactor(enemy)!: 删除旧 Boss 轨 9 个脚本

BossSkillSO / AttackPatternSO / SkillSequenceSO / BossSkillExecutor /
BossSkillTypes / WeakPointSystem / TelegraphSystem / BossSkillModule /
BossSkillSequenceWindow,连同 Patterns 独立程序集(BaseGames.Enemies.Boss.Patterns.asmdef,
已核实无任何 asmdef 按名或按 GUID 引用它)整包移除。

删前先清两处引用:
- ChaoFengKnockdownCounter 击落打断改走 _boss.Abilities.InterruptAll(ExternalRequest)
- EditorScaffoldUtils 命名前缀表删掉两条指向已删类型的死条目(还与 AssetFolderSpec 的 ABL_ 冲突)

判据「有更优替代才删」逐项对应:
- 编排 → EnemyAbilitySO + EnemyAttackSO(归一化时机 / 槽位 HitBox)
- 阶段门 → BossPhaseAbilityGate
- 选招 → EnemyAttackSelector + WeightedRandomAntiRepeat
- 可弹反 → DamageSourceSO.Flags(CanBeParried),HurtBox 已在判定
- 弱点倍率 → HurtBox 自带倍率(原实现的 GetDamageMultiplier 零调用者)
- 预警 → clip 姿态 + 动画事件(TriggerFeedback / PlaySFX 已补接)
- 竞技场联动 → EnemySpawnerOnEvent
- 霸体窗口 → EnemyAttackSO.hasPoiseWindow 四字段

能力资产的编辑器总览视图随 BossSkillModule 一并消失,
后续补 EnemyAbilityModule(优先级低于 AiDefinitionValidator)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-30 16:39:57 +08:00
co-authored by Claude Opus 5
parent ef1998db2b
commit d3013b1053
24 changed files with 9 additions and 1435 deletions
@@ -1,306 +0,0 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using BaseGames.Boss;
namespace BaseGames.Editor
{
/// <summary>
/// Boss 技能序列甘特图可视化窗口(架构 23_BossSkillModule §12)。
/// 菜单:BaseGames/Tools/Boss Skill Sequence Viewer
///
/// 功能:
/// - 拖放 BossSkillSO 或 SkillSequenceSO 资产加载
/// - 甘特图:Windup(黄色)→ Active(红色)→ Recovery(灰色)各阶段时序条
/// - VulnerabilityWindow 绿色覆盖层(TriggerDelay 偏移 + Duration 宽度)
/// - DurationNormalized &lt; 0.1 时阶段条变红警告
/// - 点击阶段条高亮对应 AttackPatternSOEditorGUIUtility.PingObject
/// </summary>
public class BossSkillSequenceWindow : EditorWindow
{
// ── State ──────────────────────────────────────────────────────────
private BossSkillSO _loadedSkill;
private SkillSequenceSO _loadedSequence;
private Vector2 _scrollPos;
// ── Layout ─────────────────────────────────────────────────────────
private const float HeaderH = 24f;
private const float RowH = 28f;
private const float LabelW = 180f;
private const float MinBarWidth = 6f;
// 时间轴宽度随窗口宽度动态调整,最小 300px
private float TimelineW => Mathf.Max(300f, position.width - LabelW - 30f);
// ── Colors ─────────────────────────────────────────────────────────
private static readonly Color ColWindup = new Color(0.95f, 0.80f, 0.10f, 0.85f);
private static readonly Color ColActive = new Color(0.90f, 0.20f, 0.15f, 0.85f);
private static readonly Color ColRecovery = new Color(0.50f, 0.50f, 0.55f, 0.70f);
private static readonly Color ColVuln = new Color(0.10f, 0.90f, 0.30f, 0.45f);
private static readonly Color ColDelay = new Color(0.25f, 0.25f, 0.30f, 0.50f);
private static readonly Color ColWarn = new Color(0.95f, 0.10f, 0.10f, 0.85f);
[MenuItem("BaseGames/Data/Boss Skill Sequence", priority = 110)]
public static void OpenWindow()
{
var win = GetWindow<BossSkillSequenceWindow>("Boss Skill Sequence");
win.minSize = new Vector2(900, 400);
win.Show();
}
// ── GUI ────────────────────────────────────────────────────────────
private void OnGUI()
{
DrawToolbar();
if (_loadedSkill == null && _loadedSequence == null)
{
EditorGUILayout.HelpBox(
"将 BossSkillSO 或 SkillSequenceSO 资产拖放到此处,或使用上方字段加载。",
MessageType.Info);
HandleDragDrop();
return;
}
_scrollPos = EditorGUILayout.BeginScrollView(_scrollPos);
if (_loadedSkill != null)
DrawSkillTimeline(_loadedSkill);
else if (_loadedSequence != null)
DrawSequenceTimeline(_loadedSequence);
EditorGUILayout.EndScrollView();
}
// ── Toolbar ───────────────────────────────────────────────────────
private void DrawToolbar()
{
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
EditorGUILayout.LabelField("技能:", GUILayout.Width(36));
var newSkill = (BossSkillSO)EditorGUILayout.ObjectField(
_loadedSkill, typeof(BossSkillSO), false, GUILayout.Width(200));
if (newSkill != _loadedSkill)
{
_loadedSkill = newSkill;
_loadedSequence = null;
}
GUILayout.Space(12);
EditorGUILayout.LabelField("序列:", GUILayout.Width(36));
var newSeq = (SkillSequenceSO)EditorGUILayout.ObjectField(
_loadedSequence, typeof(SkillSequenceSO), false, GUILayout.Width(200));
if (newSeq != _loadedSequence)
{
_loadedSequence = newSeq;
_loadedSkill = null;
}
GUILayout.FlexibleSpace();
if (GUILayout.Button("清除", EditorStyles.toolbarButton, GUILayout.Width(50)))
{
_loadedSkill = null;
_loadedSequence = null;
}
EditorGUILayout.EndHorizontal();
}
// ── BossSkillSO 时间轴 ────────────────────────────────────────────
private void DrawSkillTimeline(BossSkillSO skill)
{
EditorGUILayout.LabelField($"技能:{skill.displayName} [{skill.skillId}]",
EditorStyles.boldLabel);
EditorGUILayout.Space(4);
if (skill.attackPatterns == null || skill.attackPatterns.Length == 0)
{
EditorGUILayout.HelpBox("此技能没有 AttackPattern。", MessageType.Warning);
return;
}
// 计算总时长
float totalDuration = 0f;
foreach (var p in skill.attackPatterns)
if (p != null) totalDuration += p.WindupDuration + p.ActiveDuration + p.RecoveryDuration;
if (totalDuration <= 0f) totalDuration = 1f;
DrawTimelineHeader(totalDuration);
float cursor = 0f;
for (int i = 0; i < skill.attackPatterns.Length; i++)
{
var pattern = skill.attackPatterns[i];
if (pattern == null) continue;
DrawPatternRow($"[{i}] {pattern.name}", pattern, ref cursor, totalDuration);
}
// 绘制 VulnerabilityWindows
if (skill.vulnerabilityWindows != null && skill.vulnerabilityWindows.Length > 0)
{
EditorGUILayout.Space(4);
EditorGUILayout.LabelField("弱点窗口(Vulnerability Windows", EditorStyles.miniBoldLabel);
foreach (var vw in skill.vulnerabilityWindows)
DrawVulnWindowRow(vw, totalDuration);
}
}
// ── SkillSequenceSO 时间轴 ────────────────────────────────────────
private void DrawSequenceTimeline(SkillSequenceSO sequence)
{
EditorGUILayout.LabelField($"序列:{sequence.name}", EditorStyles.boldLabel);
EditorGUILayout.Space(4);
if (sequence.steps == null || sequence.steps.Length == 0)
{
EditorGUILayout.HelpBox("此序列没有步骤。", MessageType.Warning);
return;
}
// 计算总时长
float totalDuration = 0f;
foreach (var step in sequence.steps)
{
totalDuration += step.delayBeforeStep;
if (step.pattern != null)
totalDuration += step.pattern.WindupDuration + step.pattern.ActiveDuration + step.pattern.RecoveryDuration;
}
if (totalDuration <= 0f) totalDuration = 1f;
DrawTimelineHeader(totalDuration);
float cursor = 0f;
for (int i = 0; i < sequence.steps.Length; i++)
{
var step = sequence.steps[i];
// 延迟条
if (step.delayBeforeStep > 0f)
{
DrawBar($"延迟 {step.delayBeforeStep:F2}s", cursor, step.delayBeforeStep,
totalDuration, ColDelay, null);
cursor += step.delayBeforeStep;
}
if (step.pattern != null)
DrawPatternRow($"[{i}] {step.pattern.name}", step.pattern, ref cursor, totalDuration);
}
}
// ── 共用绘制方法 ──────────────────────────────────────────────────
private void DrawTimelineHeader(float totalDuration)
{
Rect headerRect = EditorGUILayout.GetControlRect(false, HeaderH);
headerRect.x += LabelW;
headerRect.width -= LabelW;
EditorGUI.DrawRect(headerRect, new Color(0.18f, 0.18f, 0.20f));
// 刻度线(每 0.5s 一条)
float step = 0.5f;
for (float t = 0; t <= totalDuration + 0.001f; t += step)
{
float x = headerRect.x + (t / totalDuration) * headerRect.width;
EditorGUI.DrawRect(new Rect(x, headerRect.y, 1f, HeaderH * 0.6f), Color.gray);
EditorGUI.LabelField(new Rect(x + 2f, headerRect.y, 40f, HeaderH),
$"{t:F1}s", new GUIStyle(EditorStyles.miniLabel) { normal = { textColor = Color.gray } });
}
}
private void DrawPatternRow(string label, AttackPatternSO pattern, ref float cursor, float totalDuration)
{
float windupDur = pattern.WindupDuration;
float activeDur = pattern.ActiveDuration;
float recoveryDur = pattern.RecoveryDuration;
float rowStart = cursor;
EditorGUILayout.BeginHorizontal(GUILayout.Height(RowH));
// 标签 + Ping
if (GUILayout.Button(label, EditorStyles.miniLabel, GUILayout.Width(LabelW), GUILayout.Height(RowH)))
EditorGUIUtility.PingObject(pattern);
Rect timelineRect = EditorGUILayout.GetControlRect(false, RowH,
GUILayout.Width(TimelineW));
// Windup
if (windupDur > 0f)
DrawBarInRect(timelineRect, cursor, windupDur, totalDuration,
windupDur / (windupDur + activeDur + recoveryDur) < 0.1f ? ColWarn : ColWindup);
cursor += windupDur;
// Active
if (activeDur > 0f)
DrawBarInRect(timelineRect, cursor, activeDur, totalDuration,
activeDur / (windupDur + activeDur + recoveryDur) < 0.1f ? ColWarn : ColActive);
cursor += activeDur;
// Recovery
if (recoveryDur > 0f)
DrawBarInRect(timelineRect, cursor, recoveryDur, totalDuration,
recoveryDur / (windupDur + activeDur + recoveryDur) < 0.1f ? ColWarn : ColRecovery);
cursor += recoveryDur;
_ = rowStart; // suppress unused warning
EditorGUILayout.EndHorizontal();
}
private void DrawVulnWindowRow(VulnerabilityWindow vw, float totalDuration)
{
string label = $"弱点:{vw.TriggerType} +{vw.TriggerDelay:F2}s / {vw.Duration:F2}s";
DrawBar(label, vw.TriggerDelay, vw.Duration, totalDuration, ColVuln, null);
}
private void DrawBar(string label, float start, float duration, float totalDuration,
Color color, AttackPatternSO pingTarget)
{
EditorGUILayout.BeginHorizontal(GUILayout.Height(RowH));
if (GUILayout.Button(label, EditorStyles.miniLabel, GUILayout.Width(LabelW), GUILayout.Height(RowH)))
{
if (pingTarget != null) EditorGUIUtility.PingObject(pingTarget);
}
Rect timelineRect = EditorGUILayout.GetControlRect(false, RowH, GUILayout.Width(TimelineW));
DrawBarInRect(timelineRect, start, duration, totalDuration, color);
EditorGUILayout.EndHorizontal();
}
private static void DrawBarInRect(Rect timeline, float start, float duration,
float totalDuration, Color color)
{
float x = timeline.x + (start / totalDuration) * timeline.width;
float w = Mathf.Max(MinBarWidth, (duration / totalDuration) * timeline.width);
EditorGUI.DrawRect(new Rect(x, timeline.y + 2f, w, timeline.height - 4f), color);
}
// ── Drag & Drop ───────────────────────────────────────────────────
private void HandleDragDrop()
{
var evt = Event.current;
if (evt.type != EventType.DragUpdated && evt.type != EventType.DragPerform) return;
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
if (evt.type == EventType.DragPerform)
{
DragAndDrop.AcceptDrag();
foreach (var obj in DragAndDrop.objectReferences)
{
if (obj is BossSkillSO skill) { _loadedSkill = skill; _loadedSequence = null; break; }
if (obj is SkillSequenceSO seq) { _loadedSequence = seq; _loadedSkill = null; break; }
}
Repaint();
}
evt.Use();
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: d47145d394333184eb3ff822e3c4aa4d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,209 +0,0 @@
using System;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
using BaseGames.Boss;
namespace BaseGames.Editor.Modules
{
/// <summary>
/// DataHub Boss技能模块 —— Tab 切换管理 BossSkillSO 和 SkillSequenceSO。
/// </summary>
public class BossSkillModule : IDataModule, IDataModuleOrdered
{
private const string SkillFolder = "Assets/_Game/Data/Boss/Skills";
private const string SeqFolder = "Assets/_Game/Data/Boss/Sequences";
public string ModuleId => "boss";
public string DisplayName => "Boss技能";
public string IconName => null;
public int DisplayOrder => 50;
private int _activeTab = 0;
private SoListPane<BossSkillSO> _skillPane;
private SoListPane<SkillSequenceSO> _seqPane;
private Action<UnityEngine.Object> _onSelected;
private DetailHeader _header;
private BossSkillSO _selectedSkill;
private SkillSequenceSO _selectedSeq;
public void Initialize()
{
_skillPane = new SoListPane<BossSkillSO>(
SkillFolder, "ABL_Boss_",
s => s.category.ToString());
_skillPane.SelectionChanged = s => { _selectedSkill = s; _onSelected?.Invoke(s); };
_seqPane = new SoListPane<SkillSequenceSO>(SeqFolder, "ABL_Seq_");
_seqPane.SelectionChanged = s => { _selectedSeq = s; _onSelected?.Invoke(s); };
}
public void BuildListPane(VisualElement container, Action<UnityEngine.Object> onSelected)
{
_onSelected = onSelected;
container.style.flexDirection = FlexDirection.Column;
// Tab bar
var tabBar = new VisualElement();
tabBar.style.flexDirection = FlexDirection.Row;
tabBar.style.borderBottomWidth = 1;
tabBar.style.borderBottomColor = new StyleColor(new Color(0.5f, 0.5f, 0.5f, 0.3f));
container.Add(tabBar);
var btnSkill = BuildTabBtn("技能 (Skill)", 0, tabBar);
var btnSeq = BuildTabBtn("序列 (Seq)", 1, tabBar);
var listArea = new VisualElement();
listArea.style.flexGrow = 1;
container.Add(listArea);
ShowTab(0, listArea, new[] { btnSkill, btnSeq });
btnSkill.clicked += () => ShowTab(0, listArea, new[] { btnSkill, btnSeq });
btnSeq.clicked += () => ShowTab(1, listArea, new[] { btnSkill, btnSeq });
_skillPane.Refresh();
_seqPane.Refresh();
}
public void BuildDetailPane(VisualElement container, UnityEngine.Object selected)
{
_header = new DetailHeader();
_header.SetAsset(selected);
_header.RenameRequested += name => OnRenameRequested(selected, name);
container.Add(_header);
if (selected == null) return;
if (selected is BossSkillSO skill)
{
container.Add(BuildSkillCard(skill));
container.Add(BuildActionBar(skill, SkillFolder, _skillPane));
container.Add(SkillModule.MakeDivider());
var insp = new InspectorElement(skill); container.Add(insp);
}
else if (selected is SkillSequenceSO seq)
{
container.Add(BuildSeqCard(seq));
container.Add(BuildActionBar(seq, SeqFolder, _seqPane));
container.Add(SkillModule.MakeDivider());
var insp = new InspectorElement(seq); container.Add(insp);
}
}
public void OnActivated()
{
_skillPane?.Refresh();
_seqPane?.Refresh();
}
// ── 内部 ─────────────────────────────────────────────────────────────
private Button BuildTabBtn(string text, int tabIdx, VisualElement bar)
{
var btn = new Button { text = text };
btn.style.flexGrow = 1;
btn.style.paddingTop = 5;
btn.style.paddingBottom = 5;
btn.style.borderTopLeftRadius = 0;
btn.style.borderTopRightRadius = 0;
btn.style.borderBottomLeftRadius = 0;
btn.style.borderBottomRightRadius = 0;
btn.style.borderLeftWidth = 0;
btn.style.borderRightWidth = 0;
btn.style.borderTopWidth = 0;
btn.style.borderBottomWidth = 0;
btn.style.backgroundColor = new StyleColor(Color.clear);
btn.userData = tabIdx;
bar.Add(btn);
return btn;
}
private void ShowTab(int tab, VisualElement area, Button[] tabBtns)
{
_activeTab = tab;
area.Clear();
for (int i = 0; i < tabBtns.Length; i++)
{
if (i == tab)
{
tabBtns[i].style.borderBottomWidth = 2;
tabBtns[i].style.borderBottomColor = new StyleColor(new Color(0.4f, 0.65f, 1f, 1f));
tabBtns[i].style.opacity = 1f;
}
else
{
tabBtns[i].style.borderBottomWidth = 0;
tabBtns[i].style.opacity = 0.65f;
}
}
if (tab == 0) { _skillPane.style.flexGrow = 1; area.Add(_skillPane); }
else { _seqPane.style.flexGrow = 1; area.Add(_seqPane); }
}
private void OnRenameRequested(UnityEngine.Object asset, string newName)
{
var (ok, err) = AssetOperations.Rename(asset, newName);
if (!ok) EditorUtility.DisplayDialog("重命名失败", err, "确定");
else
{
_header.SetAsset(asset);
if (_activeTab == 0) _skillPane.Invalidate();
else _seqPane.Invalidate();
}
}
private static VisualElement BuildSkillCard(BossSkillSO s)
{
var card = SkillModule.MakeCard();
SkillModule.AddChip(card, "分类", s.category.ToString());
SkillModule.AddChip(card, "类型", s.skillType.ToString());
SkillModule.AddChip(card, "模式数", (s.attackPatterns?.Length ?? 0).ToString());
SkillModule.AddChip(card, "弱点窗口", (s.vulnerabilityWindows?.Length ?? 0).ToString());
if (!string.IsNullOrEmpty(s.skillId))
SkillModule.AddChip(card, "ID", s.skillId);
return card;
}
private static VisualElement BuildSeqCard(SkillSequenceSO s)
{
var card = SkillModule.MakeCard();
SkillModule.AddChip(card, "步骤数", (s.steps?.Length ?? 0).ToString());
SkillModule.AddChip(card, "循环", s.RepeatIfPlayerInRange ? "是" : "否");
SkillModule.AddChip(card, "最大循环次数", s.MaxRepeatCount.ToString());
return card;
}
private VisualElement BuildActionBar<T>(T asset, string folder, SoListPane<T> pane)
where T : ScriptableObject
{
var bar = SkillModule.MakeActionBar();
new Button(() => { EditorGUIUtility.PingObject(asset); Selection.activeObject = asset; })
{ text = "定位" }.AlsoAddTo(bar);
new Button(() =>
{
var c = AssetOperations.Clone(asset, folder);
if (c != null) pane.Refresh(c);
}) { text = "克隆..." }.AlsoAddTo(bar);
var del = new Button(() =>
{
if (AssetOperations.Delete(asset)) pane.Refresh(null);
}) { text = "删除" };
del.style.borderLeftColor = new StyleColor(new Color(0.8f, 0.3f, 0.3f, 0.6f));
del.style.borderRightColor = new StyleColor(new Color(0.8f, 0.3f, 0.3f, 0.6f));
del.style.borderTopColor = new StyleColor(new Color(0.8f, 0.3f, 0.3f, 0.6f));
del.style.borderBottomColor = new StyleColor(new Color(0.8f, 0.3f, 0.3f, 0.6f));
del.style.borderLeftWidth = 1;
del.style.borderRightWidth = 1;
del.style.borderTopWidth = 1;
del.style.borderBottomWidth = 1;
del.style.marginLeft = 8;
del.AlsoAddTo(bar);
return bar;
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: f0d0425e529293e469da3762fe3bf8f0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -18,8 +18,6 @@ namespace BaseGames.Editor
{
{ "WeaponSO", ("WPN_", "WPN_{ID},例:WPN_SkyBlade") },
{ "FormSkillSO", ("SKL_", "SKL_{Name},例:SKL_SoulBlade") },
{ "BossSkillSO", ("SKL_", "SKL_{Name},例:SKL_BossRage") },
{ "SkillSequenceSO", ("SKL_", "SKL_Seq_{Name},例:SKL_Seq_RageCombo") },
{ "EnemyStatsSO", ("ENM_", "ENM_E{ID}_Stats,例:ENM_E001_Stats") },
{ "LootTableSO", ("ENM_", "ENM_E{ID}_Loot,例:ENM_E001_Loot") },
{ "FormConfigSO", ("PLY_", "PLY_{FormID},例:PLY_Player01") },