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