🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

RTS Server architecture

Started by
9 comments, last by jeskeca 9 years, 1 month ago

Hello there!

I've been doing a lot of reading on the topic of multiplayer-servers, specifically on servers for RTS-like games.

The basic idea as far as I can tell is:

- The server runs the game and does all processing

- The clients send the userinput, e.g Player X wants to cast spell Y

- The server validates that request, and then broadcasts to all clients "Player X is casting Spell Y"

- The clients run a copy of the gamelogic, and then handle what happens (create a new Spell(), move it over time, deal damage when it hits)

- The server has the authority over the clients, validating their state of the world from time to time and sending corrections

The problems that I can see with this are:

- UDP packets are not reliable, therefore each packet has to be send until an acknowledgement arrives. This means even when a packet gets through first try, it'll still be send a few times due to latency. Not exactly optimal, and after using wireshark on some popular RTS games, I figured that this is not what they are doing.

- Every player is always behind the "true state" of the world by his latency. The only way to work around this is by predicting what is going to happen. This is only viable in 1st or 3rd person games like an FPS or an RPG, and it is only done for the users input, not for the rest of the game.

(When a client recives "player X starts moving to X,Y", it is supposed to handle that movement on its own. But since it got that command for example 100ms late, player X is always 100ms behind where he actually is at the moment ... But then again, if you resend the "correct" information, the player already has traveled 100ms further. So you again had to do predicting, which you want to avoid in an RTS scenario.)

This is, however, acceptable for an RTS.

- But how would it be possible for the server to validate any current state of a client, since every client is behind the server by its latency anyway, and by the time the "to-validate" data arrives, its already old. Also, latencys change constantly.

- Also it feels like if a player has "lagspikes" (his latency going up and down again suddenly), there would be huge problems. Since you can't wait for packets, you might have to process them in the wrong order. With little delay not that big of a deal, but when a packet suddenly gets stuck and arrives 500ms late while the rest got through, everything would get messed up really fast. And to get everything back in order you'd, again, need some kind of validation.

I can't send a snapshot of everything on screen 64 times a second like FPS games can, since there is way to much going on.

I'm clueless what to do here, any piece of advice or thought is highly apprechiated.

Thank you for your time and have a nice day!

-gnomgrol

Advertisement
The model you described sounds more like "client server" and it is *not* used for most RTS games. It is used for FPS, MMO, and some MOBA.

RTS games typically use lockstep deterministic state simulation. You can google that for articles an info.

http://forrestthewoods.com/synchronous-rts-engines-and-a-tale-of-desyncs/

Here is a really quick description...

Every machine (client and server) runs an identical simulation in lockstep. For each simulation timestep, all clients send unit commands to the host. Once the host receives commands from all clients for the timestep, it sends them to all clients and everyone can run a simulation tick. Those commands are high level orders, such as "group 1 attack position x". The actual unit simulation must be deterministic to cause the same outcome on all simulations. This keeps network bandwidth low even for large unit counts.

The clients checksum their game state periodically, and send the checksum to the host. If the host had a different checksum for that frame, the game has desynchronized and must be fixed or ended.

If any client doesn't ack the previous frame, the host pauses the game, to assure lockstep. Since all requests must be received to maintain lockstep, the game can use TCP or reliable UDP.

To get deterministic results requires either fixed point integer math, or very careful FPU usage.

Because the simulation runs much slower than framerate (typically 8-20hz), unit positions are interpolated between the previous frame and the current frame.

Hi.

Let the game play out on all clients like spells and shooting and movement, But don't change damage sates and things until the server

sends a valid data flag back, so the game looks like its playing but no unit dies until the sever gets back to clients.

RTS game are not like FPS games for networking movement can all be client side predicted, All the server needs to do is damage value checks and upgrades.

See a client can only cheat on his system the other clients have the cheaters remote object set by server.

But don't change damage sates and things until the server sends a valid data flag back,


What would a client do if the server doesn't think it's valid? At that point, you have a de-sync, and to recover, you'd have to re-send all of the world state.

As said above, RTS games almost always use a deterministic simulation. For each network tick, all players send their input commands. This sending can be echoed through a host, but you don't really need a central server simulation. When a client has gotten the input commands for tick X from all players, it can simulate tick X, and wait for input commands for tick X+1.

To hide the latency of getting the commands to all other players, RTS games typically use a "Yes, Sir!" acknowledgement animation whenever a command is given. By the time the sound effect/animation is played, all players will have gotten the command and the units start moving.

UDP is lossy. Because command inputs are so small (a few bytes per network tick at 10-20 Hz,) you can stuff multiple command ticks into each packet, so that if you drop one packet, next next packet will contain whatever was dropped. If a client is slow, the other clients won't get the commands for tick X in time, and will have to stall ("Waiting for player X.")

The canonical article describing this mechanism is the "1,500 archers on a 28,800 bps modem" description of Age of Empires, from Gamasutra ten or fifteen years ago. I think the link from the FAQ still works.
enum Bool { True, False, FileNotFound };

We released an RTS game earlier this year that is client/server, with the server-authoritative state being updated to clients. It's not the most common approach, but it works. The biggest advantage to client/server in this case is that the architecture is applicable to just about every type of game, even if it isn't typically considered the best fit for RTS, whereas a lock-step deterministic solution really only works well for the RTS problem space, and games with similar constraints and limitations. So what you sacrifice in specificity, you make up for in being able to use the same technology across multiple projects.

But that's the mindset of a larger, multi-project studio. As an indie with only a handful of developers and starting an engine from scratch, definitely use the best approach for your immediate project. If/when you start working on more games, you can write more tech... but I'd consider that a *good* problem to have.

We released an RTS game earlier this year that is client/server, with the server-authoritative state being updated to clients. It's not the most common approach, but it works


I think World at War did the same thing. The draw-back is that you simply cannot do 1,000 units per player in a game that's built that way. A 4v4 would be... laggalicious :-) With proper game design, the limited unit counts might not be a problem for a particular game, of course.
enum Bool { True, False, FileNotFound };

Slightly offtopic question:

With lock-step deterministic architicture each client needs to know everything that other clients are doing.

Would it mean that this solution is inherently prone to "all map visible" type of cheats ?

We released an RTS game earlier this year that is client/server, with the server-authoritative state being updated to clients. It's not the most common approach, but it works

I think World at War did the same thing. The draw-back is that you simply cannot do 1,000 units per player in a game that's built that way. A 4v4 would be... laggalicious :-) With proper game design, the limited unit counts might not be a problem for a particular game, of course.
Off topic, but Planetary Annihilation does. They don't even have a unit limit per player :D
Datacentres have come a long way :)
They update the sim at 10Hz, cull units that you cant see, and only send a unit update if the client will be mispredicting it's state from previous updates. For the common case of stationary or predictably moving units, there's very little data required.

Slightly offtopic question:

With lock-step deterministic architicture each client needs to know everything that other clients are doing.
Would it mean that this solution is inherently prone to "all map visible" type of cheats ?

Yes. Knowledge cheats are a major downside to lockstep networking models. You need something like VAC/Punkbuster/Warden to catch these cheaters, and some basic obfuscation of the network data to stop packet sniffing.
You can't cheat if server says that player a hit points don't match server rules send less hit points and punish player a, and keep game running. You can have 800 units for 8 players on a 166 MHz CPU( back in the day) and no lag.

You can't cheat if server says that player a hit points don't match server rules send less hit points and punish player a, and keep game running.


But you can cheat by reading out where the enemy units are, and what they are doing, even if you don't have line of sight to them.
In RTS-es, information/intelligence is important, so the incentive to cheat for those that need to "win" more than they need to "challenge themselves," is high.
enum Bool { True, False, FileNotFound };

This topic is closed to new replies.

Advertisement