Network Efficiency

Started by
18 comments, last by doynax 18 years, 6 months ago
Hey Everyone, I was wondering how games like Warcraft III and Neverwinter Nights decrease the amount of data sent to each client. I assume that a game company can only assume that people playing over the Internet have about 10KB/s upload speeds so how do these games update the locations of all of the players and all of the creatures. I first thought that by using tiles you can limit the amount of information that needs to be sent to update the coordinates of each player. I also know only the players that move need their coordinates updated. It is also possible to only update the players and creatures that each player can see. Are there any tricks or do game companies simply assume fast connections. Over a LAN most this is probably not an issue, but accross the Internet when 8 players are playing over battle.net and each player has 40 units moving to battle someone else I would think that would cause unacceptable lag. (If a map is 256x256 tiles then player coordinates can be represented by two bytes. 40*8*2=640bytes sent to each player to update all player coordinates). If anyone knows more about the networking of these games I would be very interested. In particular I have read that Neverwinter Nights II plans to support 64 players in a server like the original, but the graphics engine is not tile based. This leads me to believe that networking support never relied on players being at specific tiles.
Advertisement
i would think that the games send only useful information [that which is seen or near seen]. they also should perhaps when player of 1 sends unit to x,y position only position and unit id is sent to player of 2,3,4,etc. you may also consider other compression of data such as only send full unit positions every 2sec.
Quote:Original post by AllTheLuck
I assume that a game company can only assume that people playing over the Internet have about 10KB/s upload speeds so how do these games update the locations of all of the players and all of the creatures.


Simple, they don't. And 10KB/s upload is quite high.

If you are going to make a multiplayer game, you have to design to code to sync in a low bandwidth networked environment. Multiplayer games work by making smart code, not by trying to compress all the data produced by dumb code.

The two cases you brought up use two different models:

Warcraft 3 is an RTS. The strategy for an RTS multiplayer game is to create a peer-peer networking strategy - while the game may have a host (and in newer games the host may be required as a conduite for transfering data), there is really no central controller.

Instead all the games have an identical copy of the environment, and an identical sets of games rules. When you order a grunt to attack the enemies town hall, the command is sent to all machines. All machines execute the command at the same network frame, and since they are running at the same network framerate, the same thing happens and continues to happen until another order is issued (at the next network frame all machines will have placed the grunt 1 step closer to the town hall, even though no network communication was used).

The network frame is a fixed processing scale - all important decisions (units health is decreased by attack, building is completed, etc) are done at these frames, which are identical on all machines. The calculations done between frames (essentially intra-frames) should only affect the visual presentation on the local client, to provide a smooth game. For example if a units walking speed is 10 units per network frame, then each network frame it should calculate the units position based on the last network frame, while the intra frames can use standard variable framerate movement so that it looks smooth, but with the intra frame position being discarded each time a network frame is reached.

To do this, things like random number generators must be designed to be completely replicatable (if given the same input, they'll give exactly the same result every time, on every machine). Even so, it is still required to do synchronization checks to prevent the games from going off in different directions - for example different processors can do floating point calculations at slightly different precisions, resulting in tiny bits of drift, which can cause a unit to see another unit one network frame too soon, which sets of a chain of events that puts the different players out of sync with each other (they are no longer seeing the same game).

Synchronization checks can be done by creating state hashs - take all the information that affects the game (position of units, health, current orders, etc) and make them into one or more hashs. The hashes are stamped with the network frame number and compared. If the hashes don't match up, the game pauses (if done well, pauses should not be noticible to the player) and figures out what information doesn't match up - this can be done by combining the hashing with a binary search (split one group of objects being hashed into two groups of objects, each with their own hash. Once it is narrowed down to a specific object the data can be sync'ed).

To hide lag, the RTS doesn't execute commands as soon as you click. Instead, it sends the command stamped with a value of 1 or more network frames in the future. At the end of each network frame, the player tells all other players how many commands where sent (this isn't needed with TCP, only a "I'm done this frame" message, since communication is serial). At the start of each network frame, players check to see that they've recieved all orders that are scheduled to be executed on this frame. If they haven't been recieved, the game pauses until they are (this is why laggy RTS games stutter)


I'll post in a little while about other game network models (like Neverwinter nights)
Many other games use something similar to the RTS model, but substitute the lock step system (commands are sent ahead of time, and are executed at the same time on all clients) with a more aggressive real-time command system backed up by a central server providing synchronization.

This sacrifices the ability to handle a game world with hundreds of directly controllable entities for one with a faster "twitch" style of game play. To do this, a central server is responsible for maintaining the true game state, while each client runs their own approximation of the game state based on the information they've received from the server.

When a player does something (turns their head, shoots their gun, steps forward), they send that information to the server, but also do it immediately in their own copy of the game state. When the server gets the command it executes it (note that in both models the game should always check that the command is valid if possible), creating the true version of the game state. This information is then transformed with time information and forwarded to the other clients. The other clients receive it, and use the time information (when the server executed the command) to approximate how it should affect the client copy of the game state.

For example if I start moving forward on my local copy of the game state (at 0ms), the command is sent to the server, which takes 50ms. The server starts moving my character forward (in the "real" copy of the game state my player didn't start moving until 50ms) and sends the information to all other clients. The other clients (who we'll say are slower) receive it 60ms after it was sent from the server. Unlike the server, which executed the command when it was received, the clients make an approximation - they decided that the other players character started moving forward 60ms ago on the server, so that character is pushed ahead on the clients copy of the state (there are many articles on GameDev about how to hide this, for example), so that it is in the position it would be if it had started moving 60ms ago.

As all important decisions are made on the server using the server’s copy of the game world, player’s actions and their results don't always match the local copy. For example in a multiplayer FPS, you have to aim ahead of a moving target, depending on your lag, in order to hit it - even if it looks like a hit on your client, it's up to the server and its view of the world to decide if it was. The server is responsible for telling the player (and other clients) when important events happen, and clients usually won’t show those actions (even if their copy of the game says it should happen) until the server confirms it.

This kind of on the fly action means that individual player’s copy of the game state quickly drifts apart from what the server and other players see. This means clients must constantly be making small synchronization adjustments to remain close to the server. To avoid the game stopping every 2 seconds, the usual hash check system is loosened, so that corrections only happen if the client drifts too far off (the corrects are obviously only one way, the server correcting the client). The server also reduces the number of objects that must be synchronized by only considering things that will directly affect the client in the near future – for example a player on the other side of the map can’t be seen and won’t affect the game state of the client, so the server doesn’t both correcting the client on that players position.
Well, I also think 10KB/s is a good upload rate.

For example, If you have to send 4 bytes to update your position and you send it every time you move then you would send a maximum of (datasize * game_fps) assuming that the player can move every FPS.

The most common framerate limit is 30 FPS, so you would send per second, a maximum of 120 bytes. But there are also more information in the game than just the player position but let´s suppose that you would send a maximum of 300 bytes if other events happen.

So that´s 300 bytes per second you will need to send, right?
But you can send up to 10.000 bytes per second.

Of course, this is for a game with a server, so for the client I think 10KB/s is already very good.

I never found a game which I couldn´t run well with my 128kbps upload rate.

Am I correct?

But for peer-to-peer games, I´ve seen tricks used in Baldur´s Gate 2 for example, in which if you play by LAN and the two computers are next to each other, they don´t show the exact position of the players.
Well, you don't have to update your position every frame when both the client and the server are running their own simulation of the game world. In our mmorpg, the client moves the player into it's copy of the world using the same rules as the server and synch itself to the server at regular intervals. The server then check if the player position of plausible and then validates it or synchs the client to it's own state. That would be the rubber band effect you experience in some mmorpg. I think UO was the worst one I've experienced but then again those where the days when I ran it on a p133 mhz with a 56k connection :p.

Since the updates really aren't that regular and it's a space mmorpg with inertia, we also send messages for the player starting an acceleration, turning, stopping to accelerate etc.

As the others said, it's all about being clever about what you send rather than how you can compress it. Of course if you have a really great compression scheme with that,it's good too.

On a sidenote, if you've ever played a RTS called Total Annihilation, it doesn't use lock step. It can make for some weird situations sometimes when there's a lot of lag ( only happened with 56k back then and it was mostly the cpu lagging and causing synch problems ) but on the other hand, it made for a smooth experience even if there was a lot of lag vs games like AOE where it was painful for everyone when one person lagged.
4 bytes per packet times 30 packets per second is:

(20+8+4)*30 = 960 bytes per second using UDP
(20+20+4)*30 = 1320 bytes per second using TCP

Luckily, only the "4" part of that grows with the number of players you need to update in the same packet -- but if all players send packets to all players using pure peer-to-peer, the packet flooding will be quite extreme.

The numbers involved are:

20 = IP header size
8 = UDP header size
20 = TCP header size
4 = your assumed payload
enum Bool { True, False, FileNotFound };
When it comes to RTS response, the command queue timing should be equal to the round-trip time to the most delayed player. That way, the most delayed player gets the packets just in time for execution, whereas everyone else gets them sooner. If some player starts getting more delayed, then the RTT could be increased.

You might note that a unit in an RTS will typically say something like "yessir" when you first ask it to do something, and then it will perform a little getting-ready-to-go animation, and all of this takes half a second before it starts its order -- this half-second is used to hide the order latency.

Last: kudos to Michalson for summarizing these approaches in this thread.
enum Bool { True, False, FileNotFound };
Quote:Original post by Michalson
Warcraft 3 is an RTS. The strategy for an RTS multiplayer game is to create a peer-peer networking strategy - while the game may have a host (and in newer games the host may be required as a conduite for transfering data), there is really no central controller.
[...]

I don't think that's really the case, because the random number generator can not be syncronized on all the machines, due to the fact that it's a deterministic state machine. Not all the clients view the same things (some units are hidden by the fog of war, for example) so then not all the clients do the same calculations at the same time, which results in the random number generator to produce totally different results.
Of course, this can be fixed by sending ALL the units and buildings and stuff on all the clients, regardless of their visibility. However, this can cause cheats such as 'patches' that disable the fog of war.
Another thing is, different clients have different video cards and different capabilities, so some spell effects that require radomness would also affect the random seed. I guess they can for example use a different seed/algorithm for those spells, leaving the damage calculations unaffected, but still, there can be various other problems. Besides, if they are out of sync for various reasons, and their hashes are different, then that machine that is off has to get ALL the data again, which makes it ineffective.

Finally, one more problem is if all the machines are off, then the server machine would have to update all of them at the same time, which could cause a LOT of lag.

I am not really sure how Warcraft3 works (even though I play it on Battlenet every day :) )so it is possible that it works the way you describe. If that's the case, then it is pretty insecure and possible not effective.

The way I would do it is assign a few octets for every unit (it's ID, which contains what kind of unit it is and what color) and a few octets for it's speed, items (if any) and health/mana.
If you can have maximum 256 units at a time, then each unit can have it's own ID, so after the client gets all the information about them an unit move can consist only of 2 octets (unit ID and direction). If we assume only 8 directions, then we can have 12 bits for the unit ID (allowing up to 4096 units at the same time) and 4 bits for the movement.
Since not all the units move at the same time, the bandwidth is under a few Kb/second on the clients, and maybe up to 20KB for the server (assuming 8 players).

Quote:Original post by hplus0603
4 bytes per packet times 30 packets per second is:

(20+8+4)*30 = 960 bytes per second using UDP
(20+20+4)*30 = 1320 bytes per second using TCP

Luckily, only the "4" part of that grows with the number of players you need to update in the same packet -- but if all players send packets to all players using pure peer-to-peer, the packet flooding will be quite extreme.

The numbers involved are:

20 = IP header size
8 = UDP header size
20 = TCP header size
4 = your assumed payload


The TCP/IP protocol will not send 30 different packets/second. It will usually put all the data in one or two packets. It depends on how it is set, but it's a big waste of bandwidth to tell it to send independent packets for each command.

This topic is closed to new replies.

Advertisement