C ++ - variable change variables (more than 100 classes)

I am trying to create an architecture for an MMO game, and I canโ€™t understand how I can store as many variables as I need in GameObjects without having a lot of calls to send them to the wire while updating them.

Now I have:

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

This makes the code garbage, it is difficult to maintain, understand, expand. So think about the Champion example:

GameObject Champion Example

I would need to do many functions for each variable. And this is just a generalization for this Champion, I can have 1-2 other member variables for each type of Champion / class.

It would be ideal if I could OnPropertyChange from .NET or something like that. The architecture I'm trying to guess will work well if I had something similar to:

For HP: when I update it, automatically call SendFloatOnWire("HP", hp);

For position: when I update it, automatically call SendVector3OnWire("Position", Position)

For the name: when I update it, automatically call SendSOnWire("Name", Name);

What is SendFloatOnWire , SendVector3OnWire , SendSOnWire ? Functions that serialize these types in the char buffer.

OR METHOD 2 (Prospective), but can be expensive

Update Hp, the position is usually, and then each network thread checks all instances of GameObject on the server for the changed variables and sends them.

How will this be implemented on a high-performance game server and what are my options? Any useful book for such cases?

Can macros be useful? I think I was hacked into some kind of source code for something like that, and I think it used macros.

Thanks in advance.

EDIT . I think I found a solution, but I do not know how reliable it is. I am going to go at him and see where I will be later. https://developer.valvesoftware.com/wiki/Networking_Entities

+5
source share
2 answers

The general conclusion I came to: Another call after I updated the position is not so bad. This line of code is longer, but better for different motives:

  • This is obvious. You know exactly what is going on.
  • You do not slow down the code by doing all kinds of hacks to make it work.
  • You are not using additional memory.

Methods I tried:

  • Availability of cards for each type, as @Christophe suggests. The main drawback of this was that it was not error prone. You could declare HP and Hp on the same card, and this could add another level of problems and frustrations, for example declaring cards for each type, and then preceding each variable with the name of the card.
  • Using something SIMILAR to the engine valve: he created a separate class for each network variable that you wanted. He then used the template to combine the basic types that you declared (int, float, bool), as well as the extended operators for this template. He used too much memory and extra calls for basic functionality.
  • Using data mapper , which added pointers for each variable in the constructor, and then sent them with an offset. I left the project prematurely when I realized that the code was starting to get confused and hard to maintain.
  • Using a structure that is sent every time something changes is manually. This is easy to do using protobuf . Expanding the structure is also easy.
  • Each tick, create a new structure with data for the classes and submit it. This keeps very important things always relevant, but eats a lot of bandwidth.
  • Use reflection with boost. This was not a great solution.

In the end, I went using a combination of 4 and 5. And now I am implementing it in my game. One huge advantage of protobuf is the ability to generate structures from a .proto file, and it also offers serialization for the structure for you. It is incredibly fast.

For those special name variables that appear in subclasses, I have a different structure. Alternatively, with protobuf I could have an array of properties that are as simple as: ENUM_KEY_BYTE VALUE . Where ENUM_KEY_BYTE is just a byte that refers to enum on properties like IS_FLYING , IS_UP , IS_POISONED and VALUE - string .

The most important thing I learned from this is as much serialization as possible. It is better to use more processors at both ends than to have more Input & Output.

If anyone has any questions, please comment and I will do my best to help you.

ioanb7

0
source

In method 1:

This approach can be relatively โ€œeasyโ€ to implement using cards that are accessed through getters / setters. A general idea would be:

 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

If you prefer to store properties in your class, you should use a map for pointers or pointers of elements instead of values. When building, you then initialized the map with the appropriate pointers. If you decide to change a member variable, you should always go through the installer.

You can even continue and distract your Champion by making it just a collection of properties and behaviors that can be accessed through a map. This component architecture is revealed by Mike McShaffrey in Full Game Encoding (a book must be read for any game developer). There is a community site for a book with some source code to download. You can see actor.h and actor.cpp . However, I really recommend reading the full explanations in the book.

The advantage of componentization is that you can embed the network redirection logic in the base class of all properties: this can simplify your code by an order of magnitude.

According to method 2:

I think the basic idea is perfect, except that a complete analysis (or, even worse, transfer) of all objects would be redundant.

A good alternative would be a marker, which is set when the change is made and reset when the change is transmitted. If you pass marked objects (and perhaps only their marked properties), you should minimize the load on the synchronization stream and reduce network costs by combining several changes that affect the same object.

+1
source

All Articles