手游框架设计<二>
静态数据(配表数据)
在完成数值和数据的配置后,是否需要为每一份配置表编写一次解析代码呢?静态数据的设计主要涉及两个方面的人员:一是设计人员,通常为策划;二是使用人员,一般是程序。对于策划而言,Excel 是最为理想的工具;而对于程序来说,CSV 是性能较好且易于解析的格式。当然,也存在将 Excel 导出为 XML 或 JSON 的情况(个人比较反感 XML)。
常见方案
常见的做法是,每当策划新增一份配表,程序员就需要为该表编写一份专门的解析文件,并为表数据定义数据结构。例如:
struct HeroItem {
int hp;
int atk;
};
typedef std::vector<HeroItem> HeroDB;
void parserHeroDB(const char* buf, HeroDB& db);
改进方案
下面介绍我的方案,通过与策划约定一种方式,程序无需编写解析代码,直接使用数据即可。例如:
print(DataPort.HeroDB[heroId][lv].hp)
这个示例展示了如何查询配表中某个 heroId 的英雄在 lv 等级下的血量。
实现方式
关键在于设计一张描述其他表的表,以此告知解析器如何加载这些表。具体而言,解析器需要了解以下信息:
- 数据类型:每列数据的类型,我将其分为 4 种:数值、字符串、数值数组、字符串数组。
- 索引建立:每一列数据所属的字段,明确哪个字段作为 key,同时考虑支持三维表,即增加一个subkey,一般情况下这就足够了,并且应限制最多为三维,因为维度过多往往意味着设计存在问题。
设计 data_list.csv 表来描述 hero.csv 的解析方式,示例如下:
data_list.csv
| id | file_name | main_key | sub_key | descript | 
|---|---|---|---|---|
| HeroDB | hero.csv | 
hero.csv
| id | level | hp | atk | 
|---|---|---|---|
| 1 | 1 | 10 | 13 | 
| 1 | 2 | 11 | 14 | 
| 1 | 3 | 12 | 15 | 
| 2 | 1 | 20 | 5 | 
| 2 | 2 | 22 | 5 | 
| 2 | 3 | 24 | 5 | 
其中,data_list.csv 中的 descript 里的 0;0;0;0 表示“数值;数值;数值;数值”,恰好对应 hero.csv 中一行数据的解析方式,这里用 0 表示数值解析。
有了上述约定,我们就能够轻松编写一个解析器来完成数据的解析,解析后将数据赋值给 HeroDB,这样就可以直接从中获取数据了。
方案优势
这种做法的优势十分明显。由于数据表经常会发生变动,如果采用常见的方案,每次变动都需要修改数据结构、解析代码以及相应的逻辑,过程极为繁琐。而将这些变化交由导致变化的策划人员自行处理,能够避免浪费程序员的宝贵时间。
