using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
using BaseGames.Boss;
using BaseGames.Combat;
using BaseGames.Enemies;
using BaseGames.Enemies.Abilities;
using BaseGames.Equipment;
using BaseGames.Input;
using BaseGames.Parry;
using BaseGames.Player;
using BaseGames.Player.States;
using BaseGames.Skills;
namespace BaseGames.Editor
{
///
/// 角色创建向导(W-01)— 统一入口窗口。
/// 技术:UI Toolkit,三标签页(玩家 / 小怪 / Boss)。
/// 菜单:BaseGames / Tools / Character Wizard
///
/// 各标签页均提供:
/// ① 当前 SO 资产状态速览(绿色=已存在 / 橙色=缺失)
/// ② SO 资产工厂(一键创建所需的所有 ScriptableObject)
/// ③ 场景搭建快捷按钮(调用 SceneObjectPlacerTool.PlaceXxx)
/// ④ 跳转到对应专项编辑器窗口
///
public class CharacterWizardWindow : EditorWindow
{
// ── 常量 ──────────────────────────────────────────────────────────────
private const string UssPath = "Assets/_Game/Scripts/Editor/UIToolkit/Editor.uss";
private const string DataRoot = "Assets/_Game/Data";
private static StyleSheet _uss;
private static StyleSheet Uss =>
_uss != null ? _uss : (_uss = AssetDatabase.LoadAssetAtPath(UssPath));
[MenuItem("BaseGames/Data/Character Wizard", priority = 1)]
public static void Open()
{
var wnd = GetWindow();
wnd.titleContent = new GUIContent("Character Wizard", EditorGUIUtility.IconContent("d_Prefab Icon").image);
wnd.minSize = new Vector2(520, 600);
}
// ── 状态 ──────────────────────────────────────────────────────────────
private int _activeTab = 0;
private Button _btnPlayer, _btnEnemy, _btnBoss;
// SO 状态缓存(避免每帧重查)
private List<(string label, bool exists)> _playerSOStatus = new();
private List<(string label, bool exists)> _enemySOStatus = new();
private List<(string label, bool exists)> _bossSOStatus = new();
private double _lastRefreshTime;
// 小怪类型选择 — 具体敌人类型
private int _enemyTypeIndex = 0;
private static readonly (string id, string name)[] EnemyTypes =
{
("E001", "草蛭"),
("E002", "簧蛭"),
("E003", "幼蛭"),
("E004", "蛭母"),
("E005", "肥蛭"),
("E006", "讙"),
};
// 动态内容区(类型切换时重建)
private VisualElement _enemyContentArea;
// Boss 命名字段
private string _bossId = "NewBoss"; // kept for legacy SkillSequenceSO queries if any
private string _enemyId = "E001"; // kept for legacy status calls if any
private string _playerId = "Player";
// SO 状态面板(按标签页缓存)
private VisualElement _playerStatusPanel;
private VisualElement _enemyStatusPanel;
private VisualElement _bossStatusPanel;
// ── 生命周期 ──────────────────────────────────────────────────────────
public void CreateGUI()
{
if (Uss != null)
rootVisualElement.styleSheets.Add(Uss);
rootVisualElement.style.flexDirection = FlexDirection.Column;
BuildTabBar();
BuildTabContents();
RefreshSOStatus();
SwitchTab(0);
}
private void OnFocus() => RefreshSOStatus();
// ── 标签栏 ────────────────────────────────────────────────────────────
private void BuildTabBar()
{
var bar = new VisualElement();
bar.AddToClassList("tab-bar");
_btnPlayer = MakeTabButton("玩家", () => SwitchTab(0));
_btnEnemy = MakeTabButton("小怪", () => SwitchTab(1));
_btnBoss = MakeTabButton("Boss", () => SwitchTab(2));
bar.Add(_btnPlayer);
bar.Add(_btnEnemy);
bar.Add(_btnBoss);
rootVisualElement.Add(bar);
}
private Button MakeTabButton(string label, Action onClick)
{
var btn = new Button(onClick) { text = label };
btn.AddToClassList("tab-btn");
return btn;
}
private void SwitchTab(int idx)
{
_activeTab = idx;
var tabs = rootVisualElement.Query(className: "tab-content").ToList();
for (int i = 0; i < tabs.Count; i++)
tabs[i].style.display = (i == idx) ? DisplayStyle.Flex : DisplayStyle.None;
_btnPlayer.EnableInClassList("tab-btn--active", idx == 0);
_btnEnemy .EnableInClassList("tab-btn--active", idx == 1);
_btnBoss .EnableInClassList("tab-btn--active", idx == 2);
}
// ── 标签页内容 ────────────────────────────────────────────────────────
private void BuildTabContents()
{
rootVisualElement.Add(BuildPlayerTab());
rootVisualElement.Add(BuildEnemyTab());
rootVisualElement.Add(BuildBossTab());
}
// ════════════════════════════════════════════════════════════════════════
// 玩家标签页
// ════════════════════════════════════════════════════════════════════════
private VisualElement BuildPlayerTab()
{
var root = MakeTabContent();
root.Add(MakeSectionHeader("▶ SO 资产状态"));
_playerStatusPanel = new VisualElement();
root.Add(_playerStatusPanel);
root.Add(MakeSectionHeader("▶ SO 资产工厂"));
root.Add(MakeHelpBox("在 Project 中创建下列 ScriptableObject 资产。若已存在则跳过。"));
var idRow = MakeLabeledTextField("资产名称前缀", _playerId, v => _playerId = v);
root.Add(idRow);
var factory = MakeActionGroup();
factory.Add(MakeFactoryButton("PlayerStatsSO", () => CreatePlayerStat()));
factory.Add(MakeFactoryButton("PlayerMovementConfigSO", () => CreateMovementConfig()));
factory.Add(MakeFactoryButton("PlayerAnimationConfigSO", () => CreateAnimConfig()));
factory.Add(MakeFactoryButton("FormConfigSO + 3 FormSO", () => CreateFormConfig()));
factory.Add(MakeFactoryButton("WeaponSO × 3(形态)", () => CreateFormWeapons()));
factory.Add(MakeFactoryButton("DamageSourceSO(连击×3)", () => CreatePlayerDamageSources()));
factory.Add(MakeFactoryButton("ParryConfigSO", () => CreateParryConfig()));
factory.Add(MakeFactoryButton("ShieldConfigSO", () => CreateShieldConfig()));
factory.Add(MakeFactoryButton("EquipmentConfigSO", () => CreateEquipmentConfig()));
factory.Add(MakeFactoryButton("CharmCatalogSO", () => CreateCharmCatalog()));
root.Add(factory);
var createAllBtn = new Button(CreateAllPlayerSOs) { text = "★ 一键创建全部 Player SO" };
createAllBtn.AddToClassList("wizard-create-all-btn");
root.Add(createAllBtn);
root.Add(MakeSeparator());
root.Add(MakeSectionHeader("▶ 场景搭建"));
root.Add(MakeHelpBox("在当前活动场景中放置玩家 GameObject(带完整组件树)。"));
var sceneGroup = MakeActionGroup();
sceneGroup.Add(MakeSceneButton("放置玩家到场景", SceneObjectPlacerTool.PlacePlayer));
sceneGroup.Add(MakeSceneButton("指定所有 SO 到场景角色", AssignAllPlayerSOsToScene));
sceneGroup.Add(MakeSceneButton("放置地面平台", SceneObjectPlacerTool.PlaceGroundPlatform));
sceneGroup.Add(MakeSceneButton("放置存档点", SceneObjectPlacerTool.PlaceSavePoint));
root.Add(sceneGroup);
root.Add(MakeSeparator());
root.Add(MakeSectionHeader("▶ 专项编辑器"));
var jumpGroup = MakeActionGroup();
jumpGroup.Add(MakeJumpButton("Data Hub(武器/技能/形态)", DataHubWindow.Open));
jumpGroup.Add(MakeJumpButton("SO 全局校验", SOValidationRunner.ValidateMenu));
root.Add(jumpGroup);
return root;
}
// ════════════════════════════════════════════════════════════════════════
// 小怪标签页
// ════════════════════════════════════════════════════════════════════════
private VisualElement BuildEnemyTab()
{
var root = MakeTabContent();
root.Add(MakeSectionHeader("▶ SO 资产状态"));
_enemySOStatus.Clear();
_enemyStatusPanel = new VisualElement();
root.Add(_enemyStatusPanel);
root.Add(MakeSectionHeader("▶ 敌人类型"));
root.Add(MakeHelpBox("选择要创建的具体敌人类型,对应 SO 工厂与场景放置按钮会自动切换。"));
var typeRow = new VisualElement();
typeRow.style.flexDirection = FlexDirection.Row;
typeRow.style.flexWrap = Wrap.Wrap;
typeRow.style.marginBottom = 4;
for (int i = 0; i < EnemyTypes.Length; i++)
{
int captured = i;
var (id, name) = EnemyTypes[i];
var btn = new Button(() =>
{
_enemyTypeIndex = captured;
RefreshEnemyTypeButtons(typeRow);
RefreshEnemyTabContent(_enemyContentArea);
RefreshSOStatus();
})
{ text = $"{id} {name}" };
btn.name = $"enemy-type-{i}";
btn.EnableInClassList("type-btn--active", i == _enemyTypeIndex);
typeRow.Add(btn);
}
root.Add(typeRow);
_enemyContentArea = new VisualElement();
root.Add(_enemyContentArea);
RefreshEnemyTabContent(_enemyContentArea);
root.Add(MakeSeparator());
root.Add(MakeSectionHeader("▶ 专项编辑器"));
var jumpGroup = MakeActionGroup();
jumpGroup.Add(MakeJumpButton("Data Hub(敌人数据)", DataHubWindow.Open));
jumpGroup.Add(MakeJumpButton("SO 全局校验", SOValidationRunner.ValidateMenu));
root.Add(jumpGroup);
return root;
}
private void RefreshEnemyTabContent(VisualElement container)
{
if (container == null) return;
container.Clear();
var (id, name) = EnemyTypes[_enemyTypeIndex];
string dir = $"{DataRoot}/Enemies/{id}";
string ablDir = $"{dir}/Abilities";
container.Add(MakeSectionHeader($"▶ SO 资产工厂({id} {name})"));
container.Add(MakeHelpBox($"统计 SO 路径:{dir}\n能力配置:{ablDir}"));
var factory = MakeActionGroup();
factory.Add(MakeFactoryButton($"ENM_{id}_Stats.asset", () => { CreateEnemyStatsSO(id); RefreshSOStatus(); }));
factory.Add(MakeFactoryButton($"ENM_{id}_AnimConfig.asset", () => { CreateEnemyAnimConfigSO(id); RefreshSOStatus(); }));
foreach (var (ablName, ablId) in GetEnemyAbilityDefs(id))
{
string capturedName = ablName;
string capturedId = ablId;
factory.Add(MakeFactoryButton($"ABL_{id}_{capturedName}.asset",
() => { CreateEnemyAbilitySO(id, capturedName, capturedId); RefreshSOStatus(); }));
}
container.Add(factory);
var createAllBtn = new Button(() => { CreateAllEnemySOs(id); RefreshSOStatus(); })
{ text = $"★ 一键创建全部 {id} SO" };
createAllBtn.AddToClassList("wizard-create-all-btn");
container.Add(createAllBtn);
container.Add(MakeSeparator());
container.Add(MakeSectionHeader("▶ 场景搭建"));
container.Add(MakeHelpBox("在当前活动场景中放置完整组件树并自动绑定已有 SO。"));
var sceneGroup = MakeActionGroup();
string sceneLabel = $"放置 {id} {name} 到场景";
sceneGroup.Add(MakeSceneButton(sceneLabel, () => PlaceSpecificEnemy(id)));
container.Add(sceneGroup);
}
// ════════════════════════════════════════════════════════════════════════
// Boss 标签页(嘲风专属)
// ════════════════════════════════════════════════════════════════════════
private VisualElement BuildBossTab()
{
var root = MakeTabContent();
root.Add(MakeSectionHeader("▶ SO 资产状态"));
_bossStatusPanel = new VisualElement();
root.Add(_bossStatusPanel);
root.Add(MakeSectionHeader("▶ SO 资产工厂(嘲风 ChaoFeng)"));
root.Add(MakeHelpBox("路径:Assets/_Game/Data/Enemies/ChaoFeng/\n能力配置:Assets/_Game/Data/Enemies/ChaoFeng/Abilities/"));
var factory = MakeActionGroup();
factory.Add(MakeFactoryButton("ENM_ChaoFeng_Stats.asset", () => { CreateChaoFengStatsSO(); RefreshSOStatus(); }));
factory.Add(MakeFactoryButton("ENM_ChaoFeng_AnimConfig.asset",() => { CreateChaoFengAnimConfigSO(); RefreshSOStatus(); }));
factory.Add(MakeFactoryButton("ABL_ChaoFeng_Idle.asset", () => { CreateChaoFengSkillSO("Idle", "chaofeng_idle"); RefreshSOStatus(); }));
factory.Add(MakeFactoryButton("ABL_ChaoFeng_Slam.asset", () => { CreateChaoFengSkillSO("Slam", "chaofeng_slam"); RefreshSOStatus(); }));
factory.Add(MakeFactoryButton("ABL_ChaoFeng_Sweep.asset", () => { CreateChaoFengSkillSO("Sweep", "chaofeng_sweep"); RefreshSOStatus(); }));
factory.Add(MakeFactoryButton("ABL_ChaoFeng_WindBlade.asset", () => { CreateChaoFengSkillSO("WindBlade", "chaofeng_windblade"); RefreshSOStatus(); }));
factory.Add(MakeFactoryButton("ABL_ChaoFeng_Summon.asset", () => { CreateChaoFengSkillSO("Summon", "chaofeng_summon"); RefreshSOStatus(); }));
root.Add(factory);
var createAllBtn = new Button(() => { CreateAllChaoFengSOs(); RefreshSOStatus(); })
{ text = "★ 一键创建全部 ChaoFeng SO" };
createAllBtn.AddToClassList("wizard-create-all-btn");
root.Add(createAllBtn);
root.Add(MakeSeparator());
root.Add(MakeSectionHeader("▶ 场景搭建"));
root.Add(MakeHelpBox("放置嘲风完整组件树(ChaoFengBoss + 浮空控制器 + 击倒计数 + Phase1 HitBox × 4 + 炮口 × 3)。"));
var sceneGroup = MakeActionGroup();
sceneGroup.Add(MakeSceneButton("放置嘲风到场景并绑定 SO", SceneObjectPlacerTool.PlaceChaoFeng));
root.Add(sceneGroup);
root.Add(MakeSeparator());
root.Add(MakeSectionHeader("▶ 专项编辑器"));
var jumpGroup = MakeActionGroup();
jumpGroup.Add(MakeJumpButton("Boss 技能序列查看器", BossSkillSequenceWindow.OpenWindow));
jumpGroup.Add(MakeJumpButton("Data Hub(Boss技能)", DataHubWindow.Open));
jumpGroup.Add(MakeJumpButton("SO 全局校验", SOValidationRunner.ValidateMenu));
root.Add(jumpGroup);
return root;
}
// ── SO 资产工厂:玩家 ────────────────────────────────────────────────
private void CreatePlayerStat()
{
var asset = EditorScaffoldUtils.CreateSOAsset(
$"{DataRoot}/Player", "PLY_PlayerStats");
if (asset != null) RefreshSOStatus();
}
private void CreateMovementConfig()
{
var asset = EditorScaffoldUtils.CreateSOAsset(
$"{DataRoot}/Player", "PLY_PlayerMovementConfig");
if (asset != null) RefreshSOStatus();
}
private void CreateAnimConfig()
{
var asset = EditorScaffoldUtils.CreateSOAsset(
$"{DataRoot}/Player", "PLY_PlayerAnimationConfig");
if (asset != null) RefreshSOStatus();
}
private void CreateFormConfig()
{
string configDir = $"{DataRoot}/Player";
string formsDir = $"{DataRoot}/Player/Forms";
EditorScaffoldUtils.EnsureFolder(formsDir);
var cfg = EditorScaffoldUtils.CreateSOAsset(configDir, "PLY_FormConfig");
if (cfg == null) cfg = AssetDatabase.LoadAssetAtPath($"{configDir}/PLY_FormConfig.asset");
var formTypes = new[] { ("TianHun", FormType.TianHun, "天魂"), ("DiHun", FormType.DiHun, "地魂"), ("MingHun", FormType.MingHun, "命魂") };
var forms = new List();
foreach (var (id, ftype, dname) in formTypes)
{
string path = $"{formsDir}/PLY_Form_{id}.asset";
var form = AssetDatabase.LoadAssetAtPath(path);
if (form == null)
{
form = ScriptableObject.CreateInstance();
form.formId = $"Form_{id}";
form.displayName = dname;
form.formType = ftype;
AssetDatabase.CreateAsset(form, path);
}
forms.Add(form);
}
if (cfg != null && (cfg.forms == null || cfg.forms.Length == 0))
{
cfg.forms = forms.ToArray();
EditorUtility.SetDirty(cfg);
}
AssetDatabase.SaveAssets();
EditorGUIUtility.PingObject(cfg);
RefreshSOStatus();
}
private void CreateFormWeapons()
{
string dir = $"{DataRoot}/Combat/Weapons";
foreach (var id in new[] { "TianHun", "DiHun", "MingHun" })
EditorScaffoldUtils.CreateSOAsset(dir, $"WPN_{id}");
RefreshSOStatus();
}
private void CreatePlayerDamageSources()
{
string dir = $"{DataRoot}/Combat/DamageSources";
foreach (var label in new[] { "Attack1", "Attack2", "Attack3" })
EditorScaffoldUtils.CreateSOAsset(dir, $"CMB_Player_{label}");
RefreshSOStatus();
}
// ── SO 资产工厂:玩家(Config 类)────────────────────────────────────────
private void CreateParryConfig()
{
var asset = EditorScaffoldUtils.CreateSOAsset(
$"{DataRoot}/Player", "PLY_ParryConfig");
if (asset != null) RefreshSOStatus();
}
private void CreateShieldConfig()
{
var asset = EditorScaffoldUtils.CreateSOAsset(
$"{DataRoot}/Player", "PLY_ShieldConfig");
if (asset != null) RefreshSOStatus();
}
private void CreateEquipmentConfig()
{
var asset = EditorScaffoldUtils.CreateSOAsset(
$"{DataRoot}/Player", "PLY_EquipmentConfig");
if (asset != null) RefreshSOStatus();
}
private void CreateCharmCatalog()
{
var asset = EditorScaffoldUtils.CreateSOAsset(
$"{DataRoot}/Progression/Charms", "CHM_CharmCatalog");
if (asset != null) RefreshSOStatus();
}
///
/// 一键创建全部 Player 所需 SO(已存在则跳过)。
/// 完成后提示用户点击"指定所有 SO 到场景角色"完成绑定。
///
private void CreateAllPlayerSOs()
{
CreatePlayerStat();
CreateMovementConfig();
CreateAnimConfig();
CreateFormConfig();
CreateFormWeapons();
CreatePlayerDamageSources();
CreateParryConfig();
CreateShieldConfig();
CreateEquipmentConfig();
CreateCharmCatalog();
AssetDatabase.SaveAssets();
EditorUtility.DisplayDialog("创建完成",
"全部 Player SO 已创建(已存在的跳过)。\n" +
"请在场景中放置角色后,点击「▣ 指定所有 SO 到场景角色」完成绑定。",
"确定");
}
///
/// 查找场景中的 PlayerController,将项目中已存在的配置 SO 全部指定给对应组件字段。
/// 使用 SerializedObject 赋值,自动标记 dirty 并保存。
///
private void AssignAllPlayerSOsToScene()
{
var pc = UnityEngine.Object.FindObjectOfType();
if (pc == null)
{
EditorUtility.DisplayDialog("未找到角色",
"场景中没有 PlayerController。\n请先使用「▣ 放置玩家到场景」。", "确定");
return;
}
var plyGo = pc.gameObject;
int count = 0;
// 通过 SerializedObject 写入 SerializeField(支持撤销)
var missing = new System.Collections.Generic.List();
void TryAssign(Component comp, string field) where T : ScriptableObject
{
if (comp == null) return;
var so = EditorScaffoldUtils.FindAllAssetsOfType().FirstOrDefault();
if (so == null)
{
missing.Add($"{typeof(T).Name}({comp.GetType().Name}.{field})");
return;
}
var sObj = new SerializedObject(comp);
var prop = sObj.FindProperty(field);
if (prop == null) return;
prop.objectReferenceValue = so;
sObj.ApplyModifiedProperties();
count++;
}
var stats = plyGo.GetComponent();
var movement = plyGo.GetComponent();
var form = plyGo.GetComponent();
var parry = plyGo.GetComponent();
var shield = plyGo.GetComponent();
var equip = plyGo.GetComponent();
var wall = plyGo.GetComponent();
// PlayerStats
TryAssign (stats, "_config");
// PlayerMovement
TryAssign (movement, "_config");
// PlayerController(多个字段)
TryAssign (pc, "_movementConfig");
TryAssign (pc, "_animConfig");
TryAssign (pc, "_inputReader");
TryAssign (pc, "_formConfig");
// FormController
TryAssign (form, "_config");
// ParrySystem
TryAssign (parry, "_config");
// ShieldComponent
TryAssign (shield, "_config");
// EquipmentManager
TryAssign (equip, "_config");
TryAssign (equip, "_charmCatalog");
// PlayerWallDetector(复用移动配置)
TryAssign (wall, "_config");
EditorUtility.SetDirty(plyGo);
RefreshSOStatus();
string msg = $"已将 {count} 个 SO 引用指定到场景角色 [{plyGo.name}]。";
if (missing.Count > 0)
msg += $"\n\n★ 以下 SO 尚未创建,请先点击工厂按钮创建后再次指定:\n • " +
string.Join("\n • ", missing);
else
msg += "\n全部 SO 绑定完成,可在 Inspector 中确认各组件字段。";
EditorUtility.DisplayDialog("指定完成", msg, "确定");
}
// ── SO 资产工厂:小怪(按类型) ───────────────────────────────────────────
/// 返回指定敌人类型的 (abilityFileName, abilityId) 定义列表。
private static (string ablName, string ablId)[] GetEnemyAbilityDefs(string enemyId) => enemyId switch
{
"E001" => new[] { ("Alert", "e001_alert"), ("Chase", "e001_chase") },
"E002" => new[] { ("Strike", "e002_strike") },
"E003" => new[] { ("Fall", "e003_fall") },
"E004" => new[] { ("Bite", "e004_bite"), ("Slam", "e004_slam"), ("Acid", "e004_acid"),
("Charge", "e004_charge"), ("Chase", "e004_chase") },
"E005" => new[] { ("Bite", "e005_bite"), ("Acid", "e005_acid") },
"E006" => new[] { ("Leap", "e006_leap"), ("Chase", "e006_chase") },
_ => System.Array.Empty<(string, string)>(),
};
private static void CreateEnemyStatsSO(string id)
{
string dir = $"Assets/_Game/Data/Enemies/{id}";
EditorScaffoldUtils.CreateSOAsset(dir, $"ENM_{id}_Stats");
}
private static void CreateEnemyAnimConfigSO(string id)
{
string dir = $"Assets/_Game/Data/Enemies/{id}";
EditorScaffoldUtils.CreateSOAsset(dir, $"ENM_{id}_AnimConfig");
}
private static void CreateEnemyAbilitySO(string enemyId, string ablName, string ablId)
{
string dir = $"Assets/_Game/Data/Enemies/{enemyId}/Abilities";
string name = $"ABL_{enemyId}_{ablName}";
var so = EditorScaffoldUtils.CreateSOAsset(dir, name);
// Set abilityId on newly-created SO (skip if already existed = null returned)
if (so != null)
{
so.abilityId = ablId;
EditorUtility.SetDirty(so);
AssetDatabase.SaveAssets();
}
}
private static void CreateAllEnemySOs(string id)
{
CreateEnemyStatsSO(id);
CreateEnemyAnimConfigSO(id);
foreach (var (ablName, ablId) in GetEnemyAbilityDefs(id))
CreateEnemyAbilitySO(id, ablName, ablId);
AssetDatabase.SaveAssets();
EditorUtility.DisplayDialog("创建完成",
$"全部 {id} SO 已创建(已存在的跳过)。\n请放置到场景后检查组件绑定。", "确定");
}
private static void PlaceSpecificEnemy(string id)
{
switch (id)
{
case "E001": SceneObjectPlacerTool.PlaceE001_CaoZhi(); break;
case "E002": SceneObjectPlacerTool.PlaceE002_HuangZhi(); break;
case "E003": SceneObjectPlacerTool.PlaceE003_YouZhi_Enemy(); break;
case "E004": SceneObjectPlacerTool.PlaceE004_ZhiMu_Enemy(); break;
case "E005": SceneObjectPlacerTool.PlaceE005_FeiZhi_Enemy(); break;
case "E006": SceneObjectPlacerTool.PlaceE006_Huan(); break;
default: SceneObjectPlacerTool.PlaceEnemy(); break;
}
}
// ── SO 资产工厂:嘲风 Boss ─────────────────────────────────────────────
private static void CreateChaoFengStatsSO()
{
string dir = "Assets/_Game/Data/Enemies/ChaoFeng";
EditorScaffoldUtils.CreateSOAsset(dir, "ENM_ChaoFeng_Stats");
}
private static void CreateChaoFengAnimConfigSO()
{
string dir = "Assets/_Game/Data/Enemies/ChaoFeng";
EditorScaffoldUtils.CreateSOAsset(dir, "ENM_ChaoFeng_AnimConfig");
}
private static void CreateChaoFengSkillSO(string skillName, string skillId)
{
string dir = "Assets/_Game/Data/Enemies/ChaoFeng/Abilities";
string name = $"ABL_ChaoFeng_{skillName}";
var so = EditorScaffoldUtils.CreateSOAsset(dir, name);
if (so != null)
{
EditorUtility.SetDirty(so);
AssetDatabase.SaveAssets();
}
}
private static void CreateAllChaoFengSOs()
{
CreateChaoFengStatsSO();
CreateChaoFengAnimConfigSO();
foreach (var (n, id) in new[] { ("Idle","chaofeng_idle"), ("Slam","chaofeng_slam"),
("Sweep","chaofeng_sweep"), ("WindBlade","chaofeng_windblade"),
("Summon","chaofeng_summon") })
CreateChaoFengSkillSO(n, id);
AssetDatabase.SaveAssets();
EditorUtility.DisplayDialog("创建完成",
"全部嘲风 SO 已创建(已存在的跳过)。\n放置到场景后检查 BossSkillExecutor._skills 绑定。", "确定");
}
// ── 场景搭建(已移至 RefreshEnemyTabContent 内的内联按钮) ─────────────
// ── SO 状态面板刷新 ───────────────────────────────────────────────────
private void RefreshSOStatus()
{
_lastRefreshTime = EditorApplication.timeSinceStartup;
BuildPlayerStatus();
BuildEnemyStatus();
BuildBossStatus();
}
private void BuildPlayerStatus()
{
if (_playerStatusPanel == null) return;
_playerStatusPanel.Clear();
var checks = new (string label, UnityEngine.Object asset)[]
{
("PlayerStatsSO", FindFirst()),
("PlayerMovementConfigSO", FindFirst()),
("PlayerAnimationConfigSO", FindFirst()),
("FormConfigSO", FindFirst()),
("FormSO(天魂)", FindFormOfType(FormType.TianHun)),
("FormSO(地魂)", FindFormOfType(FormType.DiHun)),
("FormSO(命魂)", FindFormOfType(FormType.MingHun)),
("WeaponSO(≥3)", FindFirstIfCount(3)),
("ParryConfigSO", FindFirst()),
("ShieldConfigSO", FindFirst()),
("EquipmentConfigSO", FindFirst()),
("CharmCatalogSO", FindFirst()),
};
_playerStatusPanel.Add(MakeStatusGrid(checks));
}
private void BuildEnemyStatus()
{
if (_enemyStatusPanel == null) return;
_enemyStatusPanel.Clear();
var (id, name) = EnemyTypes[_enemyTypeIndex];
string dir = $"{DataRoot}/Enemies/{id}";
string ablDir = $"{dir}/Abilities";
var items = new List<(string label, UnityEngine.Object asset)>
{
($"ENM_{id}_Stats", FindAtPath($"{dir}/ENM_{id}_Stats.asset")),
($"ENM_{id}_AnimConfig",FindAtPath($"{dir}/ENM_{id}_AnimConfig.asset")),
};
foreach (var (ablName, _) in GetEnemyAbilityDefs(id))
items.Add(($"ABL_{id}_{ablName}", FindAtPath($"{ablDir}/ABL_{id}_{ablName}.asset")));
_enemyStatusPanel.Add(MakeStatusGrid(items.ToArray()));
}
private void BuildBossStatus()
{
if (_bossStatusPanel == null) return;
_bossStatusPanel.Clear();
const string dir = "Assets/_Game/Data/Enemies/ChaoFeng";
const string ablDir = "Assets/_Game/Data/Enemies/ChaoFeng/Abilities";
var checks = new (string label, UnityEngine.Object asset)[]
{
("ENM_ChaoFeng_Stats", FindAtPath($"{dir}/ENM_ChaoFeng_Stats.asset")),
("ENM_ChaoFeng_AnimConfig",FindAtPath($"{dir}/ENM_ChaoFeng_AnimConfig.asset")),
("ABL_ChaoFeng_Idle", FindAtPath($"{ablDir}/ABL_ChaoFeng_Idle.asset")),
("ABL_ChaoFeng_Slam", FindAtPath($"{ablDir}/ABL_ChaoFeng_Slam.asset")),
("ABL_ChaoFeng_Sweep", FindAtPath($"{ablDir}/ABL_ChaoFeng_Sweep.asset")),
("ABL_ChaoFeng_WindBlade", FindAtPath($"{ablDir}/ABL_ChaoFeng_WindBlade.asset")),
("ABL_ChaoFeng_Summon", FindAtPath($"{ablDir}/ABL_ChaoFeng_Summon.asset")),
};
_bossStatusPanel.Add(MakeStatusGrid(checks));
}
// ── 辅助:状态格 ─────────────────────────────────────────────────────
private static VisualElement MakeStatusGrid((string label, UnityEngine.Object asset)[] items)
{
var grid = new VisualElement();
grid.style.flexDirection = FlexDirection.Row;
grid.style.flexWrap = Wrap.Wrap;
grid.style.marginBottom = 6;
foreach (var (label, asset) in items)
{
bool exists = asset != null;
VisualElement chip;
if (exists)
{
var captured = asset;
var btn = new Button(() =>
{
EditorGUIUtility.PingObject(captured);
Selection.activeObject = captured;
}) { text = $"✔ {label}" };
btn.AddToClassList("status-chip--ok");
chip = btn;
}
else
{
var lbl = new Label($"✘ {label}");
lbl.AddToClassList("status-chip--missing");
chip = lbl;
}
grid.Add(chip);
}
return grid;
}
// ── 辅助:UI 组件构建 ─────────────────────────────────────────────────
private static VisualElement MakeTabContent()
{
var root = new ScrollView(ScrollViewMode.Vertical);
root.AddToClassList("tab-content");
root.style.flexGrow = 1;
root.contentContainer.style.paddingLeft = 8;
root.contentContainer.style.paddingRight = 8;
root.contentContainer.style.paddingTop = 8;
root.contentContainer.style.paddingBottom = 8;
return root;
}
private static Label MakeSectionHeader(string text)
{
var lbl = new Label(text);
lbl.AddToClassList("section-header");
return lbl;
}
private static VisualElement MakeActionGroup()
{
var group = new VisualElement();
group.AddToClassList("action-buttons");
return group;
}
private static Button MakeFactoryButton(string label, Action onClick)
{
var btn = new Button(onClick) { text = $"+ {label}" };
btn.AddToClassList("wizard-factory-btn");
return btn;
}
private static Button MakeSceneButton(string label, Action onClick)
{
var btn = new Button(onClick) { text = $"▣ {label}" };
btn.AddToClassList("wizard-scene-btn");
return btn;
}
private static Button MakeJumpButton(string label, Action onClick)
{
var btn = new Button(onClick) { text = $"⇒ {label}" };
btn.AddToClassList("wizard-jump-btn");
return btn;
}
private static HelpBox MakeHelpBox(string text)
{
return new HelpBox(text, HelpBoxMessageType.Info);
}
private static VisualElement MakeSeparator()
{
var sep = new VisualElement();
sep.style.height = 1;
sep.style.backgroundColor = new Color(0.5f, 0.5f, 0.5f, 0.25f);
sep.style.marginTop = 8;
sep.style.marginBottom = 8;
return sep;
}
private static VisualElement MakeLabeledTextField(string label, string value, Action onChange)
{
var row = new VisualElement();
row.style.flexDirection = FlexDirection.Row;
row.style.marginBottom = 4;
row.style.alignItems = Align.Center;
var lbl = new Label(label) { style = { minWidth = 110, marginRight = 6 } };
var field = new TextField { value = value, style = { flexGrow = 1 } };
field.RegisterValueChangedCallback(e => onChange(e.newValue));
row.Add(lbl);
row.Add(field);
return row;
}
private void RefreshEnemyTypeButtons(VisualElement typeRow)
{
for (int i = 0; i < EnemyTypes.Length; i++)
{
var btn = typeRow.Q