Gravity not working properly

Godot Version

4.5

Question

I can not for the life of me figure out why my gravity isnt working as it should when I leave a wall.

extends CharacterBody2D

@export var max_speed: float = 50.0
@export var acceleration: float = 10000.0
@export var friction: float = 10000.0
@export var air_acceleration: float = 2000.0  
@export var air_friction: float = 500.0
@export var down_gravity: float = 500 
@export var up_gravity: float = 500

@export var max_decay: float = 100.0
@export var decay_rate: float = 1.0
@export var recharge_rate: float = 30.0
@export var min_decay_to_live: float = 0.0 
@export var death_delay: float = 2.0       

@export var burn_decay_multiplier: float = 3.0    
@export var normal_zoom: Vector2 = Vector2(1.0, 1.0)
@export var burn_zoom: Vector2 = Vector2(0.5, 0.5) 
@export var burn_light_boost: float = 3.0        

@export var jump_min_power: float = 150.0
@export var jump_max_power: float = 500.0
@export var jump_charge_time: float = 2.0  
@export var jump_arc_angle_deg: float = 60.0  

@export var max_jump_height: float = 250.0  
@export var min_jump_power: float = 0.3      
@export var max_jump_power: float = 2.0   
@export var jump_h_multiplier: float = 12.0  

var player_fire_shell_scene: PackedScene = preload("res://scenes/player_fire_shell.tscn")


var is_charging_jump: bool = false
var jump_charge: float = 0.0   
var is_in_jump: bool = false

var was_on_floor: bool = false
var wall_direction := 0
var last_wall_direction := 0
var last_on_wall := false
var last_on_floor := false
var decay: float
var is_dead: bool = false

@onready var anim = $AnimationPlayer       
@onready var anchor: Node2D = $Anchor      
@onready var steps = $Steps                 
@onready var left_raycast: RayCast2D = $"Left Raycast"
@onready var right_raycast: RayCast2D = $"Right Raycast"
@onready var down_raycast: RayCast2D = $"Down Raycast"
@onready var down_raycast_2: RayCast2D = $"Down Raycast/Down Raycast2"
@onready var down_raycast_3: RayCast2D = $"Down Raycast/Down Raycast3"
@onready var up_raycast: RayCast2D = $"Up Raycast"
@onready var up_raycast_2: RayCast2D = $"Up Raycast/Up Raycast2"
@onready var up_raycast_3: RayCast2D = $"Up Raycast/Up Raycast3"
@onready var gpu_particles_2d: GPUParticles2D = $GPUParticles2D
@onready var point_light_2d: PointLight2D = $PointLight2D
@onready var texture_progress_bar: TextureProgressBar = $CanvasLayer/TextureProgressBar
@onready var camera_2d: Camera2D = $Camera2D
@onready var water: TileMapLayer = $"../Water"
@onready var jump_bar: TextureProgressBar = $"CanvasLayer/Jump Bar"

enum PlayerState { NORMAL, CHARGING, JUMPING }
var state: PlayerState = PlayerState.NORMAL


func _ready() -> void:
	decay = max_decay


func _physics_process(delta: float) -> void:
	if is_dead:
		return 
	var burning := Input.is_action_pressed("burn")
	GameState.burning = burning
	
	if GameState.absorb == false:
		var rate := decay_rate
		if burning:
			rate *= burn_decay_multiplier
		decay -= rate * delta
	else:
		decay += recharge_rate * delta
	
	if is_in_water():
		decay_rate = 200.0
	
	decay = clamp(decay, 0.0, max_decay)
	texture_progress_bar.value = decay
	jump_bar.value = lerp(0, 100, jump_charge)
	

	
	if decay <= min_decay_to_live:
		_kill_player()
		return
	
	var on_floor_now := is_on_floor()
	var x_input := Input.get_axis("left", "right")
	var y_input := Input.get_axis("up", "down")
	var on_wall_left = test_move(transform, Vector2(-2,0))
	var on_wall_right = test_move(transform, Vector2(2,0))
	
	_update_surface_state()


	match state:
		PlayerState.NORMAL:
			if Input.is_action_just_pressed("jump") and _can_start_jump_charge():
				state = PlayerState.CHARGING
				jump_charge = 0.0
			else:
				_handle_horizontal_movement(delta, x_input)
				_handle_vertical_movement(delta, y_input)
				_handle_air_movement(delta, x_input)
				_handle_corner_movement_up(delta, x_input, y_input)
				_handle_corner_movement_down(delta, x_input, y_input)
				_apply_gravity(delta)

		PlayerState.CHARGING:
			print("charging")
			var left_pressing := Input.is_action_pressed("left")
			var right_pressing := Input.is_action_pressed("right")
			if Input.is_action_pressed("jump") and _can_start_jump_charge():
				jump_charge += delta / jump_charge_time
				
				print(jump_charge)
			else:
				if jump_charge > 0.0 and _is_on_surface():
					jump_charge = clamp(jump_charge, 0.0, 0.9)
					print(jump_charge)
					var dir := 0
					if left_pressing:
						dir = -1
					elif right_pressing:
						dir = 1
					_perform_charged_jump(dir)
					state = PlayerState.JUMPING
				else:
					state = PlayerState.NORMAL
				jump_charge = 0.0

			velocity.x = move_toward(velocity.x, 0.0, friction * delta)
			velocity.y = move_toward(velocity.y, 0.0, friction * delta)
			_apply_gravity(delta)

		PlayerState.JUMPING:
			print("jumping")
			var air_x := Input.get_axis("left", "right")
			if air_x != 0.0:
				var target_air_speed := air_x * max_speed
				var air_control := 0.3
				velocity.x = lerp(velocity.x, target_air_speed, air_control * delta)

			_apply_gravity(delta)

			if _is_on_surface():
				state = PlayerState.NORMAL

	
	
	_update_fire_visuals(burning)

	move_and_slide()

	if is_in_jump and _is_on_surface():
		is_in_jump = false

	was_on_floor = on_floor_now



func _can_start_jump_charge() -> bool:
	return _is_on_surface()


func _perform_charged_jump(dir: int) -> void:
	var shell_instance = player_fire_shell_scene.instantiate()
	shell_instance.global_position = global_position
	get_parent().add_child(shell_instance)
	
	var power: float = lerp(min_jump_power, max_jump_power, jump_charge)
	power = clamp(power, min_jump_power, max_jump_power)
	
	var decay_loss: float = lerp(0.0, 30.0, jump_charge)
	decay -= decay_loss
	decay = max(decay, 0.0)  
	
	velocity.y = 0.0

	velocity.y -= max_jump_height * power

	if dir == 0:
		velocity.x = 0.0
	else:
		print("sideways jump")
		velocity.x = dir * max_speed
		if anchor:
			anchor.scale.x = dir 
	
	jump_charge = 0.0






func _get_intensity() -> float:
	if max_decay <= 0.0:
		return 0.0
	return decay / max_decay
	

func _update_fire_visuals(burning: bool) -> void:
	var intensity := _get_intensity()
	
	var min_light_scale := 0.3
	var max_light_scale := 3.0
	var target_scale: float = lerp(min_light_scale, max_light_scale, intensity)
	
	
	if burning:
		target_scale *= burn_light_boost
	
	point_light_2d.scale = Vector2.ONE * target_scale
	
	var min_particle_scale := 0.4
	var max_particle_scale := 1.0
	var p_scale: float = lerp(min_particle_scale, max_particle_scale, intensity)
	gpu_particles_2d.scale = Vector2.ONE * p_scale
	
	
	var min_particle_life: float = 0.5
	var max_particle_life: float = 2.0
	var lifetime: float = lerp(min_particle_life, max_particle_life, intensity)
	gpu_particles_2d.lifetime = lifetime
	
	
	var min_particle_speed: float = 0.5
	var max_particle_speed: float = 2.0
	var speed: float = lerp(min_particle_speed, max_particle_speed, intensity)
	gpu_particles_2d.speed_scale = speed
	

func _update_surface_state() -> void:
	var on_wall := is_on_wall_custom()
	var on_floor := is_on_floor_custom()

	if left_raycast.is_colliding():
		wall_direction = -1
	elif right_raycast.is_colliding():
		wall_direction = 1
	else:
		wall_direction = 0

	if on_wall:
		last_on_wall = true
		last_wall_direction = wall_direction
	elif not on_wall and not on_floor:
		pass
	else:
		last_on_wall = false



func _is_on_surface() -> bool:
	return is_on_floor_custom() or is_on_wall_custom() or _is_on_ceiling_custom()
	
	
func _is_on_corner_down() -> bool:
	return (up_raycast_2.is_colliding() or up_raycast_3.is_colliding()) and not up_raycast.is_colliding()
	
	
func _is_on_corner_up() -> bool:
	return (down_raycast_2.is_colliding() or down_raycast_3.is_colliding()) and not down_raycast.is_colliding()


func is_on_wall_custom() -> bool:
	return left_raycast.is_colliding() or right_raycast.is_colliding()
	
	
func is_on_floor_custom() -> bool:
	return down_raycast.is_colliding() or down_raycast_2.is_colliding() or down_raycast_3.is_colliding()
	
	
func _is_on_ceiling_custom() -> bool:
	return up_raycast.is_colliding() or up_raycast_2.is_colliding() or up_raycast_3.is_colliding()


func _handle_horizontal_movement(delta: float, x_input: float) -> void:
	#if is_on_floor_custom() or _is_on_ceiling_custom():
	if x_input == 0.0:
		_apply_horizontal_friction(delta)
	else:
		_accelerate_horizontally(x_input, delta)
		if anchor:
			anchor.scale.x = sign(x_input)
				

func _handle_corner_movement_up(delta: float, x_input: float, y_input: float) -> void:
	if _is_on_corner_up():
		if y_input == 0.0:
			_apply_vertical_friction(delta)
		else:
			_accelerate_vertically(y_input, delta)
			
			
func _handle_corner_movement_down(delta: float, x_input: float, y_input: float) -> void:
	if _is_on_corner_down():
		if y_input == 0.0:
			_apply_vertical_friction(delta)
		else:
			_accelerate_vertically(y_input, delta)



func _handle_air_movement(delta: float, x_input: float) -> void:
	
	if not _is_on_surface():
		
		if x_input == 0.0:
			_apply_horizontal_friction(delta)
		else:
			_accelerate_horizontally(x_input, delta)
			if anchor:
				anchor.scale.x = sign(x_input)


func _handle_vertical_movement(delta: float, y_input: float) -> void:
	if _is_on_ceiling_custom() or is_on_wall_custom():
		if y_input == 0.0:
			_apply_vertical_friction(delta)
		

		if is_on_wall_custom() or _is_on_ceiling_custom():
			_accelerate_vertically(y_input, delta)

		


func _accelerate_horizontally(x_input: float, delta: float) -> void:
	var target_speed := x_input * max_speed
	var accel := acceleration
	velocity.x = move_toward(velocity.x, target_speed, accel * delta)


func _accelerate_vertically(y_input: float, delta: float) -> void:
	var target_speed := y_input * max_speed
	var accel := acceleration
	velocity.y = move_toward(velocity.y, target_speed, accel * delta)


func _apply_horizontal_friction(delta: float) -> void:
	var amount := friction * delta
	velocity.x = move_toward(velocity.x, 0.0, amount)


func _apply_vertical_friction(delta: float) -> void:
	var amount := friction * delta
	velocity.y = move_toward(velocity.y, 0.0, amount)


func _apply_gravity(delta: float) -> void:
	
	if not _is_on_surface():
		if velocity.y <= 0.0:
			velocity.y += up_gravity * delta
			print("moving up")
			
		print("moving down")
		velocity.y += down_gravity * delta
		

func _kill_player() -> void:
	if is_dead:
		return
	is_dead = true

	gpu_particles_2d.emitting = false

	point_light_2d.enabled = false

	velocity = Vector2.ZERO

	_respawn_later()


func _respawn_later() -> void:
	await get_tree().create_timer(death_delay).timeout
	_reload_scene()
	
	
func _reload_scene() -> void:
	var tree := get_tree()
	var current_scene := tree.current_scene
	if current_scene:
		tree.reload_current_scene()
		
		
func is_in_water() -> bool:
	var tile_pos = water.local_to_map(position)
	var tile_data = water.get_cell_tile_data(tile_pos)
	return tile_data != null 

Sorry I dont know how to paste code into here

Here’s an example on how to do so! People are a lot likely to help you if you format your code properly, otherwise very few people bother to try and read it in it’s current messy form.

2 Likes

Thank you! Fixed. I may have also just fixed the code anyway

You seem to be calling _apply_gravity() under all circumstances, so I assume the problem is inside that function or something it calls. It does nothing if _is_on_surface() returns true, so my first inclination would be:

Check whether _is_on_surface() is behaving the way you think it should be. Maybe put a label over the character’s head and update it every frame with the output of _is_on_surface()

1 Like

I think the issue was in how vertical movement function was working. I added a check to see if the player was on a wall or ceiling and it maybe works now?

1 Like