◆ 持续开发中 · 当前为非稳定版本,如需抢先体验请通过 GitHub Issues 联系作者

配置系统 Config

把 Excel / 模板定义的配置表,在生成期烘焙成不可变 Blob,运行期以世界无关的静态表提供无分配查询。

一句话:编辑期是 ScriptableObject + 生成的代码,运行期只有一个 Blob 指针。

全链路

① 配置模板 ConfigTemplate         定义表结构与字段
        ↓(Editor:代码生成)
② 生成的 C# 类 [CfgAsset]         blittable struct,命名空间 {root}.config
        ↓(Editor:数据编辑器填数据)
③ 配置资产 .asset                 每行一个数据条目
        ↓(Editor:导出)
④ BlobAssetReference<BlobRoot<TRow>>
        ↓(Bootstrap:注册)
⑤ ConfigRegistry 静态表(框架内部,宿主不可见)
        ↓(运行期:查询)
⑥ 玩法 / UI 走门面 {ns}.ConfigManager.{Type}.GetByID(id)

核心类

类位置职责
ConfigTemplateRuntime表结构模板(本身也是配置资产,可在数据编辑器里重开再生成)
{ns}.ConfigManager(生成)ConfigManager.cs宿主唯一查询入口:每类型一个嵌套类,GetByID(id) → 可写 ref
ConfigRegistryRuntime运行期低层注册表——框架内部机制,宿主禁用
ConfigAssetFactoryEditor配置资产唯一创建入口(强制发号)
ConfigExportServiceEditor把数据烘焙成 Blob 并导出(客户端/服务端可分开)

创建配置资产(发号服)

创建任何 [CfgAsset] 配置资产,必须调用 ConfigAssetFactory,禁止 ScriptableObject.CreateInstance<T>() + 手填 ID / GUID——手填会绕过发号服,可能跨工程 / 跨机冲突。

// 在编辑器工具 / 菜单里
ConfigAssetFactory.Create<CharacterConfig>("Boss_01");
ConfigAssetFactory.Create(typeof(CharacterConfig), "Boss_01");
ConfigAssetFactory.CreateMany(typeof(CharacterConfig), "Mob", 10);
⚠
ID / GUID 必须由发号服派发;id == 0 视为未分配。复制资产产生的重复 ID 会被 DuplicateIdGuard 在复制后自动重派发,应用层无需记忆「别复制」。

运行期查询(门面 ConfigManager)

代码生成时为每个配置类型产出嵌套查询类,静态、无 World 依赖、零字符串键:

// ① 按 id 查一行(零拷贝 ref,未命中抛 KeyNotFoundException)
ref var row = ref ConfigManager.FashionRow.GetByID(1001);
int price = row.Price;

// ② 遍历全表:Count 作循环边界,GetByIndex(i) 按行序取(同样零拷贝)
for (int i = 0; i < ConfigManager.FashionRow.Count; i++)
{
    ref var r = ref ConfigManager.FashionRow.GetByIndex(i);
    Debug.Log(r.Price);
}

// ③ 缺失兜底:先预判存在性,再取行
if (ConfigManager.FashionRow.Contains(1001))
{
    ref var safe = ref ConfigManager.FashionRow.GetByID(1001);
}
✕
blob 行必须 ref 访问(EA0001):GetByID 返回的可写 ref 必须整个放进 ref 局部变量。Cfg{Type} 行含 BlobString / BlobArray,其偏移是自相对的——值拷贝到栈上后寻址错位,开发构包直接抛 InvalidOperationException,正式包静默读垃圾。
ref var row = ref ConfigManager.FashionRow.GetByID(1001);   // ✅ 正确
var  row =       ConfigManager.FashionRow.GetByID(1001);     // ❌ EA0001 编译错
CfgFashionRow row; row = ConfigManager.FashionRow.GetByID(1001);  // ❌ 值拷贝,寻址错位

ref 局部变量的硬约束:不能跨方法传递(传 id,方法内部自查)、不能存进字段 / 数组 / List、不能被 lambda 捕获、不能跨帧缓存。

配置引用字段(Reference)

若某字段要引用另一个配置,必须用 ConfigFieldType.Reference(UI 称「配置引用」)直接选目标类型,而非定义 long 手填 ID。导出时在「取成员」里选要落盘的成员(如 id)。引用关系由框架维护,重派发 / 复制不会悬空。

◈
运行期经生成式静态门面 ConfigManager.{Type}.GetByID(id) 取回(零拷贝 ref)。禁止调用 ConfigRegistry——它是框架低层注册表,不受宿主兼容承诺,且历史上曾与门面同名造成 CS0104。

实例基数(Cardinality)

一个类型在工程中能同时存在几个资产实例:

基数含义声明方式
Multiple(默认)可存在任意多个不标 unique
Single全工程唯一[CfgAsset(name, order, unique: true)]
// 全工程唯一的音频总览
[CfgAsset("音频/音频总览", 60, unique: true)]
public class AudioCatalogOverview : ConfigBase { }

// 可多个的角色配置(默认,不写 unique)
[CfgAsset("角色/角色配置", 10)]
public class CharacterConfig : ConfigBase { }

Single 在创建期即强制:已存在即抛 InvalidOperationException,CreateMany 直接不支持;导出期还有第二道查重闸门。

编辑器入口

菜单用途
HYC Framework / Config / Config Window配置窗口
HYC Framework / Config / Data Editor数据编辑器(填表,主力)
HYC Framework / Config / Validate Config配置结构校验

常见坑

  • TRow 必须 blittable:字符串选 BlobString / FixedString,Vector3 → Unity.Mathematics.float3,否则烘焙失败。
  • 门面取行必须 ref var row = ref ...(EA0001),否则寻址错位。
  • GetByID 未命中抛 KeyNotFoundException:宽松语义用 Contains(id) 预判,不要为绕开去调低层 TryGet。
  • 引导期注册完所有表之后再开始查询(Materialize 缓存会被 Register 清空)。
  • 不需要 World 句柄,UI 层可直接查。
◈
字段类型、导出设置、检查规则详见框架 Documentation~/11-字段类型与导出设置.md;blittable 铁律见 99-Blob-与数据约定.md。