using System; using UnityEditor; using UnityEditor.UIElements; using UnityEngine; using UnityEngine.UIElements; using BaseGames.Enemies; namespace BaseGames.Editor.Modules { /// /// DataHub 敌人模块 —— Tab 切换管理 EnemyStatsSO 和 LootTableSO。 /// public class EnemyModule : IDataModule, IDataModuleOrdered { private const string StatsFolder = "Assets/_Game/Data/Enemies/Stats"; private const string LootFolder = "Assets/_Game/Data/Enemies/Loot"; public string ModuleId => "enemy"; public string DisplayName => "敌人"; public string IconName => null; public int DisplayOrder => 30; private int _activeTab = 0; // 0=Stats, 1=Loot private SoListPane _statsPane; private SoListPane _lootPane; private VisualElement _listContainer; private Action _onSelected; private DetailHeader _header; private EnemyStatsSO _selectedStats; private LootTableSO _selectedLoot; public void Initialize() { _statsPane = new SoListPane(StatsFolder, "ENM_"); _statsPane.SelectionChanged = s => { _selectedStats = s; _onSelected?.Invoke(s); }; _lootPane = new SoListPane(LootFolder, "ENM_Loot_"); _lootPane.SelectionChanged = l => { _selectedLoot = l; _onSelected?.Invoke(l); }; } public void BuildListPane(VisualElement container, Action onSelected) { _onSelected = onSelected; _listContainer = container; 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 btnStats = BuildTabBtn("属性 (Stats)", 0, tabBar); var btnLoot = BuildTabBtn("掉落 (Loot)", 1, tabBar); // 列表区域占位 var listArea = new VisualElement(); listArea.style.flexGrow = 1; container.Add(listArea); ShowTab(0, listArea, new[] { btnStats, btnLoot }); btnStats.clicked += () => ShowTab(0, listArea, new[] { btnStats, btnLoot }); btnLoot.clicked += () => ShowTab(1, listArea, new[] { btnStats, btnLoot }); _statsPane.Refresh(); _lootPane.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 EnemyStatsSO stats) { container.Add(BuildStatsCard(stats)); container.Add(BuildActionBar(stats, StatsFolder, _statsPane)); container.Add(SkillModule.MakeDivider()); var insp = new InspectorElement(stats); container.Add(insp); } else if (selected is LootTableSO loot) { container.Add(BuildLootCard(loot)); container.Add(BuildActionBar(loot, LootFolder, _lootPane)); container.Add(SkillModule.MakeDivider()); var insp = new InspectorElement(loot); container.Add(insp); } } public void OnActivated() { _statsPane?.Refresh(); _lootPane?.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) { _statsPane.style.flexGrow = 1; area.Add(_statsPane); } else { _lootPane.style.flexGrow = 1; area.Add(_lootPane); } } 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) _statsPane.Invalidate(); else _lootPane.Invalidate(); } } private static VisualElement BuildStatsCard(EnemyStatsSO s) { var card = SkillModule.MakeCard(); SkillModule.AddChip(card, "HP", s.MaxHP.ToString()); SkillModule.AddChip(card, "防御", s.Defense.ToString()); SkillModule.AddChip(card, "移速", $"{s.WalkSpeed}/{s.RunSpeed}"); SkillModule.AddChip(card, "攻击", s.AttackDamage.ToString()); SkillModule.AddChip(card, "感知", $"{s.DetectRange}m"); return card; } private static VisualElement BuildLootCard(LootTableSO l) { var card = SkillModule.MakeCard(); SkillModule.AddChip(card, "掉落项", (l.Entries?.Length ?? 0).ToString()); SkillModule.AddChip(card, "灵珠保底", $"{l.GuaranteedLingZhuMin}-{l.GuaranteedLingZhuMax}"); return card; } private VisualElement BuildActionBar(T asset, string folder, SoListPane 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; } } }