using System; using System.Collections.Generic; using BaseGames.Core.Assets; namespace BaseGames.Editor { /// /// Addressable 分组与标签的权威规则数据。 /// 规范来源:Docs/Standards/AddressablesLabelSpec.md §3 与 /// Docs/Standards/AssetFolderSpec.md §8。 /// /// 均引用此处, /// 保证两个工具的分组/标签判断完全一致,修改规则时只需改这一处。 /// public static class AddressableRules { // ── 已知标签白名单 ──────────────────────────────────────────────────────── // AddressableRuleSyncWindow 用此白名单区分: // • 规则要求但缺失 → 红色,必须补齐 // • 规则不要求但存在且不在白名单 → 黄色警告(自定义标签,建议写进规范) // • 规则不要求但存在且在白名单 → 白色,合法的人工附加标签 // 每次向 AddressablesLabelSpec 新增 Label 时,须同步在此处添加。 public static readonly HashSet KnownLabels = new(StringComparer.Ordinal) { AddressKeys.Labels.Preload, AddressKeys.Labels.Poolable, AddressKeys.Labels.Enemy, AddressKeys.Labels.BGM, AddressKeys.Labels.SFX, AddressKeys.Labels.Charms, AddressKeys.Labels.Config, AddressKeys.Labels.Weapon, }; // ── 前缀 → 分组名 ────────────────────────────────────────────────────── // 规则:按 AssetFolderSpec §8.1 Group 划分策略。 // 顺序:更长/更具体的前缀必须排在更短/更泛化的前缀之前,否则短前缀会先匹配。 // 特殊:Room_/Boss_ 地址的分组名在运行时动态计算,见 GetExpectedGroup()。 public static readonly (string Prefix, string Group)[] PrefixGroupMap = { // ── 场景 ───────────────────────────────────────────────────────── ("Scene_", "Scenes"), // ── 玩家 & 武器 ────────────────────────────────────────────────── ("PLY_", "Player"), ("WPN_", "Player"), // 武器与玩家 Prefab 同组(AssetFolderSpec §8.1) // ── 敌人 & 投射物 ──────────────────────────────────────────────── ("ENM_", "Enemies"), ("PROJ_", "Projectiles"), // ── 特效 & UI ──────────────────────────────────────────────────── ("VFX_", "VFX_Common"), // 通用特效(AssetFolderSpec §8.1) ("UI_", "UI"), // ── 收集物 ────────────────────────────────────────────────────── ("COL_", "Collectibles"), // ── 配置数据(更具体的前缀排在泛化前缀之前)────────────────────── ("CHM_", "Config"), // 护身符 SO(AddressablesLabelSpec §3.9) ("SKL_", "Config"), // 技能配置 SO(AssetFolderSpec §4) ("SPL_", "Config"), // 法术配置 SO ("ABL_", "Config"), // 能力配置 SO ("MAP_", "Config"), // 地图数据 SO(AssetFolderSpec §4) ("Config/", "Config"), // 路径前缀配置(AssetFolderSpec §8.2) // ── 音频(AUD_BGM_ / AUD_SFX_ 必须在通配 AUD_ 之前)───────────── ("AUD_BGM_", "Audio_Music"), // BGM 流式音频 ("AUD_SFX_", "Audio_SFX"), // SFX 音效(独立分组便于包体按需加载) ("AUD_", "Audio_Music"), // 未细分音频归 BGM 组 // ── 世界 & 持久化 ──────────────────────────────────────────────── ("WLD_", "World"), // 可交互世界物件 Prefab ("SYS_", "Persistent"), // Persistent 场景管理器 Prefab }; // ── 精确地址 → 标签(优先级高于前缀规则)──────────────────────────────── private static readonly Dictionary ExactLabelMap = new(StringComparer.Ordinal) { // Scene_MainMenu 是唯一需要 Preload 的场景 { AddressKeys.SceneMainMenu, new[] { AddressKeys.Labels.Preload } }, // Persistent 场景无需标签(随引擎启动,不通过 label 批量加载) { AddressKeys.ScenePersistent, Array.Empty() }, // FloatingDamageText 是 Poolable + Preload(UI_ 前缀通常无 label,此处例外) { AddressKeys.PrefabUIFloatingDmgText, new[] { AddressKeys.Labels.Poolable, AddressKeys.Labels.Preload } }, // FootstepCatalog 是首帧必须可用的配置 { AddressKeys.DataFootstepCatalog, new[] { AddressKeys.Labels.Config, AddressKeys.Labels.Preload } }, }; // ── 前缀 → 标签列表 ───────────────────────────────────────────────────── // 顺序:更具体的前缀(AUD_BGM_)在更泛化的前缀(AUD_)之前。 private static readonly (string Prefix, string[] Labels)[] PrefixLabelMap = { // ── 音频(更具体的 AUD_BGM_ / AUD_SFX_ 必须排在 AUD_ 之前)────── ("AUD_BGM_", new[] { AddressKeys.Labels.BGM }), ("AUD_SFX_", new[] { AddressKeys.Labels.SFX }), ("AUD_", new[] { AddressKeys.Labels.BGM }), // 未细分音频默认归 BGM // ── 场景(除 MainMenu 外无 label,由 ExactLabelMap 特殊处理)────── ("Scene_", Array.Empty()), // ── 玩家 & 武器 ────────────────────────────────────────────────── ("PLY_", new[] { AddressKeys.Labels.Preload }), ("WPN_", new[] { AddressKeys.Labels.Weapon, AddressKeys.Labels.Preload }), // ── 敌人 & 投射物 ──────────────────────────────────────────────── ("ENM_", new[] { AddressKeys.Labels.Enemy }), ("PROJ_", new[] { AddressKeys.Labels.Poolable, AddressKeys.Labels.Preload }), // ── 特效 & UI ──────────────────────────────────────────────────── ("VFX_", new[] { AddressKeys.Labels.Poolable, AddressKeys.Labels.Preload }), ("UI_", Array.Empty()), // 除 FloatingDamageText 外 UI 无默认 label // ── 收集物 ────────────────────────────────────────────────────── ("COL_", new[] { AddressKeys.Labels.Poolable, AddressKeys.Labels.Preload }), // ── 配置数据 ───────────────────────────────────────────────────── ("CHM_", new[] { AddressKeys.Labels.Charms }), ("MAP_", new[] { AddressKeys.Labels.Config }), // 地图数据 SO 为动态加载配置 ("Config/", new[] { AddressKeys.Labels.Config }), // ── 技能 / 法术 / 能力 / 世界物件 / 持久化:无批量加载需求,不加 Label ── ("SKL_", Array.Empty()), ("SPL_", Array.Empty()), ("ABL_", Array.Empty()), ("WLD_", Array.Empty()), ("SYS_", Array.Empty()), }; // ── 公开 API ─────────────────────────────────────────────────────────── /// /// 根据 Addressable 地址字符串返回期望的分组名称。 /// /// Room_Forest_01Room_Forest(动态计算) /// Boss_CaoZhiBoss_CaoZhi(动态计算) /// 无匹配前缀时返回 null(调用方可回退到 Default Group) /// /// public static string GetExpectedGroup(string address) { if (string.IsNullOrEmpty(address)) return null; // Room_/Boss_ 的分组名在地址中动态编码 if (address.StartsWith("Room_", StringComparison.Ordinal)) { var parts = address.Split('_'); return parts.Length >= 2 ? $"Room_{parts[1]}" : "Room_Unknown"; } if (address.StartsWith("Boss_", StringComparison.Ordinal)) { // Boss_CaoZhi → 整个地址即为分组名(与 AssetFolderSpec §8.1 一致) return address; } foreach (var (prefix, group) in PrefixGroupMap) { if (address.StartsWith(prefix, StringComparison.Ordinal)) return group; } return null; } /// /// 根据 Addressable 地址字符串返回期望的标签集合。 /// 精确地址匹配优先,其次前缀匹配,均无匹配时返回空数组。 /// public static string[] GetExpectedLabels(string address) { if (string.IsNullOrEmpty(address)) return Array.Empty(); if (ExactLabelMap.TryGetValue(address, out var exact)) return exact; foreach (var (prefix, labels) in PrefixLabelMap) { if (address.StartsWith(prefix, StringComparison.Ordinal)) return labels; } return Array.Empty(); } } }