通用属性 Attr
框架级、per-entity 的基础属性能力:一个属性清单作单一真源,代码生成类型安全的 AttrKey 枚举 + 读写 AttrBag,预设集自动初始化实体。
命名空间 HYC.Framework.Attr。仅 ECS;框架不认识 "HP/ATK" 等具体属性名,也不内置公式 / buff。
Runtime/Attr/ + Editor/Attr/),但沙箱禁 csc、尚未在 Unity 中编译验证。本文档即当前实现的事实说明,供评审;评审通过后提交。设计目标
| 方向 | 解决什么 |
|---|---|
| 单一属性清单 | 属性定义散落各处、易漂移 → 一个 AttributeCatalog 作单一真源 |
| 内置代码生成器 | 手写 key 字符串易拼错 → 由目录生成枚举 + 映射 + 类型安全 bag |
| 预设 + 自动初始化 | 每个实体手写初始化循环 → AttributeSetAsset + AttributeInitSystem 自动灌值 |
| 编辑器 + 校验 | 没有校验/编辑器 → AttributeValidator + 两个 CustomEditor |
用法
1. 定义属性清单
Project 右键 HYC Framework / Attributes / Attribute Catalog 建资产,加条目(key / type / defaultRaw / minRaw / maxRaw)。这是唯一需要手填的地方。也可从菜单 HYC Framework / Attributes / Attribute Catalog Editor 打开独立面板编辑——窗口把 entries 画成类型感知表格(Float 显示小数并自动 bits↔double 转换,Bool 只显示勾选框隐藏 min/max,整数显示整数),行首 ≡ 拖拽重排、行尾 ✕ 删除,底部「+ 添加属性」。
窗口顶部有两个分页:属性目录(上面这张表)与属性组(组装 AttributeSetAsset)。
2.(可选)做预设集
建 HYC Framework / Attributes / Attribute Set,绑定 catalog,勾选「这个实体要哪些属性」;保存时 OnValidate 自动把所选属性烘成 AttributeValue 列表。
3. 生成访问器
在 Catalog 编辑器点「生成属性访问器」,或菜单 HYC Framework / Attributes / Generate Accessor,选 catalog → 向导生成到 Assets/HYC/Generated/Attr/(生成代码统一总根下):
<prefix>AttrKey.cs:枚举(序数 = 目录顺序)<prefix>AttrKeyMap.cs:static ulong[] Hash(=AttributeKey.Hash(key))+AttributeType[] Type<prefix>AttrBag.cs:实现IAttrBag,暴露GetInt(<prefix>AttrKey.ATK)等友好重载
4. 代码里用枚举(零手写 key)
// em, e 来自你的 World
var bag = new GameAttrBag(em, e);
int atk = bag.GetInt(GameAttrKey.ATK); // IntelliSense + 拼错编译期报错
bag.SetInt(GameAttrKey.ATK, atk + 5);
<prefix> 默认 Game,命名空间由「生成代码命名空间 root」派生为 {root}.Attr,可在向导里改。
5. 自动初始化
实体挂 AttributeSetRef { asset = 你的Set } → AttributeInitSystem 进 Play 后自动 AddBuffer<AttributeValue> 并灌入预设值;或手动 AttributeInitializer.InitFromSet(em, e, set)。
6. 校验
Catalog 编辑器点「校验目录」跑 AttributeValidator,命中空 key / 重名 / 非法标识符 / min>max / 默认值越界 / Float bits 为 NaN·Inf 时给出 AttributeIssue。
关键类
| 类型 | 职责 |
|---|---|
AttributeType | enum AttributeType : byte:Int / Float / Long / Bool,决定 raw 解释方式 |
AttributeValue | struct 袋元素:keyHash(FNV-1a) + type + raw(long,Float 存 IEEE754 bits) |
AttributeKey | Hash(string) → ulong(FNV-1a 64 位) |
IAttrBag | ulong-key 类型安全读写契约(Job/Burst 外) |
AttributeStore | 主线程 EntityManager 读写,按 keyHash 线性查找 |
AttributeInitializer | Init<T> / InitFromSet:把默认值钳制后灌入 buffer |
AttrBag | readonly struct 包 (EntityManager, Entity),转调 AttributeStore |
AttributeCatalog | 单一真源;ToValue() 把条目烘成 AttributeValue |
AttributeSetAsset | OnValidate → Rebuild() 把所选属性烘成 List<AttributeValue> |
AttributeInitSystem | partial class SystemBase,每帧查「有 Ref 无 buffer」实体→ AddBuffer+灌值 |
取舍
- 枚举是目录的快照:目录增删属性后必须重新 Generate,否则枚举与运行时袋数据序数错位。keyHash 按名算(非序数),同 key 改名不影响旧袋;但枚举序数变了要重生成。
- 运行期
Set不钳制:袋是运行时真相,配置边界只约束默认值;若需运行期 clamp,由使用方自己包一层。 AttributeSetRef是托管IComponentData(class):不走 Burst/Job,适合低频初始化,不进逐帧热路径。- 完全生成、无
#region USER:生成文件不要手改,改目录后重生成即可整体覆盖。 - keyHash 命中失败是静默的:Get 返调用方默认值、Set 为 no-op(袋即「声明过的属性集合」,不自动扩容)。
坑(实现者须知)
- 改目录后必须重生成:枚举版
AttrBag/AttrKeyMap是生成物的硬编码副本,目录变了不同步会串号。 - Float 存 bits:
AttributeValue.raw对 Float 走BitConverter.DoubleToInt64Bits/Int64BitsToDouble,校验器对 Float 的 defaultRaw/minRaw/maxRaw 会按 double 解释,NaN/Inf 会被AttributeValidator拦下。 AttributeInitSystem的查询用CreateEntityQuery(typeof(AttributeSetRef), ComponentType.Exclude<AttributeValue>()),只处理「还没初始化」的实体(幂等)。ConfigRowRef已从Talent迁至Runtime/ConfigRowRef.cs(HYC.Framework.Config),消除Attr → Talent反向依赖。