From c1268be3a53b8bf7428a52f458f7aff17d26fc44 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 16 Sep 2025 14:59:40 +0200 Subject: [PATCH] Sword Attack and Knockback --- enemies/ghost.gd | 12 +++++++ enemies/ghost.tscn | 17 +++++++-- enemy_hurtbox.gd | 24 +++++++++++++ enemy_hurtbox.gd.uid | 1 + enemy_hurtbox.tscn | 7 ++++ main.tscn | 14 ++++---- player/player.gd | 50 ++++++++++++++------------ player/player.tscn | 11 +++--- player/player_walk.png.import | 6 ---- player/sword.gd | 25 +++++++++++++ player/sword.gd.uid | 1 + player/sword.tscn | 66 +++++++++++++++++++++++++++++++++++ project.godot | 11 +++++- utils/earth_aligner.gd | 8 ++++- 14 files changed, 208 insertions(+), 45 deletions(-) create mode 100644 enemy_hurtbox.gd create mode 100644 enemy_hurtbox.gd.uid create mode 100644 enemy_hurtbox.tscn create mode 100644 player/sword.gd create mode 100644 player/sword.gd.uid create mode 100644 player/sword.tscn diff --git a/enemies/ghost.gd b/enemies/ghost.gd index 22a7260..0cebf27 100644 --- a/enemies/ghost.gd +++ b/enemies/ghost.gd @@ -1,7 +1,10 @@ extends Area2D +@onready var earth_aligner = $EarthAligner var speed = 100 var damage = 1 var player : CharacterBody2D +var current_knockback = Vector2.ZERO +var knockback_weight = 800 func _ready() -> void: player = get_parent().get_node("Player") @@ -11,5 +14,14 @@ func _physics_process(delta: float) -> void: var dist = (position - player.position).length() self.position += motion * delta * min(1, dist/(motion.length()*delta)) + self.position += earth_aligner.global_from_local(current_knockback) * delta + current_knockback = current_knockback/pow(1.3, 60*delta) + if(self.overlaps_body(player)): player.hurt(damage, self.position-player.position) + +func _on_death(): + self.queue_free() + +func _on_damage_taken(_damage : int, dir: Vector2): + current_knockback = dir * knockback_weight diff --git a/enemies/ghost.tscn b/enemies/ghost.tscn index 24a4c40..9bf770a 100644 --- a/enemies/ghost.tscn +++ b/enemies/ghost.tscn @@ -1,8 +1,9 @@ -[gd_scene load_steps=5 format=3 uid="uid://chu67ci7sl488"] +[gd_scene load_steps=6 format=3 uid="uid://chu67ci7sl488"] -[ext_resource type="Script" uid="uid://12jns4dppxxj" path="res://ghost.gd" id="1_6attn"] +[ext_resource type="Script" uid="uid://12jns4dppxxj" path="res://enemies/ghost.gd" id="1_6attn"] +[ext_resource type="PackedScene" uid="uid://mtfsdd4cdf3a" path="res://enemy_hurtbox.tscn" id="2_34o1m"] [ext_resource type="Texture2D" uid="uid://cy70quh6k3s1j" path="res://icon.svg" id="2_obmiq"] -[ext_resource type="PackedScene" uid="uid://chs0u61f45nau" path="res://earth_aligner.tscn" id="3_obmiq"] +[ext_resource type="PackedScene" uid="uid://chs0u61f45nau" path="res://utils/earth_aligner.tscn" id="3_obmiq"] [sub_resource type="CircleShape2D" id="CircleShape2D_6attn"] @@ -10,6 +11,13 @@ scale = Vector2(0.4, 0.4) script = ExtResource("1_6attn") +[node name="EnemyHurtbox" parent="." instance=ExtResource("2_34o1m")] +max_hp = 50 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="EnemyHurtbox"] +scale = Vector2(6, 6) +shape = SubResource("CircleShape2D_6attn") + [node name="Sprite2D" type="Sprite2D" parent="."] texture = ExtResource("2_obmiq") @@ -18,3 +26,6 @@ scale = Vector2(6, 6) shape = SubResource("CircleShape2D_6attn") [node name="EarthAligner" parent="." instance=ExtResource("3_obmiq")] + +[connection signal="damage_taken" from="EnemyHurtbox" to="." method="_on_damage_taken"] +[connection signal="died" from="EnemyHurtbox" to="." method="_on_death"] diff --git a/enemy_hurtbox.gd b/enemy_hurtbox.gd new file mode 100644 index 0000000..11c7785 --- /dev/null +++ b/enemy_hurtbox.gd @@ -0,0 +1,24 @@ +extends Area2D + +@export var max_hp : int +@onready var hp = max_hp +var hit_invulnerability = 0.15 +var inv_time = 0; + +signal damage_taken +signal died + +func _process(delta: float) -> void: + inv_time = max(inv_time-delta, 0) + +func hurt(damage : int, dir : Vector2 = Vector2.ZERO): + if(inv_time<=0): + inv_time = hit_invulnerability + hp = max(hp-damage, 0) + damage_taken.emit(damage, dir) + if(hp <= 0): + die() + +func die(): + died.emit() + print(get_parent().name + " has died.") diff --git a/enemy_hurtbox.gd.uid b/enemy_hurtbox.gd.uid new file mode 100644 index 0000000..989e70a --- /dev/null +++ b/enemy_hurtbox.gd.uid @@ -0,0 +1 @@ +uid://ct8am2xeyymuj diff --git a/enemy_hurtbox.tscn b/enemy_hurtbox.tscn new file mode 100644 index 0000000..9b87d2b --- /dev/null +++ b/enemy_hurtbox.tscn @@ -0,0 +1,7 @@ +[gd_scene load_steps=2 format=3 uid="uid://mtfsdd4cdf3a"] + +[ext_resource type="Script" uid="uid://ct8am2xeyymuj" path="res://enemy_hurtbox.gd" id="1_wa58b"] + +[node name="EnemyHurtbox" type="Area2D"] +collision_layer = 2 +script = ExtResource("1_wa58b") diff --git a/main.tscn b/main.tscn index ceff802..a2ce4e0 100644 --- a/main.tscn +++ b/main.tscn @@ -1,20 +1,20 @@ [gd_scene load_steps=5 format=3 uid="uid://cxo6bq26huau7"] -[ext_resource type="PackedScene" uid="uid://cmaovvr15b3qk" path="res://player.tscn" id="2_1bvp3"] -[ext_resource type="PackedScene" uid="uid://chu67ci7sl488" path="res://ghost.tscn" id="3_h2yge"] -[ext_resource type="PackedScene" uid="uid://jjoyj1ldafkf" path="res://earth.tscn" id="3_lquwl"] -[ext_resource type="Script" uid="uid://colvx6wq0e8n7" path="res://building_generator.gd" id="4_1bvp3"] +[ext_resource type="PackedScene" uid="uid://cmaovvr15b3qk" path="res://player/player.tscn" id="2_1bvp3"] +[ext_resource type="PackedScene" uid="uid://chu67ci7sl488" path="res://enemies/ghost.tscn" id="3_h2yge"] +[ext_resource type="PackedScene" uid="uid://jjoyj1ldafkf" path="res://world/earth.tscn" id="3_lquwl"] +[ext_resource type="Script" uid="uid://colvx6wq0e8n7" path="res://world/building_generator.gd" id="4_1bvp3"] [node name="main" type="Node2D"] [node name="Earth" parent="." instance=ExtResource("3_lquwl")] +[node name="Ghost" parent="." instance=ExtResource("3_h2yge")] +position = Vector2(0, -3200) + [node name="Player" parent="." instance=ExtResource("2_1bvp3")] position = Vector2(500, -3100) scale = Vector2(3, 3) -[node name="Ghost" parent="." instance=ExtResource("3_h2yge")] -position = Vector2(0, -3200) - [node name="Building Generator" type="Node" parent="."] script = ExtResource("4_1bvp3") diff --git a/player/player.gd b/player/player.gd index fc35b00..7ca7207 100644 --- a/player/player.gd +++ b/player/player.gd @@ -1,8 +1,9 @@ extends CharacterBody2D # Child Nodes -var camera : Camera2D; -var anim_sprite : AnimatedSprite2D -var earth_aligner : Node2D +@onready var camera : Camera2D = $Camera2D; +@onready var anim_sprite : AnimatedSprite2D = $AnimatedSprite2D; +@onready var earth_aligner : Node2D = $EarthAligner; +@onready var sword : Area2D = $Sword; # Gravity var earth_center = Vector2.ZERO; @@ -10,6 +11,7 @@ var max_fall_speed = 700; var gravity = 100; # Movement +var facing = -1; var hspeed = 150; var jump_strength = 1100; var air_jumps_max = 1; @@ -20,29 +22,35 @@ var current_hp = 5; var max_hp = 5; var hit_invulnerability = 0.5 var inv_time = 0; +var dead = false; # Received Knockback var reset_to_velocity = Vector2.ZERO var knockback_strength = 1500 var damage_knockup = 500 - -func _ready() -> void: - camera = $Camera2D - anim_sprite = $AnimatedSprite2D - earth_aligner = $EarthAligner +# Attack Handling +var atk_cooldown = 0.6 +var atk_timer = 0 func _physics_process(delta: float) -> void: manage_iframes(delta) + manage_attack(delta) manage_movement_options() manage_animation() - manage_velocity() + manage_velocity(delta) move_and_slide() 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(Input.is_action_just_pressed("attack") and atk_timer <= 0): + sword.swing() + atk_timer = atk_cooldown + func manage_movement_options() -> void: if(is_on_floor()): air_jumps_current = air_jumps_max @@ -55,7 +63,8 @@ func manage_animation() -> void: walk_dir -= 1 if(walk_dir != 0): - anim_sprite.scale.x = - abs(anim_sprite.scale.x) * walk_dir + facing = walk_dir + anim_sprite.scale.x = - abs(anim_sprite.scale.x) * facing if(is_on_floor()): anim_sprite.play("walk") else: @@ -63,10 +72,10 @@ func manage_animation() -> void: else : anim_sprite.stop() -func manage_velocity() -> void: +func manage_velocity(delta: float) -> void: up_direction = (position - earth_center).normalized(); - var old_local_velocity = local_from_global(velocity) - var local_velocity = Vector2(old_local_velocity.x/1.7, old_local_velocity.y); + var old_local_velocity = earth_aligner.local_from_global(velocity) + var local_velocity = Vector2(old_local_velocity.x/pow(1.7,60*delta), old_local_velocity.y); local_velocity += Vector2(0, gravity) if(Input.is_action_pressed("move_right")): @@ -85,14 +94,7 @@ func manage_velocity() -> void: if(reset_to_velocity != Vector2.ZERO): local_velocity = reset_to_velocity reset_to_velocity = Vector2.ZERO - velocity = global_from_local(local_velocity) - - -func global_from_local (_velocity: Vector2) -> Vector2: - return _velocity.rotated(earth_aligner.angle) - -func local_from_global (_velocity: Vector2) -> Vector2: - return _velocity.rotated(-earth_aligner.angle) + velocity = earth_aligner.global_from_local(local_velocity) func hurt(dmg: int, dir: Vector2 = Vector2.ZERO): if(inv_time <= 0): @@ -101,7 +103,9 @@ func hurt(dmg: int, dir: Vector2 = Vector2.ZERO): die() inv_time = hit_invulnerability - reset_to_velocity = Vector2(-sign(local_from_global(dir).x)*knockback_strength, -damage_knockup) + reset_to_velocity = Vector2(-sign(earth_aligner.local_from_global(dir).x)*knockback_strength, -damage_knockup) func die(): - print("You Died") # Placeholder + if not dead: + print("You Died") # Placeholder + dead = true diff --git a/player/player.tscn b/player/player.tscn index f8d091c..9251c59 100644 --- a/player/player.tscn +++ b/player/player.tscn @@ -1,8 +1,9 @@ -[gd_scene load_steps=11 format=3 uid="uid://cmaovvr15b3qk"] +[gd_scene load_steps=12 format=3 uid="uid://cmaovvr15b3qk"] -[ext_resource type="Script" uid="uid://ddidj1uau28ck" path="res://player.gd" id="1_4flbx"] -[ext_resource type="Texture2D" uid="uid://cyvxm1hf1rc12" path="res://player_walk.png" id="2_onrkg"] -[ext_resource type="PackedScene" uid="uid://chs0u61f45nau" path="res://earth_aligner.tscn" id="3_i3pqv"] +[ext_resource type="Script" uid="uid://ddidj1uau28ck" path="res://player/player.gd" id="1_4flbx"] +[ext_resource type="Texture2D" uid="uid://cyvxm1hf1rc12" path="res://player/player_walk.png" id="2_onrkg"] +[ext_resource type="PackedScene" uid="uid://chs0u61f45nau" path="res://utils/earth_aligner.tscn" id="3_i3pqv"] +[ext_resource type="PackedScene" uid="uid://d3e3kuyeh6mr1" path="res://player/sword.tscn" id="4_yw30f"] [sub_resource type="CapsuleShape2D" id="CapsuleShape2D_onrkg"] @@ -68,3 +69,5 @@ position = Vector2(0, -50) ignore_rotation = false [node name="EarthAligner" parent="." instance=ExtResource("3_i3pqv")] + +[node name="Sword" parent="." instance=ExtResource("4_yw30f")] diff --git a/player/player_walk.png.import b/player/player_walk.png.import index ab112ae..418970e 100644 --- a/player/player_walk.png.import +++ b/player/player_walk.png.import @@ -18,8 +18,6 @@ dest_files=["res://.godot/imported/player_walk.png-59515d82f701e5545419ecd7b3460 compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 @@ -27,10 +25,6 @@ mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false diff --git a/player/sword.gd b/player/sword.gd new file mode 100644 index 0000000..523868c --- /dev/null +++ b/player/sword.gd @@ -0,0 +1,25 @@ +extends Area2D +var anim_sprite: AnimatedSprite2D +var slash_duration = 0.15 +var slash_timer = 0 +var damage = 20 +var facing = -1 + +func _ready() -> void: + anim_sprite = $AnimatedSprite2D + +func swing() -> void: + facing = -get_parent().facing + anim_sprite.visible = true + slash_timer = slash_duration + +func _physics_process(delta: float) -> void: + scale.x = facing + if slash_timer > 0: + slash_timer = max(0, slash_timer-delta) + if(slash_timer == 0): + anim_sprite.visible = false + for area in get_overlapping_areas(): + area.hurt(damage, Vector2(-facing, 0)) + + diff --git a/player/sword.gd.uid b/player/sword.gd.uid new file mode 100644 index 0000000..766c6a9 --- /dev/null +++ b/player/sword.gd.uid @@ -0,0 +1 @@ +uid://cpyc4qqgpyx38 diff --git a/player/sword.tscn b/player/sword.tscn new file mode 100644 index 0000000..6c4b80b --- /dev/null +++ b/player/sword.tscn @@ -0,0 +1,66 @@ +[gd_scene load_steps=10 format=3 uid="uid://d3e3kuyeh6mr1"] + +[ext_resource type="Texture2D" uid="uid://cyvxm1hf1rc12" path="res://player/player_walk.png" id="1_74bdg"] +[ext_resource type="Script" uid="uid://cpyc4qqgpyx38" path="res://player/sword.gd" id="1_hv1tj"] + +[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_e4ynd"] + +[sub_resource type="AtlasTexture" id="AtlasTexture_r76a6"] +atlas = ExtResource("1_74bdg") +region = Rect2(0, 0, 240, 240) + +[sub_resource type="AtlasTexture" id="AtlasTexture_dwvek"] +atlas = ExtResource("1_74bdg") +region = Rect2(240, 0, 240, 240) + +[sub_resource type="AtlasTexture" id="AtlasTexture_rx5id"] +atlas = ExtResource("1_74bdg") +region = Rect2(480, 0, 240, 240) + +[sub_resource type="AtlasTexture" id="AtlasTexture_v4ynp"] +atlas = ExtResource("1_74bdg") +region = Rect2(720, 0, 240, 240) + +[sub_resource type="AtlasTexture" id="AtlasTexture_83fnj"] +atlas = ExtResource("1_74bdg") +region = Rect2(960, 0, 240, 240) + +[sub_resource type="SpriteFrames" id="SpriteFrames_fahsa"] +animations = [{ +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_r76a6") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_dwvek") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_rx5id") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_v4ynp") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_83fnj") +}], +"loop": true, +"name": &"default", +"speed": 5.0 +}] + +[node name="Sword" type="Area2D"] +collision_mask = 2 +script = ExtResource("1_hv1tj") + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +position = Vector2(-13, 5) +rotation = 1.5708 +scale = Vector2(-0.3, -0.9) +shape = SubResource("CapsuleShape2D_e4ynd") + +[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="."] +visible = false +position = Vector2(-13, 5) +rotation = 1.5708 +scale = Vector2(-0.1, -0.15) +sprite_frames = SubResource("SpriteFrames_fahsa") diff --git a/project.godot b/project.godot index e7a6cbd..78ec4ac 100644 --- a/project.godot +++ b/project.godot @@ -12,7 +12,7 @@ config_version=5 config/name="The Dark Side of Earth" run/main_scene="uid://cxo6bq26huau7" -config/features=PackedStringArray("4.5", "Forward Plus") +config/features=PackedStringArray("4.4", "Forward Plus") config/icon="res://icon.svg" [display] @@ -38,3 +38,12 @@ jump={ "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null) ] } +attack={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":67,"key_label":0,"unicode":99,"location":0,"echo":false,"script":null) +] +} + +[layer_names] + +2d_physics/layer_2="EnemyHurtBox" diff --git a/utils/earth_aligner.gd b/utils/earth_aligner.gd index b8d60c3..547efde 100644 --- a/utils/earth_aligner.gd +++ b/utils/earth_aligner.gd @@ -7,6 +7,12 @@ var angle = 0 func _ready() -> void: parent = get_parent() -func _physics_process(delta: float) -> void: +func _physics_process(_delta: float) -> void: angle = -(parent.position - center).angle_to(Vector2.UP) parent.rotation = angle; + +func global_from_local (_velocity: Vector2) -> Vector2: + return _velocity.rotated(angle) + +func local_from_global (_velocity: Vector2) -> Vector2: + return _velocity.rotated(-angle)