- Add RoomStreamingManager to manage room loading and unloading based on player proximity. - Create StreamingBudgetConfigSO for memory and performance budgeting of the streaming system. - Introduce TransitionDirector to handle seamless and atmospheric fade transitions between rooms. - Develop WorldGraph to represent room connectivity and facilitate neighbor queries and distance calculations. - Implement RoomNode and RoomEdge classes to structure room data and connections.
175 lines
12 KiB
C#
175 lines
12 KiB
C#
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
|
||
{
|
||
// ── 已知标签白名单 ────────────────────────────────────────────────────────
|
||
// AddressableRuleSyncWindow 用此白名单区分:
|
||
// • 规则要求但缺失 → 红色,必须补齐
|
||
// • 规则不要求但存在且不在白名单 → 黄色警告(自定义标签,建议写进规范)
|
||
// • 规则不要求但存在且在白名单 → 白色,合法的人工附加标签
|
||
// 每次向 AddressablesLabelSpec 新增 Label 时,须同步在此处添加。
|
||
public static readonly HashSet<string> 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)
|
||
("STR_", "Config"), // 流式加载配置 SO(StreamingBudgetConfigSO)
|
||
("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<string, string[]> ExactLabelMap =
|
||
new(StringComparer.Ordinal)
|
||
{
|
||
// Scene_MainMenu 是唯一需要 Preload 的场景
|
||
{ AddressKeys.SceneMainMenu, new[] { AddressKeys.Labels.Preload } },
|
||
// Persistent 场景无需标签(随引擎启动,不通过 label 批量加载)
|
||
{ AddressKeys.ScenePersistent, Array.Empty<string>() },
|
||
// FloatingDamageText 是 Poolable + Preload(UI_ 前缀通常无 label,此处例外)
|
||
{ AddressKeys.PrefabUIFloatingDmgText, new[] { AddressKeys.Labels.Poolable, AddressKeys.Labels.Preload } },
|
||
// FootstepCatalog 是首帧必须可用的配置
|
||
{ AddressKeys.DataFootstepCatalog, new[] { AddressKeys.Labels.Config, AddressKeys.Labels.Preload } },
|
||
// 流式加载预算配置,运行时初始化前必须可用
|
||
{ AddressKeys.DataStreamingBudgetConfig, 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<string>()),
|
||
// ── 玩家 & 武器 ──────────────────────────────────────────────────
|
||
("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<string>()), // 除 FloatingDamageText 外 UI 无默认 label
|
||
// ── 收集物 ──────────────────────────────────────────────────────
|
||
("COL_", new[] { AddressKeys.Labels.Poolable, AddressKeys.Labels.Preload }),
|
||
// ── 配置数据 ─────────────────────────────────────────────────────
|
||
("CHM_", new[] { AddressKeys.Labels.Charms }),
|
||
("MAP_", new[] { AddressKeys.Labels.Config }), // 地图数据 SO 为动态加载配置
|
||
("STR_", new[] { AddressKeys.Labels.Config }), // 流式加载配置 SO(StreamingBudgetConfigSO)
|
||
("Config/", new[] { AddressKeys.Labels.Config }),
|
||
// ── 技能 / 法术 / 能力 / 世界物件 / 持久化:无批量加载需求,不加 Label ──
|
||
("SKL_", Array.Empty<string>()),
|
||
("SPL_", Array.Empty<string>()),
|
||
("ABL_", Array.Empty<string>()),
|
||
("WLD_", Array.Empty<string>()),
|
||
("SYS_", Array.Empty<string>()),
|
||
};
|
||
|
||
// ── 公开 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>();
|
||
}
|
||
}
|
||
}
|