H2Engine游戏服务器设计之性质管理器

玩耍服务器设计之性质管理器

  游戏中角色有所的属性值很多,运营多年的玩乐,往往会有那多少个个成才线,每个属性都有可能被N个成长线模块增减数值。举例当角色戴上武器时候hp+100点,卸下武器时HP-100点,那样加减逻辑唯有一处还比较好控制,若是某天有个特殊功效当被某技能攻击时,角色武器会被击落,那样就会油然则生减数值的操作不止一处。如果逻辑处理不当,比如击落的时候从不确切的减数值,再一次穿戴武器就招致属性值加了两边,也就是玩家平时说的刷属性。那种bug对娱乐平衡性影响很大,反响很恶劣,bug又很难被测试发现。本文将介绍一种管理属性的笔触,最大限度的防止此类bug,如若出现bug,也可以很好的排查。

Tinyxml2学习,tinyxml2

转自http://www.360doc.com/content/13/1223/16/3684846\_339528825.shtml,尊重原文

什么是XML?

XML全称EXtensible 马克up
Language,翻译为可增加标记语言,简单来讲就是你可以自定义数据的标识,以此来分别种种不一致的数码,以便于进行数据沟通,例如html就足以领略为一种简易的xml语言。XML文件一般就是一个文本文件,可以动用任何编码。

 

上图就是自身系统中一个xml文件的图标,使用VC2005打开它,你可以看来如下内容:

 

XML也是有那多少个对象组成了,一般的话我们平时应用的类如下:

l TiXmlDocument:文档类,它表示了全部xml文件。

l TiXmlDeclaration:评释类,它代表文件的扬言部分,如上图所示。

l TiXmlComment:注释类,它象征文件的诠释部分,如上图所示。

l
TiXmlElement:元素类,它是文本的主要性部分,并且协助嵌套结构,一般选用那种结构来分类的贮存消息,它能够分包属性类和文本类,如上图所示。

n
TiXmlAttribute/TiXmlAttributeSet:元素属性,它一般嵌套在要素中,用于记录此因素的一部分特性,如上图所示。

n TiXmlText:文本对象,它嵌套在某个元素内部,如上图所示。

 

保存文档对象

 

当然你也足以行使SaveFile()函数来进展另存为,这些函数的本质如下:

bool SaveFile( const std::string& filename ) const

在程序中你可以如下使用:

 

//载入xml文档

TiXmlDocument doc(“tutorial.xml”);

doc.LoadFile();

doc.Print(); //输出文档

cout<<endl;

doc.SaveFile(“tutorial.txt”);

运用记事本打开tutorial.txt,你可以看出如下内容。

 

 

 

回到第二个根元素

 

别的文档对象还提供了一个实用的函数用于再次来到第二个根对象,它可以让你方便的遍历整个文档结构,查找自己索要的数量。函数原形如下:

+TiXmlElement* RootElement()

我们在介绍元素类的时候再详尽介绍它的行使。

声明类

 

在正式的XML文件中,表明为文件的率先项,例如<?xml version=”1.0″
standalone=”yes”?>,声明对象拥有多少个属性值,版本,编码和独立文件宣称

相似的话文档的第一行就是宣称对象,你可以把文档对象的首先身长节点转换为注明对象。

 

//使用TinyXml的扬言对象

TiXmlDeclaration *decl;

decl = doc.FirstChild()->ToDeclaration();

下一场就可以应用它的效果了,它能够让你回到当前的版本,编码等新闻,函数原形如下:

+const char *Version() const

+const char *Encoding() const

+const char *Standalone() const

在先后中您可以如下使用:

 

//使用TinyXml的申明对象

TiXmlDeclaration *decl;

decl = doc.FirstChild()->ToDeclaration();

cout<<“使用TinyXml的注明对象(TiXmlDeclaration)”<<endl;

//输出表明对象对应的xml内容

decl->Print(0,4,&str);

cout<<str<<endl;

//分别出口评释对象的性质

cout<<“版本:”<<decl->Version()<<”
是或不是为绝对文件:”<<decl->Standalone()<<”
编码格局:”<<decl->Encoding()<<endl;

cout<<endl;

 

 

注释类

 

本条类一般为xml数据提供解释表达,在程序中貌似不行使它,由此,那里就不介绍了。

元素类

 

要素为一个容器类,它富有元素名称,并可以分包其他元素,文本,注释和未知节点,那么些目的统称为因素的节点,即节点可以为要素、文本、注释和茫然节点类型。元素也足以涵盖自由个数的属性。

俺们依然以如下的XML代码来表明这一个类的作用。

 

<element attribute=”this a attribute(那是一个品质)” int= “1” float =
“3.14”>

<subelement1>

This a text(那是一个文书)

</subelement1>

<subelement2/>

<subelement3/>

<subelement4/>

</element>

节点名

 

在上边元素的代码中,element为根元素的称谓,你可以经过如下的函数来安装和重临它。

+const std::string& ValueStr() const

+void SetValue( const std::string& _value )

父节点

 

subelement1,subelement2,subelement3,subelement4都是element的子元素,即使当前元素对象的指针指向subelement1,subelement2,subelement3,subelement4,你可以经过Parent()函数来回到指向element对象的指针,Parent()函数的评释如下:

+TiXmlNode* Parent()

子节点

 

经过父节点的指针,你可以遍历所有的子节点。

+TiXmlNode* FirstChild()

+TiXmlNode* FirstChild( const std::string& _value )

地方多少个函数用于再次来到第一身长节点目的的指针,带参数名的格外函数表示回去第三个名为_value的子节点。

+TiXmlNode* LastChild()

+TiXmlNode* LastChild( const std::string& _value )

地方的四个函数用于再次来到最后一个节点目的的指针,带参数名的那么些函数表示回去最终一个名为_value的子节点。

你也得以利用IterateChildren()函数来挨家挨户遍历所有的节点,它们的函数注明如下:

+TiXmlNode* IterateChildren( const TiXmlNode* previous )

+TiXmlNode* IterateChildren( const std::string& _value, const
TiXmlNode* previous )

带参数名的相当函数表示只遍历同名的节点。

编辑子节点

 

你可以插入、删除替换所有的子节点。

+TiXmlNode* InsertEndChild( const TiXmlNode& addThis );

+TiXmlNode* InsertBeforeChild( TiXmlNode* beforeThis, const TiXmlNode&
addThis );

+TiXmlNode* InsertAfterChild( TiXmlNode* afterThis, const TiXmlNode&
addThis );

地点七个函数用于插入节点,InsertEndChild函数让你把新节点插入到终极,InsertBeforeChild和InsertAfterChild函数允许你在指定的节点地点前后插入节点。

+TiXmlNode* ReplaceChild( TiXmlNode* replaceThis, const TiXmlNode&
withThis );

ReplaceChild函数用于替换指定的节点。

+bool RemoveChild( TiXmlNode* removeThis );

RemoveChild函数让您剔除指定的节点。

void Clear();

Clear函数会删除本节点的所有子节点(包蕴子节点蕴含的从子节点),但不会修改本节点。

同级节点

 

 

<element attribute=”this a attribute(那是一个特性)” int= “1” float =
“3.14”>

<subelement1>

This a text(那是一个文书)

</subelement1>

<subelement2/>

<subelement3/>

<subelement4/>

</element>

在上头的xml代码中,subelement1、subelement2、subelement3、subelement4都属于同级节点,大家也提供了相关的函数用于在这一个同级节点中遍历。

+TiXmlNode* PreviousSibling()

+TiXmlNode* PreviousSibling( const std::string& _value )

可以按照当前的节点,重回上一个节点的指针。带参数名的可怜函数表示回去上一个名为_value的节点。

本来你也能够根据方今的节点,重临下一个节点的指针。带参数名的极度函数表示回去下一个名为_value的节点。

+TiXmlNode* NextSibling()

+TiXmlNode* NextSibling( const std::string& _value)

遍历元素

 

要素是一种奇特的节点,以'<‘为开端字符,后接元素名称。函数NextSiblingElement用于重临下一个同级元素,而忽视任何连串的节点。它们的函数注明如下:

+TiXmlElement* NextSiblingElement()

+TiXmlElement* NextSiblingElement( const std::string& _value)

带参数名的越发函数表示回去下一个名为_value的同级元素。

本类也提供了相关的函数,让您回去首个子元素。

+TiXmlElement* FirstChildElement()

+TiXmlElement* FirstChildElement( const std::string& _value )

带参数名的那多少个函数表示回去下一个名为_value的子元素。

要素属性

 

属性一般保存在要素中,它们为运用”=”号连接的三个字符串,左侧的意味属性名,等号左边的表示属性值,平日采取字符串、整数和浮点数等数据类型表示。例如,pi
= 3.14。

您可以经过如下的函数,重临属性值。

+const std::string* Attribute( const std::string& name ) const;

+const std::string* Attribute( const std::string& name, int* i )
const;

+const std::string* Attribute( const std::string& name, double* d )
const;

在上头3个函数中,首个函数使用字符串保存重临的属性值,第一个函数把属性值转换为整数然后回来,第一个函数把属性值转换为浮点数然后回来。不过,第二、两个函数都会以字符串的样式记录属性值,并作为函数的重返值重返。

此外,你也得以拔取模板函数:

+template< typename T > int QueryValueAttribute( const
std::string& name, T* outValue ) const

来回到特点的属性值,它会按照你传入的参数,自动选用恰当数据类型。

除此以外,本类也提供了之类多个函数让您设置属性,参数的连串和重临函数类似。

+void SetAttribute( const std::string& name, const std::string& _value
);

+void SetAttribute( const std::string& name, int _value );

+void SetDoubleAttribute( const char * name, double value );

FirstAttribute和LastAttribute可以让您回去第三个和尾声一个性质,它们的函数注解如下:

+TiXmlAttribute* FirstAttribute()

+TiXmlAttribute* LastAttribute()

RemoveAttribute函数可以让您剔除指定名称的习性,它的函数表明如下:

+void RemoveAttribute( const std::string& name )

要素函数总括

 

ValueStr //重回元素名称

SetValue //设置元素名称

Parent //重临父节点对象

FirstChild //重返第三个头节点

LastChild //重回最终一个子节点

IterateChildren //再次来到下一个子节点

InsertEndChild //在最终一个子节点后插入子节点

InsertBeforeChild //在指定的子节点前插入子节点

InsertAfterChild //在指定的子节点后插入子节点

ReplaceChild //替换指定的子节点

RemoveChild //删除指定的子节点

Clear //删除所有的子节点

PreviousSibling //再次回到同级中前一个节点

NextSibling //重返同级中后一个节点

NextSiblingElement //再次来到同级中后一个因素

FirstChildElement //重回首个子元素节点

Attribute //重回元素中的属性值

QueryValueAttribute //再次回到元素中的属性值

SetAttribute //设置元素中的属性值

FirstAttribute //重返元素中第四个属性对象

LastAttribute //重回元素中最终一个属性对象

RemoveAttribute //删除元素中指定的习性对象

属性类

 

特性为名称=”值”对,元素得以有所属性值,但名称必须唯一。

您能够透过

+const std::string& NameTStr() const

回去属性名称

也得以经过上面多少个函数重回属性值:

+const std::string& ValueStr() const

+int IntValue() const;

+double DoubleValue() const;

理所当然你也得以安装属性值,它们的函数表明如下:

+void SetName( const std::string& _name )

+void SetIntValue( int _value );

+void SetDoubleValue( double _value );

+void SetValue( const std::string& _value )

如上函数与元素类中的相关函数类似,那里不重复介绍了。

在要素属性中,平时拥有许多性质,你可以经过Next函数重临下一个特性对象的指针,也足以透过Previous函数得到上一个质量对象的指针。它们的函数注脚如下:

+TiXmlAttribute* Next()

+TiXmlAttribute* Previous()

 

TinyXml使用文档对象模型(DOM)来解析xml文件,这种模型的处理情势为在解析时,五回性的将全方位XML文档举办分析,并在内存中形成对应的树结构,同时,向用户提供一密密麻麻的接口来访问和编排该树结构。那种办法占据内存大,但可以给用户提供一个面向对象的拜会接口,对用户越来越协调,至极便于用户使用。上边大家逐一来介绍各类类的用法。

文档类

 

 

 

文档类代表一个XML文档,通过它,你能够保存,载入和打印输出文档。你可以经过以下措施载入xml文档到TiXmlDocument。

创立文档对象

 

l 创立一个空的文档对象,然后载入一个xml文档

运用到的函数原形如下:

+TiXmlDocument();

+bool LoadFile( const std::string& filename)

在程序中你可以如下使用:

 

//载入xml文档

TiXmlDocument doc();

doc.LoadFile(“tutorial.xml”);

l 2、在构造函数中流传文档的名目,然后调用load函数达成解析载入

运用到的函数原形如下:

+TiXmlDocument( const std::string& documentName );

+bool LoadFile();

在程序中你可以如下使用:

 

//载入xml文档

TiXmlDocument doc(“tutorial.xml”);

doc.LoadFile();

输出文档对象

 

文档类提供了Print()函数用于在控制台出口当前的文档内容,那一个函数的真相如下:

+void Print() const

在程序中你能够如下使用:

 

//载入xml文档

TiXmlDocument doc(“tutorial.xml”);

doc.LoadFile();

doc.Print(); //输出文档

tutorial.xml的内容如下:

 

<?xml version=”1.0″ standalone=”yes” encoding=”utf-8″?>

<!–comment 注释–>

<element attribute=”this a attribute(那是一个特性)” int= “1” float =
“3.14”>

<subelement1>

This a text(那是一个文件)

</subelement1>

<subelement2/>

<subelement3/>

<subelement4/>

</element>

在控制罗利你可以得到如下输出:

鉴于文件使用UTF-8编码,而Windows下的控制台默许使用gb2312编码,由此会生成乱码。

http://www.bkjia.com/cjjc/1214389.htmlwww.bkjia.comtruehttp://www.bkjia.com/cjjc/1214389.htmlTechArticleTinyxml2学习,tinyxml2
转自http://www.360doc.com/content/13/1223/16/3684846\_339528825.shtml,尊重原文
什么是XML? XML全称EXtensible 马克up Language,翻译为可增添…

设计思路

  刷属性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;
    }
    
  2. 至于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

相关文章

Leave a Comment.