Advertisement

Predicting Over-Time Skill from Single Input

Started by June 29, 2024 04:07 PM
5 comments, last by hplus0603 5 months, 2 weeks ago

Where I'm at

2D MMO Unity game with client/server networking architecture (server is also a headless Unity instance) containing only basic movement. My early/simple approach has worked thus far:

  • Client and server operating on fixed tick rate
  • Client sends movement input to the server and predicts movement locally
    • Predictions stored as [input → amount moved]
  • Server receives/buffers inputs and processes all on next tick
  • Server uses snapshot approach where it sends entire game state to each client with the client specific last processed input
  • Client receives and performs reconciliation
    • Throws away any predictions before last processed input
    • ‘Replays’ remaining predictions after given server state and checks if the same, if not adjust to server position

Note: No timestamps or ticks are sent from the client yet. Just an input number and the input itself.

What I'm trying to do next (including the problem)

A ‘dash’ skill that lasts several frames from a single input. i.e.

  • Client taps dash skill and begins ‘dashing’ over the next 10 seconds/ x number of ticks
    • On every tick until the skill is complete the client stores a prediction
  • Client sends dash skill input to the server
  • Server receives dash input sometime later and also begins dashing for next 10 seconds
  • Game snapshots being sent out every tick with the current player position
    • Last input received has not changed though since it stemmed from a single ‘dash’ input
  • Client receives updates, but does not know which dash related predictions to throw out

What I've Tried

OK, I've thought of some ways I could solve this if input is locked during the skill

  • Send some sort of ‘dash progress’ info about how much of the dash is complete from the server to client

But, I've lost determinism if another input occurs. i.e.

  • I jump in the middle of the dash which occurred on tick x of the dash
  • Server receives jump input, but if the server and client and even a little out of sync in terms of processing the fixed tick rate, the jump may take place at different point during the dash which would produce a different outcome.

I could also:

  • Include a timestamp in the packet about client dash start time then start the skill on the server at the point where the server believes the client is. But, this feels wrong since the client will snap to a spot on the server if we have a lot of latency. The server will always have a brief ‘catch-up’ since it receives client input late. Is this normal?
  • Syncing server/client tick rates using RTT/2 (the ‘Overwatch’ model I believe). I then store locally the predicted time the dash will be received by the server (client always a but in future). But, this seems overkill for the game I'm trying to create.

What I'm Looking For

Help with resources about how to solve such a problem (or with my problem specifically if I managed to explain clearly enough :P. I'm making a simple Top-Down 2D MMO w/ PVE Only so I don't need advanced rollback for player fairness or anything (yet). I may be overthinking the situation, but can't seem to find any resources about this type of ‘over-time’ prediction.

In almost all theses cases, you end up needing a shared clock of some sort (e g, “game tick number since start of world” or something) and a mapping from “local clock walltime” to “game tick number” for ordering events.

Every ability should then be expressed as “if my state starts at tick X, and it's now tick X+D, here's my evolved state.” For most efficiency, evolution shouldn't involve any other actors/entities, so forward playback is fully deterministic, but if you have to do collision detection against entities, you'll need to simulate one tick at a time.

Some other observations:

  • “MMO” and “Unity for server” aren't a great match-up, as Unity limitations will limit how large your server-side instance can be.
  • “MMO” and “snapshot” isn't a great match-up, either, because many entities times many snapshots means much network choke. You're probably better off baselining and only occasionally correcting.

enum Bool { True, False, FileNotFound };
Advertisement

Thanks for the confirmation that I’ll need a shared/synced clock to accomplish this type of prediction. A follow up concern that I'm unsure how to handle:

  • Client starts the 5 second dash skill’ at Tick X and sends input to server.
    • Client continues predicting movement for next 5 seconds.
  • Server receives dash input at Tick X + (let’s say latency) 5.
    • These inputs will always be received for past ticks?
  • How should the server process the dash given the latency?
    • Should it ‘catch up’ to the client by immediately processing 5 ticks?
      • If we do ‘catch up’ won’t the first 5 ticks of a dash look sped up for remote players even if we’re interpolating? I’m unsure if this is acceptable or what's an acceptable way to handle it.
    • Should I predict based on the average latency the tick that the server will receive the input and mark that as the tick in the packet?
  • What about intermediate inputs? If I get a jump input during the dash? I get I need to timestamp it now, but do I need to retroactively jump on the server at the same past tick and replay to get the same deterministic result?

Also thanks for the additional observations:

  • Ok, for now maybe I’ll diverge from a true MMO approach and just have instances with a max of let’s say 20 players. This involves more learning than I expected so I’ll swap server technologies if I ever make something more robust in the future. Using Unity for server will at least keep that part familiar for now and lessen the learning burden.
  • Interesting note about the snapshot approach. I heard games like Apex Legends use snapshotting so figured it was easier and more efficient than individual packets for every network entity. I guess a true MMO could require magnitudes higher requirements than a battle royale with max 60 players in a game.

There are a couple of “standard” solutions.

The first is to have the client run their local actor simulation at timestep (global time + L) where L is the transmission latency from client to server. This in turn splits into two: Do you show “other objects” also simulated up to client time (e g, predicted and possibly wrong) or do you show other objects in their own time frames (e g, delayed compared to the client.)

The second is to not actually start the physical effects until the server has had time to see it. So, every effect is built with some kind of acknowledgement or wind-up initiation. This is very common in RTS-es – “yes, sir!” – and spell casting (fancy light shows) as well as fighter games (telegraphs/wind-ups.)

enum Bool { True, False, FileNotFound };

Interesting. I believe the compromise I’ll make is for movement (including movement skills) I’ll try to be predictive, but for anything else I wont. It’s fine for me (for now) if a fire skill shows an animation immediately for a client, but doesn’t show damage until the server tells me.

If you wouldn’t mind clarifying the first solution, what would my client to server packets say about my input timing?

  • Is the tick in the input packet the T + L ? So we are predicting what time the server will receive it and the server actually applies it at this time? If it arrives late it just gets processed immediately (or too late It gets thrown out)
  • Is the tick still T and packets always arrive “late” and we somehow adjust? This seems like it would involve some type of rollback and replay on the server to remain deterministic.

Yes, in case 1, the client tries to send inputs so they arrive in time for the server.

If the packet is late, you can either throw it away, or support server rewind, or apply it on a later tick and correct the client.

When the server notices the client sends packets way too early, it should send a time correction message to the client saying “send packets later.” When the server notices the client sends packets too late (e g, misses a deadline) then it should send a time correction message saying “send packets earlier.”

It's important to not send those values too frequently, though, because you can get into terrible oscillations. Maybe once every two seconds, the server can send statistics of average, minimum, and maximum time discrepancy in received messages, and the client can then use this to adjust their ahead-ness in whatever way it feels is appropriate, targeting whatever trade-off between responsiveness and jitter tolerance makes most sense.

Fully lockstep/deterministic RTS games typically won't actually progress the server tick until all client commands for the tick number have arrived. Thus, a single lagging client can cause slowdowns for everyone in those kinds of games.

enum Bool { True, False, FileNotFound };
Advertisement

This topic is closed to new replies.

Advertisement