游戏服务器设计之属性管理器

  游戏中角色拥有的属性值很多,运营多年的游戏,往往会有很多个成长线,每个属性都有可能被N个成长线模块增减数值。举例当角色戴上武器时候hp+100点,卸下武器时HP-100点,这样加减逻辑只有一处还比较好控制,如果某天有个特殊功能当被某技能攻击时,角色武器会被击落,这样就会出现减数值的操作不止一处。如果逻辑处理不当,比如击落的时候没有恰当的减数值,再次穿戴武器就导致属性值加了两边,也就是玩家经常说的刷属性。这种bug对游戏平衡性影响很大,反响很恶劣,bug又很难被测试发现。本文将介绍一种管理属性的思路,最大限度的避免此类bug,如果出现bug,也能够很好的排查。

设计思路

  刷属性bug的核心原因是某功能的模块数值加了N次,所以各个模块加的属性要被记录,加过了必须不能重复加。设计这样的数据结构。

//!各个属性对应一个总值
//!各个属性对应各个模块的分值
template<typename T>
class PropCommonMgr
{
public:
typedef T ObjType;
typedef int64_t (*functorGet)(ObjType);
typedef void (*functorSet)(ObjType, int64_t);
struct PropGetterSetter
{
PropGetterSetter():fGet(NULL), fSet(NULL){}
functorGet fGet;
functorSet fSet;
std::map<std::string, int64_t> moduleRecord;
};
void regGetterSetter(const std::string& strName, functorGet fGet, functorSet fSet){
PropGetterSetter info;
info.fGet = fGet;
info.fSet = fSet;
propName2GetterSetter[strName] = info;
}
public:
std::map<std::string, PropGetterSetter> propName2GetterSetter;
};
  1. 关于数据结构的get和set,我们为每个属性命名一个名字,这样处理数据的时候会非常方便(比如道具配增加属性等等),角色属性有很多种,这里不能一一定义,所以属性管理器只是映射属性,并不创建属性值。通过regGetterSetter接口,注册get和set的操作映射。为什么不需要提供add和sub接口能,因为add和sub可以通过get和set组合实现。get和set的接口实现如下:
int64_t get(ObjType obj, const std::string& strName) {
typename std::map<std::string, PropGetterSetter>::iterator it = propName2GetterSetter.find(strName);
if (it != propName2GetterSetter.end() && it->second.fGet){
return it->second.fGet(obj);
}
return 0;
}
bool set(ObjType obj, const std::string& strName, int64_t v) {
typename std::map<std::string, PropGetterSetter>::iterator it = propName2GetterSetter.find(strName);
if (it != propName2GetterSetter.end() && it->second.fSet){
it->second.fSet(obj, v);
return true;
}
return false;
}
  1. 关于add和sub,前面提到要避免刷属性,就必须避免重复加属性。所以每个模块再加属性前必须检查一下是否该模块已经加了属性,如果加过一定要先减后加。因为每次模块加属性都记录在属性管理器中,那么减掉的数值一定是正确的。这样可以避免另外一种常见bug,如加了100,减的时候计算错误减了80,也会积少成多造成刷属性。add和sub的代码如下:
int64_t addByModule(ObjType obj, const std::string& strName, const std::string& moduleName, int64_t v) {
typename std::map<std::string, PropGetterSetter>::iterator it = propName2GetterSetter.find(strName);
if (it != propName2GetterSetter.end() && it->second.fGet && it->second.fSet){
int64_t ret =it->second.fGet(obj);
std::map<std::string, int64_t>::iterator itMod = it->second.moduleRecord.find(moduleName);
if (itMod != it->second.moduleRecord.end()){
ret -= itMod->second;
itMod->second = v;
}
else{
it->second.moduleRecord[moduleName] = v;
}
ret += v;
it->second.fSet(obj, ret);
return ret;
}
return 0;
}
int64_t subByModule(ObjType obj, const std::string& strName, const std::string& moduleName) {
typename std::map<std::string, PropGetterSetter>::iterator it = propName2GetterSetter.find(strName);
if (it != propName2GetterSetter.end() && it->second.fGet && it->second.fSet){
int64_t ret =it->second.fGet(obj);
std::map<std::string, int64_t>::iterator itMod = it->second.moduleRecord.find(moduleName);
if (itMod == it->second.moduleRecord.end()){
return ret;
}
ret -= itMod->second;
it->second.moduleRecord.erase(itMod);
it->second.fSet(obj, ret);
return ret;
}
return 0;
}
int64_t getByModule(ObjType obj, const std::string& strName, const std::string& moduleName) {
typename std::map<std::string, PropGetterSetter>::iterator it = propName2GetterSetter.find(strName);
if (it != propName2GetterSetter.end() && it->second.fGet && it->second.fSet){
int64_t ret =it->second.fGet(obj);
std::map<std::string, int64_t>::iterator itMod = it->second.moduleRecord.find(moduleName);
if (itMod != it->second.moduleRecord.end()){
return itMod->second;
}
}
return 0;
}
std::map<std::string, int64_t> getAllModule(ObjType obj, const std::string& strName) {
std::map<std::string, int64_t> ret;
typename std::map<std::string, PropGetterSetter>::iterator it = propName2GetterSetter.find(strName);
if (it != propName2GetterSetter.end() && it->second.fGet && it->second.fSet){
ret = it->second.moduleRecord;
}
return ret;
}

  如上代码所示,addByModule和subByModule必须提供模块名,比如穿装备的时候加血量:addByModule('HP', 'Weapon', 100),而卸下武器的时候只要subByModule('HP', 'Weapon'),因为属性管理器知道减多少。

总结

  1. 属性提供一个名字映射有很多好处,比如装备配属性,buff配属性的,有名字相关联会特别方便
  2. 提供一个get和set接口的映射,这样属性管理器就和具体的对象的属性字段解耦了。即使是现有的功能模块也可以集成这个属性管理器。
  3. 属性的add和sub操作,都在属性管理器中留下记录,这样即使出现问题,通过getByModule getAllModule两个接口亦可以辅助查找问题。
  4. 属性管理已经集成到H2Engine中,github地址: https://github.com/fanchy/h2engine

H2Engine游戏服务器设计之属性管理器的更多相关文章

  1. h2engine游戏服务器设计之聊天室示例

    游戏服务器设计之聊天室示例 简介 h2engine引擎建群以后,有热心网友向我反馈,想尝试h2engine但是没有服务器开发经验觉得无从入手,希望我能提供一个简单明了的示例.由于前一段时间工作实在忙碌 ...

  2. 游戏服务器设计之NPC系统

    游戏服务器设计之NPC系统 简介 NPC系统是游戏中非常重要的系统,设计的好坏很大程度上影响游戏的体验.NPC在游戏中有如下作用: 引导玩家体验游戏内容,一般游戏内有很多主线.支线任务,而任务的介绍. ...

  3. JMeter学习(二十五)HTTP属性管理器HTTP Cookie Manager、HTTP Request Defaults

    Test Plan的配置元件中有一些和HTTP属性相关的元件:HTTP Cache Manager.HTTP Authorization Manager.HTTP Cookie Manager.HTT ...

  4. 【jmeter】HTTP属性管理器HTTP Cookie Manager、HTTP Request Defaults

    Test Plan的配置元件中有一些和HTTP属性相关的元件:HTTP Cache Manager.HTTP Authorization Manager.HTTP Cookie Manager.HTT ...

  5. HTTP属性管理器详解

      1)HTTP Cache Manager 2)HTTP Cookie 管理器 3)HTTP 信息头管理器 4)HTTP 授权管理器 5)HTTP 请求默认值 为什么会有这些http属性的配置元件? ...

  6. HTTP属性管理器 初探

      1)HTTP Cache Manager 2)HTTP Cookie 管理器 3)HTTP 信息头管理器 4)HTTP 授权管理器 5)HTTP 请求默认值 为什么会有这些http属性的配置元件? ...

  7. <转>jmeter(十九)HTTP属性管理器

    本博客转载自:http://www.cnblogs.com/imyalost/category/846346.html 个人感觉不错,对jmeter讲解非常详细,担心以后找不到了,所以转发出来,留着慢 ...

  8. jmeter(十九)HTTP属性管理器

    jmeter是一个开源灵活的接口和性能测试工具,当然也能利用jmeter进行接口自动化测试.在我们利用它进行测试过程中,最常用的sampler大概就是Http Request, 使用这个sampler ...

  9. JMeter学习(二十四)HTTP属性管理器HTTP Cookie Manager、HTTP Request Defaults(转载)

    转载自 http://www.cnblogs.com/yangxia-test Test Plan的配置元件中有一些和HTTP属性相关的元件:HTTP Cache Manager.HTTP Autho ...

随机推荐

  1. HDU 1043 Eight (BFS&#183;八数码&#183;康托展开)

    题意  输出八数码问题从给定状态到12345678x的路径 用康托展开将排列相应为整数  即这个排列在全部排列中的字典序  然后就是基础的BFS了 #include <bits/stdc++.h ...

  2. 单点登录(一)使用Cookie+File实现单点登录

    本文使用Cookies+Filter实现www.taobao.tgb.com 和 www.tianmao.tgb.com的单点登录. 源代码分享:链接: http://pan.baidu.com/s/ ...

  3. UVA 11324 The Largest Clique(强连通分量+缩点DAG的DP)

    题意:给定一个有向图,求出一个最大的结点集,这个节点集中的随意两个点之间至少一个能到达还有一个点. 思路:假设一个点在这个节点集中,那么它所在的强连通分量中的点一定所有在这个节点集中,反之亦然, 求出 ...

  4. ASP.NET WebAPI使用Swagger生成测试文档

    ASP.NET WebAPI使用Swagger生成测试文档 SwaggerUI是一个简单的Restful API测试和文档工具.简单.漂亮.易用(官方demo).通过读取JSON配置显示API .项目 ...

  5. 基于MATLAB边缘检测算子的实现

    基于MATLAB边缘检测算子的实现 作者:lee神 1.   概述 边缘检测是图像处理和计算机视觉中的基本问题,边缘检测的目的是标识数字图像中亮度变化明显的点.图像属性中的显著变化通常反映了属性的重要 ...

  6. intellij idea svn使用一 导入、更新、提交、解决冲突

    大体上是转载,针对版本14有一些特殊的添加. 查看svn的资源库: 下面的多出了一个svn的窗口,在左边有加号可以添加一个svn的库 输入svn的地址,我用的是本地的测试,所以地址为svn://127 ...

  7. 单点登录,session,jsonp(待更新)

    单点登录理解: 单点登录系统设计: ajax跨域:

  8. 《算法 (第4版)》【PDF】下载

    <算法 (第4版)>[PDF]下载链接: https://u253469.ctfile.com/fs/253469-231196349 (第4版)>[PDF]"  TITL ...

  9. Swift 开源项目练习应用

    小的View.动画实现练习 拥有着苹果先天生态优势的Swift自发布以来,各种优秀的开源项目便层出不穷.本文作者站在个人的角度,将2014年Swift开源项目做了一个甄别.筛选,从工具.存储.网络.界 ...

  10. 实现WebSocket和WAMP协议的开源库WampSharp

    Websocket Application Messaging Protocol 协议:https://github.com/wamp-proto/wamp-proto 1. 基础档案 引入: WAM ...