Physics (Bullet) Client-side prediction

Interesting solution haha. How well does Bullet integrate with UE4, like all the collision profiles, tracing etc? I know everything in the engine is wrapped in WITH_PHYSX but I imagine bullet was quite tricky?

Goes without saying, if you could find any time to put a page on the Wiki about implementing bullet and controlling replay etc, that would be great! Might consider doing that myself.

It’s no full integration yet, more like a “plugin” / extension (without being an actual plugin [yet]). Collision profiles run via broadphases and masks. One could write something to use data from UE4 to create equivalent Bullet profiles though. For traces I work completely inside Bullet, I did not wrap any UE4 functions around bullet. However, I found a nice piece of code of someone, who implemented a nice debug drawer, which allows you to easily debug draw the world with lines (like the wireframe mode). Can’t give him credit, unfortunaley, don’t find the origin of it on the internet anymore :confused:

When everything in fact works fine and is polished, I would like to do a Wiki-Page, however, netoworking might be a bit too specific to be used by others, yet I could surely explain and show the basic concepts of it.

Years ago I worked on a game that had the same problem: the engine we were using back then (Torque 3D) used PhysX but our game was using an input-synchronous networking that required fully deterministic physics and we ended up using Bullet for that.

Full integration of Bullet might require tons of source code changes, but using a custom movement component that runs on Bullet sounds very doable.

While we’re speaking of bullet, is there a comparison of bullet vs physx anywhere?

Here:

Bullet = YAY
Physx = NAY

In all honesty:

Physx: Generally faster on NVidia cards, more features (destructibles and more), strange license, very complex integration.
Bullet: zlib (open source) license, a bit limited sometimes, easy to understand, needs some workarounds though. And as far as current testing goes it also seems more deterministic than physx.

Working on networking in Bullet, I managed to successfully implement the same stuff I worked on with physx for half a year, in not kidding, two days. This of couse doesn’t include the integration of bullet itself or writing various background logic for the UE visuals, just the networking code. And this code already runs way more stable than the physx one. You just have 100% controll about the scene, are totally aware of all the possible side-effects, know where what gets called and why - In Physx debugging, it felt like every function was called by a obscure callback, where you had not a real idea why it got called by whom. And tracing those delegates back, leads you to poorly documented UE classes with thousands of lines. The whole Bullet World management is realized in around 100 lines of code.

Can you say anything about performance compared to PhysX?

Not really. As long as you do not plan to implement gigantic open world or battlefield-sized maps, I think performance wise, the difference should be unnoticeable. Rocket League for example also uses Bullet and UE4

UE4’s Physx has been so far a purely CPU based solution, what GPU you have on your system makes no difference whatsoever as far as Unreals physics system is concerned.

edit-> physx has same license as UE4 when UE4 use is concerned. So as Unreal developer, you don’t have to worry about that.

Rocket League is UE3 game.

whops, sorry, UE3, sure. My fault.

Okay, here a “short” “tutorial” / “explanation” of what I am basically doing.** Fun fact: The actual client-side-prediction is the easiest part of it all and only takes abound 20 lines.**

You need one central function that calculates all the physics for a frame / step inside of it, which only takes a float DeltaTime as parameter and will be called every tick (or more often when you use substepping, a topic for later on) with the DeltaTime of the tick. So, in this function you apply ALL the forces that will apply to the vehicle this frame / tick.
At the end of this function (but inside of it) another function will be called, that will copy all physics-relevant informations into a special struct that you have to create.
So, what are physics-relevant informations? Basically everything, that you need to 1:1 restore a physical moment in time of the actor: Location, Rotation, Velocity (+Angular), the player input, that can influence the movement of the body (important) and in case of our project, the last SuspensionHingeToGroundDistance, since the damping of the springs will relay on that data. (we work with vehicles)
This struct also needs a float for the deltaTime of the frame and a time. Do not use unreal time here, it has to be world time, and to make it extra difficult, it has to be server synced time.

Okay, quickly: How to sync time with the server?
-What is syncing? Syncing basically means figuring out what time on the client corresponds to what time on the server, WHEN IT ARRIVES THERE. So that you could answer this question on the client: When I send a package to the server at time X, at which time will it be received?
-Why do we need syncing? We need it because when the server will send corrections of the physics-body, it will have a time value. And we need to know to which local time this time correspondates. This makes it easier to understand: You move the forward button on your keyboard, and your body should move forward. Now this information will travel to the server, which takes, lets’s say 100ms. So we have a ping of 100ms. Now the data gets applied on the server and the body starts moving forward there too. Correction data about the physics body will now be sent back to the client. It again takes 100ms. So this corrective data now contains informations about the real position of our body - 200ms ago. It is not where the position of the vehicle NOW is on the server. It is were it was 100ms ago on the server and 200ms ago on the client. So we need synced time, to tell for which moment this correction data was meant to be.
-How do we sync? It’s fairly easy: Send a request to a server, just a RPC Server-Function call. Note down the local time when you called this function in a variable. When the RPC reaches the server, immediately call a CLIENT function now on the server, which sends the local time of the server back to the client. (The client doesnt need to send any time value to the server). Now back on the client, when the serverTime arrives at the client, we again retreive our current local time, and see how much time has past, since we send the request. For our above example, this should be around 200ms +/- 20ms. Okay, now we have the **RoundTripTime **, which basically is Ping * 2 (Time to travel to server and immeadiately back). Okay, what do we need the serverTime for now? Well, we still need to be able to assign each serverTime a localTime (to tell when any sent information with a timestamp was relevant, remember?). So now we calculate the server’s system time at the moment we actually sent the request for it. Therefore, we have to substract the ping (RoundTripTime / 2) from the server time, since the serverTime first could be fetched after the 100ms travel of the RPC to the Server. Then we simply can substract the time when we sent the request, and we have the offset between Server and the Client, for example inaccuracys between the internal clocks of the pc’s or differences in timezones. However, this is only the synced time for any exact moment of generall time. Basically, at the time where it was X time on the client, it was Y time on the server. This does not yet fullfill our requirement, to be able to tell “to which moment in local time a package of correction data belongs to”. Therefore we however just have to add the ping again, to the offset.

So, we have our generall clock offset, that synces time for the aspects of timezones and divergence of the system clocks, and add the ping time to it, to now have a value, with which we can accuratly tell, when a package, send from the server, was meant to be valid on the client.
**
If that all sounds a bit complicatd, here a simple analogy:** On the client, we let Sarah walk forward. She is on Position 6, and now walks to Position 7. We want that the server checks if this calculation is right, or if we need to correct anything. So the information, that Sarah walks forward gets send to the server. This takes 100ms. Then on the server, Sarah also walks forward. The result on the server is also 7. Now this data will be send back to the client. It arrives 100ms seconds later. But oh: On the client, Sarah already walked again and now is on position 8! The corrected values from the server say Sarah is on Position 7… Do we have a problem? No! Because with our technique, we now can tell, that this correction value was basically “a response” to the moment in time on the client, in which Sarah started walking forward. So the server correction basically says: “At time XYZ, which corresponds to your time ZYX, Sarah was on Position 7”. The client then checks where Sarah was at time ZYX, sees she was at Position 7, and everything is fine - everything was correct.

So, this took a little bit, right? Where where we. Ah, the struct we need to fill out every physics step with all the physics data. Here we now need the timeOffset we just calculated. We will directly store the synced time (localTime + timeOffset) into the timestep variable, so we now have a way to correlate server and client instances of those structs.
So now that we have our struct filled out, what do we do with it? We need to store it! More precisely, we need a history of physics states. Therefore you can write a new struct, which contains an array of those PhysicsState Structs, and each time a new state gets added, you push all of the existing ones into the next element of the array and write the new state at position 0. So we have our history array, from index 0 (just happened) to index X [maximum] (the state which is the longest time ago of all states). X can be anthing, usually 100 should be fine, but just use more in the beginnign, just to make sure. About the “Push all of them into the next element”: Maybe sounds more intuitive when I show you a simple line of code:


for(int32 i = NumberOfItemsInTheHistoryArray - 1; i > 0; i--) HistoryArray* = HistoryArray[i-1];

So, for an array of [10,8,6,4,2], after this cycle we would have [10,10,8,6,4] We abandoned the last element, and now can override the first element with our newest state. :slight_smile:
A simple analogy: A row of parking lots, from left to right. In each lot, a vehicle is parked. When a new vehicle arrives (from the left) and wants to park, the vehicle at the at last lot (right-most) of the row has to to drive away. All the other cars now move one parking lot to the right, so that the left-most spot is free, for the new car.

The hardest parts are done!
Okay, we are still in this function that creates this PhysicsState for us and puts it into the history. On the server, we do not need this history, but we need to send this struct to the client. This will be the current state of the body on the server: The correction data.
For the time value in the struct: You can use the same function to get the “synced time” on the server, however make sure, the timeOffset is set to 0 on the server, because there is no timeOffset, since the server - is the server (eg just write the local time in there).
Replicate this struct to the client, either by writing it into an instance of the struct which is marked “Replicated”, or via an RPC Client Function. Use a ReplicatedUsing function in case you choose the Replicated Struct. So, when this data arrives on the client, we are now either in the RPC Client function or the Replicated Using Function, which doesnt make any difference. However, here we now have our server correction. So what we now want to check is, “Was our body at the right position, 200 ms back in time”. But actually, we do not really ask this question. It just helps understanding, what we are doing. In fact, we are always treating it that way, as if we would have NEVER had the right position (and rotation, velocity etc).

**What we are now doing is finally the real client-side-prediction. **
1.) We go into our history and search for the entry with the CLOSEST TIME TO THE TIME WITHIN THE CORRECTION-STRUCT RECEIVED FROM THE SERVER. This is now our equivalent local state, where we can check how much divergence we have.
2.) “Write down” (note in a variable) on which INDEX this state was in the array.
3.) RESET the vehicle to the values in the correction data. When you have Position, Rotation, Velocity, and Angular Velocity in your PhysicsState struct, set the physics body to those values of the serverState. DO NOT OVERRIDE THE CLIENT INPUT HOWEVER!
5.) Create a copy of the current input of the user.
4.) We now enter a for-loop! We start at the INDEX we marked down, and go down all the way to 0!
As a reference:


for(int32 i = INDEX; i >= 0; i--)

**
In this loop, we now do the following:**
1.) Get the History array element at position i.
2.) Override the client’s input structure (or values, however you manage them) by the input values from this physicsState that you got in the step above.
3.) Call the PhysicsFunction of your PhysicsBody, and pass the DeltaTime from the physicsState struct as the DeltaTime of the PhysicsFunction(float DeltaTime) parameter. Just make sure to now NOT call the function, that writes new physicStates into the history, because we are not introducing new physicsState but are basically just “recalculating”. Set a boolean or an enum to a value “Replaying” or “Simulating”, to differentiate between those two options (normal call of the function vs call for a “replay”)
4.) Tick your physics world by the same amount of time

At the end of the loop, reset your client input back to what it was before (we made a copy of it before).

**Now, what did we do here? **

We figured out the equivalent state in history the data is for, and now are doing this logic “If I would have been at (X,Y) at time T, where would I be now, when the input would remain the same”.
So, we reset our body to the data received by the server, and then calculate all the physical steps we did since this moment in time until the present moment again, with the same input as we had originally.
At the end of the loop, we are back at the current time, and we answered the above logic question “where would I be now”. You are there now: Your body was just client-side-predicted. :slight_smile:

Edit: Phew, that got longer than I intended it to be :smiley:

1 Like

If there is any interest I can rework the above with some more code snippets and make it an wiki article or a seperate Forum Thread. Not sure where to put it, doesnt feel specific and good enough to be a wiki entry and is there something like a tutorial place in this form?

I would definetly welcome a wiki entry or maybe even a YT video! I wouldnt even mind paying for this stuff, thats how epic this is imho :smiley:

Maybe I’ll do a YouTube video. Don’t think thats something I could demand money for, at least not in it’s current state. Would have to rework it into a plugin, but it also is based on Bullet Physics, so you would have to use a new physics engine for that.

Ofc, I could write a bullet physics plugin and a seperate client side prediction plugin, which requires the bullet physics plugin.

Concerning your question, thats an “advanced” subtopic. When you have physics influencing objects in your scene, you have to create position / rotation buffers for them as well and apply them in a replay-step. If you do not want to, just make sure to not apply any forces within the replay (also no gravity), and there won’t be any movement at all.

But true, after re-reading it myself, it’s very theoretical. I’ll spice this up with some more code this weekend.

Haha wish I knew about bullet 8 months ago. Fascinating thread - good work.

Interesting read.

I already implemented a basic system for bodies, but there is no client input for them, so it simply estimates where they should be by the time the next update arrives and fudges its way towards that. While not as accurate, its good enough for what we need.

I keep meaning to create a component to handle the replication and prediction instead, so it can be used for bodies, dropped items and anything else we want to use actual physics.
This has given me some insight into what to do.
Thank you :slight_smile:

Thanks for the feedback, is there any way to rename the thread? :slight_smile:

Hey guys, this topic has been split into two separate threads. Newer posts found here: