Wednesday, August 31, 2011

Getting game objects working over a network

First, a quick progress update on the project:
- I've got most of the game logic/high-level network code finished. There's still a kink or two I have to work out, and I need to implement a map rotation system, but everything that's needed for a good ol' deathmatch game is there. From here, it's mostly improving the look and feel of the game, and getting new weapons, maps, and stuff in.
- Still nothing in the way of animation, but I intend to change that soon.
- I've decided to give the game a wacky sort of theme. This achieves two goals. First, it makes the game fun to play for people of varying skill levels, one of my original goals in making this game. Second, it makes my game not "just another deathmatch shooter", and makes it different from all the Call of Duties, Halos, and Unreal Tournaments out there.

I'd like to describe my system for managing game object replication. In making any kind of network game, you'll need some way to instruct the client to create objects, destroy objects, and change the current state of objects. We also need to make sure that only certain game objects are replicated. For example, there's no need for the server to spawn little particle effects and replicate that to the client if it's not gameplay-critical. And the process of network replication should be as simple as possible. To solve all these problems, I created a component called NetRep (short for Network Replication).

Any object that can be replicated over the network will need a NetRep component. This component takes the current state of the object as input and produces what is called a "proxy" version of the object. A proxy is a stripped down version of the object, containing just enough information that the client can mimic the server's version. For example, when replicating a player, you can get away with just transmitting his position, velocity, look direction, health, current weapon, and a couple of state flags. That sounds like a lot, but this will be much smaller than the full blown Player! Since the client will only see these other players from the outside, you don't need to transmit everything.

If you add an object server-side to the Scene class, which I use for object management, it will check to see if the object has a NetRep component. If it does, it will automatically inform the server that this object needs to be replicated. The server will then tell the NetRep component to generate a GOCreateInfo structure. This is a superset of the proxy and contains additional information needed to recreate the object from scratch on the client, such as the object's class name. The server then sends all clients a MSG_CREATEOBJECT message, including the creation info. From then on, the server will continue to send clients a proxy of the object at regular intervals (I've found 50 ms to work well). Likewise, when the object is removed from the scene, a MSG_DELETEOBJECT message is sent to all clients.

NetRep contains a couple of default input fields, such as position, velocity, and health, but it also contains a user data field where you can specify your own custom state data. You can also set which input fields you're actually using, so if your object doesn't need its velocity replicated, you can leave it out of the proxy and reduce bandwidth usage.

Once the client receives a proxy of an object, it grabs the NetRep component of that object and calls ApplyProxy() on it. Depending on how it's configured, NetRep will then perform default proxy application, call a custom function specified by the object, or both.

This whole system allows objects to not have to worry about netcode (for the most part) when doing their stuff. For example, if my rocket launcher spawns a rocket, I don't need to keep track of the rocket or inform the server or anything. It's all done for me automatically. I just add it to the scene like it was a single-player game.

There is one aspect of netcode, however, that objects need to be aware of. They must be aware of which side of the network they exist on: server, client, or both (a listen server). This will affect their behavior. For example, firing a gun means very different things between client and server. Both will need to cast a ray, but the server will use it to determine damage, while the client is only concerned with sounds and pretty effects, like sparks and bullet holes. This is simply achieved by including a little field in the object that says which side of the network it's on. This field is set by Scene when the object is added to it.