using System.Collections.Generic;
using BaseGames.UI;
using BaseGames.UI.HUD;
using TMPro;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
namespace BaseGames.Editor.UI
{
///
/// HUD 脚手架工具(架构 10_UIModule §HUD)。
/// 在当前活动场景中生成完整的 HUD Canvas 层级结构并自动绑定已存在的事件频道资产。
/// 执行路径:BaseGames ▸ Scene ▸ Setup ▸ Scaffold HUD Canvas
///
public static class HUDScaffoldWizard
{
[MenuItem("BaseGames/Scene/Setup/Scaffold HUD Canvas", priority = 203)]
public static void ScaffoldHUDCanvas()
{
var report = new List();
Undo.SetCurrentGroupName("Scaffold HUD Canvas");
int undoGroup = Undo.GetCurrentGroup();
// ── Canvas ────────────────────────────────────────────────────────
GameObject canvasGo = GetOrCreateRootCanvas("HUD Canvas", 0);
// ── HUDRoot ───────────────────────────────────────────────────────
GameObject hudRootGo = GetOrCreateChild(canvasGo.transform, "HUDRoot").gameObject;
HUDController hudCtrl = GetOrAddComponent(hudRootGo);
// 若场景中已存在 UIManager,自动将 _hudRoot 指向本 HUDRoot
UIManager uiManager = Object.FindFirstObjectByType();
if (uiManager != null)
AssignRef(uiManager, "_hudRoot", hudRootGo);
// ── HP 区域 ───────────────────────────────────────────────────────
GameObject hpContainerGo = GetOrCreateChild(hudRootGo.transform, "HP_Container").gameObject;
var hpLayout = GetOrAddComponent(hpContainerGo);
hpLayout.childForceExpandWidth = false;
hpLayout.childForceExpandHeight = false;
hpLayout.spacing = 4f;
// ── 魄元(Soul)量条 ──────────────────────────────────────────────
GameObject soulGaugeGo = GetOrCreateChild(hudRootGo.transform, "Gauge_Soul").gameObject;
Image soulFill = GetOrAddComponent(soulGaugeGo);
soulFill.type = Image.Type.Filled;
soulFill.fillMethod = Image.FillMethod.Horizontal;
soulFill.fillAmount = 1f;
// ── 灵力(Spirit)量条 ────────────────────────────────────────────
GameObject spiritGaugeGo = GetOrCreateChild(hudRootGo.transform, "Gauge_Spirit").gameObject;
Image spiritFill = GetOrAddComponent(spiritGaugeGo);
spiritFill.type = Image.Type.Filled;
spiritFill.fillMethod = Image.FillMethod.Horizontal;
spiritFill.fillAmount = 1f;
// ── 灵铢(LingZhu)文本 ───────────────────────────────────────────
GameObject lingZhuGo = GetOrCreateChild(hudRootGo.transform, "Text_LingZhu").gameObject;
TMP_Text lingZhuText = GetOrAddComponent(lingZhuGo);
lingZhuText.text = "0";
// ── 回春图标(Spring Charges)─────────────────────────────────────
GameObject springContainerGo = GetOrCreateChild(hudRootGo.transform, "Spring_Container").gameObject;
var springLayout = GetOrAddComponent(springContainerGo);
springLayout.childForceExpandWidth = false;
springLayout.childForceExpandHeight = false;
springLayout.spacing = 4f;
// ── 形态图标(Form Icons × 4)────────────────────────────────────
const int kFormIconCount = 4;
GameObject formIconsRoot = GetOrCreateChild(hudRootGo.transform, "FormIcons").gameObject;
Image[] formImages = new Image[kFormIconCount];
for (int i = 0; i < kFormIconCount; i++)
{
GameObject iconGo = GetOrCreateChild(formIconsRoot.transform, $"FormIcon_{i}").gameObject;
formImages[i] = GetOrAddComponent(iconGo);
}
// ── 交互提示(InteractPrompt)─────────────────────────────────────
GameObject interactPromptGo = GetOrCreateChild(hudRootGo.transform, "InteractPrompt").gameObject;
InteractPromptWidget interactWidget = GetOrAddComponent(interactPromptGo);
GameObject promptRootGo = GetOrCreateChild(interactPromptGo.transform, "Root").gameObject;
GameObject promptKeyIconGo = GetOrCreateChild(promptRootGo.transform, "KeyIcon").gameObject;
Image keyIcon = GetOrAddComponent(promptKeyIconGo);
GameObject promptLabelGo = GetOrCreateChild(promptRootGo.transform, "LabelText").gameObject;
TMP_Text promptLabel = GetOrAddComponent(promptLabelGo);
promptLabel.text = "互动";
promptRootGo.SetActive(false);
AssignRef(interactWidget, "_keyIcon", keyIcon);
AssignRef(interactWidget, "_labelText", promptLabel);
AssignRef(interactWidget, "_root", promptRootGo);
// ── 法术槽(SpellSlot)───────────────────────────────────────────
GameObject spellSlotGo = GetOrCreateChild(hudRootGo.transform, "SpellSlot").gameObject;
SpellSlotWidget spellWidget = GetOrAddComponent(spellSlotGo);
GameObject spellIconGo = GetOrCreateChild(spellSlotGo.transform, "IconImage").gameObject;
Image spellIcon = GetOrAddComponent(spellIconGo);
GameObject spellCdGo = GetOrCreateChild(spellSlotGo.transform, "CooldownFill").gameObject;
Image spellCd = GetOrAddComponent(spellCdGo);
spellCd.type = Image.Type.Filled;
spellCd.fillMethod = Image.FillMethod.Radial360;
spellCd.fillAmount = 0f;
GameObject spellEmptyGo = GetOrCreateChild(spellSlotGo.transform, "EmptySlot").gameObject;
AssignRef(spellWidget, "_iconImage", spellIcon);
AssignRef(spellWidget, "_cooldownFill", spellCd);
AssignRef(spellWidget, "_emptySlot", spellEmptyGo);
// ── 工具栏(ToolHUD × 2 slots)──────────────────────────────────
GameObject toolHUDGo = GetOrCreateChild(hudRootGo.transform, "ToolHUD").gameObject;
ToolHUD toolHUD = GetOrAddComponent(toolHUDGo);
const int kToolSlotCount = 2;
var toolSlots = new ToolSlotUI[kToolSlotCount];
for (int i = 0; i < kToolSlotCount; i++)
{
GameObject slotGo = GetOrCreateChild(toolHUDGo.transform, $"ToolSlot_{i}").gameObject;
ToolSlotUI slotUI = GetOrAddComponent(slotGo);
GameObject iconGo = GetOrCreateChild(slotGo.transform, "Icon").gameObject;
Image slotIcon = GetOrAddComponent(iconGo);
GameObject usesGo = GetOrCreateChild(slotGo.transform, "UsesText").gameObject;
TMP_Text usesText = GetOrAddComponent(usesGo);
usesText.text = "0";
GameObject cdGo = GetOrCreateChild(slotGo.transform, "CooldownMask").gameObject;
Image cdMask = GetOrAddComponent(cdGo);
cdMask.type = Image.Type.Filled;
cdMask.fillMethod = Image.FillMethod.Horizontal;
cdMask.fillAmount = 0f;
AssignRef(slotUI, "_icon", slotIcon);
AssignRef(slotUI, "_usesText", usesText);
AssignRef(slotUI, "_cooldownMask", cdMask);
toolSlots[i] = slotUI;
}
AssignArrayRefs(toolHUD, "_slots", toolSlots, report);
// ── Boss 血条(BossHPBar)────────────────────────────────────────
GameObject bossBarGo = GetOrCreateChild(hudRootGo.transform, "BossHPBar").gameObject;
bossBarGo.SetActive(false);
BossHPBar bossHPBar = GetOrAddComponent(bossBarGo);
GameObject bossNameGo = GetOrCreateChild(bossBarGo.transform, "BossName").gameObject;
TMP_Text bossName = GetOrAddComponent(bossNameGo);
bossName.text = "Boss 名称";
GameObject bossHPFillGo = GetOrCreateChild(bossBarGo.transform, "HPFill").gameObject;
Image bossHPFill = GetOrAddComponent(bossHPFillGo);
bossHPFill.type = Image.Type.Filled;
bossHPFill.fillMethod = Image.FillMethod.Horizontal;
bossHPFill.fillAmount = 1f;
GameObject phaseRootGo = GetOrCreateChild(bossBarGo.transform, "PhaseMarkersRoot").gameObject;
AssignRef(bossHPBar, "_bossNameText", bossName);
AssignRef(bossHPBar, "_hpFill", bossHPFill);
AssignRef(bossHPBar, "_phaseMarkersRoot", phaseRootGo.transform);
// ── 状态效果 HUD(StatusEffectHUD)──────────────────────────────
GameObject statusHUDGo = GetOrCreateChild(hudRootGo.transform, "StatusEffectHUD").gameObject;
StatusEffectHUD statusHUD = GetOrAddComponent(statusHUDGo);
GameObject statusTemplateGo = GetOrCreateChild(statusHUDGo.transform, "SlotTemplate").gameObject;
statusTemplateGo.SetActive(false);
GetOrAddComponent(statusTemplateGo);
GameObject statusContainerGo = GetOrCreateChild(statusHUDGo.transform, "Container").gameObject;
var statusLayout = GetOrAddComponent(statusContainerGo);
statusLayout.childForceExpandWidth = false;
statusLayout.spacing = 4f;
AssignRef(statusHUD, "_slotTemplate", statusTemplateGo);
AssignRef(statusHUD, "_container", statusContainerGo.transform);
// ── 任务追踪(QuestTracker)──────────────────────────────────────
GameObject questTrackerGo = GetOrCreateChild(hudRootGo.transform, "QuestTracker").gameObject;
QuestTrackerWidget questWidget = GetOrAddComponent(questTrackerGo);
GameObject questTitleGo = GetOrCreateChild(questTrackerGo.transform, "QuestTitle").gameObject;
TMP_Text questTitle = GetOrAddComponent(questTitleGo);
questTitle.text = "任务名称";
GameObject objTemplateGo = GetOrCreateChild(questTrackerGo.transform, "ObjectiveRowTemplate").gameObject;
objTemplateGo.SetActive(false);
GetOrAddComponent(objTemplateGo);
GameObject objContainerGo = GetOrCreateChild(questTrackerGo.transform, "ObjectiveContainer").gameObject;
var objLayout = GetOrAddComponent(objContainerGo);
objLayout.childForceExpandWidth = false;
objLayout.spacing = 2f;
AssignRef(questWidget, "_questTitleText", questTitle);
AssignRef(questWidget, "_objectiveRowTemplate", objTemplateGo);
AssignRef(questWidget, "_objectiveContainer", objContainerGo.transform);
// ── HUDController 子组件引用 ──────────────────────────────────────
AssignRef(hudCtrl, "_hpContainer", hpContainerGo.transform);
AssignRef(hudCtrl, "_soulGaugeFill", soulFill);
AssignRef(hudCtrl, "_spiritGaugeFill", spiritFill);
AssignRef(hudCtrl, "_lingZhuText", lingZhuText);
AssignRef(hudCtrl, "_springContainer", springContainerGo.transform);
AssignRef(hudCtrl, "_interactPromptWidget", interactWidget);
AssignArrayRefs(hudCtrl, "_formIcons", formImages, report);
// ── HP 格子 / 回春图标 Prefab(自动创建并绑定,无需手工补)──────────
GameObject hpCellPrefab = EnsureHUDIconPrefab("UI_HUD_HPCell",
new Color32(0xD8, 0x3A, 0x3A, 0xFF), new Vector2(40, 40), report); // 面具红
GameObject springPrefab = EnsureHUDIconPrefab("UI_HUD_SpringIcon",
new Color32(0x4A, 0xC8, 0xF0, 0xFF), new Vector2(32, 32), report); // 灵泉青
AssignRef(hudCtrl, "_hpCellPrefab", hpCellPrefab);
AssignRef(hudCtrl, "_springIconPrefab", springPrefab);
// ── 事件频道 ──────────────────────────────────────────────────────
AssignAsset(hudCtrl, "_onHPChanged", report, true, "EVT_HPChanged", "EVT_PlayerHPChanged");
AssignAsset(hudCtrl, "_onMaxHPChanged", report, false, "EVT_MaxHPChanged", "EVT_PlayerMaxHPChanged");
AssignAsset(hudCtrl, "_onSoulPowerChanged", report, false, "EVT_SoulPowerChanged", "EVT_MagicPowerChanged");
AssignAsset(hudCtrl, "_onSpiritPowerChanged", report, false, "EVT_SpiritPowerChanged");
AssignAsset(hudCtrl, "_onLingZhuChanged", report, false, "EVT_LingZhuChanged", "EVT_CurrencyChanged");
AssignAsset(hudCtrl, "_onSpringChargesChanged", report, false, "EVT_SpringChargesChanged", "EVT_SpringCharges");
AssignAsset(hudCtrl, "_onFormChanged", report, false, "EVT_FormChanged");
AssignAsset(interactWidget, "_onShowPrompt", report, false, "EVT_ShowInteractPrompt", "EVT_InteractPromptShow");
AssignAsset(interactWidget, "_onHidePrompt", report, false, "EVT_HideInteractPrompt", "EVT_InteractPromptHide");
AssignAsset(bossHPBar, "_onBossFightToggled", report, false, "EVT_BossFightToggled", "EVT_BossFightStarted");
AssignAsset(bossHPBar, "_onBossHPChanged", report, false, "EVT_BossHPChanged");
AssignAsset(bossHPBar, "_onBossHPMaxSet", report, false, "EVT_BossHPMaxSet");
AssignAsset(bossHPBar, "_onBossNameSet", report, false, "EVT_BossNameSet");
AssignAsset(bossHPBar, "_onBossPhaseThreshold", report, false, "EVT_BossPhaseThreshold");
AssignAsset(statusHUD, "_onStatusEffectApplied", report, false, "EVT_StatusEffectApplied");
AssignAsset(statusHUD, "_onStatusEffectExpired", report, false, "EVT_StatusEffectExpired");
AssignAsset(questWidget, "_onQuestStarted", report, false, "EVT_QuestStarted");
AssignAsset(questWidget, "_onQuestCompleted", report, false, "EVT_QuestCompleted");
AssignAsset(questWidget, "_onQuestFailed", report, false, "EVT_QuestFailed");
AssignAsset(questWidget, "_onQuestAbandoned", report, false, "EVT_QuestAbandoned");
AssignAsset(questWidget, "_onQuestReadyToComplete", report, false, "EVT_QuestReadyToComplete");
AssignAsset(questWidget, "_onObjectiveBatchUpdated", report, false, "EVT_QuestObjectiveBatchUpdated");
AssignAsset(toolHUD, "_onToolUsed", report, false, "EVT_ToolUsed");
// ── 手工步骤说明 ──────────────────────────────────────────────────
// _hpCellPrefab / _springIconPrefab 已自动创建并绑定(占位红/青方块,美术可替换)。
report.Add("BossHPBar._phaseMarkerPrefab:请将阶段标记点 Prefab 赋给该字段。");
report.Add("StatusEffectHUD._slotConfigs:请在 Inspector 中配置各状态效果的图标映射。");
Undo.CollapseUndoOperations(undoGroup);
MarkDirtyAndLog("HUD Canvas 脚手架", canvasGo, report);
}
// ─────────────────────────────────────────────────────────────────────
// Private helpers
// ─────────────────────────────────────────────────────────────────────
///
/// 创建(或复用)一个 HUD 图标 Prefab(含 RectTransform + Image + LayoutElement),
/// 用作 HUDController._hpCellPrefab / _springIconPrefab。占位纯色方块,美术后续可替换 sprite。
/// 路径:Assets/_Game/Prefabs/UI/{prefabName}.prefab(符合 AssetFolderSpec.md §4 UI 前缀)。
///
private static GameObject EnsureHUDIconPrefab(string prefabName, Color color, Vector2 size, List report)
{
const string uiDir = "Assets/_Game/Prefabs/UI";
string path = $"{uiDir}/{prefabName}.prefab";
var existing = AssetDatabase.LoadAssetAtPath(path);
if (existing != null) return existing;
if (!AssetDatabase.IsValidFolder("Assets/_Game/Prefabs"))
AssetDatabase.CreateFolder("Assets/_Game", "Prefabs");
if (!AssetDatabase.IsValidFolder(uiDir))
AssetDatabase.CreateFolder("Assets/_Game/Prefabs", "UI");
var go = new GameObject(prefabName, typeof(RectTransform));
var rt = go.GetComponent();
rt.sizeDelta = size;
var img = go.AddComponent();
img.color = color;
var le = go.AddComponent();
le.preferredWidth = size.x;
le.preferredHeight = size.y;
var prefab = PrefabUtility.SaveAsPrefabAsset(go, path);
Object.DestroyImmediate(go);
report.Add($"已自动创建 HUD 图标 Prefab:{path}(占位纯色,美术可替换)。");
return prefab;
}
///
/// 在活动场景中查找或创建 HUD Canvas。
/// 查找顺序:
/// 1. 遍历场景根节点中的直接同名节点
/// 2. 遍历各根节点下的 [UI]/UIRoot/{name} 或 UIRoot/{name} 子路径
/// 若均不存在则新建 Canvas,并优先挂载至 UIRoot(兼容 ScaffoldPersistentScene 层级);
/// 若场景中不存在 UIRoot,则回退到场景根层并发出警告。
///
private static GameObject GetOrCreateRootCanvas(string name, int sortOrder)
{
Scene scene = SceneManager.GetActiveScene();
// ── Phase 1: 搜索已存在的 Canvas ─────────────────────────────────
foreach (GameObject root in scene.GetRootGameObjects())
{
if (root.name == name)
return root;
// 兼容 ScaffoldPersistentScene 层级:[Persistent] ▸ [UI] ▸ UIRoot ▸ HUD Canvas
foreach (string path in new[] { $"[UI]/UIRoot/{name}", $"UIRoot/{name}", name })
{
Transform found = root.transform.Find(path);
if (found != null) return found.gameObject;
}
}
// ── Phase 2: 定位 UIRoot(Canvas 的正确父节点)───────────────────
Transform uiRoot = null;
foreach (GameObject root in scene.GetRootGameObjects())
{
uiRoot = root.transform.Find("[UI]/UIRoot")
?? root.transform.Find("UIRoot");
if (uiRoot != null) break;
}
if (uiRoot == null)
Debug.LogWarning(
"[HUDScaffold] 未找到 UIRoot(期望路径:[Persistent]/[UI]/UIRoot)。" +
"将在场景根层创建 HUD Canvas。建议先执行 BaseGames/Scene/Setup/Scaffold Persistent Scene。");
// ── Phase 3: 创建 Canvas 并挂载至正确父节点 ──────────────────────
GameObject canvasGo = new GameObject(name);
Undo.RegisterCreatedObjectUndo(canvasGo, $"Create {name}");
if (uiRoot != null)
canvasGo.transform.SetParent(uiRoot, false);
Canvas canvas = canvasGo.AddComponent