Files
Omens-Ascent/scenes/actors/player.gd

286 lines
7.2 KiB
GDScript

class_name Player
extends CharacterBody2D
# Player States
enum State {
IDLE,
RUN,
JUMP,
ATTACK_1,
ATTACK_2,
ATTACK_3,
HEAVY_ATTACK, # A rajouter ?
DASH,
DASH_ATTACK, # A rajouter
DEFEND, # A rajouter
HURT,
DEATH,
}
# Player Nodes Variables
@onready var _sprite: AnimatedSprite2D = $AnimatedSprite2D
@onready var _hitbox: HitBox = $HitBox
@onready var _hitbox_collision: CollisionShape2D = $HitBox/CollisionShape2D
@onready var _health: HealthComponent = $HealthComponent
@onready var _hurtbox: HurtBox = $HurtBox
# Collision masks (World = 1, EnemyBody = 8)
const _DEFAULT_COLLISION_MASK: int = 1 | 8
const _DASH_COLLISION_MASK: int = 1
const _DEATH_SCREEN_PATH: String = "res://scenes/components/death_screen.tscn"
# Damage dealt per attack state
const _ATTACK_DAMAGE: Dictionary = {
State.ATTACK_1: 3,
State.ATTACK_2: 3,
State.ATTACK_3: 5,
State.HEAVY_ATTACK: 8,
State.DASH_ATTACK: 2,
}
# Les frames ou la Hitbox est active
const _ATTACK_HIT_FRAMES: Dictionary = {
State.ATTACK_1: Vector2i(2, 4),
State.ATTACK_2: Vector2i(2, 4),
State.ATTACK_3: Vector2i(2, 4),
State.DASH_ATTACK: Vector2i(4, 6),
State.HEAVY_ATTACK: Vector2i(6, 8),
}
# Frames d'invincibilité lors du DEFEND
const _DEFEND_INVULN_FRAMES: Vector2i = Vector2i(0, 4)
# Player Runtime Variables
var direction: float = 0.0
var facing_direction: float = 1.0
var current_state: State = State.IDLE
var is_attacking: bool = false
var combo_step: int = 0
var combo_timer: float = 0.0
var is_dashing: bool = false
var dash_timer: float = 0.0
var dash_cooldown_timer: float = 0.0
var is_hurt: bool = false
var is_dead: bool = false
var is_defending: bool = false
# Player Exported Variables
@export_group("Movement")
@export var move_speed: float = 200.0
@export_group("Jump")
@export var jump_velocity: float = -350.0
@export_group("Combat")
@export var attack_cooldown: float = 0.4
@export var heavy_attack_cooldown: float = 0.6
@export var combo_window: float = 0.6
@export var attack_move_speed: float = 0.0
@export_group("Dash")
@export var dash_speed: float = 500.0
@export var dash_duration: float = 0.2
@export var dash_cooldown: float = 0.8
func _ready() -> void:
add_to_group("player")
_sprite.animation_finished.connect(_on_animation_finished)
_health.health_changed.connect(_on_health_changed)
_health.died.connect(_on_died)
_hurtbox.hit_received.connect(_on_hurtbox_hit_received)
EventBus.player_heal_requested.connect(_on_player_heal_requested)
if EventBus.has_checkpoint:
global_position = EventBus.last_checkpoint_position
_on_health_changed.call_deferred(_health.current_health, _health.max_health)
func _physics_process(delta: float) -> void:
_apply_gravity(delta)
_process_dash_input()
_process_defend_input()
_apply_movement(delta)
_process_attack_input()
_update_combo_timer(delta)
_update_dash_timers(delta)
_update_state()
_update_animation()
_update_hitbox()
move_and_slide()
func _apply_gravity(delta: float) -> void:
if is_dashing:
return
if not is_on_floor():
velocity.y += ProjectSettings.get_setting("physics/2d/default_gravity") * delta
func _apply_movement(delta: float) -> void:
if is_dead or is_hurt:
velocity.x = 0.0
return
if is_dashing:
velocity.x = facing_direction * dash_speed
return
if is_attacking:
velocity.x = facing_direction * attack_move_speed
return
if is_defending:
velocity.x = 0.0
return
direction = Input.get_axis("move_left", "move_right")
velocity.x = direction * move_speed
if direction != 0.0:
facing_direction = direction
if is_on_floor() and Input.is_action_just_pressed("jump"):
velocity.y = jump_velocity
AudioManager.play_sfx("jump")
func _process_dash_input() -> void:
if is_dashing or is_attacking or is_defending or is_hurt or is_dead or dash_cooldown_timer > 0.0:
return
if not Input.is_action_just_pressed("dash"):
return
is_dashing = true
dash_timer = dash_duration
dash_cooldown_timer = dash_cooldown
velocity.y = 0.0
current_state = State.DASH
collision_mask = _DASH_COLLISION_MASK
func _process_defend_input() -> void:
if is_defending or is_attacking or is_dashing or is_hurt or is_dead:
return
if not Input.is_action_just_pressed("defend"):
return
is_defending = true
current_state = State.DEFEND
func _process_attack_input() -> void:
if is_attacking or is_dashing or is_defending or is_hurt or is_dead or not Input.is_action_just_pressed("light_attack"):
return
if combo_step > 0 and combo_timer > 0.0:
combo_step += 1
else:
combo_step = 1
is_attacking = true
combo_timer = 0.0
current_state = State.values()[State.ATTACK_1 + combo_step - 1]
AudioManager.play_sfx("slash_1")
func _update_combo_timer(delta: float) -> void:
if combo_timer > 0.0:
combo_timer -= delta
if combo_timer <= 0.0:
combo_step = 0
func _update_dash_timers(delta: float) -> void:
if is_dashing:
dash_timer -= delta
if dash_timer <= 0.0:
is_dashing = false
collision_mask = _DEFAULT_COLLISION_MASK
if dash_cooldown_timer > 0.0:
dash_cooldown_timer -= delta
func _on_animation_finished() -> void:
if current_state == State.HURT:
is_hurt = false
return
if current_state == State.DEFEND:
is_defending = false
return
if not is_attacking:
return
is_attacking = false
if current_state == State.ATTACK_3:
combo_step = 0
else:
combo_timer = combo_window
func _update_state() -> void:
if is_dead:
return
if is_hurt:
current_state = State.HURT
return
if is_defending:
return
if is_attacking:
return
if is_dashing:
current_state = State.DASH
return
if not is_on_floor():
current_state = State.JUMP
elif direction != 0.0:
current_state = State.RUN
else:
current_state = State.IDLE
func _update_animation() -> void:
var animation_name: String = State.keys()[current_state].to_lower()
if _sprite.animation != animation_name:
_sprite.play(animation_name)
_sprite.flip_h = facing_direction < 0.0
func _update_hitbox() -> void:
_hitbox.scale.x = facing_direction
_hitbox.damage = _ATTACK_DAMAGE.get(current_state, 0)
var hit_frames: Vector2i = _ATTACK_HIT_FRAMES.get(current_state, Vector2i(-1, -1))
var is_active_hit_frame: bool = _sprite.frame >= hit_frames.x and _sprite.frame <= hit_frames.y
_hitbox_collision.disabled = not (is_attacking and is_active_hit_frame)
func kill() -> void:
_health.kill()
func _on_player_heal_requested(amount: int) -> void:
_health.heal(amount)
func _on_hurtbox_hit_received(hitbox: HitBox) -> void:
if _is_blocking_hit():
return
_health.take_damage(hitbox.damage)
is_attacking = false
is_defending = false
if is_dashing:
is_dashing = false
collision_mask = _DEFAULT_COLLISION_MASK
is_hurt = true
current_state = State.HURT
func _is_blocking_hit() -> bool:
return is_defending \
and _sprite.frame >= _DEFEND_INVULN_FRAMES.x \
and _sprite.frame <= _DEFEND_INVULN_FRAMES.y
func _on_health_changed(current_health: int, max_health: int) -> void:
EventBus.player_health_changed.emit(current_health, max_health)
func _on_died() -> void:
EventBus.player_died.emit()
is_dead = true
_hurtbox.set_deferred("monitoring", false)
_hitbox_collision.set_deferred("disabled", true)
_switch_to_death_screen()
func _switch_to_death_screen() -> void:
EventBus.last_level_path = get_tree().current_scene.scene_file_path
EventBus.last_death_facing_direction = facing_direction
SceneManager.call_deferred("change_scene", _DEATH_SCREEN_PATH)