peer to peer racing game - how to get smooth animation

hi, i recently started working on a peer to peer racing game. server and client exchange the position and orientation information with UDP packets. there is a communicator thread which constantly updates the remote car’s position.
the problem is that the smoothness of the remote car movement depends on the current load on the network. i tried implementing an extrapolating thread which moves the remote car to its anticipated position if the network is slow and the packets are not coming in fast enough. but still the stuttering is too disturbing.

i wanted to ask you guys if you know some clever technique how to cope with that.

any comment is appreciated …

[quote]i recently started working on a peer to peer racing game. server and client exchange the position and orientation information with UDP packets
[/quote]
Note that in a true p2p game you wouldn’t have the concept client/server like you mention. For a racing game with a limited amount of players, p2p communication could have a (slight) benefit over a client/server architecture in terms of latency. A client/server architecture would have the benefit of one entity being authorative (which can fx make cheating more difficult)… Ofcourse hybrids of these two architectures are possible; allowing you to take advantage of both.

[quote]there is a communicator thread which constantly updates the remote car’s position.
[/quote]
When you read/write to an shared state from different threads, you have to make sure to lock that state somehow. Imagine your gameloop thread modifies some car attributes, while at the same time your communicator thread is reading some attributes (or changing them too). The result would be inconsistent.

So you could implement somekind of lock, or do this from 1 thread: In your gameloop you just sample/update your network timebased. I’d recommend the latter.

[quote]the problem is that the smoothness of the remote car movement depends on the current load on the network. i tried implementing an extrapolating thread which moves the remote car to its anticipated position if the network is slow and the packets are not coming in fast enough. but still the stuttering is too disturbing
[/quote]
If you’d use raw network updates to render your opponents cars it probably would render very choppy (at least if the network conditions: latency or packet loss are high enough).

About extrapolation: It was originally designed (for the millitary simulation environement DIS) to cut down on the bandwidth required to send updates. What happens is that for each extrapolated object all peers run the same extrapolation algorithm. When the peer that is the owner of the object notices that fx the extrapolated pos for the object exceeds some threshold, it sends an update again. That way you wouldn’t need to keep sending updates blindly.

For a car game extrapolation would be very usefull indeed, when no update is available we could make a fairly adequate guess what the next pos will be (at least for small timeframes, <150ms or so). No if we get a network update and we seem to have extrapolated the cars pos wrong, we should draw the car at that updated pos instead, which causes snapping. To make this some more pleasing to the eye you could apply interpolation between the extrapolated pos and the real network updated pos. Most times a simple linear interpolator (lerp) will do the trick although i’ve heard cubic splines might give even better results for racing games

Now about your game still stuttering with your extrapolation thread; could it be that your extrapolation thread guesses the next pos, but when a network update comes in, the car will snap back to that network updated pos? You would need to send somekind of timestamp wth your network updates… if the currenttime > network update, save the difference, rollback to the network update timestamp and use your extrapolation thread to calculate the new pos according to the time difference. And maybe use a lerp to make the transition from the misguessed pos to the newly guessed pos…

Hope that helps!

Thijs

first of all: thx a lot for your comments!

well, the server creates a game session and the client connects to the server’s ip/port. that’s all. anyway, it seems like i’m using the wrong terminology …

that’s exactly the problem …

yes, but the directions of the cars might change all the time. i don’t think this would be a good approach.

my framerate is 30fps and i’m sending one packet every frame (33 ms).

how do cubic splines work? (tell me only if it’s easy to explain, if not i’ll google a bit …)

yes, that could be true. especially when a car is steering left or right. the extrapolator is not smart enough to take care of the steering as well. it only does a linear extrapol.

i already use a timestamp mechanism. i know the time when the last packet was coming in and the extrapolator uses the difference (now - last update) for it’s calculations. i’ll have to think about your approach.

but anyway, even if i can do perfect extrapolation there will still be stuttering if the network latency is too big. i wonder how professional games deal with that. i’ve seen games where many people play against each other over the (slow) internet and the movements are still very smooth. with my game i even have problems in a (relatively fast) intranet.

[quote]many people play against each other over the (slow) internet and the movements are still very smooth
[/quote]
internet is not so slow, most of the time you will have only 100-200 ms between each client(ping) even with tcp/ip

[quote] i wonder how professional games deal with that
[/quote]
my opinion is that the key is not to try to have the fastest network by using udp (i recomend you to use tcp and standard client/server ) or other low-level optimisation but rather to delay player action.

what i mean is that if i turn left my car : a packet should be immediatly send to the server but this order will be take in consideration client side (and server side) only 100/200ms later so when the car will start to turn left client side server will already know(already receive packet) and both will be synchronized. all client should play in past to enable server to know the futur. the delay you introduce to client side(about 100/200 ms) will not be visible.

basically something like that:
I press the key turn left at time =1000ms
imediatly send a packet to server saying i will turn left at time=1100 ms
at time =1100 ms start turning left

[quote]well, the server creates a game session and the client connects to the server’s ip/port. that’s all. anyway, it seems like i’m using the wrong terminology …
[/quote]
Yup, you’re using a client/server model then.

Well it depends on the type of race game; the cars motion might have pretty steady derivates if its somewhat realistic. Unless you’re creating somekind of arcade race game where you have very swift & unrealistic steering for example. With steady derivates, latencies up to 100ms - 150ms should be ok. You could check out turbosliders (google on it) to see a nice example, it works really well even at relatively high pings. it predicts (extrapolates) up to 150ms or something, it sets this prediction value according to the latency. For a car game you could even have an A.I. algorithm help you predict the motion of the car, as cars most probably try to stay on the track for example.

[quote]my framerate is 30fps and i’m sending one packet every frame (33 ms).
[/quote]
Note that you should never limit your framerate to sync the network or gamelogic, ever! :slight_smile:
Instead run your logic at a fixed rate, fx 60hz and draw as many frames as possible in the cpu time left. (http://www.gaffer.org/articles/Timestep.html)

Also dont send out a network update each frame, this is what doom did in the early days, it could hog up entire networks back then :wink:
Though 30 updates/sec might not be that bad of a sampling rate for a racing game. Just check whether a certain sampling time has been exceeded in your gameloop, if so, assemble an update.

[quote]Bruno wrote: my opinion is that the key is not to try to have the fastest network by using udp (i recomend you to use tcp and standard client/server ) or other low-level optimisation but rather to delay player action
[/quote]
Well, for internet play i’d advice to use udp (at least for fast non reliable updates like car pos etc). TCP tends to be less suited for such updates (reliable delivery causes delay with latency spikes while youre not interested in old pos data), but there plenty of discussions about that topic on in the networking section.

[quote]Bruno wrote: what i mean is that if i turn left my car : a packet should be immediatly send to the server but this order will be take in consideration client side (and server side) only 100/200ms later so when the car will start to turn left client side server will already know(already receive packet) and both will be synchronized. all client should play in past to enable server to know the futur. the delay you introduce to client side(about 100/200 ms) will not be visible.
[/quote]
Yup, thats a very good way to snoop off some of the message delay. There’s a good research paper on that subject with its focus on racing games:
http://portal.acm.org/citation.cfm?id=507674&dl=GUIDE&coll=GUIDE
http://portal.acm.org/citation.cfm?id=566512&dl=ACM&coll=portal

*there should be free versions of these papers around (ACM requires an account)

[quote]how do cubic splines work? (tell me only if it’s easy to explain, if not i’ll google a bit …)
[/quote]
http://www.gamedev.net/reference/articles/article914.asp

[quote]but anyway, even if i can do perfect extrapolation there will still be stuttering if the network latency is too big. i wonder how professional games deal with that. i’ve seen games where many people play against each other over the (slow) internet and the movements are still very smooth. with my game i even have problems in a (relatively fast) intranet.
[/quote]
Well with the techniques outlined in these posts you should be able to make it run smooth. Ofcourse with latencies >300ms or so it becomes almost impossible to have both a consistent and a realtime display. With multiplayer games theres always a tradeoff between consistency /real-time / scalability

I had a nice chat with the developer of turbosliders once. I could dig up some old email conversations and outline some of the techniques he used if you’re interested…

of yourse i’m interested. but before you spend too much time searching for the right emails i should read the proposed articles. if i still can’t figure out how to improve the game then i will most likely give up and start some other project which is not realtime-network based. :-
thx again for your help!

Now this is an interesting idea…LERP the old position with the new position according to their lag time. So say their lag is 100ms (common and thats probably the best you can hope for), just LERP the old position with the new one and SLERP the rotation.

When a new package arrives, simply position their car on the last packet and start interpolating with the new packet…

There are definite problems with this, notably that your adding on to the lag time, so you have no hope of doing collision detection between the cars, just let them go straight through (thats what TrackMania does). What do you think?

DP

[quote]There are definite problems with this, notably that your adding on to the lag time, so you have no hope of doing collision detection between the cars, just let them go straight through (thats what TrackMania does). What do you think?
[/quote]
Yup that would be a good approach if smooth display is your main concern. This way you trade in consistency in favor for real-time display; collisions won’t work and who’s first over the finish line? (you might see yourself finish before your opponent, while he wins as your display was lagged). Indeed having no collisions doesnt mean the game should be less fun (trackmania proves this) and saves some serious headaches.

Yet collisions feel like a natural part of any racing game. With pings up to 300ms gameplay should not suffer too much (see turbosliders). You could just require a ping lower than some value and have players browse through a server list to pick out a server which has a good ping…

some more explanation about what I tried to explain upper:

first of all, use a synchronized timer : each client must use the same timer as the server

client send its time: basically client send clientpingtime=System.currentMillis()
server answer: baseoffset=System.currentMillis()-clientpingtime
client receive server answer and compute real offset : offset=baseoffset-(System.currentMilis()-clientpingtime)*0.5;

do this periodically while your game is running, for example every 10s/20s

so each client can run using the same timer (server one): servertime=System.currentMillis()+offset;

other things to do:
delay all player by 100 to 250 ms according to the worst ping of all client connected to your game, if a player have a ping over max allowed ping (250ms) for more than 10s remove this player.

each client must send a sample of his position periodically (could be every 100 ms) with the tilme of this position : you can send x,y,z,time (with time=servertime using our synchronized timer)

in client side use a time-based position tracks for all player (even the current player) basically this object will have at least two method:
addKey(x,y,z,time)
and getPosAt(time) should return x,y,z position for the given time using linear interpolation between keys

server will also have a time-based position tracks for all connected client, so when a player pos sample is received server add a key to this player tracks and brodcast to all other player after having performing collision with other player(if needed(collision) feedback to player that have sent his position sample).

when you render a frame just set all your player position according to the servertime your frame is rendered:
for each player
player.setPlayerPos(player.getPositionTrack().getPosAt(servertime-worstping))
display player

they are some few more things to do to make it run perfeclty, but it is already a simple and working way that you can start with.

cool hope it good

i read this article which thijs proposed: http://www.gamedev.net/reference/articles/article914.asp

the idea is great, i’d really love to implemtent cubic splines in the game.
the problem is that either the article has two mistakes or i’m just doo dumb to get it.

at point “using cubic splines”, section D, point 4 the author explains how coords3 and coords4 is calculated. if you look at the equations coords3 is a position calculation and coords4 is a velocity calculation. and coords3 is part of the coords4 equation! this is wrong, isn’t it? you cannot add meters and meters per second.

i also don’t understand the image below the calculation (the one where the cubic spline is shown with x0, y0 etc.). why is the packet data point outside the spline? shouldn’t it be one of the corner points (x0, y0 or x3, y3)?

this article is related to perlin noise but also provide simple source code implementation of cubic interpolation.

maybe it can be useful

EDIT: :slight_smile:
http://freespace.virgin.net/hugo.elias/models/m_perlin.htm

More information about (implementing) cubic splines can be found here:

http://www.gamedev.net/community/forums/topic.asp?topic_id=386485
http://www.gamedev.net/community/forums/topic.asp?topic_id=390618

i finally found an article which was understandable for me: http://www.tinaja.com/glib/hack62.pdf
thanks a lot for helping!

The approach with the lag interpolation works very very well for time based games and not lap based. If you are trying to get the best possible single lap time, then it doesn’t really matter who crosses the line first, you or your opponent, its the server timing that sets the winner. You are no doubtly going to start the lap again (because lets face it, your not schumacher, and your opponents are going to improve over the next few laps) so the whole “who crosses the line first” problem will go away as your not starting at the same time as your opponents after the first lap…

Or what you can do (and I haven’t thought this out through very well) is add the person’s lag time to the start time. So say the start time was “now” and a player has 300ms of lag, the start now + 300. Obviously biased, but again, if its time based and not lap based, your fine…

DP