extends KinematicBody2D class_name Player export var run_speed := 100.0 export var jump_power := 180.0 export var gravity: = 500.0 export var max_gravity:= 450.0 export var respawn_position:=Vector2.ZERO export var wall_slide_friction:=.2 export var wall_jump_speed_factor := Vector2(1 ,.8) export var dash_thrust = 550.0 export var max_wall_slide_gravity := 100.0 var LandingDust = load("res://src/Actors/LandingDust.tscn") var JumpDust = load("res://src/Actors/JumpDust.tscn") var LightBeam = load("res://src/Actors/LightBeam.tscn") signal landed signal jumping signal died var _velocity: Vector2 = Vector2.ZERO var _landing_position:Vector2 = Vector2.ZERO var _is_wall_jumping := false var _alive := true var _can_dash := true var _is_jump_canceled := false var _direction := Vector2.ZERO var _falling_start_position := .0 enum States { IDLE, IN_AIR, DASHING, WALL_SLIDING, ATTACKING } var _state = States.IDLE enum Direction{ RIGHT = 1, LEFT = -1 } export var abilities:= { "dash": false, "wall_jump": false, "attack": false, "light_beam": false } func _ready() -> void: respawn_position = position _falling_start_position = respawn_position.y $AnimationPlayer.play("idle") func _physics_process(delta: float) -> void: if _alive: _update_state() _input_check() _velocity = calculate_move_velocity(_direction,_is_jump_canceled, delta) _velocity = move_and_slide(_velocity, Vector2.UP) _check_collisions() update_sprite(_direction) func _input_check() -> void: if self._alive and (GameState.get_state() == GameState.States.GAME or GameState.get_state() == GameState.States.NEW_GAME): _is_jump_canceled = Input.is_action_just_released("jump") and !_is_wall_jumping and _velocity.y < 0.0; _get_direction() _check_dash() if Input.is_action_just_pressed("attack") and abilities.attack: _attack() else: _direction = Vector2.ZERO func _check_collisions(): for i in get_slide_count(): var collision = get_slide_collision(i) if collision.collider.name == 'TrapTiles': die() return func _update_state(new_state:int = -1): var auto_update = new_state == -1; if new_state == -1: new_state = _state if _state == States.DASHING: return if _state == States.ATTACKING: return if _state == States.IDLE: if _velocity.y > 0 and not is_on_floor(): # Falling from ground new_state = States.IN_AIR elif _state == States.IN_AIR: if is_on_floor(): _on_landed() new_state = States.IDLE elif _velocity.y >=0 and is_on_wall(): if _state != States.WALL_SLIDING: _velocity.y=0 _falling_start_position = position.y new_state = States.WALL_SLIDING elif _state == States.WALL_SLIDING: if is_on_floor() or not is_on_wall(): new_state = States.IDLE else: new_state = States.IDLE _transition_state(_state, new_state, auto_update) func _transition_state(old_state, new_state, auto_update): if new_state != old_state: # print("[Player]: State Update: [%s] -> [%s] | Automatic: %s" % [old_state, new_state, auto_update]) _state = new_state if old_state == States.IDLE: # idle -> attak = ground attack - stop _velocity.x if new_state == States.ATTACKING: _direction.x = 0; _velocity.x = 0; if old_state == States.DASHING: if new_state == States.IDLE: _can_dash = true if old_state == States.WALL_SLIDING: if new_state == States.IDLE: _on_landed() func _on_landed(): _can_dash = true var dust = LandingDust.instance() dust.position = position get_parent().add_child(dust) var fall_distance = position.y - _falling_start_position if fall_distance > 150: $Camera.start_shake() _falling_start_position = position.y emit_signal("landed", position) func _on_jump(wall_jump:bool = false): AudioManager.play_sfx(AudioManager.Sfx.PLAYER_JUMP) var dust:Node = JumpDust.instance() dust.position = position; get_parent().add_child(dust) if wall_jump: dust.position.x += 2 * -$Sprite.scale.x dust.get_node("Sprite").rotate(deg2rad(90) * -$Sprite.scale.x) emit_signal("jumping", position) func _get_direction() -> void: var right_strength = round(Input.get_action_strength("direction_right")) var left_strength = round(Input.get_action_strength("direction_left")) var vertical_movement = right_strength - left_strength if not _state == States.DASHING else 0 var is_in_jumpable_state = _state == States.IDLE or _state == States.WALL_SLIDING _direction = Vector2( vertical_movement, -1.0 if Input.is_action_just_pressed("jump") and is_in_jumpable_state else 1.0 ) func _check_dash(): if Input.is_action_just_pressed("dash") and _can_dash and abilities.dash: var dash_velocity := Vector2($Sprite.scale.x * dash_thrust,0) $Camera.start_shake(.1, 15, 2) _can_dash = false # Wall dash first if _state == States.WALL_SLIDING: dash_velocity.x = $Sprite.scale.x * dash_thrust * -1 if dash_velocity.x < 0 : $DashParticlesLeft.emitting = true; else: $DashParticlesRight.emitting = true _velocity = dash_velocity $DashTimeout.start() _update_state(States.DASHING) # turn off gravity while dashing func calculate_move_velocity(direction:Vector2, is_jump_canceled:bool, delta:float)->Vector2: var output: = _velocity var current_gravity = gravity * wall_slide_friction if _state == States.WALL_SLIDING else gravity output.x = lerp(_velocity.x, run_speed * direction.x, .1 if _state == States.IN_AIR else .5) output.y += current_gravity * delta if _state == States.DASHING: return _velocity if direction.y == -1.0: # we are jumping var wall_jump := false if _state == States.WALL_SLIDING and abilities.wall_jump: # wall jump wall_jump = true var walljump__x_direction = -1 * direction.x var desired = (run_speed * wall_jump_speed_factor.x * walljump__x_direction) output.x = desired output.y = jump_power * wall_jump_speed_factor.y * direction.y else: #jump output.y = jump_power * direction.y _update_state(States.IN_AIR) _on_jump(wall_jump) else: if is_jump_canceled: output.y = 0 if output.y < max_gravity * -1: output.y = max_gravity if _state == States.WALL_SLIDING and output.y > max_wall_slide_gravity: output.y = max_wall_slide_gravity return output; func _respawn(): position = respawn_position _velocity = Vector2.ZERO func _revive(): _alive = true func update_sprite(_direction:Vector2) -> void: if not _alive: return; var air_animation = "jump" if _velocity.y <= 0 else "fall" if _state == States.ATTACKING: return var attack = Input.is_action_just_pressed("attack") and abilities.attack if _velocity.x > .5 and not _state == States.WALL_SLIDING: face_player(true) $AnimationPlayer.play("run" if is_on_floor() else air_animation) elif _velocity.x < -.5 and not _state == States.WALL_SLIDING: face_player(false) $AnimationPlayer.play("run" if is_on_floor() else air_animation) else: if not _state == States.IN_AIR: $AnimationPlayer.play("idle") if _state == States.WALL_SLIDING: $AnimationPlayer.play("wall_slide") return if _state == States.IN_AIR: $AnimationPlayer.play(air_animation) if air_animation == "fall" and position.y < _falling_start_position: _falling_start_position = position.y return if _state == States.DASHING: $AnimationPlayer.play("jump") return func _attack(): if _state == States.DASHING: var beam = LightBeam.instance() beam.direction = Vector2($Sprite.scale.x, 0) beam.position = $Sprite/LightBeamInitPoint.get_global_transform().get_origin() get_parent().add_child(beam) $DashTimeout.stop() _on_DashTimeout_timeout(false) _update_state(States.ATTACKING) AudioManager.play_sfx(AudioManager.Sfx.PLAYER_ATTACK) var attack_animation = "attack_right" if $Sprite.scale.x > 0 else "attack_left" $AnimationPlayer.play(attack_animation) $AnimationPlayer.playback_speed = 2.5 yield($AnimationPlayer, "animation_finished") $AnimationPlayer.seek(0) $AnimationPlayer.playback_speed = 1 _update_state(States.IDLE) func _on_die_animation_done(): _respawn() _revive() func die(): emit_signal("died") _alive = false $AnimationPlayer.play("die") $Camera.start_shake() GameState.player_died() func set_ability(ability:String, enabled:bool=false): if ability == 'dash': abilities.dash = enabled elif ability == 'wall_jump': abilities.wall_jump = enabled elif ability == 'attack': abilities.attack = enabled else: pass func _on_DashTimeout_timeout(timed_out=true) -> void: $AnimationPlayer.playback_speed = 1 $DashParticlesLeft.emitting = false $DashParticlesRight.emitting = false _velocity.x = run_speed * _velocity.normalized().x; if timed_out: _update_state(States.IN_AIR) pass # Replace with function body. func _on_SordRange_body_entered(body: Node) -> void: if body.is_in_group("Enemies"): body.take_damage($Sprite.scale.x) pass # Replace with function body. func get_camera(): return $Camera func face_player(right:bool = true): $Sprite.scale = Vector2( 1 if right else -1, 1 )