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. :slight_smile:

func _physics_process(delta):
    velocity.x = direction.x * base_walk_speed * delta
    # If underwater, we're buoyant.
        # 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
        # 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
                direction.y = Input.get_axis("none","descend")
            else: direction.y = -1
        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.

func human_form_physics():
	var delta
	var loop = true
		delta = await on_next_physics_frame
			# Handle x movement physics
			if(!is_equal_approx(direction.x, 0)):
				velocity.x += direction.x * land_acceleration * delta
				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,
			# Handle x movement physics
				# Lateral swimming.
				velocity.x += direction.x * water_acceleration
				# Diving, if dive is pressed.
				if(direction.y >= 0):
					velocity.y += direction.y * water_acceleration
				# 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.

Privacy & Terms