Oh wow, thanks so much for the effort!
I tried it. There was one compile error, as line_start
towards the end of the function was no longer defined.
After fixing that, it unfortunately still doesn’t work and I also understand why now.
I debugged the engine, and the gravity is indeed only updated every real tick.
The code that updates the state gravity is GodotBody2D::integrate_forces
in godot_body_2d.cpp
.
Going after the name, you might think that this is called with integrate_forces from the gdscript. It is, however, only invoked by the physics server’s step function.
Instead when calling state.integrate_forces() in script, the function
PhysicsDirectBodyState2D::integrate_forces()
in physics_server_2d.cpp
is called.
This function takes the gravity stored in the state, and then calculates a velocity and angular vector based on that.
What I did now was to calculate the gravity myself.
The following code is essentially a recreation of the GodotBody2D function adapted to my use case:
# Function to simulate the gravity at a given global coordinate
# This is mostly adapted from GodotBody2D::integrate_forces in godot_body_2d.cpp
func simulate_gravity(ball: RigidBody2D) -> Vector2:
var planet_gravity := Vector2(0,0)
for planet in PLANETS:
planet_gravity += get_gravity_for_body(ball, planet)
return planet_gravity
# Function to simulate the gravity at a given global coordinate
# This is mostly adapted from GodotBody2D::integrate_forces in godot_body_2d.cpp
# and https://www.reddit.com/r/gamedev/comments/w6woww/adding_orbital_gravity_to_your_game/
func get_gravity_for_body(satellite: RigidBody2D, planet: StaticBody2D) -> Vector2:
var planet_gravity_area : Area2D = planet.get_node("Area2D")
var planet_gravity := Vector2(0,0)
var gr_unit_dist : float = 0.0
if planet_gravity_area.gravity_point:
gr_unit_dist = planet_gravity_area.gravity_point_unit_distance
# Get the direction in-between the planet and the satellite
var v : Vector2 = planet.global_position - satellite.global_position
if gr_unit_dist > 0:
var v_length_sq = v.length_squared()
if (v_length_sq > 0):
var gravity_strength = planet_gravity_area.gravity * gr_unit_dist * gr_unit_dist / v_length_sq
planet_gravity = v.normalized() * gravity_strength
else:
planet_gravity = v.normalized() * planet_gravity_area.gravity
return planet_gravity
My initial idea was to combine this with your code.
Unfortunately, I can’t set the state gravity, so integrate_forces will always use the fixed gravity from the last tick. It also doesn’t actually move the object, but simply updates the vectors.
So I modified my former implementation to include my changes from above.
# Simulate the physics process to predict the path
func update_trajectory() -> void:
PHYSICS_TEST_BALL.hide()
var body_id = PHYSICS_TEST_BALL.get_rid()
var state = PhysicsServer2D.body_get_direct_state(body_id)
PHYSICS_TEST_BALL.set_deferred("freeze", false)
var line_start := Vector2(global_position.x, global_position.y + 100)
var line_end : Vector2
var drag : float = 0.0
# Lower timestep value = more precise physics calculation. Engine uses 0.0166..
var timestep := 0.0166
# Alternating colors of the line
var colors := [Color.RED, Color.BLUE]
PHYSICS_TEST_BALL.global_position = line_start
# Initial calculation and force application
var gravity_vec = simulate_gravity(PHYSICS_TEST_BALL)
var velocity : Vector2 = calculate_velocity(PHYSICS_TEST_BALL, timestep, gravity_vec, Vector2(0, EXPLOSION_FORCE))
# Smooth sailing without force application from here
# Predict until a goal is hit, or max steps
for i : int in 500:
#var gravity_vec = state.total_gravity
gravity_vec = simulate_gravity(PHYSICS_TEST_BALL)
#print("Sim: " + str(gravity_vec))
velocity += calculate_velocity(PHYSICS_TEST_BALL, timestep, gravity_vec)
velocity = velocity * clampf(1.0 - drag * timestep, 0, 1)
line_end = line_start + (velocity * timestep)
var collision:= PHYSICS_TEST_BALL.move_and_collide(velocity * timestep)
# If it hits something
if collision:
# TODO: differentiate between bodies (calculate bounce and continue)
# and the goal (break and finish)
velocity = velocity.bounce(collision.get_normal())
draw_line_global(line_start, PHYSICS_TEST_BALL.global_position, Color.YELLOW)
line_start = PHYSICS_TEST_BALL.global_position
continue
draw_line_global(line_start, line_end, colors[i%2])
line_start = line_end
func calculate_velocity(object: RigidBody2D, timestep: float, gravity = Vector2(), applied_force = Vector2(), constant_force = Vector2()) -> Vector2:
return object.mass * gravity + applied_force + constant_force
You can see from the following screenshot that it is somewhat close, but not yet perfect.
It especially falls apart after the first collision. I’ll keep trying to get closer to the actual movement.