- Added RequestTransition method to ISceneService for direct scene transition requests without needing Inspector SO references. - Updated DoorTransition and RoomTransition to utilize the new RequestTransition method via ServiceLocator. - Introduced SceneFadeController to manage scene fade effects during transitions, with event channel integration for fade requests. - Created HUDScaffoldWizard to automate HUD Canvas setup, including various UI elements and event channel bindings. - Updated assembly definitions to include necessary dependencies for new UI components. - Added Streaming assets for budget configuration to optimize scene loading and memory management.
403 lines
23 KiB
C#
403 lines
23 KiB
C#
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
|
||
{
|
||
/// <summary>
|
||
/// HUD 脚手架工具(架构 10_UIModule §HUD)。
|
||
/// 在当前活动场景中生成完整的 HUD Canvas 层级结构并自动绑定已存在的事件频道资产。
|
||
/// 执行路径:BaseGames ▸ Scene ▸ Setup ▸ Scaffold HUD Canvas
|
||
/// </summary>
|
||
public static class HUDScaffoldWizard
|
||
{
|
||
[MenuItem("BaseGames/Scene/Setup/Scaffold HUD Canvas", priority = 203)]
|
||
public static void ScaffoldHUDCanvas()
|
||
{
|
||
var report = new List<string>();
|
||
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<HUDController>(hudRootGo);
|
||
|
||
// 若场景中已存在 UIManager,自动将 _hudRoot 指向本 HUDRoot
|
||
UIManager uiManager = Object.FindFirstObjectByType<UIManager>();
|
||
if (uiManager != null)
|
||
AssignRef(uiManager, "_hudRoot", hudRootGo);
|
||
|
||
// ── HP 区域 ───────────────────────────────────────────────────────
|
||
GameObject hpContainerGo = GetOrCreateChild(hudRootGo.transform, "HP_Container").gameObject;
|
||
var hpLayout = GetOrAddComponent<HorizontalLayoutGroup>(hpContainerGo);
|
||
hpLayout.childForceExpandWidth = false;
|
||
hpLayout.childForceExpandHeight = false;
|
||
hpLayout.spacing = 4f;
|
||
|
||
// ── 魄元(Soul)量条 ──────────────────────────────────────────────
|
||
GameObject soulGaugeGo = GetOrCreateChild(hudRootGo.transform, "Gauge_Soul").gameObject;
|
||
Image soulFill = GetOrAddComponent<Image>(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<Image>(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<TextMeshProUGUI>(lingZhuGo);
|
||
lingZhuText.text = "0";
|
||
|
||
// ── 回春图标(Spring Charges)─────────────────────────────────────
|
||
GameObject springContainerGo = GetOrCreateChild(hudRootGo.transform, "Spring_Container").gameObject;
|
||
var springLayout = GetOrAddComponent<HorizontalLayoutGroup>(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<Image>(iconGo);
|
||
}
|
||
|
||
// ── 交互提示(InteractPrompt)─────────────────────────────────────
|
||
GameObject interactPromptGo = GetOrCreateChild(hudRootGo.transform, "InteractPrompt").gameObject;
|
||
InteractPromptWidget interactWidget = GetOrAddComponent<InteractPromptWidget>(interactPromptGo);
|
||
|
||
GameObject promptRootGo = GetOrCreateChild(interactPromptGo.transform, "Root").gameObject;
|
||
GameObject promptKeyIconGo = GetOrCreateChild(promptRootGo.transform, "KeyIcon").gameObject;
|
||
Image keyIcon = GetOrAddComponent<Image>(promptKeyIconGo);
|
||
GameObject promptLabelGo = GetOrCreateChild(promptRootGo.transform, "LabelText").gameObject;
|
||
TMP_Text promptLabel = GetOrAddComponent<TextMeshProUGUI>(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<SpellSlotWidget>(spellSlotGo);
|
||
|
||
GameObject spellIconGo = GetOrCreateChild(spellSlotGo.transform, "IconImage").gameObject;
|
||
Image spellIcon = GetOrAddComponent<Image>(spellIconGo);
|
||
GameObject spellCdGo = GetOrCreateChild(spellSlotGo.transform, "CooldownFill").gameObject;
|
||
Image spellCd = GetOrAddComponent<Image>(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<ToolHUD>(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<ToolSlotUI>(slotGo);
|
||
|
||
GameObject iconGo = GetOrCreateChild(slotGo.transform, "Icon").gameObject;
|
||
Image slotIcon = GetOrAddComponent<Image>(iconGo);
|
||
GameObject usesGo = GetOrCreateChild(slotGo.transform, "UsesText").gameObject;
|
||
TMP_Text usesText = GetOrAddComponent<TextMeshProUGUI>(usesGo);
|
||
usesText.text = "0";
|
||
GameObject cdGo = GetOrCreateChild(slotGo.transform, "CooldownMask").gameObject;
|
||
Image cdMask = GetOrAddComponent<Image>(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<BossHPBar>(bossBarGo);
|
||
|
||
GameObject bossNameGo = GetOrCreateChild(bossBarGo.transform, "BossName").gameObject;
|
||
TMP_Text bossName = GetOrAddComponent<TextMeshProUGUI>(bossNameGo);
|
||
bossName.text = "Boss 名称";
|
||
GameObject bossHPFillGo = GetOrCreateChild(bossBarGo.transform, "HPFill").gameObject;
|
||
Image bossHPFill = GetOrAddComponent<Image>(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<StatusEffectHUD>(statusHUDGo);
|
||
|
||
GameObject statusTemplateGo = GetOrCreateChild(statusHUDGo.transform, "SlotTemplate").gameObject;
|
||
statusTemplateGo.SetActive(false);
|
||
GetOrAddComponent<Image>(statusTemplateGo);
|
||
GameObject statusContainerGo = GetOrCreateChild(statusHUDGo.transform, "Container").gameObject;
|
||
var statusLayout = GetOrAddComponent<HorizontalLayoutGroup>(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<QuestTrackerWidget>(questTrackerGo);
|
||
|
||
GameObject questTitleGo = GetOrCreateChild(questTrackerGo.transform, "QuestTitle").gameObject;
|
||
TMP_Text questTitle = GetOrAddComponent<TextMeshProUGUI>(questTitleGo);
|
||
questTitle.text = "任务名称";
|
||
GameObject objTemplateGo = GetOrCreateChild(questTrackerGo.transform, "ObjectiveRowTemplate").gameObject;
|
||
objTemplateGo.SetActive(false);
|
||
GetOrAddComponent<TextMeshProUGUI>(objTemplateGo);
|
||
GameObject objContainerGo = GetOrCreateChild(questTrackerGo.transform, "ObjectiveContainer").gameObject;
|
||
var objLayout = GetOrAddComponent<VerticalLayoutGroup>(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);
|
||
|
||
// ── 事件频道 ──────────────────────────────────────────────────────
|
||
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");
|
||
|
||
// ── 手工步骤说明 ──────────────────────────────────────────────────
|
||
report.Add("HUDController._hpCellPrefab:请将 HP 格子 Prefab 赋给该字段。");
|
||
report.Add("HUDController._springIconPrefab:请将回春图标 Prefab 赋给该字段。");
|
||
report.Add("BossHPBar._phaseMarkerPrefab:请将阶段标记点 Prefab 赋给该字段。");
|
||
report.Add("StatusEffectHUD._slotConfigs:请在 Inspector 中配置各状态效果的图标映射。");
|
||
|
||
Undo.CollapseUndoOperations(undoGroup);
|
||
MarkDirtyAndLog("HUD Canvas 脚手架", canvasGo, report);
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────────────────
|
||
// Private helpers
|
||
// ─────────────────────────────────────────────────────────────────────
|
||
|
||
/// <summary>
|
||
/// 在活动场景中查找或创建 HUD Canvas。
|
||
/// 查找顺序:
|
||
/// 1. 遍历场景根节点中的直接同名节点
|
||
/// 2. 遍历各根节点下的 [UI]/UIRoot/{name} 或 UIRoot/{name} 子路径
|
||
/// 若均不存在则新建 Canvas,并优先挂载至 UIRoot(兼容 ScaffoldPersistentScene 层级);
|
||
/// 若场景中不存在 UIRoot,则回退到场景根层并发出警告。
|
||
/// </summary>
|
||
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<Canvas>();
|
||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||
canvas.sortingOrder = sortOrder;
|
||
|
||
CanvasScaler scaler = canvasGo.AddComponent<CanvasScaler>();
|
||
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||
scaler.referenceResolution = new Vector2(1920, 1080);
|
||
|
||
canvasGo.AddComponent<GraphicRaycaster>();
|
||
return canvasGo;
|
||
}
|
||
|
||
private static Transform GetOrCreateChild(Transform parent, string name)
|
||
{
|
||
Transform child = parent.Find(name);
|
||
if (child != null) return child;
|
||
|
||
GameObject go = new GameObject(name);
|
||
Undo.RegisterCreatedObjectUndo(go, $"Create {name}");
|
||
go.transform.SetParent(parent, false);
|
||
return go.transform;
|
||
}
|
||
|
||
private static T GetOrAddComponent<T>(GameObject go) where T : Component
|
||
{
|
||
T c = go.GetComponent<T>();
|
||
return c != null ? c : Undo.AddComponent<T>(go);
|
||
}
|
||
|
||
private static void AssignRef(Object target, string propertyName, Object value)
|
||
{
|
||
var so = new SerializedObject(target);
|
||
var prop = so.FindProperty(propertyName);
|
||
if (prop == null)
|
||
{
|
||
Debug.LogWarning($"[HUDScaffold] 未找到属性 {target.GetType().Name}.{propertyName}", target);
|
||
return;
|
||
}
|
||
prop.objectReferenceValue = value;
|
||
so.ApplyModifiedPropertiesWithoutUndo();
|
||
}
|
||
|
||
private static void AssignArrayRefs<T>(Object target, string propertyName, T[] values, List<string> report)
|
||
where T : Object
|
||
{
|
||
var so = new SerializedObject(target);
|
||
var prop = so.FindProperty(propertyName);
|
||
if (prop == null || !prop.isArray)
|
||
{
|
||
report.Add($"{target.GetType().Name}.{propertyName} 不是可写数组字段。");
|
||
return;
|
||
}
|
||
prop.arraySize = values.Length;
|
||
for (int i = 0; i < values.Length; i++)
|
||
prop.GetArrayElementAtIndex(i).objectReferenceValue = values[i];
|
||
so.ApplyModifiedPropertiesWithoutUndo();
|
||
}
|
||
|
||
private static void AssignAsset(Object target, string propertyName, List<string> report,
|
||
bool required, params string[] candidates)
|
||
{
|
||
Object asset = FindFirstAsset(candidates);
|
||
if (asset == null)
|
||
{
|
||
if (required)
|
||
report.Add($"未找到 {target.GetType().Name}.{propertyName} 所需资产: {string.Join(" / ", candidates)}");
|
||
return;
|
||
}
|
||
AssignRef(target, propertyName, asset);
|
||
}
|
||
|
||
private static Object FindFirstAsset(params string[] candidates)
|
||
{
|
||
foreach (string candidate in candidates)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(candidate)) continue;
|
||
string[] guids = AssetDatabase.FindAssets(candidate);
|
||
foreach (string guid in guids)
|
||
{
|
||
string path = AssetDatabase.GUIDToAssetPath(guid);
|
||
Object asset = AssetDatabase.LoadMainAssetAtPath(path);
|
||
if (asset != null && asset.name == candidate)
|
||
return asset;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
private static void MarkDirtyAndLog(string scaffoldName, GameObject root, List<string> report)
|
||
{
|
||
EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
|
||
Selection.activeGameObject = root;
|
||
|
||
if (report.Count == 0)
|
||
{
|
||
Debug.Log($"[HUDScaffold] {scaffoldName} 完成。", root);
|
||
return;
|
||
}
|
||
|
||
Debug.LogWarning(
|
||
$"[HUDScaffold] {scaffoldName} 完成,以下 {report.Count} 项需手动确认:\n- {string.Join("\n- ", report)}",
|
||
root);
|
||
}
|
||
}
|
||
}
|