Godot: RigidBody2D does not clear forces at end of integration, RigidBody does

Created on 11 May 2020  路  9Comments  路  Source: godotengine/godot

Godot version:

3.2.1

OS/device including version:

Windows 10

Issue description:

RigidBody2D does not appear to clear accumulated forces at the end of integration in the same way RigidBody appears to do.

This results in calls to add_force in scripts to accumulate forces over multiple physics steps, which I feel might be unintuitive for new users since:

  • The behavior is inconsistent between 3D and 2D
  • In Box2D the forces are cleared at the end of the step by default (appears to be the case in Unity as well), users coming from frameworks like this may be confused
  • There's no option to enable or disable this behavior, or any documentation to expose it.

Not actually sure if this is a bug or if it's intended this way. If it was intended, it would be good to document it.

Steps to reproduce:

Call add_force on any RigidBody2D in a script in _physics_process.

Minimal reproduction project:

PhysicsTest.zip

bug physics

Most helpful comment

The issue I see is that in the 2D physics applied_force is added in its entirety during the integration step, but is never reduced or cleared, so it will continue to accumulate indefinitely. The purpose of apply_impulse is to cause an immediate, large change to the velocity, where add_force applies a continuous change over time by being called during each _physics_process(). In the documentation it's explicitly stated that apply_impulse should not be used each frame to apply a continuous force, since its change is immediate and framerate dependent, where applied_force is modified by the step delta during integration.

Another way to look at it is that add_force should be used each physics step where it is affected by the physics delta in much the same way as moving a Node2D over time by multiplying a velocity vector by delta time. The current implementation, however, does not do this, as calling add_force each physics step will cause the rigidbody's velocity to increase exponentially.

This is how other physics engines like Box2D handle this, and how Godot's 3D physics handles this, so I believe the issue is Godot's 2D physics not clearing applied_force after each integration. Making this change would be considered breaking, but as long as it's documented I think 4.0 is the perfect time to make such a change.

All 9 comments

In the attached reproduction project, nothing happens when I run the 2D scene. I'm not sure which behavior is desired, but in addition to an inconsistency between 2D and 3D, this is inconsistent between Bullet and Godot physics in 3D.

@aaronfranke sorry, you move one of the nodes (Player) with wasd and the other (Magnet) has a force applied to it to follow if it is certain distance away from Player. Same with the 3D scene.

If you replace the apply_central_impulse call in the Magnet script with add_central_force, Magnet flies off the screen because the forces are accumulating on top of the forces from the previous frames.

My opinion is that the behavior should be consistent within Godot, whether it's 2D or 3D, or to make it configurable with the same defaults.

Also fwiw I think clearing forces at the end of integration is probably the most intuitive for common use case.

add_force(), add_central_force() and add_torque() are accumulative and are not cleared both in 2D and 3D. From the documentation: They are used to add a "constant directional force (i.e. acceleration)". Instead linear_damp and angular_damp are used to slowly decrease these values over time.
apply_impulse(), apply_central_impulse() and apply_torque_impulse() in both 2D and 3D are used to simulate one-off impacts.
Your minimal reproduction project doesn't show the difference between 2D and 3D because the 3D project uses add_central_force() and the 2D project uses apply_central_impulse(); so the different behaviour is to be expected.

Regarding the difference between Godot and Bullet physics 3D: The issue is to do with the RigidBody pushing the KinematicBody in Godot physics, which it shouldn't be able to do. Without looking into it, I suspect it is related to the issues with KinematicBody's test_body_motion() called by move_and_collide() that I've discussed here.

add_force(), add_central_force() and add_torque() are accumulative and are not cleared both in 2D and 3D. From the documentation: They are used to add a "constant directional force (i.e. acceleration)". Instead linear_damp and angular_damp are used to slowly decrease these values over time.

In 3D the applied_force is cleared at the end of integration. The equivalent line of code is not there for 2D, unless I'm missing something.

In both 2D and 3D, the damping values only seem to decrease _velocity_ over time (3D, 2D), not applied_force, unless again I'm missing something?

In the phrase "constant directional force" I take constant to mean that the magnitude of the force is constant, not that it will be applied persistently.

Fwiw analogous calls in other frameworks don't exhibit this behavior by default, so it feels like a reasonable assumption it wouldn't here too, especially since it doesn't in 3D RigidBodies.

Your minimal reproduction project doesn't show the difference between 2D and 3D because the 3D project uses add_central_force() and the 2D project uses apply_central_impulse(); so the different behaviour is to be expected.

I'll upload another test project today.

In 3D the applied_force is cleared at the end of integration. The equivalent line of code is not there for 2D, unless I'm missing something.

You're right. I had not noticed that before.

In the phrase "constant directional force" I take constant to mean that the magnitude of the force is constant, not that it will be applied persistently.

The question then is, what is the difference between add_force() and apply_impulse() (other than the units). In other words, is the problem that RigidBody2D is not clearing the applied_force or that RigidBody3D is?

I'll upload another test project today.

I've created the following minimal project to demonstrate the difference:
38646.zip

Both implement the following functions (except under 2D the vector is Vector2(10,0)):

var previous_velocity
func _ready():
    previous_velocity = linear_velocity
    add_central_force(Vector3(1,0,0))
func _process(delta):
    #add_central_force(Vector3(1,0,0))
    #apply_central_impulse(Vector3(1,0,0)*delta)
    print("Acceleration = " + str((linear_velocity - previous_velocity)/delta))
    previous_velocity = linear_velocity

Under 2D using add_central_force() is sufficient to continually accelerate the RigidBody. Under 3D it has not effect (the body stays still). Under both 2D and 3D using apply_central_impulse() continually in _process() (or _physics_process()) has the same effect: it provides a constant force on the RigidBody. Under 3D using add_central_force() continually in _process() has the same effect as apply_central_impulse(). Under 2D it continually increases the acceleration.

The issue I see is that in the 2D physics applied_force is added in its entirety during the integration step, but is never reduced or cleared, so it will continue to accumulate indefinitely. The purpose of apply_impulse is to cause an immediate, large change to the velocity, where add_force applies a continuous change over time by being called during each _physics_process(). In the documentation it's explicitly stated that apply_impulse should not be used each frame to apply a continuous force, since its change is immediate and framerate dependent, where applied_force is modified by the step delta during integration.

Another way to look at it is that add_force should be used each physics step where it is affected by the physics delta in much the same way as moving a Node2D over time by multiplying a velocity vector by delta time. The current implementation, however, does not do this, as calling add_force each physics step will cause the rigidbody's velocity to increase exponentially.

This is how other physics engines like Box2D handle this, and how Godot's 3D physics handles this, so I believe the issue is Godot's 2D physics not clearing applied_force after each integration. Making this change would be considered breaking, but as long as it's documented I think 4.0 is the perfect time to make such a change.

The docs could probably also use another sentence or two on add_force() clarifying that it should/shouldn't be called every frame, since it's pretty ambiguous as it is now. Should I open an issue on godot-docs referencing this one?

@name-here Better, you could contribute to the class reference directly :slightly_smiling_face:

Was this page helpful?
0 / 5 - 0 ratings