配置读取流程
说的透彻一点,配置读取的本质就是把xml文件中的数据读取到程序中定义的数据结构中去。
开始读取配置
首先要提醒的是,在读取的过程中,我们必须做严格的检查,对读取失败的情况给出易读的说明。通常情况,读取失败意味着进程终止。这项工作并不容易,它伴随我们工作的自始至终,我们必须严谨对待。
当程序启动后,首先要做的事情就是加载配置,游戏中的硬编码配置极少,大部分都是通过配置文件来获取的。
配置之间也会有依赖关系,比如大部分的逻辑配置都依赖于物品配置,解决依赖的办法就是让物品配置先于逻辑配置加载。在World::InitGameWorld
方法中就是全部游戏配置的加载步骤,其调用顺序是:
ServerLogic::Start
World::Start
World::InitGameWorld
...
LogicConfigManager::Init
yourconfig::Init // 你自己要实现的配置
我们大部分的配置都属于逻辑配置,它们被统一放在了(LogicConfigManager)里面管理。
定义配置结构体
我们需要把一张配置表的内容抽象成一个数据结构。我们的配置表就是一张行列二维表,因此用数组抽象是最合适的。
比如,有这样的配置结构:
cs | cs | cs | cs | cs |
---|---|---|---|---|
宠物id | 激活所需物品 | 攻击 | 防御 | 血量 |
id | active_item_id | gongji | fangyu | maxhp |
0 | 26000 | 100 | 100 | 100 |
1 | 26001 | 200 | 200 | 200 |
2 | 26002 | 300 | 300 | 300 |
前两行是说明,真正有效的内容从第三行开始。cs代表配置内容生成到客户端(c)和服务端(s)。中文的说明是给策划看的。
我们可以用这样的数据结构来抽象这个配置:
struct PetConfigItem
{
int id = 0; // 宠物id
ItemID active_item_id = 0; // 激活所需物品
int gongji = 0; // 攻击
int fangyu = 0; // 防御
int maxhp = 0; // 血量
};
static const int MAX_PET_COUNT = 8;
PetConfigItem m_petconfig_list[MAX_PET_COUNT]; // 宠物配置
注意,PetConfigItem里的类内初始值是C++11的语法,部分项目并不支持,这时候应该用构造函数列表初始化。忘记初始化将是一种严重的编程错误,请务必牢记!
Question
以上配置结构的定义还有什么问题,有哪些优化方法?
提供获取配置的接口
配置读取完毕后,必须提供一个接口访问配置内容。这个接口的返回值必须是一个const指针(或引用)。
比如要获取一个宠物配置项,可以这样编写接口:
inline bool IsValidPetID(int id)
{
// 保证是合法的、不会导致数组越界的id
// ...
}
const PetConfigItem* GetPetConfigItem(int id)
{
if (IsValidPetID(id))
{
return &m_petconfig_list[id];
}
return nullptr;
}
在实际编程中,我建议把这样有关数组下标判断的工作放到一个简单的inline函数中去。因为你可能在许多地方要做这样的判断工作,而且一旦出现错误,后果非常严重。