Context: This is for a multi-player shooting game. It's coded in TypeScript however this question is moreso a netcode/theoretical question rather than a question regarding implementation.
My game currently employs client side prediction to give an "lag-free" experience. We use a home-grown reliable (but unordered) UDP for data packet transmission. Each client sends the server their inputs and only their inputs, and the server then returns them with any data needed regarding the game state.
Problem:
In client-side prediction, there are various things that cannot be predicted, one thing being shooting (we can calculate a person's position based on their current inputs but we can't magically predict when they will click their mouse.)
Approach 1:
- When a client presses their button, we transmit them sending a mouse click to the server including a timestamp as to when that mouse was clicked.
- On the client side, we begin rendering bullets immediately.
- On the server side, we begin creating bullet entities and emitting these new entities to the other connected clients.
- With the mouse click and time of when that mouse clicked and the current position of the player at that time, we can easily figure out where a bullet will be at any given moment, and henceforth, we're able to compensate for this lack of ability to predict.
To kind of visualize this better: If we're in a 0 ping world, our bullets will spawn directly at the tip of a gun barrel. In our current world, instead, they'll be spawning several pixels away from that gun barrel, and the distance away from the barrel is effected by RTT.
Now this works for pressing a button, but what about letting go?
We need to wait for the client to send the server the data packet indicating that they just let go their mouse. Let's say this data packet takes a minute to get from the client to the server, by the time that happens, the server believes that they completely emptied their mag while the client let go their firing button a minute ago.
This is clearly a problem. Network spikes, bandwidth issues, etc., can create a discrepancy regarding how a client vs server understands how this shooting happened.
Approach 2:
Instead of sending inputs, why don't we send the bullets the player spawned in to the server? (There's potential security flaws to this, but for now we'll ignore it and say we're able to validate things on the server side.)
The next issue becomes if a player has really spotty internet connection, their bullets can be sent in all different kinds of rates such that on a receiving end, even if they were holding down their mouse button, their bullets fire-rate would be jittery and not consistent.
Question:
I feel like this problem is a solved problem, since this is a fundamental aspect to any shooting game for the past several decades. And so I wish to clarify: How would you go about networking the event of shooting such that we are able to account for various networking edge cases? What are things that need to be sacrificed to achieve this sort of idealized gameplay?
Thanks a bunch for reading through this!