Using AddForce with Delta Seconds in event tick is not consistent

Hello,

I am trying to write my own simple gravity, that is frame rate independent. So I decided multiply my Force with Delta Seconds to compensate.

However, when checked in detail, there is an issue with using AddForce function like this:

To test the theory that this will be frame rate independent, I start the game with the object from a high point, and measured the time it reaches the floor with different frame rates using the commands:
t.maxfps 60, t.maxfps 45, t.maxfps 30, t.maxfps 15

Then I measured the time on my phone stopwatch looking at the pc screen, and the results are inconsistent, and as follows;

60FPS - 15.5seconds
45FPS - 13.7seconds
30FPS - 11.4seconds
15FPS - 15.5seconds

Now, of course measuring by hand in phone introduces some error, but this error should be below a second. There is a clear difference in them. So next, I tried to calculate the real acceleration of the object in the blueprint editor as well, following this graph attached to event tick:

Then I measured acceleration like this:

Finally, measured the acceleration during this free fall, I get the resulting numbers:

60FPS - 15.5seconds , Acceleration=16.7 cm/s2

50FPS - 14.5seconds , Acceleration=20.0cm/s2

45FPS - 13.7seconds , Acceleration=22.2 cm/s2

30FPS - 11.4seconds , Acceleration=33.3 cm/s2

15FPS - 15.5seconds , Acceleration=16.7 cm/s2

10FPS - 19.1seconds , Acceleration=11.1 cm/s2

Until we go down to 15FPS, it seems like the acceleration is increasing with frame time, and in an eerily similar number to it in milliseconds… (My Gravity float is 1000.0), but it gets back to the same number with 60FPS when I go to 15FPS.

What is going on? Am I missing something very simple? How can we fix this?

Short answer :
Try use impulse not add force.

Some detail :

In unreal AddForce already integrates time aspects, so when you use add force it kinda applies time again. So in short times would vary cause of fps and phsyics steps. However if you use impulse its literally on frame force x delta time so it would be consistent.

Actually you don’t even have to use phsyics simulation for that even if you are just trying to do a gravity. You can add kinematic velocity which will override the phsyics velocity.

CurrentVerticalVelocity = CurrentVerticalVelocity - DeltaTime * 980;

Basically SetLinearVelocity(GetLinearVelocityX, GetLinearVelocityX, CurrentVerticalVelocity )

If you want to change directions of gravity manually you can do same but this time you need to project currect CurrentVerticalVelocity to the direction of desired gravity.

1 Like

Thank you for your reply Grimnir, very informative.

Digging deeper into the differences between the impulse and add force, I have noticed the following two are resulting in the same velocity if they are executed for one frame(i.e. button press)

Here Add Impulse directly adds 100cm/s velocity, and Add Force adds 100cm/(s * frame time [s]) of acceleration for a (frame time [s]) time window, resulting in a velocity of 100cm/s.

Next, I have replaced the add force function with impulse on the first post, and well, the results are better:

Acceleration Add Force Add Impulse Linear Velocity
60FPS 16.7 cm/s2 100 cm/s2 1000 cm/s2
45FPS 22.2 cm/s2 100 cm/s2 1000 cm/s2
30FPS 33.3 cm/s2 100 cm/s2 1000 cm/s2
15FPS 16.7 cm/s2 50 cm/s2 500 cm/s2

We can ignore the absolute values, but above 30FPS it seems that this is working to be more consistent.

I was thinking that if the numbers will stay like this in any circumstance, maybe we can just multiply the Gravity value with a curve map that corrects for each frame rate.

My pleasure, If I am not mistaken, around some fps below 30, physics tick is limited by game tick so the numbers are lower/different. Chaos is ticking clamped.

For gravity it really depends on the use case and the end results you want to achieve. If you are looking for something global there is one already and you can override that. Also you can create one gravity as kinematic.

However the current setup you do I assume for a local custom gravity that doesn’t effect the whole world. This also can be achieved by writing custom movements or actor oriented force fields too. Like walking on a small size planet. Gravity change when you contact to a special surface etc.

Local (Kinematic) : Here is some movement base gravity tutorials Tutorial: Custom Gravity in UE 5.4

Global (Physics Solver) : If you are into global gravity like planet to planet gravity change or that effects many things that can be done, but in C++ with overriding phsyics scene gravity. Didn’t try it before but should work.

Local (Physics Solver) : If you are looking for not global changes but local changes that effect multiple objects maybe you can take a look at chaos fields. Chaos Fields User Guide in Unreal Engine | Unreal Engine 5.7 Documentation | Epic Developer Community

I that works, then great! But EPIC’s own doc recommend against using AddImpulse for continuous forces (like gravity):

This is correct in principle, but a bit incomplete in the context.
Apply AddForce inside Async Physics Tick (or a physics substep callback), not in normal Event Tick Afaik.

It’s acceptable to apply on Event Tick if :
-Async is disabled
-No Substepping
-Physics Tick and Game Thread is in sync.

It would be still fragile. If there is frame rate drops that will cause changes in acceleration too. It would be correct generally without fixed time step but think by the nature of bigger time steps cause more error.

1 Like

Exactly my thoughts, that’s why it was confusing, but here is the twist; I noticed this too late:
For AddForce, it becomes stable (above 30FPS, like the others) when the float is not multiplied by delta time, and its directly inputted to the function. It is because by its nature, it is an acceleration function that already needs a time to calculate, like Grimnir said.

Also yes, I will check out/use async physics tick and substepping next!