Unity MyFramework 用法说明(三十一):使用 ExcelManager 统一管理游戏配置表
游戏中的怪物、物品、技能、地图等大量数据通常都来自配置表。如果业务代码自己负责读取文件、解析数据和建立索引表格数量一多就会产生大量重复代码。MyFramework 使用ExcelManager、ExcelTable和ExcelData统一管理配置数据并采用文件预加载 数据延迟解析的方式减少启动阶段的集中开销。项目地址https://github.com/ZHOURUIH/MyFramework一、表格系统的基本结构整个系统主要分成三层ExcelManager ↓ ExcelTableTT ↓ ExcelData对应关系可以理解为ExcelManager 管理游戏中的所有表 ExcelMonster 管理 Monster 表 EDMonster 表示 Monster 表中的一行数据例如怪物表中的一条数据public class EDMonster : ExcelDataTEDMonster { public string mName; public ushort mTemplateID; public ushort mLevel; public int mHP; public ushort mMoveSpeed; public override bool read(SerializerRead reader) { bool result base.read(reader); result result reader.readString(out mName); result result reader.read(out mTemplateID); result result reader.read(out mLevel); result result reader.read(out mHP); result result reader.read(out mMoveSpeed); return result; } }ExcelData中已经包含每行数据必须存在的 IDpublic class ExcelData { public int mID; public virtual bool read(SerializerRead reader) { return reader.read(out mID); } }业务代码不负责解析字段这部分代码由表格工具生成。二、注册所有配置表应用层通过ExcelRegister统一注册public static void registeAll() { registeTable( out mExcelMonster, typeof(EDMonster), Monster); registeTable( out mExcelItem, typeof(EDItem), Item); registeTable( out mExcelSkillBullet, typeof(EDSkillBullet), SkillBullet); mExcelManager.resourceAvailable(); }实际注册最终调用mExcelManager.registe( tableName, typeof(T), dataType);框架保存的是数据类型 → ExcelTable因此每种ExcelData都对应唯一的一张表。resourceAvailable()表示资源更新已经结束之后这些表格才允许访问实际文件避免热更新过程中读取到旧数据。三、最常用的 query()查询配置基本只需要EDMonster monster mExcelMonster.query(monsterID);然后直接访问monster.mName; monster.mLevel; monster.mHP; monster.mMoveSpeed;如果 ID 不存在默认会输出错误。某些允许配置不存在的情况可以EDMonster monster mExcelMonster.query( monsterID, false);这种写法比业务层自己维护Dictionaryint, EDMonster更加统一。四、查询整张表需要遍历全部配置时foreach (EDMonster monster in mExcelMonster.queryAll()) { log(monster.mName); }ExcelTableTT内部会建立protected ListT mDataList new();第一次调用queryAll()时才初始化这个列表public ListT queryAll() { if (!mDataAvailable) { initDataList(); } return mDataList; }所以单条查询使用字典整表遍历使用列表两种访问方式都由表格类内部处理。五、配置数据实际上是延迟解析的这是这套系统比较重要的一点。ExcelManager.loadAllAsync()会提前加载 Excel 所在资源包并打开所有.bytes文件Excel/Monster.bytes Excel/Item.bytes Excel/SkillBullet.bytes ...但此时并不会把所有表格全部解析成 C# 对象。真正第一次调用mExcelMonster.query(id);或者mExcelMonster.queryAll();发现当前数据字典为空时才会执行读取 bytes ↓ 解密 ↓ SerializerRead 逐行解析 ↓ 创建 ExcelData ↓ 按照 ID 放入 Dictionary ↓ 释放原始 TextAsset 引用也就是说文件可以提前准备好 数据什么时候使用什么时候解析对于拥有大量配置表的项目不需要启动时一次性解析所有表格。六、为表格增加业务索引默认表格只提供按 ID 查询 遍历全部数据如果业务经常按其他条件查询可以直接继承ExcelTableTT增加缓存。例如怪物图鉴只显示部分怪物public class ExcelMonster : ExcelTableTEDMonster { protected ListEDMonster mPreviewMonsterList; public ListEDMonster getPreviewMonsterList() { if (mPreviewMonsterList null) { initMonsterList(); } return mPreviewMonsterList; } protected void initMonsterList() { mPreviewMonsterList new(); foreach (EDMonster item in queryAll()) { mPreviewMonsterList.addIf( item, item.mDisplayOrderID 0); } mPreviewMonsterList.Sort( (a, b) a.mDisplayOrderID - b.mDisplayOrderID); } }以后业务直接调用mExcelMonster.getPreviewMonsterList();而不是每次打开图鉴都重新筛选整张表。所以ExcelTable不只是数据容器也适合存放针对这张表建立的二级索引。七、配置表还能主动检查错误框架中的表格支持mExcelManager.checkAll();它会调用每张表的checkAllData();例如怪物配置引用了怪物模板mExcelMonsterTemplate.checkData( item.mTemplateID, item.mID, this);技能列表也可以统一检查mExcelMonsterSkill.checkData( item.mSkill, item.mID, this);枚举字段checkEnum( item.mStrengthType, mStrengthType, item.mID);这样很多配置错误可以在开发阶段直接发现引用了不存在的ID 枚举值非法 两个关联列表长度不同 资源路径不存在 路径包含反斜杠或空格而不是等玩家真正进入对应玩法后才报错。八、支持运行时重新加载需要重新读取配置时可以调用mExcelManager.reloadAllAsync( onReloadFinished);重新解析时会尽量保留相同 ID 的ExcelData对象只重新读取其中的数据新增 ID 创建新对象已经删除的 ID 则从数据表中移除。这样其他代码已经保存的配置对象引用不会因为重新加载整张表而全部失效。九、总结MyFramework 的配置表系统主要负责注册所有配置表 统一加载 .bytes 文件 按 ID 查询数据 整表遍历 延迟解析配置 建立业务二级索引 检查配置引用关系 支持配置重新加载业务层最常见的代码最终只剩EDMonster monster mExcelMonster.query(monsterID);配置文件如何加载、什么时候解析、如何建立 ID 索引以及配置之间的引用是否合法都交给ExcelManager和具体的ExcelTable统一处理。对于表格数量越来越多的项目这种方式最大的价值不是“读取 Excel”而是让配置数据拥有统一的生成、加载、查询、检查和更新流程。