Unity MyFramework 用法说明(三十一):使用 ExcelManager 统一管理游戏配置表

游戏中的怪物、物品、技能、地图等大量数据通常都来自配置表。如果业务代码自己负责读取文件、解析数据和建立索引,表格数量一多就会产生大量重复代码。

MyFramework 使用 ExcelManagerExcelTableExcelData 统一管理配置数据,并采用文件预加载 + 数据延迟解析的方式减少启动阶段的集中开销。

项目地址:

github.com/ZHOURUIH/My...

一、表格系统的基本结构

整个系统主要分成三层:

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",而是让配置数据拥有统一的生成、加载、查询、检查和更新流程

相关推荐
SmalBox9 小时前
【节点】[Keyword节点]原理解析与实际应用
unity3d·游戏开发·图形学
_zhourui_h_1 天前
Unity MyFramework 用法说明(三十):使用 GlobalTouchSystem 统一处理 UI 与场景交互
unity3d
SmalBox1 天前
【节点】[GetLocalVariable节点]原理解析与实际应用
unity3d·游戏开发·图形学
SmalBox2 天前
【节点】[CustomFunction节点]原理解析与实际应用
unity3d·游戏开发·图形学
_zhourui_h_3 天前
Unity MyFramework 用法说明(二十九):使用 CameraManager 统一管理游戏摄像机
unity3d
SmalBox3 天前
【节点】[Or节点]原理解析与实际应用
unity3d·游戏开发·图形学
_zhourui_h_4 天前
Unity MyFramework 用法说明(二十七):使用 InputSystem 与 KeyMappingSystem 统一管理输入和快捷键
unity3d
_ZHOURUI_H_4 天前
Unity MyFramework 用法说明(二十五):使用 AtlasManager 统一管理图集与 Sprite 引用
ui·unity·游戏引擎·unity3d·游戏开发
_zhourui_h_4 天前
Unity MyFramework 用法说明(二十六):使用 RedPointSystem 构建树形红点逻辑
unity3d