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