Wednesday, March 10, 2010

Space Combat Multiplayer

Debugging network code for XNA is a huge pain in the arse! Typically when I'm bug hunting I'll place a breakpoint where I roughly think a bug is taking effect, look at the state of any relavent data, then step through the code until the bug manifests. This doesn't work in XNA. If you leave one player or the other player in the debugger for more than about ten seconds their network connection will drop. This means that I have to then reset the game and re-join the session. It gets quite frustrating spending an hour on a simple one line logic error with about ten minutes of that hour being spent actually debugging with the rest being hitting a breakpoint, checking variables for a few second and having the connection drop requiring yet another restart.

But it was worth it. After much pain multiplayer is now working. I got to show it off to the prof' this Monday. It's still buggy as hell but man, it's satisfying to be able to finally fly around and shoot other people. Even if getting a kill means crashing the program immidiately afterward.

As promised I'm going to talk about how I've implemented multiplayer so far.

The game uses a basic client/server architecture. Whoever starts the game acts as the server and has final say on stuff like where players are and who scored what kills. All other players are clients and passively receive data from the server. The only data sent to the server by the clients are an initial "I'm ready to receive data" signal and player input. The data sent to the players dictates stuff like what direction they're facing and how fast they're going.

There is a bit of hairyness here however. The client needs to know stuff like what graphics to use to display the game. If you remember my earlier entry on multiplayer, XNA allows a game session to have properties, in the form of integers, associated with it. By associating a particular play scenario with a number the client can find out what to load immidiately by checking the properties of the session he's joining.

The whole sequence of joining an active game goes like this

  • The client player chooses a server to join
  • The server notices the new client and creates an object to track what is to be sent to him
  • The client grabs the scenario number from the server's session properties
  • The client then runs the normal load sequence
  • After loading concludes the client tells the server it's ready to receive data
  • In response, the server sends the client basic information on all players (their score, team, assigned spawn points and so on)
  • At this point the client can begin playing. Normally they're waiting to spawn.

Wait... there's no state data sent yet. In fact, if nothing else were sent the player would see nothing except for some background scenery. The magic occurs in the object the server creates to track the data sent to the client.

Each client tracking object has a list of game entities which matter to the clients. The tracking object allows the server to know stuff like whether the client has been informed that the entity exists (or has ceased to exist) and if any state updates are pending. The server sends entity creation data first, updates second and entity destruction data last. The server can also check for certain combinations (e.g. neither creation nor destruction have been sent but both are pending) to save on bandwidth. In the case of the previous example the server would give up on sending either to the client since creation and destruction effectively cancel eachother out.

State updates are a little more complicated than creation and destruction of entities. These are managed by a class called StateItemCollection. StateItems are objects which describe stuff like an entity's position, movement, damage and so on. When some significant change occurs, say the player suddenly accelerates, the associated StateItem flags that interested players should be notified of the change. The client tracking object queries each entity's StateItemCollection for this flag. If any StateItem's flag is set then the server queues up an update for the client. Once the server has queued the update for all relavent players it clears the flag.

There are a couple advantages to using this flagging method. One is that I can allow the server to choose which players receive the state updates. This allows for the server to throttle itself if it's sending too much data, or to skip sending state data that isn't relavent to a particular player. Other advantages lie in the ability to compensate for lag and in merging updates by having later updates replace earlier updates if neither have been sent.

I'll leave you with this screenshot of one (poorly lit) player shooting grey bullets. Notice I have some (glitched) lighting working now :)

No comments:

Post a Comment