C++成员变量变化监听器(100+类)

C++ member variable change listeners (100+ classes)

我正在尝试为 MMO 游戏制作架构,但我无法弄清楚如何在 GameObjects 中存储我需要的尽可能多的变量,而无需大量调用以同时通过线路发送它们什么时候更新它们。

我现在拥有的是:

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

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

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

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

对于 HP:当我更新它时,自动调用 SendFloatOnWire("HP", hp);

对于职位:当我更新它时,自动调用SendVector3OnWire("Position", Position)

对于名称:当我更新它时,自动调用SendSOnWire("Name", Name);

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

或方法 2(首选),但可能很昂贵

正常更新 Hp、Position,然后每个 Network Thread tick 扫描服务器上的所有 GameObject 实例以查找更改的变量并发送它们。

如何在大型游戏服务器上实施?我有哪些选择?对这种情况有什么有用的书吗?

宏会有用吗?我想我接触过一些类似东西的源代码,我认为它使用了宏。

提前致谢。

编辑:我想我找到了解决方案,但我不知道它实际上有多稳健。我将尝试一下,然后看看我的立场。 https://developer.valvesoftware.com/wiki/Networking_Entities

方法一:

这种方法相对 "easy" 可以使用通过 getters/setters 访问的地图来实现。总体思路是这样的:

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

如果您希望将属性保留在您的 class 中,您会选择指向指针或成员指针而不是值的映射。在构建时,您将使用相关指针初始化地图。如果您决定更改成员变量,您应该始终通过 setter。

您甚至可以更进一步,将您的 Champion 抽象为只是属性和行为的集合,可以通过地图进行访问。 Mike McShaffry in Game Coding Complete (a must read book for any game developer). There's a community site 公开了此组件体系结构,供本书下载一些源代码。您可以查看 actor.hactor.cpp 文件。不过,我真的建议阅读书中的完整解释。

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

关于方法二:

我认为基本想法非常合适,除了对所有对象进行完整分析(或更糟糕的是,传输)会有点矫枉过正。

一个不错的替代方法是设置一个标记,该标记在更改完成时设置,并在更改传输时重置。如果您传输标记的对象(并且可能只传输那些标记的属性),您将最大限度地减少同步线程的工作量,并通过集中传输影响同一对象的多个更改来减少网络开销。

我得出的总体结论:在我更新职位后再次打电话,还不错。一行代码比较长,但是针对不同的动机更好:

  1. 这是明确的。你很清楚发生了什么。
  2. 您不会通过各种 hack 来降低代码的运行速度。
  3. 您没有使用额外的内存。

我尝试过的方法:

  1. 按照@Christophe 的建议,为每种类型制作地图。它的主要缺点是不容易出错。您可能已经在同一个地图中声明了 HP 和 Hp,这可能会增加另一层问题和挫败感,例如为每种类型声明地图,然后在每个变量前加上地图名称。
  2. 使用一些东西 SIMILAR to valve's engine:它为您想要的每个网络变量创建了一个单独的 class。然后,它使用一个模板来包装您声明的基本类型(int、float、bool)以及该模板的扩展运算符。它使用了太多内存和对基本功能的额外调用。
  3. 使用 data mapper 为构造函数中的每个变量添加指针,然后将它们与偏移量一起发送。当我意识到代码开始变得混乱且难以维护时,我过早地离开了这个项目。
  4. 使用每次更改时手动发送的结构。这可以通过使用 protobuf 轻松完成。扩展结构也很容易。
  5. 每个报价单,用 classes 的数据生成一个新结构并发送它。这使非常重要的内容始终保持最新状态,但会占用大量带宽。
  6. 在 boost 的帮助下使用反射。这不是一个很好的解决方案。

毕竟,我混合使用了 4 和 5。现在我正在我的游戏中实现它。 protobuf 的一大优势是能够从 .proto 文件生成结构,同时还为您提供结构序列化。速度快得惊人。

对于那些出现在子classes中的特殊命名变量,我制作了另一个结构。或者,在 protobuf 的帮助下,我可以获得一组简单的属性:ENUM_KEY_BYTE VALUE。其中 ENUM_KEY_BYTE 只是一个引用 enumIS_FLYINGIS_UPIS_POISONED 等属性的字节,而 VALUEstring.

我从中学到的最重要的事情是尽可能多地连载。在两端使用更多 CPU 比使用更多 Input&Output 更好。

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

ioanb7