|Top Previous Next|
This month we are going to start working at a brand new template. It's a multiplayer template that should make the things easy for all the beginners that are interested in learning how to create multiplayer games. But before we start writing some code, it is important to understand how multiplayer programming works.
If you have ever played a multiplayer game, you know how it feels: two or more players are sharing a nice gaming experience in what appears to be the same environment, as if they were in the same room. Actually, they may even be in the same room, but often times many of them are playing the same game even though they reside on different continents.
Multiplayer games aren't a recent invention, but the technology that made fast action games playable over the internet was only created about a decade ago. In the beginning, the games were using a peer-to-peer approach, just like in the picture below.
The peer-to-peer architecture isn't limited to two computers (two players) of course, but the main idea is that every computer needs to be connected to every other computer in the network, and each computer exchanges information with every other computer in the network. I know I said that this type of multiplayer gaming was used "in the beginning", but believe it or not, it's still being used today in games that don't require a lot of speed (real time strategy games, turn based games, and so on).
The PTP (peer-to-peer) approach assumes that the computers share a common initial state, and then the sets of commands that are executed have a similar behavior on all the computers. As an example, if Player1 is sending out a peasant to cut down a tree, the same command is sent on all the computers, and that particular peasant's path, speed, and so on is identical on all the machines.
You won't need a rocket scientist diploma to figure out that this is approach is a bit naive; how could you guarantee that the peasant will move with the exact same speed and choose the exact same path on all the computers? This could make the peasant appear as if it has already cut down the tree on a computer, while showing it still chopping on another computer.
A potential solution would be to wait until all the players have received updated game information, but this would slow down the game a lot, making it playable at a speed that is equal to the latency of the slowest internet connection. As an example, if an American citizen played a game with his U.S. friends, everything would work OK, but the game would become unplayable the moment their Asian friend would join in. The same thing would happen to the Asian team as soon as they would add an American friend.
So how can PTP still be used in nowadays' RTS games? The answer is very simple: through controlled lag. Did you notice that when you give your people a command, you're getting audio feedback most of the time? You will hear something like "Yes, commander", and only then, after this audio pause, your units will start to move. This is only an example of intelligent "cheating"; the developers use this technique to gain time and send the needed data packets across the network.
So why is PTP still used if it has these major problems? The answer is simple once again: serious RTS games can have thousands of units running on the screen at the same time, so the amount of data that needs to be sent over the network is huge. So rather than sending information about each unit, the way the modern shooter games do for the players, the PTP / RTS games send each computer commands that make the game advance to the next stage.
Some brave people have tried to use PTP technology for shooter games, but it only worked fine over local area networks (and not over the internet). The famous DOOM game was a great example of pushing this technology to the limit, and yet the games weren't playable over the internet, even when using the fast 28.8Kbps modems ;) that were available back then. The reason for this failure was very easy to understand: each key press was exchanged with all the other players, because the world needed to be kept in sync on all the machines.
I don't want you to think that using PTP is a very poor idea; it's a technology that has its own strengths:
- Since it is a client-client system, it has a much smaller latency (the data doesn't have to go the client -> server -> client route);
- Since PTP doesn't use a central server, the game can continue even if one or more computers disconnect from the network - even if the host disconnects!
OK, so the PTP architecture wasn't good enough for fast action games. The solution to the problem was discovered by John Carmack, lead programmer at id, who has worked at DOOM, Quake, Rage, etc. The client / server solution was implemented in Quake, a successful game that was released back in 1996.
So rather than having each computer run the same game code, the game was now run on a single computer (the server) and the players were sending and receiving the information from the server. And since the game only existed on the server, there was no need to guess the player positions, etc - the server knew the precise positions and was able to share them with the clients.
With client / server multiplayer coding, the player only has to send input data (key presses, mouse clicks, mouse movement, etc) to the server. Then, the server computes your correct player position, animation, etc and updates it on the server, sending the info to the clients as well. This means that the server has to do a lot of computing; the load isn't spread among the clients, the way it does with PTP. More than that, the server must have a solid internet connection, because it will receive and send info to all the clients.
The main problem with the client / server model is latency. Think about it for a second: you want to fire a rocket at an enemy, so you press the left mouse button. There must have been at least 100-150 ms since you've spotted the enemy and until you've pressed the mouse button. Your computer will instantly send the firing data, but your modem lags a bit. Then, your modem's data is sent to your ISP's modem who (you've guessed it!) lags a bit. Then your ISP sends the data to the game server, delaying the data packet even more. Hopefully, the server is lightning fast, so there's no need to wait for the data to be processed. But then, the computed data needs to travel all the way back to your ISP (internet service provider), your modem and finally to the game client. Trust me, that is a lot of wasted time.
So how do you fix these issues?
I'm old enough to be your father (well, maybe I'm not that old) so I have played all the early shooter games. With Quake played over the internet, you had to wait until the data packets traveled to the server and back before actually moving. Sometimes, launching a rocket took several seconds. The things were even worse at the other end of the gun; often times, you were killed by a rocket which was appearing out of nowhere. I'm sure that the server saw it coming, but my client didn't update at the right time (I had a poor internet connection) and with a lag of 500 ms or more you simply can't play shooter games without instantly growing a big chunk of gray hair.
The lag problems are over with all the modern shooter games. It's true that we have much faster internet connections (mine is about 400 times faster now!) but the latencies didn't improve that much. So how did they fix the latency problems? John Carmack (Quake) and Yahn Bernier (Counterstrike) are the ones responsible for the smooth action games that we can play today.
Carmack implemented client-side movement prediction for QuakeWorld, while the latency compensation algorithm was created by Bernier for Counterstrike.
With client movement prediction, the client moves the player model by itself according to player's input, until a correction response arrives from the server. This approach gives the client computer more work to do, because the player needs to be aware of its environment. Still, the client player will start moving as soon as the actual player presses one of the movement keys - there's no need to wait for the server response. The only problem is finding a way to correct player's position, speed, etc when the server determines that player's data on the server and the client are out of sync.
But wouldn't it be much easier to have the client run the entire movement code, without having the server do anything about it? We could simply inform the server about player's position and that would be it! This approach would work fine if it weren't for cheaters, people who will do anything in their power to win the games by altering the client code, instantly teleporting the player wherever and whenever they want to, for example, and thus taking advantage of the honest players. This is the only important reason why the server has to decide the positions of the players, updating their coordinates when it is needed. Most servers check the position of the player 20... 30 times per second, which is more than enough for any fast action game.
Still, the things get complicated when we take network bandwidth limits into account. Since the server can't update all the clients each frame, it usually takes snapshots of the current game status (think "game_save", for example) and then sends them to the clients. Still, this makes the clients lag a bit behind the server. Not only that, but some of the data packets get lost along the way. This explains the need for additional data compression, interpolation, lag compensation, and so on. Valve used delta compression for their games based on the Source engine; this way, the server doesn't have to send entire world snapshots 30 times per second, but only the things that have changed (the delta snapshots) from the last update.
What about latency compensation? Let us assume that the client fires a bullet at its 310th frame, but since the data packet needs some time to travel on the network, it arrives at the destination while the server is running its 315th frame. Since five frames have passed, the enemy has moved out of sight, even though it was there when the player has fired the bullet. So how do we fix this problem? The solution is to keep track of all the player positions during the last few seconds. If we do this, we can easily determine if the enemy was supposed to get hit when that bullet was fired. If the bullet was supposed to hit an enemy, the enemy is moved back to where it was supposed to be for a frame, and then it is returned to its actual position. I know that this may sound a bit cheesy because we're actually moving the players back, but with proper movement interpolation code the results will not be noticeable.
Latency compensation can create unpleasant situations for the players every now and then, but this is the price that needs to be paid for motion fluidity. As an example, you could sneak behind a wall and discover that you still got killed, because the server decided that you were actually exposed when one of the players has used the sniper gun. It's important to understand that the server response reflects the reality, though.
That is all for today! I hope that you now have a clear understanding of the client / server architecture, as well as of the best motion fluidity and latency reduction algorithms.