using System.Collections.Generic; using UnityEditor; using UnityEngine; using UnityEngine.UI; using TMPro; using BaseGames.UI; using BaseGames.UI.Theme; using BaseGames.Core.Events; using BaseGames.Localization; using BaseGames.Editor.Localization; namespace BaseGames.Editor.UI { /// /// 护符面板脚手架:生成 UI_CharmPanel 预制件(CharmEquipPanel + 已装备区 + 收藏目录 + 凹槽进度条 + 卡片模板)。 /// 数据来源运行期 ServiceLocator → IEquipmentService(EquipmentManager);变更经 EVT_EquipmentChanged 反应式重建。 /// /// 该面板既可 standalone 打开(PanelId.CharmPanel),也由 InventoryHub 内嵌为「护符」Tab(脚手架内嵌时中和 Canvas 链 + 隐藏 chrome)。 /// 美术 → 改 UI_CharmPanel / 卡片样式;策划 → 改护符数据(CharmSO/CharmCatalogSO)。 /// 菜单:BaseGames/UI/控件库/生成护符面板(预制件) /// public static class UICharmScaffold { private const string PrefabDir = "Assets/_Game/Prefabs/UI"; private const string ThemePath = "Assets/_Game/Data/UI/Themes/UI_Theme_Default.asset"; private const string PrefabName = "UI_CharmPanel"; [MenuItem("BaseGames/UI/控件库/生成护符面板(预制件)")] public static void GeneratePrefab() { EnsureFolder(PrefabDir); var report = new List(); var theme = AssetDatabase.LoadAssetAtPath(ThemePath); SeedLocalization(report); BuildPrefab(theme, report); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); var sb = new System.Text.StringBuilder("[UICharm] 护符面板已生成:\n"); foreach (var r in report) sb.AppendLine(" • " + r); sb.AppendLine("运行期读 IEquipmentService(EquipmentManager);重跑「Scaffold Inventory Hub」把它收编为护符 Tab。"); Debug.Log(sb.ToString()); } private static void SeedLocalization(List report) { var zh = new Dictionary { { "MENU_CHARM", "护符" }, { "CHARM_TITLE", "护符" }, { "CHARM_EQUIPPED", "已装备" }, { "CHARM_CATALOG", "收藏" }, { "CHARM_NOTCHES", "凹槽" }, }; var en = new Dictionary { { "MENU_CHARM", "Charms" }, { "CHARM_TITLE", "Charms" }, { "CHARM_EQUIPPED", "Equipped" }, { "CHARM_CATALOG", "Collected" }, { "CHARM_NOTCHES", "Notches" }, }; int added = MergeWriteUI(Language.ChineseSimplified, zh) + MergeWriteUI(Language.English, en); report.Add($"本地化新增 {added} 条(已存在不覆盖)。"); } private static int MergeWriteUI(Language lang, Dictionary kv) { var dict = LocalizationFileIO.Read(lang, LocalizationTable.UI); int added = 0; foreach (var p in kv) if (!dict.ContainsKey(p.Key)) { dict[p.Key] = p.Value; added++; } if (added > 0) LocalizationFileIO.Write(lang, LocalizationTable.UI, dict); return added; } // ── 预制件 ─────────────────────────────────────────────────────────── private static void BuildPrefab(UIThemeSO theme, List report) { var root = new GameObject(PrefabName, typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster), typeof(CanvasGroup)); Stretch((RectTransform)root.transform); SetupCanvas(root, 61); var panel = root.AddComponent(); var applier = root.AddComponent(); if (theme != null) AssignRef(applier, "_theme", theme); // 全屏遮罩(chrome:内嵌时隐藏) var overlayGo = NewUIChild(root.transform, "Overlay", out var overlayRt); Stretch(overlayRt); var overlay = overlayGo.AddComponent(); overlay.color = new Color(0f, 0f, 0f, 0.6f); // 主框 var boxGo = NewUIChild(root.transform, "DialogBox", out var boxRt); SetRect(boxRt, C(0.5f), C(0.5f), C(0.5f), Vector2.zero, new Vector2(1280f, 720f)); var box = boxGo.AddComponent(); box.sprite = Std(); box.type = Image.Type.Sliced; box.color = new Color(0.10f, 0.11f, 0.15f, 0.98f); SetEnum(boxGo.AddComponent(), "_kind", (int)UIThemeRoleKind.Graphic_Background); // 标题 var titleGo = NewUIChild(boxGo.transform, "Title", out var titleRt); SetRect(titleRt, C(0.5f, 1f), C(0.5f, 1f), C(0.5f, 1f), new Vector2(0f, -50f), new Vector2(600f, 60f)); var title = titleGo.AddComponent(); title.alignment = TextAlignmentOptions.Center; title.fontSize = 40f; title.raycastTarget = false; titleGo.AddComponent(); SetString(titleGo.GetComponent(), "_key", "CHARM_TITLE"); SetEnum(titleGo.AddComponent(), "_kind", (int)UIThemeRoleKind.Text_Header); // 凹槽进度条(已用/总数) var notchGo = NewUIChild(boxGo.transform, "NotchBar", out var notchRt); SetRect(notchRt, C(0.5f, 1f), C(0.5f, 1f), C(0.5f, 1f), new Vector2(0f, -110f), new Vector2(420f, 34f)); var notchBg = notchGo.AddComponent(); notchBg.color = new Color(1f, 1f, 1f, 0.08f); notchBg.sprite = Std(); notchBg.type = Image.Type.Sliced; var fillGo = NewUIChild(notchGo.transform, "Fill", out var fillRt); Stretch(fillRt); var notchFill = fillGo.AddComponent(); notchFill.color = new Color(0.85f, 0.80f, 0.55f, 0.85f); notchFill.sprite = Std(); notchFill.type = Image.Type.Filled; notchFill.fillMethod = Image.FillMethod.Horizontal; notchFill.fillOrigin = 0; notchFill.fillAmount = 0f; var notchTextGo = NewUIChild(notchGo.transform, "NotchText", out var notchTextRt); Stretch(notchTextRt); var notchText = notchTextGo.AddComponent(); notchText.alignment = TextAlignmentOptions.Center; notchText.fontSize = 20f; notchText.raycastTarget = false; SetEnum(notchTextGo.AddComponent(), "_kind", (int)UIThemeRoleKind.Text_Primary); // 已装备区(左) var equippedLabel = MakeSectionLabel(boxGo.transform, "EquippedLabel", "CHARM_EQUIPPED", C(0f, 1f), new Vector2(60f, -160f), new Vector2(420f, 34f)); var equippedGo = NewUIChild(boxGo.transform, "EquippedContainer", out var equippedRt); SetRect(equippedRt, C(0f, 1f), C(0f, 1f), C(0f, 1f), new Vector2(40f, -200f), new Vector2(440f, 460f)); var eqGrid = equippedGo.AddComponent(); eqGrid.cellSize = new Vector2(200f, 96f); eqGrid.spacing = new Vector2(8f, 8f); eqGrid.childAlignment = TextAnchor.UpperLeft; eqGrid.constraint = GridLayoutGroup.Constraint.FixedColumnCount; eqGrid.constraintCount = 2; // 收藏目录(右,滚动) var catalogLabel = MakeSectionLabel(boxGo.transform, "CatalogLabel", "CHARM_CATALOG", C(1f, 1f), new Vector2(-490f, -160f), new Vector2(440f, 34f)); var scrollGo = NewUIChild(boxGo.transform, "CatalogScroll", out var scrollRt); SetRect(scrollRt, C(1f, 1f), C(1f, 1f), C(1f, 1f), new Vector2(-40f, -200f), new Vector2(680f, 460f)); var scrollImg = scrollGo.AddComponent(); scrollImg.color = new Color(1f, 1f, 1f, 0.03f); var scroll = scrollGo.AddComponent(); scroll.horizontal = false; scroll.vertical = true; var viewportGo = NewUIChild(scrollGo.transform, "Viewport", out var viewportRt); Stretch(viewportRt); var vpImg = viewportGo.AddComponent(); vpImg.color = new Color(1f, 1f, 1f, 0.01f); viewportGo.AddComponent(); var catalogGo = NewUIChild(viewportGo.transform, "CatalogContainer", out var catalogRt); SetRect(catalogRt, C(0f, 1f), C(1f, 1f), C(0.5f, 1f), Vector2.zero, new Vector2(0f, 0f)); var catGrid = catalogGo.AddComponent(); catGrid.cellSize = new Vector2(200f, 96f); catGrid.spacing = new Vector2(8f, 8f); catGrid.childAlignment = TextAnchor.UpperLeft; catGrid.constraint = GridLayoutGroup.Constraint.FixedColumnCount; catGrid.constraintCount = 3; catGrid.padding = new RectOffset(8, 8, 8, 8); var catFitter = catalogGo.AddComponent(); catFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize; AssignRef(scroll, "m_Content", catalogRt); AssignRef(scroll, "m_Viewport", viewportRt); // 卡片模板(CharmCardView,inactive) var cardGo = BuildCardTemplate(boxGo.transform); // 关闭按钮(chrome:内嵌时隐藏) var closeGo = NewUIChild(boxGo.transform, "Btn_Close", out var closeRt); SetRect(closeRt, C(0.5f, 0f), C(0.5f, 0f), C(0.5f, 0f), new Vector2(0f, 36f), new Vector2(240f, 56f)); var closeImg = closeGo.AddComponent(); closeImg.sprite = Std(); closeImg.type = Image.Type.Sliced; closeImg.color = new Color(1f, 1f, 1f, 0.06f); var closeBtn = closeGo.AddComponent