Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I'm developing a real time strategy game for a computer science course I'm taking. One of the harder aspects of it seems to be client-server networking and synchronization. I've read up on this topic (including 1500 archers), but I've decided to take a client-server approach as opposed to other models (over LAN, for instance).

This real time strategy game comes with some problems. Thankfully, every action the player takes is deterministic. However, there are events that happen on scheduled intervals. For instance, the game is made up of tiles, and when a player takes a tile, the 'energy level', a value on that tile, should grow by one every second after it's taken. This is a very quick explanation that should justify my use case.

Right now I'm doing thin clients, which just send packets to the server and wait for a response. However, there are several problems.

When games between players develop into endgame, there are often over 50 events per second (due to the scheduled events, explained earlier, piling up), and synchronization errors start to show up then. My biggest problem is that even a small deviation in state between clients could mean different decisions the clients take, which snowball into entirely separate games. Another problem (which isn't as important right now) is that there is latency and one has to wait a few milliseconds, even seconds after they make their move to see the result.

I'm wondering what strategies and algorithms I could use to make this easier, faster, and more enjoyable for the end-user. This is especially interesting given the high amount of events per second, along with several players per game.

TL;DR making an RTS with >50 events per second, how do I synchronize clients?

share|improve this question
    
You can maybe implement what Eve-online does and "slow down" time to allow everything to process properly. –  Ryan Erb May 21 at 14:34
2  
Here's an obligatory link to Planetary Annihilation's client/server model: forrestthewoods.ghost.io/… This is an alternative to the lockstep model that seems to be working very well for them. –  DallonF May 21 at 17:08
    
Consider reducing the number of events by sending a single update for all taken tiles instead of updates for each tile or, as answered by Ilmari, by decentralising non-player actions. –  Lilienthal May 22 at 12:30

5 Answers 5

up vote 11 down vote accepted

Your goal of synchronizing 50 events per second in real-time sounds to me like it is not realistic. This is why the lock-step approach talked about in the 1500 archers article is, well, talked about!

In one sentence: The only way to synchronize too many items in too short time over a too slow network is to NOT synchronize too many items in too short time over a too slow network, but instead progress state deterministically on all clients and only synchronize the bare necessities (user input).

share|improve this answer

every action the player takes is deterministic, however, there are events that happen on scheduled intervals

I think there's your problem; your game should only have one timeline (for gameplay-affecting things). You say that certain things grow at a rate of X per second; find out how many game steps are in a second and convert that to a rate of X per Y game steps. Then even though the game may slow down, everything stays deterministic.

Having the game run independently to real time has other advantages:

  • you can benchmark by running it as fast as possible
  • you can debug by slowing the game way down to see fleeting events, and as mentioned
  • the game stays deterministic which is very very important for multiplayer.

You also mentioned that you run into problems when there are >50 events, or there are delays of up to seconds. This is much smaller in scale than the scenario described in 1500 archers, so see if you can profile your game and find out where the slowdown is.

share|improve this answer
1  
+1: Frame-based is the right choice, not time-based. You can attempt to keep N frames per second, of course. A slight hitch is better than a full-on desync. –  PatrickB May 21 at 15:10
    
@PatrickB: I see that many games use a "simulated" time which is not tied to the video frames. World of Warcraft only updates things like mana every 100ms, and Dwarf Fortress defaults to 10 ticks per video frame. –  Mooing Duck May 21 at 21:23
    
@Mooing Duck: My comment was specific to RTSs. For something where small errors can be tolerated and corrected later (e.g. MMORPGs, FPSs), then using continuous values is not only fine, but critical. However, deterministic simulations that have to be synched across multiple machines? Stick to frames. –  PatrickB May 22 at 16:12

First, to solve the problem with scheduled events, don't broadcast the events when they happen, but when they're initially scheduled. That is, instead of sending an "increment the energy of tile (x,y)" message every second, just send a single message saying "increment the energy of tile (x,y) once per second until it is full, or until interrupted". Each client is then responsible for scheduling the updates locally.

In fact, you can take this principle further, and only transmit player actions: everything else can be computed locally by each client (and the server, as necessary).

(Of course, you probably should also occasionally transmit checksums of the game state, to detect any accidental desynchronization, and have some mechanism for re-synchronizing clients if that happens, e.g. by resending all the game data from the server's authoritative copy to the clients. But this should hopefully be a rare event, only encountered in testing or during rare malfunctions.)


Second, to keep the clients synchronized, make sure that your game is deterministic. Other answers have already provided good advice for this, but let me include a brief summary of what to do:

  • Make your game internally turn-based, with each turn or "tick" taking, say, 1/50 seconds. (In fact, you could probably get away with 1/10 second turns or longer.) Any player actions occurring during a single turn should be treated as simultaneous. All messages, at least from the server to the clients, should be tagged with the turn number, so that each client knows which turn each event happens on.

    Since your game is using a client-server architecture, you can have the server act as the final arbiter of what happens during each turn, which simplifies some things. Do note, however, that it means that clients must also reconfirm their own actions from the server: if a client sends a message saying "I move unit X one tile left", and the server's reply says nothing about unit X moving, the client must assume it did not happen, and possibly cancel any predictive movement animation they may have already started playing.

  • Define a consistent order for "simultaneous" events occurring on the same turn, so that each client will execute them in the same order. This order can be anything, as long as it's deterministic and the same for all clients (and the server).

    For example, you can first increment all resources (which can be done all at once, if the resource growth in one tile cannot interfere with that in another), then move each player's units in a predetermined sequence, then move NPC units. To be fair to players, you may want to vary the order of unit movement between turns, so that each player gets to go first equally often; this is fine, as long as it's done deterministically (e.g. based on the turn number).

  • If you're using floating-point math, do ensure that you're using it in strict IEEE mode. This can slow things down a bit, but that's a small price to pay for consistency between clients. Also make sure no accidental rounding happens during communications (e.g. a client transmitting a rounded value to the server, but still using the unrounded value internally). As noted above, having a protocol to detect and recover from desynchronization is also a good idea, just in case.

share|improve this answer
1  
Also, synchronize the RNG to start, and only pull from the synchronized RNG when the server tells you to. Starcraft1 had a bug for a long time where the RNG seed wasn't saved during replays, so replays would slowly deviate from the actual games. –  Mooing Duck May 21 at 21:29
1  
@MooingDuck: Good point. In fact, I'd suggest transmitting the current RNG seed at every turn, so that RNG desynchronization is immediately detected. Also, if your UI code needs any randomness, don't pull it from the same RNG instance as used for the game logic. –  Ilmari Karonen May 21 at 21:53

You should make your games logic completely independent from real time and essentially make it a Turn-Based. That way you know exactly the turn on which "tiles energy change happens". In your case, each turn is just 1/50th of a second.

That way you have to worry only about player inputs, everything else gets managed by the games logic and completely identical on all clients. Even if the game stalls for a moment, due to Net lag, or extra-complicated calculation, the events are still happening in sync for everyone.

share|improve this answer

First of all you have to understand that PC float/double math IS NOT deterministic, unless you specify to stricly use IEEE-754 for your calculation (will be slow)

Then this is how i would implement it: the client connect to the server, and sincronize time (take care of the ping latency!) (for long gameplay it may be necessary to resync timestamp/turn)

now, every time a client do an action, it include a timestamp/turn, and is up to the server to reject bad timestamp/turn. Then the server send back the action to the clients, and every time a turn is "closed" (aka server will not accept turn/timestamp so old), server send and end-turn action to clients.

Clients will have 2 "world": one is in sync with the end-turn, the other is calculated starting from end-turn, adding up the action arrived on queue, until the current client turn/timestamp.

because server will accept a bit old action, client may add its own action directly in the queue, so the round trip time over the network will be hidden, at least for your own action.

last thing is to queue more action so you can fill the MTU packet, causing less protocoll overhead; a nice idea is to do that on server, so every end-turn event contain action on queue.

i use this algoritm on a real-time-shooting game, and works fine (with a and without client chaching its own action, but with server ping low as 20/50ms), also every X end-turn server send a special "all client map" packet, to correct drifted values.

share|improve this answer
    
Floating point math issues can generally be avoided as such - in an RTS, you can usually easily do the simulation and movement with integer/fixed point, and use floating point only for the display layer that doesn't affect game behaviour. –  Peteris May 26 at 11:54
    
With integer is hard to do horizontal tiles, unless it is an octogonal board. There is no hw acceleretion for fixed point, so it may be slower than float ieee754 –  lesto May 28 at 6:41

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.