From d9dae634ef8d9fa42ffd81a6f875db0c854760e2 Mon Sep 17 00:00:00 2001 From: RealMelwei Date: Tue, 14 Oct 2025 14:43:38 +0200 Subject: [PATCH] Refactored player code and added quick access to cardinal directions to EarthALigner --- enemies/boss/boss.gd | 5 +- enemies/boss/boss_spawner.gd | 2 +- enemies/leech/leech.gd | 2 +- items/consumables/bow/bow.gd | 2 +- .../horizontal_dash/horizontal_dash.gd | 9 +- player/player.gd | 139 ++++++++++-------- player/player.tscn | 12 +- player/sword.gd | 2 +- utils/earth_aligner.gd | 12 ++ 9 files changed, 109 insertions(+), 76 deletions(-) diff --git a/enemies/boss/boss.gd b/enemies/boss/boss.gd index a0472f7..1a84b2f 100644 --- a/enemies/boss/boss.gd +++ b/enemies/boss/boss.gd @@ -1,5 +1,4 @@ extends CharacterBody2D -@onready var earthaligner = $EarthAligner @onready var player = get_tree().get_root().get_node("main/Player") var moves = ["slam", "wave", "water_rise", "splash"] @export var big_blob : PackedScene @@ -50,7 +49,7 @@ func _process(_delta: float) -> void: func _physics_process(delta: float) -> void: if dead: return - up_direction = earthaligner.global_from_local(Vector2.UP) + up_direction = $EarthAligner.up if(is_on_floor()): grounded.emit() if idle_move: move_idle(delta) @@ -62,7 +61,7 @@ func move_idle(delta : float): # Pick a random target roughly above the player's head every 0.5 seconds. idle_dir_remaining -= delta if(idle_dir_remaining <= 0): - target_pos = player.position + player.earth_aligner.global_from_local(Vector2.UP) * 400 + target_pos = player.position + player.get_node("EarthAligner").up * 400 target_pos += randf_range(0, max(200, (target_pos - global_position).length())*0.25) * Vector2.from_angle(randf_range(0,TAU)) idle_dir = (target_pos - global_position).normalized()* max(200, (target_pos - global_position).length()) * 0.4 idle_dir_remaining = 0.5 diff --git a/enemies/boss/boss_spawner.gd b/enemies/boss/boss_spawner.gd index aa7e3a1..c7bdcb6 100644 --- a/enemies/boss/boss_spawner.gd +++ b/enemies/boss/boss_spawner.gd @@ -9,5 +9,5 @@ func _on_water_water_reached_max_height() -> void: # The boss spawns once the water has risen to its maximum height. var node = boss.instantiate() add_sibling(node) - node.position = %Player.position + %Player.earth_aligner.local_from_global(Vector2.UP) * 1000; + node.position = %Player.position + %Player.get_node("EarthAligner").up * 1000; queue_free() diff --git a/enemies/leech/leech.gd b/enemies/leech/leech.gd index 5fd6fa9..65b2537 100644 --- a/enemies/leech/leech.gd +++ b/enemies/leech/leech.gd @@ -15,7 +15,7 @@ func _process(delta: float) -> void: if dead: return # Fall slowly while not grounded if not $GroundSensor.has_overlapping_bodies(): - position += 200 * delta * $EarthAligner.global_from_local(Vector2.DOWN) + position += 200 * delta * $EarthAligner.down var y = position.length() var ratio = - move_dir * broadth / (2 * y) diff --git a/items/consumables/bow/bow.gd b/items/consumables/bow/bow.gd index 60948ee..1d8abc4 100644 --- a/items/consumables/bow/bow.gd +++ b/items/consumables/bow/bow.gd @@ -13,7 +13,7 @@ func activate(): get_tree().get_root().add_child(arrow) arrow.position = player.position arrow.rotation = player.rotation - arrow.direction = player.earth_aligner.global_from_local(Vector2(player.facing, 0)) + arrow.direction = player.get_node("EarthAligner").right * player.facing # Make sure the arrow sprite faces the right direction if(player.facing == -1): diff --git a/items/consumables/horizontal_dash/horizontal_dash.gd b/items/consumables/horizontal_dash/horizontal_dash.gd index dd314ce..addbac5 100644 --- a/items/consumables/horizontal_dash/horizontal_dash.gd +++ b/items/consumables/horizontal_dash/horizontal_dash.gd @@ -2,14 +2,15 @@ extends ActiveItem @export var cooldown = 0.2 var dash_time = 0.15 var dash_timer : SceneTreeTimer -var dash_dir +var dash_velocity +var dash_speed = 1600 func _process(delta: float) -> void: super(delta) # While the dash is active, move the player in the # (absolute!) direction fixed at the start of the dash if dash_timer != null and dash_timer.time_left > 0: - player.reset_to_velocity = player.earth_aligner.local_from_global(dash_dir) + player.reset_to_velocity = player.get_node("EarthAligner").local_from_global(dash_velocity) func actually_collect(): player.set_cooldown(cooldown) @@ -20,8 +21,8 @@ func activate(): # The dash refills one air jump if possible and provides iframes. player.air_jumps_current += 1 dash_timer = get_tree().create_timer(dash_time) - dash_dir = player.earth_aligner.global_from_local(Vector2.RIGHT * player.facing * 1600) - player.inv_time = max(player.inv_time, dash_time) + dash_velocity = player.get_node("EarthAligner").right * player.facing * dash_speed + player.get_node("IFrames").start(dash_time) func remove(reset_player_active = true): # If the item is removed during the dash, clear the active item slot, diff --git a/player/player.gd b/player/player.gd index 40a14a4..e527f38 100644 --- a/player/player.gd +++ b/player/player.gd @@ -1,10 +1,4 @@ class_name Player extends CharacterBody2D -# Child Nodes -@onready var camera : Camera2D = $Camera2D; -@onready var anim_sprite : AnimatedSprite2D = $AnimatedSprite2D; -@onready var earth_aligner : Node2D = $EarthAligner; -@onready var sword : Area2D = $Sword; -@onready var active_item_cooldown : Timer = $ActiveItemCooldown @export var double_jump_animation : PackedScene @@ -31,8 +25,11 @@ signal health_changed(new_health : int) signal max_hp_changed(new_max_hp : int) signal player_died +var hit_iframes = 0.8 var current_hp = 5: set(new_hp): + # HP can't be increased above Max HP or reduced below 0 + # When reduced to 0, the player dies. new_hp = min(new_hp, max_hp) if new_hp <= 0: new_hp = 0 @@ -41,14 +38,14 @@ var current_hp = 5: current_hp = new_hp health_changed.emit(current_hp) @export var max_hp = 5: + # When Max HP is reduced below current health, current health is adjusted. set(new_max_hp): max_hp = new_max_hp if max_hp <= 0: max_hp = 0 - die() + if current_hp > max_hp: + current_hp = max_hp max_hp_changed.emit(max_hp) -var hit_invulnerability = 0.8 -var inv_time = 0; var dead = false; # Received Knockback @@ -58,10 +55,7 @@ var damage_knockup = 500 @export var friction = 0.5 # Attack Handling -var atk_cooldown = 0.35 -var atk_timer = 0 var can_upslash = false - signal attack # Active Item @@ -73,56 +67,57 @@ var active_item : ActiveItem = null: func set_cooldown(cooldown): - active_item_cooldown.wait_time = cooldown + $ActiveItemCooldown.wait_time = cooldown func activate_cooldown(): - active_item_cooldown.start() + $ActiveItemCooldown.start() func _ready() -> void: + # Update the Health Bar initially max_hp_changed.emit(max_hp) health_changed.emit(current_hp) func _physics_process(delta: float) -> void: + # Velocity management uses the physics framework, hence runs at fix 60FPS manage_velocity(delta) move_and_slide() - update_vine_statuses() func _process(delta: float) -> void: - manage_iframes(delta) + # All non-velocity management can run without FPS cap + update_vine_statuses() manage_movement_options() manage_interaction() - manage_active(delta) manage_animation() - manage_attack(delta) + if handle_input: + manage_active(delta) + manage_attack() -func manage_iframes(delta: float): - if(inv_time > 0): - inv_time = max(0, inv_time-delta) - -func manage_attack(delta : float): - atk_timer = max(0, atk_timer-delta) - if handle_input: - if(Input.is_action_just_pressed("attack") and atk_timer <= 0): - if Input.is_action_pressed("up") and can_upslash: - attack.emit("up") - else: - attack.emit("horizontal") - $AnimatedSprite2D.play("attack") - $SwordSwingAudio.play() - atk_timer = atk_cooldown +func manage_attack(): + # If an attack is possible, a signal is sent which the weapon can connect to + if(Input.is_action_just_pressed("attack") and $AttackCooldown.time_left <= 0): + if Input.is_action_pressed("up") and can_upslash: + attack.emit("up") + else: + attack.emit("horizontal") + $Sprite.play("attack") + $SwordSwingAudio.play() + $AttackCooldown.start() func manage_active(_delta : float): - if(active_item != null and Input.is_action_just_pressed("item") and active_item_cooldown.is_stopped()): + # Activate or remove items. Cooldown + use management is handled by the item. + if(active_item != null and Input.is_action_just_pressed("item") and $ActiveItemCooldown.is_stopped()): active_item.trigger_activation() if(Input.is_action_just_pressed("drop_item") and active_item != null): active_item.remove() func manage_movement_options() -> void: + # Reset Air Jumps when grounded if(is_on_floor()): air_jumps_current = air_jumps_max func manage_interaction(): + # Interacts with all overlapping interactables on button press if Input.is_action_just_pressed("interact"): for area in $InteractBox.get_overlapping_areas(): if area.has_method("interact"): area.interact() @@ -135,68 +130,81 @@ func manage_animation() -> void: if(Input.is_action_pressed("move_left")): walk_dir -= 1 - if(walk_dir != 0): + # Set the direction the player faces + if walk_dir != 0: facing = walk_dir - anim_sprite.scale.x = - abs(anim_sprite.scale.x) * facing - if(is_on_floor() and not $AnimatedSprite2D.is_playing()): - anim_sprite.play("walk") + $Sprite.scale.x = - abs($Sprite.scale.x) * facing + + # Play the walk or idle animation when appropriate + if(walk_dir != 0): + if(is_on_floor() and not $Sprite.is_playing()): + $Sprite.play("walk") else: - if anim_sprite.animation == "walk": - anim_sprite.stop() + if $Sprite.animation == "walk": + $Sprite.stop() func manage_velocity(delta: float) -> void: - up_direction = (position - earth_center).normalized(); - var old_local_velocity = earth_aligner.local_from_global(velocity) + up_direction = $EarthAligner.up + # Convert the current velocity into local coordinates, then compute changes there. + # This is important for capped values such as fall speed. + var old_local_velocity = $EarthAligner.local_from_global(velocity) + # Apply friction horizontally, exponentially. Factor 1 - friction per frame at 60FPS. var local_velocity = Vector2(old_local_velocity.x * pow(1 - friction,60*delta), old_local_velocity.y); local_velocity += Vector2(0, gravity) if handle_input: - #if has_node("Slow"): print(get_node("Slow").params) + # Apply Slow Status if present. var hspeed = base_hspeed if not Status.affects("Slow", self) else base_hspeed * get_node("Slow").params.slow_factor + + # Change the local velocity by the movement speed, or more if moving in the opposite direction, + # for more responsive turning around. if(Input.is_action_pressed("move_right")): - if local_velocity.x > -700: + if local_velocity.x > - 3 * hspeed: local_velocity += Vector2(hspeed, 0) else: - local_velocity += Vector2(hspeed/2.0, 0) + local_velocity += Vector2(2 * hspeed, 0) if(Input.is_action_pressed("move_left")): - if local_velocity.x < 700: + if local_velocity.x < 3 * hspeed: local_velocity += Vector2(-hspeed, 0) else: - local_velocity += Vector2(-hspeed/2.0, 0) - + local_velocity += Vector2(-2 * hspeed, 0) + if(Input.is_action_just_pressed("jump") and (is_on_floor() or air_jumps_current > 0)): - var dropped = false - if(not is_on_floor()): - air_jumps_current -= 1; - play_double_jump_animation() - elif (Input.is_action_pressed("drop")): - dropped = true - self.position += earth_aligner.global_from_local(Vector2(0,12)) - if(not dropped): + # If the player holds drop, just move through the platform + if Input.is_action_pressed("drop"): + self.position += 12 * $EarthAligner.down + else: + # Otherwise, either jump from the ground or perform a double jump. if is_on_floor(): local_velocity.y = -ground_jump_strength else: + air_jumps_current -= 1; + play_double_jump_animation() local_velocity.y = -air_jump_strength if(local_velocity.y > max_fall_speed): local_velocity.y = max_fall_speed + # When knockback is applied, momentum is reset to the knockback value instead. if(reset_to_velocity.x != 0): local_velocity.x = reset_to_velocity.x if(reset_to_velocity.y != 0): local_velocity.y = reset_to_velocity.y reset_to_velocity = Vector2.ZERO - velocity = earth_aligner.global_from_local(local_velocity) + + # Return to world coordinates and apply the changes to velocity. + velocity = $EarthAligner.global_from_local(local_velocity) func hurt(dmg: int, dir: Vector2 = Vector2.ZERO): - if(inv_time <= 0): + # If the player has no iframes, apply knockback and damage and start iframes. + if $IFrames.time_left <= 0: if Status.affects("Vulnerable", self): dmg += 1 $AudioStreamPlayer2D.play() current_hp -= dmg - inv_time = hit_invulnerability - reset_to_velocity = Vector2(-sign(earth_aligner.local_from_global(dir).x)*knockback_strength, -damage_knockup) + $IFrames.start(hit_iframes) + reset_to_velocity = Vector2(-sign($EarthAligner.local_from_global(dir).x)*knockback_strength, -damage_knockup) return true return false @@ -205,15 +213,19 @@ func die(): player_died.emit() dead = true +# Connected to the signal marking the end of an attack. +# Returns to default animation. func _on_attack_end(): - if($AnimatedSprite2D.animation != "idle"): - $AnimatedSprite2D.play("idle") - $AnimatedSprite2D.stop() + if($Sprite.animation != "idle"): + $Sprite.play("idle") + $Sprite.stop() +# Take away player control while the Death Screen is active. func _on_death_screen_visibility_changed() -> void: handle_input = !handle_input func play_double_jump_animation() -> void: + # Instantiate a sprite which plays a double jump animation, then deletes itself. $AirJumpAudio.play() var node = double_jump_animation.instantiate() add_child(node) @@ -221,6 +233,7 @@ func play_double_jump_animation() -> void: node.scale = .5 * Vector2.ONE node.reparent(get_parent()) +# If there is any vine nearby, gain the corresponding statusses. func update_vine_statuses(): var location = Grid.get_location_from_world_pos(global_position) for vine : Vine in Grid.vines_per_node[location.x][location.y]: diff --git a/player/player.tscn b/player/player.tscn index d5023d4..352b7fc 100644 --- a/player/player.tscn +++ b/player/player.tscn @@ -88,7 +88,7 @@ position = Vector2(0.2, 6) scale = Vector2(0.7, 0.72) shape = SubResource("RectangleShape2D_e7oew") -[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="."] +[node name="Sprite" type="AnimatedSprite2D" parent="."] scale = Vector2(0.37, 0.37) sprite_frames = SubResource("SpriteFrames_dw050") animation = &"attack" @@ -100,6 +100,14 @@ animation = &"attack" [node name="ActiveItemCooldown" type="Timer" parent="."] one_shot = true +[node name="IFrames" type="Timer" parent="."] +wait_time = 0.8 +one_shot = true + +[node name="AttackCooldown" type="Timer" parent="."] +wait_time = 0.35 +one_shot = true + [node name="AudioStreamPlayer2D" type="AudioStreamPlayer2D" parent="."] stream = ExtResource("11_2ieo8") volume_db = 15.0 @@ -122,4 +130,4 @@ position = Vector2(0.2, 6) scale = Vector2(0.7, 0.72) shape = SubResource("RectangleShape2D_e7oew") -[connection signal="animation_finished" from="AnimatedSprite2D" to="." method="_on_attack_end"] +[connection signal="animation_finished" from="Sprite" to="." method="_on_attack_end"] diff --git a/player/sword.gd b/player/sword.gd index aa3793a..5e84be4 100644 --- a/player/sword.gd +++ b/player/sword.gd @@ -37,7 +37,7 @@ func _process(delta: float) -> void: if(slash_timer == 0): anim_sprite.visible = false for area in get_overlapping_areas(): - var hurt_dir = -get_parent().earth_aligner.global_from_local(Vector2(-facing, 0)).rotated(facing*rotation) + var hurt_dir = -get_parent().get_node("EarthAligner").global_from_local(Vector2(-facing, 0)).rotated(facing*rotation) if area.hurt(damage, hurt_dir, dmg_id) and apply_swing_knockback: get_parent().reset_to_velocity += Vector2(swing_knockback * facing, 0) apply_swing_knockback = false diff --git a/utils/earth_aligner.gd b/utils/earth_aligner.gd index 4778d6d..74e4e40 100644 --- a/utils/earth_aligner.gd +++ b/utils/earth_aligner.gd @@ -3,6 +3,18 @@ extends Node2D var parent : Node2D @export var center = Vector2.ZERO var angle = 0 +var up : Vector2 : + get(): + return global_from_local(Vector2.UP) +var down : Vector2 : + get(): + return global_from_local(Vector2.DOWN) +var right : Vector2 : + get(): + return global_from_local(Vector2.RIGHT) +var left : Vector2 : + get(): + return global_from_local(Vector2.LEFT) func _ready() -> void: parent = get_parent()