using System.Collections.Generic; using UnityEditor; using UnityEngine; using UnityEngine.UI; using TMPro; using BaseGames.UI; using BaseGames.UI.Theme; using BaseGames.Player; using BaseGames.Core.Events; using BaseGames.Localization; using BaseGames.Editor.Localization; namespace BaseGames.Editor.UI { /// /// 能力解锁总览脚手架:生成 UI_AbilityCell + UI_AbilityOverviewPanel 预制件 /// + 默认 UI_AbilityMeta 表(参照 AbilityTypeDrawer 分组)+ ABL_* 中英文案。 /// 面板据 AbilityMetaSO 列出全部能力 + 已解锁(亮)/未解锁(暗+锁)态,订阅 EVT_AbilityUnlocked 实时刷新。 /// /// 菜单:BaseGames/UI/控件库/生成能力解锁总览(预制件 + 默认表) /// public static class UIAbilityOverviewScaffold { private const string PrefabDir = "Assets/_Game/Prefabs/UI"; private const string ControlsDir = "Assets/_Game/Prefabs/UI/Controls"; private const string ConfigDir = "Assets/_Game/Data/UI"; private const string ThemePath = "Assets/_Game/Data/UI/Themes/UI_Theme_Default.asset"; private const string PanelName = "UI_AbilityOverviewPanel"; private const string CellName = "UI_AbilityCell"; private const string MetaName = "UI_AbilityMeta"; // 默认能力项(参照 Editor/Equipment/AbilityTypeDrawer.Groups):type, labelKey, 中文, 英文 private static readonly (AbilityType t, string key, string zh, string en)[] Defaults = { (AbilityType.WallCling, "ABL_WALLCLING", "贴墙悬挂", "Wall Cling"), (AbilityType.WallJump, "ABL_WALLJUMP", "墙跳", "Wall Jump"), (AbilityType.Dash, "ABL_DASH", "冲刺", "Dash"), (AbilityType.DoubleJump, "ABL_DOUBLEJUMP", "二段跳", "Double Jump"), (AbilityType.SuperJump, "ABL_SUPERJUMP", "超级跳", "Super Jump"), (AbilityType.Swim, "ABL_SWIM", "游泳", "Swim"), (AbilityType.DownDash, "ABL_DOWNDASH", "下冲刺", "Down Dash"), (AbilityType.InvincibleDash,"ABL_INVINCIBLEDASH","无敌冲刺","Invincible Dash"), (AbilityType.Spell1, "ABL_SPELL1", "法术槽 1", "Spell Slot 1"), (AbilityType.Spell2, "ABL_SPELL2", "法术槽 2", "Spell Slot 2"), (AbilityType.Spell3, "ABL_SPELL3", "法术槽 3", "Spell Slot 3"), (AbilityType.SpiritForm, "ABL_SPIRITFORM", "灵魄形态", "Spirit Form"), (AbilityType.SpiritDash, "ABL_SPIRITDASH", "灵魄冲刺", "Spirit Dash"), (AbilityType.FormTianHun, "ABL_FORM_TIANHUN", "天魂", "Sky Soul"), (AbilityType.FormDiHun, "ABL_FORM_DIHUN", "地魂", "Earth Soul"), (AbilityType.FormMingHun, "ABL_FORM_MINGHUN", "命魂", "Death Soul"), (AbilityType.Parry, "ABL_PARRY", "弹反", "Parry"), (AbilityType.ChargeAttack, "ABL_CHARGEATTACK", "蓄力攻击", "Charge Attack"), (AbilityType.DownSlash, "ABL_DOWNSLASH", "下斩", "Down Slash"), (AbilityType.Interact, "ABL_INTERACT", "互动", "Interact"), (AbilityType.FastTravel, "ABL_FASTTRAVEL", "快速旅行", "Fast Travel"), }; [MenuItem("BaseGames/UI/控件库/生成能力解锁总览(预制件 + 默认表)")] public static void GeneratePrefab() { EnsureFolder(PrefabDir); EnsureFolder(ControlsDir); EnsureFolder(ConfigDir); var report = new List(); var theme = AssetDatabase.LoadAssetAtPath(ThemePath); var meta = EnsureMeta(report); SeedLocalization(report); var cell = BuildCellPrefab(theme, report); BuildPanelPrefab(theme, meta, cell, report); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); var sb = new System.Text.StringBuilder("[UIAbilityOverview] 能力总览已生成:\n"); foreach (var r in report) sb.AppendLine(" • " + r); sb.AppendLine("策划改 UI_AbilityMeta 增删/重排能力;美术改 UI_AbilityCell / 面板预制件改样式。"); Debug.Log(sb.ToString()); } private static AbilityMetaSO EnsureMeta(List report) { string path = $"{ConfigDir}/{MetaName}.asset"; var meta = AssetDatabase.LoadAssetAtPath(path); bool created = meta == null; if (created) { meta = ScriptableObject.CreateInstance(); AssetDatabase.CreateAsset(meta, path); } if (created) { var so = new SerializedObject(meta); var p = so.FindProperty("_items"); p.arraySize = Defaults.Length; for (int i = 0; i < Defaults.Length; i++) { var el = p.GetArrayElementAtIndex(i); el.FindPropertyRelative("type").enumValueFlag = (int)(uint)Defaults[i].t; el.FindPropertyRelative("labelKey").stringValue = Defaults[i].key; } so.ApplyModifiedPropertiesWithoutUndo(); report.Add($"{path}(默认 {Defaults.Length} 项能力)"); } return meta; } private static void SeedLocalization(List report) { var zh = new Dictionary { { "ABL_OVERVIEW_TITLE", "能力" } }; var en = new Dictionary { { "ABL_OVERVIEW_TITLE", "Abilities" } }; foreach (var d in Defaults) { zh[d.key] = d.zh; en[d.key] = d.en; } 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 AbilityCellView BuildCellPrefab(UIThemeSO theme, List report) { var root = new GameObject(CellName, typeof(RectTransform), typeof(CanvasGroup)); ((RectTransform)root.transform).sizeDelta = new Vector2(180f, 150f); var view = root.AddComponent(); var bg = root.AddComponent(); bg.sprite = Std(); bg.type = Image.Type.Sliced; bg.color = new Color(1f, 1f, 1f, 0.05f); var iconGo = NewUIChild(root.transform, "Icon", out var iconRt); SetRect(iconRt, C(0.5f, 1f), C(0.5f, 1f), C(0.5f, 1f), new Vector2(0f, -50f), new Vector2(72f, 72f)); var icon = iconGo.AddComponent(); icon.preserveAspect = true; icon.raycastTarget = false; var nameGo = NewUIChild(root.transform, "Name", out var nameRt); SetRect(nameRt, C(0.5f, 0f), C(0.5f, 0f), C(0.5f, 0f), new Vector2(0f, 18f), new Vector2(-12f, 44f)); var nameTmp = nameGo.AddComponent(); nameTmp.alignment = TextAlignmentOptions.Center; nameTmp.fontSize = 20f; nameTmp.enableWordWrapping = true; nameTmp.raycastTarget = false; var loc = nameGo.AddComponent(); SetEnum(nameGo.AddComponent(), "_kind", (int)UIThemeRoleKind.Text_Primary); // 锁定遮罩(未解锁时显示):半透明暗罩(美术可替换为锁图标 sprite) var lockGo = NewUIChild(root.transform, "LockedOverlay", out var lockRt); Stretch(lockRt); var lockImg = lockGo.AddComponent(); lockImg.color = new Color(0f, 0f, 0f, 0.55f); lockImg.raycastTarget = false; lockGo.SetActive(false); var so = new SerializedObject(view); so.FindProperty("_icon").objectReferenceValue = icon; so.FindProperty("_label").objectReferenceValue = loc; so.FindProperty("_lockedOverlay").objectReferenceValue = lockGo; so.FindProperty("_group").objectReferenceValue = root.GetComponent(); so.ApplyModifiedPropertiesWithoutUndo(); string path = $"{ControlsDir}/{CellName}.prefab"; var asset = PrefabUtility.SaveAsPrefabAsset(root, path); Object.DestroyImmediate(root); report.Add(path); return asset.GetComponent(); } // ── 面板预制件 ─────────────────────────────────────────────────────── private static void BuildPanelPrefab(UIThemeSO theme, AbilityMetaSO meta, AbilityCellView cell, List report) { var root = new GameObject(PanelName, typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster), typeof(CanvasGroup)); Stretch((RectTransform)root.transform); SetupCanvas(root, 60); var panel = root.AddComponent(); var applier = root.AddComponent(); if (theme != null) AssignRef(applier, "_theme", theme); var overlayGo = NewUIChild(root.transform, "Overlay", out var ovRt); Stretch(ovRt); overlayGo.AddComponent().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(1120f, 760f)); 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, -48f), new Vector2(-60f, 56f)); var title = titleGo.AddComponent(); title.alignment = TextAlignmentOptions.Center; title.fontSize = 40f; title.raycastTarget = false; var titleLoc = titleGo.AddComponent(); SetString(titleLoc, "_key", "ABL_OVERVIEW_TITLE"); SetEnum(titleGo.AddComponent(), "_kind", (int)UIThemeRoleKind.Text_Header); // 能力格容器(网格) var gridGo = NewUIChild(boxGo.transform, "GridContainer", out var gridRt); SetRect(gridRt, C(0.5f, 0.5f), C(0.5f, 0.5f), C(0.5f, 0.5f), new Vector2(0f, 10f), new Vector2(1056f, 600f)); var grid = gridGo.AddComponent(); // 6 列:21 项 → 4 行,落在框内不溢出(美术可在预制件再调列数/格子尺寸) grid.cellSize = new Vector2(160f, 130f); grid.spacing = new Vector2(16f, 14f); grid.childAlignment = TextAnchor.UpperCenter; grid.constraint = GridLayoutGroup.Constraint.FixedColumnCount; grid.constraintCount = 6; 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