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

  游戏中角色拥有的属性值很多,运营多年的游戏,往往会有很多个成长线,每个属性都有可能被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. 翻煎饼 Stacks of Flapjacks

    题意:本题意为煎饼排序,大的放在上面,小的放在下面(此题输入是从上到下输入的),为煎饼排序是通过一系列的"翻转"动作来完成的.翻转动作就是将一个小铲插到一叠煎饼中的某两个煎饼之间, ...

  2. 》》3D轮播

    * { margin: 0; padding: 0; } .slide { position: absolute; top: calc(50% - 150px); left: calc(50% - 3 ...

  3. opencv批处理提取图像的特征

    ____________________________________________________________________________________________________ ...

  4. linux下分析Java程序内存汇总

    使用pmap查看进程内存 执行命令 使用pmap能够查看某一个进程(非java的也能够)的内存使用使用情况, 命令格式: pmap 进程id 演示样例说明 比如执行: pmap 12358 显示结果例 ...

  5. Android4.0-4.4 加入支持状态栏显示耳机图标方法(支持带不带MIC的两种耳机自己主动识别)

    效果如图: 一. 在frameworks/base/packages/SystemUI/res/values/strings.xml 里加入 <string name="headset ...

  6. 【树状数组】POJ 2352 Stars

    /** * @author johnsondu * @time 2015-8-22 * @type Binary Index Tree * ignore the coordinate of y and ...

  7. ASP.NET Core 使用 Alipay.AopSdk.Core 常见问题解答

    1.Alipay.AopSdk.Core.AopException:"您使用的私钥格式错误,请检查RSA私钥配置,charset = UTF-8" 出现这个问题,就是配置不正确.首 ...

  8. python self

    Python要self的理由 Python的类的方法和普通的函数有一个很明显的区别,在类的方法必须有个额外的第一个参数(self),但在调用这个方法的时候不必为这个参数赋值(显胜于隐的引发). Pyt ...

  9. dubbo2.5.6从下载到编译成功并且部署成功过程

    本文基于dubbo2.5.6版本 原文链接:http://www.cnblogs.com/zhuwenjoyce/       1,下载dubbo 首先从 github 下载源代码并阅读 readme ...

  10. 在Azure上部署IPv6的App通过IOS App Store审核

    随着中国企业出海Go Global,越来越多的用户开始在Global Azure部署自己的应用.由于对Global Azure功能和文档的不熟悉,使用过程中或多或少遇到了一些坑.事实上呢,这些并不是坑 ...