MMO movement system

Started by
20 comments, last by Kylotan 2 years, 7 months ago

Hi everyone, how are you?

Today I wanted to ask for help in the desing of the movement system for my MMORPG (TCP), the idea of the game is it to be similar to WoW. I'm having some issues with the movement system so I want to explain the whole error.

Right now, my system work like this: Client A press W and start moving and send to server SERVER-MSG-MOVE with position & direction & move direction (foward, backward, etc), then that packet will be received by server and it will get broadcasted to the players nearby. Then Client B & C (when packet reach) will start to simulate A with server movement information.

Now here begins the problems, the ping will come in action, and each client will start to simulate player A at different time, not to say if Client A change move direction, let's say it press W + D (clock move or foward + turning right), since there is a ping between all clients, each one will start it at different times will result in different positions.

Now, I have been reading, and this isn't a good approach (I don't want to have perfect precise movement since it's a RPG not FPS), but I can't find a good desing about what I have to do. I have been reading alot about this, but I can't find the right answer, because lot's of them come from 2001 ~ 2005 and I think that now in 2021 there might be newer systems/solutions.

For what I have been reading, in theory, a good desing would be if server send every 100ms (can be higher or lower) current player movement position & direction, and all players nearby will set current move information to that one.

But I'm kinda lost in this subject, if anyone has any good example in C++, or a design so I can read about it an try to implement, it would be great, I really appretiate any help.

Thanks a lot!

Advertisement

Two words: movement interpolation.

Also known as lerping.

I know what what you’re thinking, but I’m not referring to LARPing. After all, why would you build an MMO if you were LARPing?

None

Rewaz said:
Now, I have been reading, and this isn't a good approach (I don't want to have perfect precise movement since it's a RPG not FPS), but I can't find a good desing about what I have to do. I have been reading alot about this, but I can't find the right answer, because lot's of them come from 2001 ~ 2005 and I think that now in 2021 there might be newer systems/solutions.

For what I have been reading, in theory, a good desing would be if server send every 100ms (can be higher or lower) current player movement position & direction, and all players nearby will set current move information to that one.

What's wrong with designs from 20 years ago? ? Multiplayer games have been around for a long time and what you're talking about has always been a problem to solve. The biggest problem you'll be facing isn't interpolation, however - it's latency.

As it happens, latency mitigation is not a trivial topic, but the basics date all the way back to early Id Software games when they were trying to figure out a responsive networking solution for Quake et al. The concept is still the same, but you need to figure out what approach is best for your use case.

In the below examples “simulate” means one of two things:

a) either interpolate from update N-1 to N (this may introduce lag)
b) or use some simple predictive algorithm to generate the next state N+1 and tentatively interpolate from N to N+1

In both cases the core concept is the same: you lerp from one value to the next over some fixed amount of time (e.g. a single logic update).

As noted above, un a networked game this introduces either lag or predictive uncertainty (e.g. your prediction keeps moving a remote client left, but they've actually already pressed right, but it'll take X amount of time for this information to arrive). So, consider the following hypothetical naive example:

ClientA: player initiates move - the input is sent to server (locally keep interpolating player according to local input, but wait for validation from server in case some other player caused something to happen)
Server: validates the input, broadcasts the event to players near ClientA (if the move is invalid, don't move the player on the server, send feedback to ClientA to act accordingly)
OtherClients: if the move was valid, receive a message from the server and move ClientA's character according to the input (interpolate exactly the same way as ClientA does locally)

This is fine until you introduce latency between both ClientA and the server, and/or the server and any other client(s).

Now, whatever happens, in a multiplayer game you need to validate player input and movement on the server no matter what, so it's not really possible to get rid of the basic chain in the above example. However, there's a few things you can do to make it seem like that latency is not there to all clients. Let's expand the example a bit:

ClientA: player initiates a move. The input is sent to server, but while waiting for validation, keep responding to player input and blindly sending new inputs to the server, assuming that everything is and will be fine. Locally, this will make it seem like the player is can play the game in real-time and if there's no lag, then that'll actually be the case. If there's lag and it makes the local simulation to go out of sync from the server's, then the server will simply not accept any invalid inputs and will, at some point, send back a message with a valid state (e.g. “all inputs after input at [timestamp] were dropped]”). At this point ClientA can act on it - usually this means instantly teleporting to the local player to the position validated by the server.

Server: pretty much remains the same.

OtherClients: adopt a similar approach to what ClientA does locally. BUT, instead of waiting for the server to send a valid state, nearby players send inputs directly to each other and simulate each player as they would locally. Once data from the server arrives, the local simulation is compared to it. If there's a discrepancy, the local simulation will simply copy the state from the server (e.g. drop all inputs after a certain time, which means reverting back to a certain state). Again, if there's latency, then the result might look like the other player is “teleporting around”. However, with no cheaters involved and reasonable lag simulations will actually match and the game will seem fluid for everyone involved.

In practice your server updates should not be spaced 100ms apart. Work on minimizing packet size and reduce jitter by increasing update frequency. You might need to repack or compress data if needed!

Rewaz said:
For what I have been reading, in theory, a good desing would be if server send every 100ms (can be higher or lower) current player movement position & direction, and all players nearby will set current move information to that one.

This is what I do in my game, The user sends a movement signal from the client, and halts movement once the user releases the key/button while nearby players receives this information. I empty the queue every 20ms to the client, cause 100ms apart caused jittering. (This is a html game and using websocket)

irreversible said:
What's wrong with designs from 20 years ago? ? Multiplayer games have been around for a long time and what you're talking about has always been a problem to solve. The biggest problem you'll be facing isn't interpolation, however - it's latency.

Nothing wrong, I just thought that now there might be newer systems, that's all. I also read lot's of systems and didn't know which one to use ( interpolation, extrapolation, dead reckoning, etc ).

irreversible said:
In practice your server updates should not be spaced 100ms apart. Work on minimizing packet size and reduce jitter by increasing update frequency. You might need to repack or compress data if needed!

Hmm, what do u think is a good tick rate (each tick it's the packet movement sync), because I read that for MMORPG 10 ticks per second it's just fine.

EDIT: I'm thinking that interpolation (lerp) might be the best option, I only have one issue with it, how to interpolate clock movement? For example, if I press W + D ( foward + rotate right ), how can I interpoltate that? I can't do linear interpolation. Also, I still don't know how to do it for client to server, because interpolation is for other players can see each other in a sync way, but what about main client to server, what if client itself start to simulate own player, but it is different from server?

I also think about start simulation locally, and if the simulation is wrong, just set position to what server say's, but I don't like this approach, because I fell it will have lot's of desync still.

If anyone has any example in github C++ I would appreciate it.

Rewaz said:
Hmm, what do u think is a good tick rate (each tick it's the packet movement sync), because I read that for MMORPG 10 ticks per second it's just fine.

For a slow paced MMO probably yes, but I wouldn't play an FPS or probably even duel someone in WoW with 10 updates/second.

At the end of the day it all comes down to bandwidth. Realistically, you have to look at what's available on your server end (which probably has a fiber connection; e.g. - for most intents you're uncapped), but you probably should make (heavy) provisions in case your client is playing on the toilet over WiFi, in which case you might be looking at speeds to the tune of tens to hundreds of kilobits per second and considerable packet loss. Now multiply that with the number of players on your server.

Leveraging between all of this is tricky and unfortunately there are no one-size-fits-all answers. However- you can ask yourself questions like: “What is the minimal amount of data I need to send every update? What can I drop/calculate locally/compress? What do I do when there's a spike in packet loss/delay?”.

In an FPS your update payload might be very small - maybe hundreds or even tens of bytes -, so you can send it literally hundreds of times per second to minimize potential lag without fear of saturating most connections. In an RTS you might be looking at datasets that are an order of magnitude, or two, or three larger than that (in the latter case you could look into how Starcraft does its networking - it's fascinating stuff).

In any case - realistically you're not writing an MMO server here. At least not yet ?. For starters, as you probably know, the initialism itself literally contains the word “massively”. If you're having trouble interpolating one player, then dealing with synchronizing thousands or tens of thousands of players will probably have to wait a bit. BUT - many of the core problems do remain the same, so you can certainly stress-test your network code with tens or hundreds of simulated players and see how it fares.

Rewaz said:
For example, if I press W + D ( foward + rotate right ), how can I interpoltate that? I can't do linear interpolation.

This no longer has anything to do with networking. ?

You most likely already store your player's position, direction (as something like a quaternion) and velocity, etc. somewhere. And you probably also already update them by a some amount in every logic step.

As it happens - in most cases you'd employ exactly the same one-step-behind or predictive logic locally to make player movement look smooth, but instead of dealing with network delays, your synchronization happens between the logic and render threads.

Let's say you update your world at 30fps (every ~33ms) in your logic thread (where you probably also process any user input accumulated since the last tick). It's here where you set the player's linear and angular velocities according to input. Let's say the player pressed W and D, so you set the player's linearspeed = dir * somespeed (somespeed could be the player's max speed or some adaptive acceleration factor or whatnot) and rotationspeed = something like quat(0, 1, 0, turnspeed) (the player is now rotation around the Y-axis at turnspeed radians/second). Assuming your engine is multithreaded, you then lock your player's render state and copy this data to a buffer that the render thread can read each frame (if your code is single-threaded, then everything remains the same, but you can forget about synchronization between threads).

In your logic thread, during the next step you simply do: playerposition = collide(playerposition, linearspeed), and update linearspeed and rotationspeed according to collision response/new inputs, etc.

Meanwhile, in your render thread, every time you draw a frame, you lock the player's state and calculate curdrawposition = playerposition + linearspeed * dtSinceLastLogicUpdate, etc. (you do this for all variables that need to be interpolated). BTW: this is why it's important to have a solid, fixed logic step interval (which is fixed at 30fps in this case) - you want stuff like the player's linearspeed to remain well defined for each logic step, so they can never do silly things like go faster than maxspeed units per second.

However meanwhile (especially if v-sync is off), your render thread might be blasting away at much larger, lower or volatile framerates, e.g. 218fps this second and (if your optimizations don't hold up) 21fps the next second*, which in turn introduces a whole lot of movement jitter. This is where the interpolation pays off, which you use to “smooth out” the relevant variables. Just to be clear, at the start of each frame you calculate:

dtSinceLastLogicUpdate = something like (GetCurrentHighFrequencyTimestamp() - lastLogicStepTimestamp) / double(logicStepLengthInWhateverUnitsYoureUsing).

Note that with a predictive approach you're still theoretically running the risk of encountering similar visual glitches as with the networked code. Let's say the player is a bullet and their linearspeed is something silly (let's arbitrarily say it's 2000, which we'll call a “silly fast speed”). If the player's computer is running your game at ~300fps (300 render updates per second), then given the tick rate of 30 logic updates per second you'll have ~10 frames that are rendered during each tick, which means that VISUALLY the bullet gets to move 200 (2000/10) units before the next logic update has a chance of correcting its position.

That is to say, if the collision test in the next logic update finds that there's a wall 10 units from the bullet's current position, then you'll have visually (already) shown that the bullet goes through the wall by as much as 190 units. Of course, this is only a visual glitch and you can remedy it by either increasing the frequency of your logic updates appropriately or switching from a predictive interpolator in the render thread to a conservative one (which might use the last two already known updates as the from and to positions and interpolates between those every frame instead of looking at the predictive values for the next step). Again, your choice depends on what game you're making and what's important for gameplay (for instance, an FPS with a high logic update frequency and predictive rendering interpolation yields smaller input lag with a low chance of visual error, which is very likely preferable to waiting a full tick (actually two, since you're likely processing player input at tick intervals) before the drawn frames reflect the player's actions).

Hopefully this gives you an inkling what the problem is how you might go about solving it.

* I can't stress this enough - your framerate should NOT fluctuate this much under any circumstances ?

Rewaz said:
I also think about start simulation locally, and if the simulation is wrong, just set position to what server say's, but I don't like this approach, because I fell it will have lot's of desync still.

Without local simulation you'll likely have to deal with lag (unless everyone involved has a pristine connection). So I'd say local simulation is probably something you won't be able to avoid. Instead, you can focus on how you move your data - minimize network payload size and take precautions to not desync. Small errors (e.g. due to precision issues or bad seeding) won't be visible if you cover them up with some trickery (possible thoughts include - don't teleport the player, but interpolate toward the correct position if the local value diverges from the what the server tells you, add small acceleration/deceleration to player movement, so motion feels more fluid, etc.). Reproducibility requires robustness and is not an easy thing to accomplish, but without it you'll likely find yourself up against even bigger problems.

Rewaz said:
If anyone has any example in github C++ I would appreciate it.

While code can often be a good way to learn stuff, it really seems to me that in this case you'd be better off getting a solid understanding of what the actual problems are that you need to overcome. Code won't help you with that, because the answer to your question ("How do I use interpolation?") can be summarized with one line:

c = lerp(a, b, frac)

where lerp() is a very well documented formula.

The problem is that I'm not sure that's the right question.

For a more fundamental understanding of the problem space, I feel it would be better if your focused on how the myriad of different aspects come together in a game engine (based on this thread, things like frame interpolation in your render thread, synchronization, dealing with real world problems related to networking, scaling your code from being able to deal with more than 10 players*, dealing with random seeds and and predictability/reproducibility on many independent systems/platforms, etc.

I'm afraid there's no single piece of code on github, or anywhere else, that will give you this ?.

* or better yet - postponing the idea of building an MMO until you have a solid game that you can play with a friend or two.

Working C++ code for interpolation can be found on an old page of mine:

https://www.mindcontrol.org/~hplus/epic/

enum Bool { True, False, FileNotFound };

A basic starting point would be:

  • track movement in ticks (e.g. a 10th of a second, or a 20th of a second) rather than real-time, so that you have a shared ‘unit’ that all clients and servers can use. I recommend starting with a very low value - e.g. 4x per second, 250ms - so that it's easier to debug and understand. You can even just printf position values to the screen while testing. Then just crank up the frequency later, once you know everything works.
  • send an update for each tick rather than just on/off inputs, to avoid network delays producing different results. You can pack multiple updates into one network message to save bandwidth, but beware of the extra latency this incurs.
  • updates can include not just the input state but also the locally-predicted position
  • have the server apply the movement inputs and compare the outputs to locally-predicted positions. This can help with debugging potential desync situations.
  • broadcast the updates out to clients, who buffer them up and use interpolation to blend between them (and extrapolation if a message is missing or delayed). Linear interpolation is a good start during debugging as it is visually obvious what data you are working with. For release you'll usually want something more advanced (e.g. hplus0603's EPIC code, linked in the previous message)
  • How you do the buffering and interpolation can vary - shooters will usually try to maintain some sort of carefully-synchronised clock but RPGs can often get away with simply dequeuing the updates for an entity once there are enough to meaningfully interpolate from. It's game-specific.
  • you can expect some minor rounding errors, so a tick in which a player stops moving is an opportunity to set a precise stopping position and effectively resync everything.

This topic is closed to new replies.

Advertisement