extends CharacterBody2D class_name Player @export_file('*.tscn') var LOSE_SCREEN; @onready var sound_player = $sound_player @onready var gliding_player = $gliding_player @onready var animator = $animator @onready var body = $oriented_container/body @onready var grab_ray = get_node('oriented_container/body/grab_ray') @onready var sprite = get_node('oriented_container/body') const SPEED = 100.0 const JUMP_VELOCITY = 200.0 const APNEA_DURATION = 10 const DURATION_BEFORE_LOSE_SCREEN = 2 const STATES= [ "climb", "die", "idle", "jump", "spawn", "walk", "swim" ] var use_gravity = { "climb": false, "die": true, "idle": true, "jump": true, "spawn": true, "walk": true, "swim": true } var can_grab_obstacles = { "climb": true, "die": false, "idle": true, "jump": true, "spawn": false, "walk": true, "swim": true } const SOUNDS = { "death": {"asset": preload("res://assets/sounds/player_death.wav"), "volume": 0.5}, "jump": {"asset": preload("res://assets/sounds/jump.wav"), "volume": 0.5}, "grab": {"asset": preload("res://assets/sounds/grab.wav"), "volume": 0.5}, "gliding": {"asset": preload("res://assets/sounds/gliding.wav"), "volume": -20}, } # Get the gravity from the project settings to be synced with RigidBody nodes. var WORLD_GRAVITY = ProjectSettings.get_setting("physics/2d/default_gravity") var gravity = WORLD_GRAVITY var grabbed_obstacle = null func _ready(): $fsm.set_states(STATES) add_to_group("player") g_game_state.restore_checkpoint.connect(on_restore_checkpoint) gliding_player.finished.connect(on_gliding_sound_finished) func _exit_tree(): g_game_state.restore_checkpoint.disconnect(on_restore_checkpoint) func on_enter_spawn_state(): $fsm.set_next_state("idle") func on_enter_idle_state(): pass func idle_state(_delta): var direction = Input.get_axis("player_left", "player_right") if is_on_floor(): velocity.x = 0 if direction: $fsm.set_next_state("walk") elif Input.is_action_just_pressed("player_jump") and is_on_floor(): $fsm.set_next_state("jump") func walk_state(_delta): var direction = Input.get_axis("player_left", "player_right") if direction != 0: velocity.x = direction * SPEED else: velocity.x = 0 $fsm.set_next_state("idle") if Input.is_action_just_pressed("player_jump") and is_on_floor(): $fsm.set_next_state("jump") if is_on_wall() or (Input.is_action_pressed('player_up') && is_on_ceiling()): velocity.y = 0 $fsm.set_next_state("climb") func on_enter_jump_state(): if is_on_floor(): velocity.y = -JUMP_VELOCITY sound_player.play_sound(SOUNDS["jump"]) func jump_state(_delta): var direction = Input.get_axis("player_left", "player_right") if(direction): velocity.x = move_toward(velocity.x, direction * SPEED, SPEED) if is_on_floor() and velocity.y > 0: if direction: $fsm.set_next_state("walk") else: velocity.x = 0 $fsm.set_next_state("idle") if is_on_wall() || (is_on_ceiling() ): $fsm.set_next_state("climb") func on_exit_jump_state(): sound_player.stop_sound() func on_enter_climb_state(): velocity = Vector2(0, 0) func climb_state(_delta): if is_on_wall(): var direction = Input.get_axis("player_up", "player_down") velocity = Vector2(0, direction * SPEED) if is_on_ceiling(): var direction = Input.get_axis("player_left", "player_right") velocity = Vector2(direction * SPEED, -1) if Input.is_action_just_pressed("player_jump") and is_on_wall(): var wall_position = get_slide_collision(0).get_normal().x var jump_normal = Vector2(wall_position, -0.5).normalized() velocity = jump_normal * JUMP_VELOCITY $fsm.set_next_state("jump") if is_on_ceiling() && Input.is_action_just_pressed("player_down"): velocity.y = 0 $fsm.set_next_state("idle") if !is_on_wall() && !is_on_ceiling(): $fsm.set_next_state("idle") func after_state(delta): move_and_slide() if is_dead(): return if use_gravity[$fsm.current_state] == true: handle_gravity(delta) if can_grab_obstacles[$fsm.current_state] == true: handle_grab() handle_glide() handle_sprite_orientation() func handle_glide(): if Input.is_action_just_pressed('player_jump'): gliding_player.play_sound(SOUNDS["gliding"]) if Input.is_action_just_released('player_jump'): gliding_player.stop_sound() func handle_gravity(delta): var gravity_delta = gravity if velocity.y > 0: if Input.is_action_pressed('player_jump'): gravity_delta = WORLD_GRAVITY / 10 else: gravity_delta = WORLD_GRAVITY velocity.y += gravity_delta * delta func handle_grab(): if Input.is_action_just_pressed('player_action'): if grabbed_obstacle == null: var collision = grab_ray.get_collider() if collision: var obstacle = collision if obstacle.is_in_group("obstacle"): do_grab(obstacle) else: drop_obstacle() func do_grab(obstacle): # BUGFIX : When grabbing a rock, allows other rocks to fall for ob in get_tree().get_nodes_in_group("obstacle"): if ob.has_method("set_sleeping"): ob.sleeping = false grabbed_obstacle = obstacle obstacle.grabbed($oriented_container) sound_player.play_sound(SOUNDS["grab"]) func drop_obstacle(): grabbed_obstacle.dropped() grabbed_obstacle = null func handle_sprite_orientation(): if is_on_wall_only(): if velocity.y != 0: var wall_position = get_slide_collision(0).get_position().x var player_position = $oriented_container.get_global_position().x var is_wall_right = wall_position > player_position if is_wall_right && velocity.y > 0: body.look_down_on_right_wall() put_grabbed_object_at(Vector2(0, 35)) if is_wall_right && velocity.y < 0: body.look_up_on_right_wall() put_grabbed_object_at(Vector2(0, -35)) if !is_wall_right && velocity.y > 0: body.look_down_on_left_wall() put_grabbed_object_at(Vector2(0, 35)) if !is_wall_right && velocity.y < 0: body.look_up_on_left_wall() put_grabbed_object_at(Vector2(0, -35)) elif is_on_ceiling_only(): if velocity.x != 0: if velocity.x > 0: body.look_right_on_ceiling() put_grabbed_object_at(Vector2(35, 0)) else: body.look_left_on_ceiling() put_grabbed_object_at(Vector2(-35, 0)) else: if velocity.x != 0: if velocity.x > 0: body.look_right() put_grabbed_object_at(Vector2(35, 0)) else: body.look_left() put_grabbed_object_at(Vector2(-35, 0)) func put_grabbed_object_at(at): if grabbed_obstacle: grabbed_obstacle.position = at func on_enter_swim_state(): sprite.modulate = Color(0, 0, 1) # TODO : light effect gravity = WORLD_GRAVITY / 2 func swim_state(_delta): var direction_x = Input.get_axis("player_left", "player_right") var direction_y = Input.get_axis("player_up", "player_down") velocity = Vector2(direction_x, direction_y).normalized() * SPEED var progress_to_black = 1 - clamp($fsm.state_duration / APNEA_DURATION, 0, 1) sprite.modulate = Color(progress_to_black, progress_to_black, progress_to_black ) if $fsm.state_duration >= APNEA_DURATION: die() func on_exit_swim_state(): sprite.modulate = Color(1, 1, 1) gravity = WORLD_GRAVITY sprite.modulate = Color(1,1,1) func swim(): if is_dead(): return $fsm.set_next_state("swim") func unswim(): if is_dead(): return $fsm.set_next_state("idle") func die(): $fsm.set_next_state("die") sound_player.play_sound(SOUNDS["death"]) func is_dead(): return $fsm.current_state == "die" func on_enter_die_state(): velocity = Vector2(0, 0) $shape.disabled = true $oriented_container.rotation = 0 sprite.flip_v = true func die_state(_delta): if $fsm.state_duration > DURATION_BEFORE_LOSE_SCREEN: get_tree().change_scene_to_file(LOSE_SCREEN) func on_restore_checkpoint(_level_rising): global_position = g_game_state.player_position_at_checkpoint func on_gliding_sound_finished(): if Input.is_action_pressed('player_jump'): gliding_player.play_sound(SOUNDS["gliding"]) func on_state_changed(_previous, current): animator.play(current)