The Essentials of Multiplayer Games
or, "Some categorization, problems, solutions, general stuff about multiplayer-games"
by Jered Wierzbicki

There are a few distinct flavors of multiplayer games, all of which require different approaches. On one hand, there're games written for many players, but designed to be played on a single machine. On the other, there are games written for local play, or in general, gameplay across more than one machine with a local connection not reliant on a network (like a null modem cable or dial-up modem game). Finally, there are games which take place over large, wide-area networks. I shall refer to these as single-machine, local, and networked multiplayer games, respectively, from here on in.

In single-machine multiplayer games, the main game loop must call the engine to process each player involved in the game; this includes getting input from them, moving them, rendering their view, and computing any other logic associated with them in the game. In a local multiplayer game, the game loop only needs to process one player, the player on the machine which the game is running, then send information about the player out to the other machines and accept information about the other players (if needed) in return. Networked multiplayer games are similar to local multiplayer games, but a client/server model is generally used whereby each machine transmits its data to one server, then gets information about other players back from the server.

Single-machine multiplayer games

When designing single-machine multiplayer games, which are not very common on PCs these days, you must take into account several questions. The most important of these would be, "How is each player going to be getting input to the game?" and "How is each player going to see their character move, with only one screen?".

Some common approaches include using two input devices, such as a joystick and a keyboard, or sharing one input device, such as two people using one keyboard (there would have to be seperate keys for every action for every player). Trying to share an input device is a very outdated approach, and I'm pretty sure that the gaming community got sick of it early on.

There have been a few more worthy approaches to the display problem. Most single-machine-many-players-at-the-same-time games simply let players share the same view. As a rule, this will work well only if the view isn't first person, and only if (for 3D games) camera control is independent of player positions. In the old side-scroller days, we sometimes saw split screens, wherein each player's view was rendered on a different half of the screen. Split-screens are a big drag on rendering time for any kind of game, and they definitely can make a game cumbersome, but they do work.

About the most effective approach to the display and input problems associated with one-machine mulitplayer games, at least for PCs, is to use a turn-based system. Considering that they're still actually implemented, I'd say that turn-based systems have had the most luck over the ages. Turn-based systems work by switching between player and player and allowing only one player to play at a time.

//A turn-based thingy

//somewhere in a header
player *cur_player;

.....

//somewhere in some source file
void switch_player(player *p) {
     cur_player = p;
     switch_level(p->level);
.....

//in the main loop
move((player *)&cur_player);

Anyone that can write a single-player game and has an understanding of scalable design knows enough to write a single-machine multiplayer game. Because I've covered scalability, and I assume that you can write a game (as a game programmer...), you can write one, if you'd like. I suspect that you wouldn't like to; you're probably more interested in networked games.

Single-machine multiplayer PC games are becoming progressively more extinct, but I find it fit to note that old single-machine techniques aren't useless. Consoles, mind you, are still very big on one-machine multiplayer.

Opinion: Turn-based systems and one-machine single-player games in general are acceptable for "the other" gaming market, that wants to remember what the Atari felt like when they used to pull it out of the closet every other Saturday, but hard-core gamers on modern consoles should be ashamed of it. Hardcore PC gamers might even loathe it, because "MyMachineOnlyMonopoly" is averting the one big advantage of the PC as a gaming platform over everything else; networking, networking, networking. The multiplayer game market paradigm shifted towards interactivity a long, long time ago, and is presently shifting towards networking. Don't overlook it.

Local multiplayer games

Local multiplayer games have been around for quite some time. Support for null-modem play is still very common in today's games. On the contrary, direct machine-to-machine modem connections are a lot less popular than they once were, due to the fact that it gets a bit limiting facing the phone charges associated with long-distance modem calls.

The architecture of a local multiplayer game is fairly simple. Each machine is responsible for updating the game for one player. Each machine then stores the changes in player state in a packet (a packet is data with a header to be transmitted across a connection), which is sent to the other machines. The other machines in the game use this data to update their game-state, and send out information about their own players' movements, and the cycle continues.

Obviously, the specific implementation of this scheme is different between different games: In realtime games, asynchronous packet transmission is a must, and the game has to stay synchronized even if data packets are missed. In non-realtime games, you can rely on sending and receiving packets at a definite time, and you also don't have to worry about overburdening your data stream.

So here's a basic recepie for a local multiplayer game. First, you establish a connection to the other machine(s) in the game. This shouldn't be too hard, considering that a phyisical one (such as a LAN or a null-modem cable, or a direct phone line [modem]) must exist for your game to be considered "local". Then, you somehow negotiate the initial setup of the game. Finally, the game begins, and you begin the data-transfer process just described. Then, of course, you close the "connection". It's conceptually simple.

Here's some code to convey all of this, and a bit more (a very simple example).

//pseudo-code

#define NEW_GAME        2
#define LEAVING_GAME    4
#define SOMETHING       8
#define RESET           16

#define ESCAPE_SEQUENCE 0xFFFFFFFF

#define MAX_QUEUE       16
#define MAX_BUFFER      256

typedef struct {
int obj,x,y,z,frame;  //none of these can be 0xffffffff
input_status i;       //no elements of this can be 0xffffffff
unsigned char flags;  //can't = 255 (0xFF)
int magic;            //always 0xFFFFFFFF to indicate the end of the packet
} game_packet;

game_packet queue[MAX_QUEUE];
int cur_queue=0, unprocessed_packets=0;
int sync_err=0, packets_lost=0; //sync_err is a reserved flag for major
                                //synchronozation errors

unsigned char serial_buffer[MAX_BUFFER];
int cur_buf=0, last=0;

//read this carefully (regarding an imaginary interrupt handler x):

//how it works:
//interrupt-handler x is called when a character is received from the
//serial buffer.  The interrupt handler sets serial_buffer[cur_buf] to the
//character received, increments cur_buf, and performs any necessary bounding
//work (making sure that cur_buf x also tracks
//packet-boundaries; if the last character received was 0xFF and the current
//is also 0xFF, then x will increment the global "last".  Last is tested at
//the end of the ISR;  when last reaches 3, queue[cur_queue] will be filled
//with the last sizeof(packet)-1 bytes in the serial buffer along with the
//current character, unprocessed_packets will be incremented, last will be
//set to 0, cur_buf will be set to 0, and cur_queue will be incremented.  If
//cur_queue > MAX_QUEUE, cur_queue is set to 0 and this entry is used to fill
//the packet; packets_lost is also incremented by 1, and unprocessed_packets
//is set to 1.

//what that does:
//it reads characters from the serial buffer, and when there's a complete
//packet, it fills one element of a recycling queue with the packet

...

void Send_Packet(game_packet *p) {
     ...
     Send_Out_Serial(open_port, p->obj);
     Send_Out_Serial(open_port, p->x);
     Send_Out_Serial(open_port, p->y);
     Send_Out_Serial(open_port, p->z);
     ...
}

void Process_Packets(packet *p) {
     ...
     if(!unprocessed_packets) //no new packets to process, so return
       return;

     asm cli

     for (int index=cur_queue-(unprocessed_packets-1);index <unprocessed_packets;index++) {
         Update_Game(queue[index]);
     }

     cur_queue = 0;
     unprocessed_packets = 0;

     asm sti
     ...
}

//Process_Packets would be called in the main game loop at a certain
//point.  If everything is running okay, the error-handling "features"
//built into it and interrupt-handler x shouldn't have to take over.

void Init_Game(int port) { //where port is a serial port
                           //connected to another machine
     game_packet init;

     init.flags = NEW_GAME | RESET;
     Send_Packet(port, (game_packet*)&init);
     Init_Local_Game();
}

...

//You get the idea?

Networked multiplayer games: Internet intro ala crash-course

RANT: These are the real hot topics these days in the multiplayer game world. Networked multiplayer games can be really, really cool. No longer are you playing a game with your friend sitting in the chair next to you, trying to figure out whose character just moved, no longer are you playing a game against some half-witted FSM, you're playing against a REAL PERSON and you can tell.

Gamers today don't want to simply interact with their games by moving a little model around, and could care less whether or not you used quaternion-based interpolation between keyframes coupled with skeletal-hierarchy animation for your two-hundred polygon optimized character with hardware bilinear-filtered perspective-correct texture mapping applied and high-end LOD processing. Rather, they want to take interaction to a higher level, whether this higher level is achieved via outstanding scene-realism or any other means. The influx of networked-multiplayer games is an exciting manifestation of this.

Networked multiplayer games are conceptually the same as local-multiplayer games, with a few exceptions. First of all, each machine in the game still handles processing for the player using it. However, instead of sending packets out to all of the other players in the game (or just the other player), in a networked game, machines send packets to one central server and receive packets from the server in return. The server is at the center of the game, storing all the game's information and keeping things running. If turn-based systems are used somehow, then the server is responsible for managing them. All the machines connected to the game's server are called clients. This model in general is called the client-server model of communication. Client-server is the communications model used in the real world, so get used to it.

There are many books on client-server and other networking concepts...see http://www.developer.com/reference/r_servers.html for a great online collection.

Networks implement communication protocols in order to give meaning and structure to communications taking place on them. All communications on a network are carried out according to a standard set of these protocols; if you are to use a network effectively, then you must use its communications protocols. The Internet has TCP/IP (Transmission Control Protocol/Internet Protocol) to serve this purpose. TCP/IP is a family of protocols, both application-level (i.e., high-level, like FTP, HTTP, Gopher) and network-level protocols (such as IP, ARP, ICMP). In TCP/IP, packets are constructed and routed through the network to the appropriate machine, based upon the headers of these packets. The data-area of these packets is the acceptable place for you to put your game's data (you don't have to handle packets quite as was done above). The Internet gaurantees that your packets will be sent to the correct machine, that is, the machine running the server.

Client applications, or the version of the game that you would distribute to your end-users (this program is often called the "client"), send TCP/IP packets to the server, a program that dissects them and processes them; the server is a program that runs on a machine identified by an IP address. The server listens to a TCP/IP "port", where data comes in, and sends data back to its clients via TCP/IP.

For the amateur, client/server based games can present a lot of problems, when one tries to take them on from the ground up: First of all, you need a dedicated machine with a dedicated IP to run the server. Next on the list, the machine has to be able to actually run the server: Many commercial end-user operating systems like Windows 95 don't ship with TCP/IP server implementations. Fortunately, there are solutions such as Linux...

There's a lot involved in sending and processing a single packet of data in networked games. Fortunately, in most operating systems, we've got access to TCP/IP client implementations that allow us to avert the technicalities of low-level Internet communication. With Winsock, for instance, it's possible to come up with programs that do things like retrieve web pages from port 80 on any machine, in about a page of code. High-level operating system APIs for Internet functions are a blessing, not a hurdle, despite what the complaints may register. After all, is it really practical to implement around thirty years of Internet yourself?

That's all for now

Hopefully, this article has helped you understand the essentials of multiplayer game programming. If you didn't understand it, go back and read through it again; this was designed to be a practical introduction that would allow you to go write some multiplayer games as soon as possible.

Anyway, happy coding, and finish that game! :)

Discuss this article in the forums


Date this article was posted to GameDev.net: 9/15/1999
(Note that this date does not necessarily correspond to the date the article was written)

See Also:
General

© 1999-2011 Gamedev.net. All rights reserved. Terms of Use Privacy Policy
Comments? Questions? Feedback? Click here!