Client-side prediction depends very heavily on a deterministic physics model that exactly replicates the way the game object behaves on both the client and the server. Even small floating point errors, differing random seeds, or slight differences in time if your time step is not fixed can cause very different results. These results can then rapidly diverge or cause game-breaking bugs (such as the player landing on the platform on one client and not others).
I suspect that is occurring here. While I can't give you an exact solution without seeing the way you've written your code I can offer some advice based on my own experience writing movement and physics using client-side prediction.
First, our research:
I also used Gaffer on Games as a source as well as the Source Multiplayer Networking article and Gabriel Gambetta's Fast-Paced Multiplayer series. While Gaffer on Games covers these concepts very well I found Gambetta's explanation simpler and easier to follow when I was first learning the concepts and I found Valve's article great to help connect the concepts to a live game application. They may help you similarly as well.
Second, your comments about your own simulation:
For the jump command..
- I send a packet indicating that i'm jumping.
- The client then predicts the jump
- The server begins to simulate the jump and tells the other clients that client X is jumping
- that then gets simulated on their clients
You did not mention it in that quote, so just in case: you should be doing client-side reconciliation when the results of the server return back to that game object's controlling client. This is described very well in the Gabriel Gambetta series however a quick rundown of this approach is as follows:
- What you listed above occurs with two differences: 1) each input is sent to the server with some unique id; common ones include the time at the moment or an incrementing integer id. 2) the inputs are stored along with that id in a queue to be used later.
- When the server updates the controlling client on the game state, the id of the last input processed server-side is sent back with it.
- The client sets the new game state (anything the server says is absolute).
- The client then looks at the queue of inputs and the 'last input id' sent from the server and drops any inputs with an id less than or equal to that returned by the server.
- All the inputs left in the queue are re-simulated all at once back to the present to ensure that the client-side prediction is an accurate as possible.
It should be noted, this process only happens on the controlling client because the other clients do not have the past input information.
The issue is that if I keep on jumping on the spot eventually the jumps will begin to overlap. I came up with some makeshift solutions to that, but realized that if I started moving and jumping at the same time it wasn't simulating everything perfectly and sometimes the player would land on the tip of a platform on one client and miss it on the server/other client.
Rather than sending a packet indicating you're moving, a packet indicating you're jumping, and so on, a common approach to sending input to the server is to bundle up all the input for all used buttons into one single packet. This is sending the state of all game relevant keys to the server. The server then has a packet containing all the inputs values as they were seen at the point of update/prediction on the client which it should be able to use to re-simulate the frame exactly as it was on the client.
Right now everything is being done locally so there's virtually no lag. I figured the reason it's becoming out of sync is because of jitter.
I'm just a little confused in figuring out how to fix this. How do I get it so the client side prediction is 100% perfect in a local environment when incorporating physics?
Some latency & jitter alone should not cause the issues that you're seeing with sync from my experience. The client-side prediction model is designed to make the simulation appear smooth and non-laggy regardless of fluctuating network conditions. A part of that is keeping the client and server in sync despite these conditions.
If I'm understanding correctly, then in your current model, It's possible out of sync errors could happen in this case for example:
- Client starts moving and jumps at the same time.
- Packet containing the movement input is sent to the server.
- Packet containing the jump command is sent to the server but experiences a delay.
- Server starts processing the movement but because the jump packet did not arrive on time, some movement has already occurred before the server computes the jump putting the client and server out of sync.
So, bundling up all of your input into one packet to send to the server rather than sending a packet for jumping separate from movement should solve the largest part of your out of sync issue since the server will be able to simulate an entire frame from exactly the same input state the client would have simulated it.
Next would be to look into implementing client-side reconciliation to reduce potential errors in the prediction code and thus warping of your character. This will be important once you are running your server on a remote box and latency begins to come into play since some inputs will occur before the server gets back to you, hence why you want to replay the ones the server hasn't processed.
Also, reviewing your code and ensuring that the update loops are happening exactly the same on client and server may help you identify some issues.
For example, in my own project I ran into a situation once I implemented collision where I was handling outstanding packets slightly differently on client and server (all at once in one tick, as opposed to one per tick) which worked perfectly for movement but caused my collision to rapidly become out of sync since the detection occurred after all movement was applied.