Buoyancy, gravity, and getting launched out of the water

So I have the beginnings of a platformer.

I want the player to be able to hop into water, swim down manually, then be pushed up when they’re not swimming down. This is where the up motion comes from. It is a desired behaviour.

When the player uses this buoyant motion to exit the water, gravity takes a second to counter the buoyancy and results in rising above the water line, letting the player leave the water. This, too, is a desired behaviour.

When the player hits the water, there is no middle ground where forces are not being applied. He is either moving up when in water or down when not in water. So, when he enters the water the next physics frame he’s leaving the water, launched at full speed upwards. This means that the problem is the sudden increase to his upwards momentum the frame after entering the water.

How would you solve this issue, keeping the desired behaviours? Measure depth in seconds, and only apply an upwards force when the player has accumulated some depth by holding the descend key? A better way? I’m all ears.

func _physics_process(delta):

velocity.x = direction.x * base_walk_speed * delta

# If underwater, we're buoyant.
if(is_underwater):
# Apply bouyancy here
velocity.y += direction.y * base_walk_speed * delta
velocity.y -= buoyancy * delta * base_walk_speed
velocity.y = clamp(velocity.y,-200,100)

# If in the air, we're subject to gravity.
if(!is_on_floor() && !is_underwater):
# Apply gravity here.
velocity.y += gravity * delta

# If we're on the floor & out of water & a jump is called,
if(!is_underwater && is_on_floor() && do_jump):
# Do jump and reset the flag.
velocity.y = -jump_power
do_jump = false

func human_form_movement():
print("Human coroutine started.")
var loop = true
while(loop):
# Wait for the next frame
await on_next_frame
direction.x = Input.get_axis("move_left","move_right")
if(!is_on_floor() && !is_underwater): direction.y = 1
if(is_underwater):
if(Input.is_action_pressed("descend")):
direction.y = Input.get_axis("none","descend")
else: direction.y = -1
if(Input.is_action_just_pressed("attack")):
attack_button_pressed.emit()
if(Input.is_action_just_pressed("jump")):
jump_button_pressed.emit()
if(active_form != "human"):
print("Ending human coroutine!")
loop = false

I ended up taking the position of the water / water’s Rectangle2D and saying essentially the global y position of the player minus the global y position of the water, when the player is in the water, results in the depth, which is then used to see if the player is in shallow enough water to jump. It still trampolines a little bit, but that can be worked on later.

1 Like

Thanks for posting your solution, I had forgotten to come back and post that it could be done by the position of the player but had not formulated as far as you had.

Well done on getting this far and look forward to seeing how you progress in this game

1 Like

Here’s the code for the solution I came up with. I’m really proud of how clean it is.

# INPUT DOES NOT GO HERE.
func human_form_physics():
var delta
var loop = true
while(loop):
delta = await on_next_physics_frame
if(!is_underwater):
# Handle x movement physics
if(!is_equal_approx(direction.x, 0)):
velocity.x += direction.x * land_acceleration * delta
else:
velocity.x -= velocity.x * land_friction * delta
# Handle jumping
if(is_on_floor() && do_jump):
velocity.y -= jump_power
do_jump = false
if(!is_on_floor() && do_jump):
#TODO: Double jump logic?
do_jump = false
# Do gravity off the floor
if(!is_on_floor()): velocity.y += gravity * delta
# Clamp movement speed to land speed
velocity.x = clamp(velocity.x,-land_max_velocity,land_max_velocity)
# Else if we're underwater,
else:
# Handle x movement physics
if(!direction.is_equal_approx(Vector2.ZERO)):

# Lateral swimming.
velocity.x += direction.x * water_acceleration
# Diving, if dive is pressed.
if(direction.y >= 0):
velocity.y += direction.y * water_acceleration
else:
# Water friction.
velocity.x -= velocity.x * water_friction * delta

# Handle jumping in shallow water
if(do_jump && depth <= is_shallow_water_value):
velocity.y -= jump_power
do_jump = false

# Handle buoyancy in deep water.
if(depth > min_depth_for_buoyancy):
velocity.y -= base_buoyancy * (depth / float(min_depth_for_buoyancy)) * delta
# Handle vertical friction in shallow water.
# If we're not buoyant we should not be moving up
# If we're in water, gravity does not affect us.

elif(direction.y <= 0):
# This line stops the trampoline effect.
velocity.y = lerp(velocity.y,0.0,water_friction * delta)

# Clamp movement speed to water speed
var min_v = Vector2(-water_max_velocity,-max_water_launch_velocity)
var max_v = Vector2(water_max_velocity,max_human_dive_velocity)
velocity = velocity.clamp(min_v, max_v)

# If we're not human, end human physics.
if(active_form != "human"):
loop = false
1 Like

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.