Categories
Game Development

Replica Manager 2

I’m working on the second iteration of the Replica Manager class. For those that don’t know it, this class is supposed to handle the details of sending and synchronizing objects across the network as new systems connect.

My first approach was data based. All the code was in the Replica Manager itself, and it would query an interface to get the data it needed to perform operations, such as serializing objects. In most cases, this worked OK. However, the problem with games is there is always complexity to work around, especially with existing systems, and if cases came up that I didn’t anticipate it was hard to go around the system. One example in particular was the initial object download. The first system would gather up all objects, serialize, and send them to you. However, this didn’t work if objects already existed globally (such as created when the map loaded). It also didn’t work if you cared about what order objects were downloaded in, which you would if they had dependencies on each other (such as cross referencing pointers).

My new approach is more code based, with data queries exposed only for very common or unlikely to change operations. So now the Replica Manager class itself mostly just encodes and decodes queries, and holds the data structures, with the actual functionality in the Replica class, the class you derive your own game objects from. This gave me a degree of flexibility I didn’t have before, because now I can implement complex operations since I know the user can override the code if necessary. One example is having a client locally create an object which is verified by the server, and deleted or synchronized on the client accordingly. Another example is automatically scoping objects with a simple callback, where before you had to do this by writing your own system.

The test case I’m solving is:


// Preexists on all systems, has objectID, still want to register it so I can call serialize automatically
class World {
int totalKills;
}

class Player : public Participant {
Soldier *soldier;
char name[256];
int teamNumber;
char killMessage[256];
int gameInstanceId; // Players may be spread out between multiple game instances
};

class Soldier {
Player *owningPlayer, *lastDamagingPlayer;
Gun gun;
float health; // Sent reliable ordered
float positionX, positionY; // Sent unreliable sequenced
bool isCloaked; // If cloaked, do not send position to non-teammates
};

class Gun {
Soldier *owningSoldier;
Player *owningPlayer;
int ammo;
};

class Bullet {
Gun *firingGun;
Player *owningPlayer;
RakNetTime timeFired;
};

So you have:

1. Classes that have pointers to classes that have pointers to themselves in return
2. The world object is assumed to preexist, so should not be dynamically downloaded, yet still needs to be updated
3. Cloaking, where an object either won’t be serialized at all, or only partly serialized (if a teammate)
4. Pointers which may be NULL
5. Object composition with serialized objects (so their parents should be dynamically downloaded, but not the objects themselves).
6. Serializing the object using a different send type, depending on what part is serialized.

These could all be solved with the old system too, but it was much harder and you had to really understand the code to know what to disable. If I do my job this test case should be solvable trivially.

Leave a Reply

Your email address will not be published. Required fields are marked *