feat: Addressables rules/sync tools, UI fixes, AddressKeys update

- Add AddressableRules.cs: single source of truth for prefix->group and prefix->label rules
- Add AddressableRuleSyncWindow.cs: scan/fix/export-CSV tool (BaseGames > Addressables > Rule Sync)
- AddressableBatchTool.cs: delegate DeriveGroupName to AddressableRules, remove duplicate PrefixGroupMap
- AddressKeys.cs: add Labels constants (Preload, Poolable, Enemy, BGM, SFX, Charms, Config, Weapon)
- Docs/Standards/AddressablesLabelSpec.md: new label naming & assignment spec
- Docs/Standards/AssetFolderSpec.md: update Addressables group strategy section
- SplashScreenController.cs: fix MainMenu loading flow
- BootFlowSetupWizard.cs / SceneScaffoldTools.cs: scene scaffold fixes
- PlayerInputActions: set UI/Point to Pass-Through type
- Persistent.unity: add BootSequencer to auto-load MainMenu on play
- EditorBuildSettings.asset: register scenes for build

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-05-20 11:10:31 +08:00
parent 5fd981f5b9
commit c88d2d0549
46 changed files with 6099 additions and 2588 deletions

View File

@@ -0,0 +1,122 @@
using System;
using System.Collections.Generic;
using BaseGames.Core.Assets;
namespace BaseGames.Editor
{
/// <summary>
/// Addressable 分组与标签的权威规则数据。
/// 规范来源:<c>Docs/Standards/AddressablesLabelSpec.md §3</c> 与
/// <c>Docs/Standards/AssetFolderSpec.md §8</c>。
///
/// <see cref="AddressableBatchTool"/> 和 <see cref="AddressableRuleSyncWindow"/> 均引用此处,
/// 保证两个工具的分组/标签判断完全一致,修改规则时只需改这一处。
/// </summary>
public static class AddressableRules
{
// ── 前缀 → 分组名 ──────────────────────────────────────────────────────
// 规则:按 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"),
("VFX_", "VFX_Common"), // 通用特效组AssetFolderSpec §8.1
("UI_", "UI"),
("COL_", "Collectibles"),
("CHM_", "Config"), // 护身符 SO 归入 Config 组AddressablesLabelSpec §3.9
("Config/", "Config"),
("AUD_", "Audio_Music"),
};
// ── 精确地址 → 标签(优先级高于前缀规则)────────────────────────────────
private static readonly Dictionary<string, string[]> ExactLabelMap =
new(StringComparer.Ordinal)
{
// Scene_MainMenu 是唯一需要 Preload 的场景
{ AddressKeys.SceneMainMenu, new[] { AddressKeys.Labels.Preload } },
// Persistent 场景无需标签(随引擎启动,不通过 label 批量加载)
{ AddressKeys.ScenePersistent, Array.Empty<string>() },
// FloatingDamageText 是 Poolable + PreloadUI_ 前缀通常无 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_", new[] { AddressKeys.Labels.BGM }),
("AUD_SFX_", new[] { AddressKeys.Labels.SFX }),
("AUD_", new[] { AddressKeys.Labels.BGM }), // 未细分音频默认归 BGM
("Scene_", Array.Empty<string>()), // 除 MainMenu 外场景无 label
("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 }),
("VFX_", new[] { AddressKeys.Labels.Poolable, AddressKeys.Labels.Preload }),
("UI_", Array.Empty<string>()), // 除 FloatingDamageText 外 UI 无默认 label
("COL_", new[] { AddressKeys.Labels.Poolable, AddressKeys.Labels.Preload }),
("CHM_", new[] { AddressKeys.Labels.Charms }),
("Config/", new[] { AddressKeys.Labels.Config }),
};
// ── 公开 API ───────────────────────────────────────────────────────────
/// <summary>
/// 根据 Addressable 地址字符串返回期望的分组名称。
/// <list type="bullet">
/// <item><c>Room_Forest_01</c> → <c>Room_Forest</c>(动态计算)</item>
/// <item><c>Boss_CaoZhi</c> → <c>Boss_CaoZhi</c>(动态计算)</item>
/// <item>无匹配前缀时返回 <c>null</c>(调用方可回退到 Default Group</item>
/// </list>
/// </summary>
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;
}
/// <summary>
/// 根据 Addressable 地址字符串返回期望的标签集合。
/// 精确地址匹配优先,其次前缀匹配,均无匹配时返回空数组。
/// </summary>
public static string[] GetExpectedLabels(string address)
{
if (string.IsNullOrEmpty(address)) return Array.Empty<string>();
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<string>();
}
}
}