C ++成员变量更改侦听器(100多个类)

时间:2016-09-01 14:25:12

标签: c++ oop mmo

我正在尝试为MMO游戏制作一个架构,我无法弄清楚如何在GameObjects中存储尽可能多的变量而不需要很多调用就可以在同一条线路上发送它们我更新它们的时间。

我现在拥有的是:

Game::ChangePosition(Vector3 newPos) {
    gameobject.ChangePosition(newPos);
    SendOnWireNEWPOSITION(gameobject.id, newPos);
}

它使代码变得垃圾,难以维护,理解和扩展。想想一个冠军的例子:

GameObject Champion example

我必须为每个变量创建很多函数。这只是这个冠军的概括,我可能每个冠军类型/"类"我可能有1-2个其他成员变量。

如果我能够从.NET或类似的东西中获得OnPropertyChange,那将是完美的。我试图猜测的架构可以很好地工作,如果我有类似的东西:

对于惠普:当我更新它时,会自动拨打SendFloatOnWire("HP", hp);

For Position:当我更新它时,自动调用SendVector3OnWire("Position", Position)

对于姓名:当我更新时,自动拨打SendSOnWire("Name", Name);

究竟是什么SendFloatOnWireSendVector3OnWireSendSOnWire?在char缓冲区中序列化这些类型的函数。

或方法2(优先),但可能很昂贵

更新Hp,正常定位,然后每个网络线程勾选扫描服务器上所有已更改变量的GameObject实例并发送它们。

如何在高规模游戏服务器上实施,我有哪些选择?这类案件有用吗?

宏会变得有用吗?我想我已经开发了类似的一些源代码,我认为它使用了宏。

提前谢谢。

编辑:我认为我找到了解决方案,但我不知道它实际上有多强大。我打算去看看,然后看看我的立场。 https://developer.valvesoftware.com/wiki/Networking_Entities

2 个答案:

答案 0 :(得分:1)

方法1:

这种方法可能相对容易"使用通过getter / setter访问的地图来实现。一般的想法是这样的:

class GameCharacter {
    map<string, int> myints; 
    // same for doubles, floats, strings
public: 
    GameCharacter() {
        myints["HP"]=100; 
        myints["FP"]=50;  
    }
    int getInt(string fld) { return myints[fld]; }; 
    void setInt(string fld, int val) { myints[fld]=val; sendIntOnWire(fld,val); }
};

Online demo

如果您希望保留类中的属性,则需要使用指针或成员指针而不是值来映射。在构造时,您将使用相关指针初始化地图。如果您决定更改成员变量,则应始终通过setter进行更改。

你甚至可以进一步抽象你的Champion,使它只是一组属性和行为,可以通过地图访问。这个组件体系结构由Mike McShaffry中的Game Coding Complete公开(对于任何游戏开发者来说都是必读书)。这本书的community site有一些源代码可供下载。您可以查看actor.hactor.cpp文件。不过,我真的建议阅读本书中的完整解释。

组件化的优势在于您可以将网络转发逻辑嵌入到所有属性的基类中:这可以将代码简化一个数量级。

方法2:

我认为基本思想是完全合适的,除了对所有对象的完整分析(或更糟糕的是,传输)将是一种过度杀伤力。

一个不错的替代方案是在更改完成时设置标记,并在传输更改时重置。如果传输标记对象(可能只标记了这些对象的属性),则可以最小化同步线程的工作负载,并通过汇集影响同一对象的多个更改的传输来减少网络开销。

答案 1 :(得分:0)

我得出的总体结论:在我更新位置后再打一次电话,并不是那么糟糕。这段代码更长,但对于不同的动机更好:

  1. 这是明确的。你确切知道发生了什么。
  2. 你不要通过制作各种黑客来减慢代码速度。
  3. 你不会使用额外的记忆。
  4. 我尝试过的方法:

    1. 根据@Christophe的建议,为每种类型制作地图。它的主要缺点是它不容易出错。你可能已经在同一张地图中声明了HP和Hp,它可能会添加另一层问题和挫折,例如为每种类型声明地图,然后在每个变量前面加上mapname。
    2. 使用SIMILAR之类的句子来engine:它为您想要的每个网络变量创建了一个单独的类。然后,它使用模板来包装您声明的基本类型(int,float,bool)以及该模板的扩展运算符。它使用了太多的内存和额外的基本功能调用。
    3. 使用为构造函数中的每个变量添加指针的data mapper,然后使用偏移量发送它们。当我意识到代码开始令人困惑且难以维护时,我提前离开了项目。
    4. 使用每次更改时发送的结构,手动。使用 protobuf 可以轻松完成此操作。扩展结构也很容易。
    5. 每次勾选,生成一个包含类数据的新结构并发送它。这使得非常重要的东西始终保持最新状态,但会占用大量带宽。
    6. 在boost的帮助下使用反射。这不是一个很好的解决方案。
    7. 毕竟,我选择使用4和5的混合物。现在我正在我的游戏中实现它。 protobuf的一个巨大优势是能够从.proto文件生成结构,同时还为您提供结构的序列化。它速度非常快。

      对于出现在子类中的那些特殊命名变量,我有另一个结构。或者,在protobuf的帮助下,我可以拥有一组简单的属性:ENUM_KEY_BYTE VALUE。其中ENUM_KEY_BYTE只是一个字节,引用enum属性,例如IS_FLYINGIS_UPIS_POISONEDVALUE是{{} 1}}。

      我从中学到的最重要的事情就是尽可能多地进行序列化。最好在两端使用更多的CPU,而不是使用更多的输入和输出。

      如果有人有任何问题,请发表评论,我会尽力帮助你。

      ioanb7