extends CharacterBody2D # ActionState should probably be expandable to enum ActionState {None, Cycling, Firing, Charging} enum MoveState {Still, Moving, Dashing, Knockback} @export var speed = 200 # How fast the player will move (pixels/sec). @export var dash_cooldown = 0.3 @onready var camera: Camera2D = %PlayerCamera @onready var sprite: AnimatedSprite2D = $PlayerSprite @onready var hand: Control = %Hand; @onready var shield: Sprite2D = $Shield @onready var score_counter: RichTextLabel = %ScoreCounter var score = 0; var high_score = 0; var shield_active = true; var move_direction = Vector2.ZERO; var target = Vector2.ZERO # The position of the player's cursor. var screen_size # Size of the game window. var action_state = ActionState.None var move_state = MoveState.Still var charge_level = 0; var charge_rate = 1; var charged = false; var dash_cooldown_timer: Timer = Timer.new() var knockback_timer: Timer = Timer.new() func get_target_pos(): return self.get_global_mouse_position() func update_move_direction(): move_direction = Vector2.ZERO if Input.is_action_pressed("move_right"): move_direction.x += 1 if Input.is_action_pressed("move_left"): move_direction.x -= 1 if Input.is_action_pressed("move_down"): move_direction.y += 1 if Input.is_action_pressed("move_up"): move_direction.y -= 1 move_direction = move_direction.normalized() func update_target_coords(): target = get_viewport().get_mouse_position() sprite.look_at(get_global_mouse_position()) func update_camera_position(): if not camera: return; var camera_target = (target - screen_size / 2) / 2 camera.position = camera.position.lerp(camera_target, 0.1) func charge(): # Charge attack if not charged and action_state == ActionState.Charging: charge_level += charge_rate; if charge_level >= 100: charged = false; charge_level = 0; elif charge_level > 0: # Gradual charge dropoff charge_level = max(charge_level - charge_rate * 2, 0); func dash(on_end): dash_cooldown_timer.start(dash_cooldown); var target_dir = position.direction_to(target) if (on_end): dash_cooldown_timer.timeout.connect(func(): on_end.call(null, self, target_dir), CONNECT_ONE_SHOT); velocity = move_direction * speed * 5; func _ready(): screen_size = get_viewport_rect().size knockback_timer.one_shot = true dash_cooldown_timer.one_shot = true add_child(knockback_timer); add_child(dash_cooldown_timer); func _process(_delta): update_target_coords(); update_camera_position(); if (move_state != MoveState.Dashing): update_move_direction(); # handle move_state if knockback_timer.time_left > 0 or dash_cooldown_timer.time_left > 0: pass elif Input.is_action_just_pressed("dash"): hand.discard(); # Must set move_state at start and end of dash elif (move_state != MoveState.Dashing): velocity = move_direction * speed; if velocity.x || velocity.y: move_state = MoveState.Moving; else: move_state = MoveState.Still; move_and_slide(); func take_hit(_damage, knockback_vector, knockback_strength = 1): # Called when the player takes damage $HitSFX.play() if shield_active: shield_active = false; # TODO visualize shield # $Shield.hide(); move_state = MoveState.Knockback; velocity = knockback_vector; knockback_timer.start(0.2 * knockback_strength); $Shield.visible = false; else: reboot(); func add_to_score(amount): score += amount; if score > high_score: high_score = score; score_counter.text = "Score: %d\nHigh Score: %d" % [score, high_score] func reset_score(): score = 0; score_counter.text = "Score: %d\nHigh Score: %d" % [score, high_score] func reboot(): # Called when the player dies # For now, just reset position and state position = Vector2.ZERO; velocity = Vector2.ZERO; move_state = MoveState.Still; shield_active = true; $Shield.visible = true; reset_score(); # hand.discard_all();