]> git.seodisparate.com - LD55/commitdiff
Work
authorStephen Seo <seo.disparate@gmail.com>
Sun, 14 Apr 2024 13:21:54 +0000 (22:21 +0900)
committerStephen Seo <seo.disparate@gmail.com>
Sun, 14 Apr 2024 13:21:54 +0000 (22:21 +0900)
Game now reaches the end after the guardian battle.

Work on parry system, battle system.

DungeonEntrance.tscn
DungeonGuard.gd [new file with mode: 0644]
MainLogic.gd
audio/LD55_sfx_enemy_hit.mp3 [new file with mode: 0644]
audio/LD55_sfx_enemy_hit.mp3.import [new file with mode: 0644]
audio/LD55_sfx_parry.mp3 [new file with mode: 0644]
audio/LD55_sfx_parry.mp3.import [new file with mode: 0644]
gander_schwartz.gd
gander_schwartz.tscn

index 10dc03dca8392b8cbfac9baa21c6b576cb4c2c76..d7915b54f8f13739fdd2202500f9f01f5ef1091e 100644 (file)
@@ -1,11 +1,14 @@
-[gd_scene load_steps=7 format=3 uid="uid://b55f770t7xs6a"]
+[gd_scene load_steps=9 format=3 uid="uid://b55f770t7xs6a"]
 
 [ext_resource type="Texture2D" uid="uid://npjqgc3tdgs1" path="res://gimp/DungeonEntrance.png" id="1_vo6aq"]
 [ext_resource type="Texture2D" uid="uid://nocjsuuft8qx" path="res://gimp/DungeonGuard.png" id="2_nujkm"]
+[ext_resource type="Script" path="res://DungeonGuard.gd" id="3_hisk5"]
 
-[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_18p8i"]
-radius = 15.0
-height = 32.0
+[sub_resource type="RectangleShape2D" id="RectangleShape2D_fey6x"]
+size = Vector2(30.4094, 31.5789)
+
+[sub_resource type="RectangleShape2D" id="RectangleShape2D_0v5y2"]
+size = Vector2(30.4094, 31.5789)
 
 [sub_resource type="WorldBoundaryShape2D" id="WorldBoundaryShape2D_ocep5"]
 normal = Vector2(1, 0)
@@ -35,13 +38,22 @@ texture_filter = 1
 position = Vector2(0, -552)
 scale = Vector2(1.71, 1.71)
 texture = ExtResource("2_nujkm")
+script = ExtResource("3_hisk5")
 
-[node name="DungeonGuardStaticBody" type="StaticBody2D" parent="DungeonGuard"]
-visible = false
+[node name="DungeonGuardBody" type="StaticBody2D" parent="DungeonGuard"]
 
-[node name="DungeonGuardCollider" type="CollisionShape2D" parent="DungeonGuard/DungeonGuardStaticBody"]
-visible = false
-shape = SubResource("CapsuleShape2D_18p8i")
+[node name="DungeonGuardCollider" type="CollisionShape2D" parent="DungeonGuard/DungeonGuardBody"]
+shape = SubResource("RectangleShape2D_fey6x")
+
+[node name="Area2D" type="Area2D" parent="DungeonGuard"]
+collision_layer = 3
+collision_mask = 0
+input_pickable = false
+monitoring = false
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="DungeonGuard/Area2D"]
+shape = SubResource("RectangleShape2D_0v5y2")
+debug_color = Color(0.643137, 0.341176, 0.988235, 0.419608)
 
 [node name="StaticBody2D" type="StaticBody2D" parent="."]
 
diff --git a/DungeonGuard.gd b/DungeonGuard.gd
new file mode 100644 (file)
index 0000000..68d84c4
--- /dev/null
@@ -0,0 +1,23 @@
+extends Sprite2D
+
+var health = 30
+var health_dirty = true
+
+# Called when the node enters the scene tree for the first time.
+func _ready():
+       pass # Replace with function body.
+
+
+# Called every frame. 'delta' is the elapsed time since the previous frame.
+func _process(delta):
+       if health_dirty:
+               health_dirty = false
+               var hp_label = find_child("GuardHPLabel")
+               if hp_label == null:
+                       hp_label = Label.new()
+                       hp_label.set_name(&"GuardHPLabel")
+                       add_child(hp_label, true)
+                       hp_label.set_owner(self)
+                       hp_label.position.y += 20.0
+                       hp_label.position.x -= 20.0
+               hp_label.text = "%d HP" % health
index c9744b84f9cfd3ae1377109a5af42938a08e1a76..fafc75eaa7ec0125196451fad0ad7fb530d48410 100644 (file)
@@ -14,6 +14,10 @@ var sfx_select
 var sfx_confirm
 var sfx_cancel
 var sfx_summon
+var sfx_enemy_hit
+var sfx_parry
+var sfx_sword_hit
+var sfx_hammer_hit
 
 @onready var camera = $Camera2D
 
@@ -36,6 +40,8 @@ const diamond_dist_rate = 50.0
 const diamond_start_dist = 800.0
 const diamond_min_dist = 150.0
 
+const parry_penalty = 0.5
+
 enum StateT {
        Start,
        Start_TextRendered,
@@ -67,8 +73,17 @@ enum BattleState {
        SummonSword,
        SummonHammer,
        EnemyAttack,
+       PlayerAttack,
+       PlayerWin,
+       PlayerLose,
+}
+
+enum GuardPhase {
+       Stomp,
 }
 
+var guard_phase = GuardPhase.Stomp
+
 static var state_dict = {}
 
 var tween_volume
@@ -80,12 +95,17 @@ var diamonds_gone = false
 var gander
 
 var level
+var level_guard_static_body = null
 var level_guard = null
+var level_guard_cached_pos = null
 
 var level_cached_pos = null
 
 var viewport_size
 
+var play_attack_sfx_type = null
+var play_attack_sfx_once = false
+
 # Called when the node enters the scene tree for the first time.
 func _ready():
        if not state_dict.has("state"):
@@ -106,6 +126,10 @@ func _ready():
                sfx_confirm = load("res://audio/LD55_sfx_confirm.mp3")
                sfx_cancel = load("res://audio/LD55_sfx_cancel.mp3")
                sfx_summon = load("res://audio/LD55_sfx_summon.mp3")
+               sfx_enemy_hit = load("res://audio/LD55_sfx_enemy_hit.mp3")
+               sfx_parry = load("res://audio/LD55_sfx_parry.mp3")
+               sfx_sword_hit = load("res://audio/LD55_sfx_sword_hit.mp3")
+               sfx_hammer_hit = load("res://audio/LD55_sfx_hammer_hit.mp3")
 
 func update_text(text, next_state):
        if state_dict["timer"] > text_speed:
@@ -144,6 +168,7 @@ func _process(delta):
                        var gander_scene = preload("res://gander_schwartz.tscn")
                        gander = gander_scene.instantiate()
                        add_child(gander)
+                       gander.set_owner(self)
                        gander.position.x = 800
                        gander.position.y = 50
                        gander.velocity.x = -gander.SPEED
@@ -206,13 +231,16 @@ func _process(delta):
                StateT.Dungeon_Entrance:
                        camera_to_gander(delta)
                        if level_guard == null:
-                               level_guard = level.find_child("DungeonGuardStaticBody")
-                       if level_guard != null and gander.last_collided_id == level_guard.get_instance_id():
+                               level_guard = level.find_child("DungeonGuard")
+                       if level_guard_static_body == null:
+                               level_guard_static_body = level.find_child("DungeonGuardBody")
+                       if level_guard_static_body != null and gander.last_collided_id == level_guard_static_body.get_instance_id():
                                print("collided with guard.")
                                gander.last_collided_id = null
                                music_player.stop()
                                music_player.stream = preload("res://audio/LD55_3.mp3")
                                music_player.stream.loop = true
+                               music_player.volume_db = -8.0
                                music_player.play()
                                level.find_child("DungeonGuardCollider").set_deferred("disabled", true)
                                gander.find_child("CollisionShape2D").set_deferred("disabled", true)
@@ -229,6 +257,8 @@ func _process(delta):
                                tween_scene.tween_callback(func():
                                        gander.auto_control_action = "facing_left"
                                        state_dict["state"] = StateT.Dungeon_Entrance_Battle
+                                       state_dict["parry_timer"] = 0.0
+                                       state_dict["parry_failed_played"] = false
                                        state_dict["battle_state"] = BattleState.MainMenu
                                        state_dict["battle_menu_setup"] = false
                                        state_dict["battle_refresh_gui"] = false
@@ -247,10 +277,19 @@ func _process(delta):
                                        tween_text.tween_callback(func():
                                                camera.remove_child(indicator_arrow)
                                        )
+                                       gander.battle_started = true
                                )
                StateT.Dungeon_Entrance_Battle:
+                       if state_dict["battle_state"] == BattleState.EnemyAttack:
+                               if level_guard_cached_pos == null:
+                                       level_guard_cached_pos = level_guard.position
                        camera_to_target(delta, level_cached_pos + Vector2(0.0, 500.0))
                        setup_battle_menu()
+                       state_dict["parry_timer"] -= delta
+                       if not gander.parry_success and gander.hit_active and not state_dict["parry_failed_played"]:
+                               sfx_player.stream = sfx_enemy_hit
+                               sfx_player.play()
+                               state_dict["parry_failed_played"] = true
                _:
                        pass
        if gander is MainCharacter and not gander.player_controlled and gander.current_scene_type == gander.GanderSceneT.Introduction:
@@ -407,6 +446,25 @@ func camera_to_target(delta, vec2):
        camera.position += diff * delta * camera_move_speed
 
 func setup_battle_menu():
+       if state_dict["battle_state"] != BattleState.PlayerWin and state_dict["battle_state"] != BattleState.PlayerLose:
+               if gander.health <= 0:
+                       state_dict["battle_state"] = BattleState.PlayerLose
+                       music_player.stop()
+                       music_player.stream = load("res://audio/LD55_5.mp3")
+                       music_player.play()
+                       remove_child(gander)
+                       main_label.text = "Thanks for playing! I ran out of time for this overly ambitious game..."
+                       lower_label.text = "Ohhhh, it ended with a game over... Better luck next time?"
+               elif level_guard.health <= 0:
+                       state_dict["battle_state"] = BattleState.PlayerWin
+                       music_player.stop()
+                       music_player.stream = load("res://audio/LD55_4.mp3")
+                       music_player.play()
+                       level_guard.get_parent().remove_child(level_guard)
+                       main_label.text = "Thanks for playing! I ran out of time for this overly ambitious game..."
+                       lower_label.text = "Good show, good show!"
+       elif state_dict["battle_state"] == BattleState.PlayerWin or state_dict["battle_state"] == BattleState.PlayerLose:
+               return
        match state_dict["battle_state"]:
                BattleState.MainMenu:
                        if not state_dict["battle_menu_setup"] or state_dict["battle_refresh_gui"]:
@@ -477,7 +535,7 @@ func setup_battle_menu():
                        var summon_node = find_child("SummonAttempt")
                        if summon_node.success_count >= summon_node.MAX_SUCCESS:
                                camera.remove_child(summon_node)
-                               var summon_item = find_child("SummonItem")
+                               var summon_item = gander.find_child("SummonItem")
                                if summon_item == null:
                                        summon_item = Sprite2D.new()
                                        summon_item.texture = load("res://gimp/sword.png")
@@ -487,19 +545,26 @@ func setup_battle_menu():
                                else:
                                        summon_item.texture = load("res://gimp/sword.png")
                                gander.summon_item = summon_item
+                               state_dict["battle_item"] = summon_item
+                               gander.attack_type = gander.AttackType.SWORD
+                               gander.summon_item_summoned = true
                                sfx_player.stream = sfx_summon
                                sfx_player.play()
                                state_dict["battle_state"] = BattleState.EnemyAttack
+                               level_guard_cached_pos = null
+                               pick_guard_phase()
                        elif summon_node.error_count >= summon_node.MAX_ERRORS:
                                tween_scene = get_tree().create_tween()
                                tween_scene.tween_method(func(c): for i in range(8): summon_node.summon_arrows_arr[i].self_modulate = c, Color(1.0, 0.0, 0.0), Color(1.0, 0.0, 0.0, 0.0), 1.0)
                                tween_scene.tween_callback(func(): camera.remove_child(summon_node))
                                state_dict["battle_state"] = BattleState.EnemyAttack
+                               level_guard_cached_pos = null
+                               pick_guard_phase()
                BattleState.SummonHammer:
                        var summon_node = find_child("SummonAttempt")
                        if summon_node.success_count >= summon_node.MAX_SUCCESS:
                                camera.remove_child(summon_node)
-                               var summon_item = find_child("SummonItem")
+                               var summon_item = gander.find_child("SummonItem")
                                if summon_item == null:
                                        summon_item = Sprite2D.new()
                                        summon_item.texture = load("res://gimp/hammer.png")
@@ -509,27 +574,92 @@ func setup_battle_menu():
                                else:
                                        summon_item.texture = load("res://gimp/hammer.png")
                                gander.summon_item = summon_item
+                               state_dict["battle_item"] = summon_item
+                               gander.attack_type = gander.AttackType.HAMMER
+                               gander.summon_item_summoned = true
                                sfx_player.stream = sfx_summon
                                sfx_player.play()
                                state_dict["battle_state"] = BattleState.EnemyAttack
+                               level_guard_cached_pos = null
+                               pick_guard_phase()
                        elif summon_node.error_count >= summon_node.MAX_ERRORS:
                                tween_scene = get_tree().create_tween()
                                tween_scene.tween_method(func(c): for i in range(8): summon_node.summon_arrows_arr[i].self_modulate = c, Color(1.0, 0.0, 0.0), Color(1.0, 0.0, 0.0, 0.0), 1.0)
                                tween_scene.tween_callback(func(): camera.remove_child(summon_node))
                                state_dict["battle_state"] = BattleState.EnemyAttack
+                               level_guard_cached_pos = null
+                               pick_guard_phase()
                BattleState.EnemyAttack:
                        if not state_dict["battle_menu_setup"] or state_dict["battle_refresh_gui"]:
                                state_dict["battle_menu_setup"] = true
                                state_dict["battle_refresh_gui"] = false
+                               state_dict["parry_failed_played"] = false
                                var battle_arrow = camera.find_child("BattleArrow")
                                var battle_menu_item_0 = camera.find_child("BattleMenuItem0")
                                var battle_menu_item_1 = camera.find_child("BattleMenuItem1")
                                battle_arrow.self_modulate = Color(1, 1, 1, 0)
                                battle_menu_item_0.text = ""
                                battle_menu_item_1.text = ""
+                               if gander.summon_item != null:
+                                       lower_label.text = "Press key-Z, button-A, or Spacebar at the right time to parry!"
+                               match guard_phase:
+                                       GuardPhase.Stomp:
+                                               tween_scene = get_tree().create_tween()
+                                               var diff = gander.position.x - level_guard_cached_pos.x
+                                               tween_scene.tween_property(level_guard, "position", level_guard_cached_pos + Vector2(diff / 2.0, -200.0), 1.0)
+                                               tween_scene.tween_property(level_guard, "position", level_guard_cached_pos + Vector2(diff / 2.0, 0.0), 0.7)
+                                               tween_scene.tween_property(level_guard, "position", level_guard_cached_pos + Vector2(diff, -200.0), 1.0)
+                                               tween_scene.tween_property(level_guard, "position", level_guard_cached_pos + Vector2(diff, 0.0), 0.7)
+                                               tween_scene.tween_callback(func():
+                                                       state_dict["battle_state"] = BattleState.MainMenu
+                                                       state_dict["battle_menu_setup"] = false
+                                                       lower_label.text = ""
+                                               )
+                                               tween_scene.tween_property(level_guard, "position", level_guard_cached_pos, 1.0)
+                                       _:
+                                               state_dict["battle_state"] = BattleState.MainMenu
+                                               state_dict["battle_menu_setup"] = false
+               BattleState.PlayerAttack:
+                       if gander.attack_target == null:
+                               state_dict["battle_state"] = BattleState.EnemyAttack
+                               state_dict["battle_menu_setup"] = false
+                               play_attack_sfx_type = null
+                               play_attack_sfx_once = false
+                       elif not play_attack_sfx_once and play_attack_sfx_type != null:
+                               var rng = RandomNumberGenerator.new()
+                               if play_attack_sfx_type == "sword":
+                                       sfx_player.stream = sfx_sword_hit
+                                       sfx_player.play()
+                                       level_guard.health -= rng.randi_range(3, 4)
+                                       level_guard.health_dirty = true
+                               elif play_attack_sfx_type == "hammer":
+                                       sfx_player.stream = sfx_hammer_hit
+                                       sfx_player.play()
+                                       level_guard.health -= rng.randi_range(2, 5)
+                                       level_guard.health_dirty = true
+                               play_attack_sfx_type = null
+                               play_attack_sfx_once = true
 
 func handle_battle_input(event: InputEvent):
-       if state_dict["battle_state"] == BattleState.SummonSword or state_dict["battle_state"] == BattleState.SummonHammer:
+       if state_dict["battle_state"] == BattleState.SummonSword or state_dict["battle_state"] == BattleState.SummonHammer \
+                       or state_dict["battle_state"] == BattleState.PlayerWin or state_dict["battle_state"] == BattleState.PlayerLose:
+               return
+       elif state_dict["battle_state"] == BattleState.EnemyAttack:
+               if event.is_pressed() and event.is_action("Confirm"):
+                       if state_dict["parry_timer"] < 0.0 and gander.parry_active and not gander.hit_active and gander.summon_item != null:
+                               lower_label.text = "Nice parry!"
+                               gander.parry_success = true
+                               sfx_player.stream = sfx_parry
+                               sfx_player.play()
+                               tween_scene.kill()
+                               tween_scene = get_tree().create_tween()
+                               tween_scene.tween_property(level_guard, "position", level_guard_cached_pos, 0.8)
+                               tween_scene.tween_callback(func():
+                                       state_dict["battle_state"] = BattleState.MainMenu
+                                       state_dict["battle_menu_setup"] = false
+                                       lower_label.text = ""
+                               )
+                       state_dict["parry_timer"] = parry_penalty
                return
        if event.is_pressed():
                if event.is_action("Down"):
@@ -550,8 +680,12 @@ func handle_battle_input(event: InputEvent):
 func handle_battle_action(action):
        match action:
                "attack":
+                       state_dict["battle_state"] = BattleState.PlayerAttack
+                       state_dict["battle_menu_setup"] = false
                        sfx_player.stream = sfx_confirm
                        sfx_player.play()
+                       if gander.summon_item != null:
+                               gander.attack_target = level_guard
                "summon":
                        state_dict["battle_state"] = BattleState.SummonMenu
                        state_dict["battle_menu_setup"] = false
@@ -592,3 +726,6 @@ func battle_arrow_positioning():
                battle_arrow.position.y = (viewport_size.y + arrow_rect.size.y) / 2.0 - (arrow_rect.size.y * (state_dict["battle_options"].size() - state_dict["battle_selection"]))
                sfx_player.stream = sfx_select
                sfx_player.play()
+
+func pick_guard_phase():
+       guard_phase = GuardPhase.Stomp
diff --git a/audio/LD55_sfx_enemy_hit.mp3 b/audio/LD55_sfx_enemy_hit.mp3
new file mode 100644 (file)
index 0000000..6eddbc7
Binary files /dev/null and b/audio/LD55_sfx_enemy_hit.mp3 differ
diff --git a/audio/LD55_sfx_enemy_hit.mp3.import b/audio/LD55_sfx_enemy_hit.mp3.import
new file mode 100644 (file)
index 0000000..e3813a8
--- /dev/null
@@ -0,0 +1,19 @@
+[remap]
+
+importer="mp3"
+type="AudioStreamMP3"
+uid="uid://bu34dnb7q3arr"
+path="res://.godot/imported/LD55_sfx_enemy_hit.mp3-33970ec45349dad7d9a01186fd03e09a.mp3str"
+
+[deps]
+
+source_file="res://audio/LD55_sfx_enemy_hit.mp3"
+dest_files=["res://.godot/imported/LD55_sfx_enemy_hit.mp3-33970ec45349dad7d9a01186fd03e09a.mp3str"]
+
+[params]
+
+loop=false
+loop_offset=0
+bpm=0
+beat_count=0
+bar_beats=4
diff --git a/audio/LD55_sfx_parry.mp3 b/audio/LD55_sfx_parry.mp3
new file mode 100644 (file)
index 0000000..ff066d6
Binary files /dev/null and b/audio/LD55_sfx_parry.mp3 differ
diff --git a/audio/LD55_sfx_parry.mp3.import b/audio/LD55_sfx_parry.mp3.import
new file mode 100644 (file)
index 0000000..44c7c83
--- /dev/null
@@ -0,0 +1,19 @@
+[remap]
+
+importer="mp3"
+type="AudioStreamMP3"
+uid="uid://cnumagq6o3qxj"
+path="res://.godot/imported/LD55_sfx_parry.mp3-7a46deb538aa1e0147d4915f42b03709.mp3str"
+
+[deps]
+
+source_file="res://audio/LD55_sfx_parry.mp3"
+dest_files=["res://.godot/imported/LD55_sfx_parry.mp3-7a46deb538aa1e0147d4915f42b03709.mp3str"]
+
+[params]
+
+loop=false
+loop_offset=0
+bpm=0
+beat_count=0
+bar_beats=4
index 55a22c1bdfb4734f876c56eb4930957bfaa967f1..08ec5df0806931a20ec9128373298776db799f18 100644 (file)
@@ -13,21 +13,149 @@ var auto_control_action = "facing_front"
 
 var last_collided_id = null
 
+@onready var parry_area = $ParryArea2D
+@onready var hit_area = $HitArea2D
+
 @onready var animated = $AnimatedSprite2D
 
+var battle_started = false
+
+var health = 30
+var health_dirty = true
+
 var summon_item = null
 var summon_item_angle = 0.0
+var summon_item_summoned = false
+var summon_tween
+
+var parry_tween = null
+
+var parry_active = false
+var parry_body = null
+var hit_active = false
+var parry_success = false
+var parry_success_checked = false
+
+enum AttackType {
+       SWORD,
+       HAMMER,
+}
+
+var attack_target = null
+var attack_tween = null
+var attack_type = AttackType.SWORD
 
 const SPEED = 150.0
 const ANIM_DEADZONE = 0.3
 const SUMMON_ITEM_DIST = 100.0
 const SUMMON_ITEM_Y_OFFSET = -30.0
 
+func parry_entered(node):
+       parry_active = true
+       parry_success_checked = false
+       parry_body = node
+func parry_exited(node):
+       parry_active = false
+       parry_body = null
+       if battle_started:
+               if parry_success:
+                       print("Successful parry")
+                       parry_success = false
+               else:
+                       print("Failed to parry")
+                       var rng = RandomNumberGenerator.new()
+                       health -= rng.randi_range(1, 5)
+                       health_dirty = true
+
+func hit_entered(node):
+       hit_active = true
+func hit_exited(node):
+       hit_active = false
+
+func _ready():
+       parry_area.area_entered.connect(parry_entered)
+       parry_area.area_exited.connect(parry_exited)
+       hit_area.area_entered.connect(hit_entered)
+       hit_area.area_exited.connect(hit_exited)
+
 func _process(delta):
-       summon_item_angle += delta
-       if summon_item != null:
-               summon_item.position.x = cos(summon_item_angle) * SUMMON_ITEM_DIST
-               summon_item.position.y = sin(summon_item_angle) * SUMMON_ITEM_DIST + SUMMON_ITEM_Y_OFFSET
+       if parry_tween == null and attack_target == null:
+               summon_item_angle += delta
+               if summon_item_angle > TAU:
+                       summon_item_angle -= TAU
+               if summon_item != null and not parry_success:
+                       summon_item.position.x = cos(-summon_item_angle) * SUMMON_ITEM_DIST
+                       summon_item.position.y = sin(-summon_item_angle) * SUMMON_ITEM_DIST + SUMMON_ITEM_Y_OFFSET
+               elif parry_success and not parry_success_checked and parry_body != null:
+                       parry_success_checked = true
+                       parry_tween = get_tree().create_tween()
+                       parry_tween.set_parallel()
+                       parry_tween.set_trans(Tween.TRANS_SPRING)
+                       var between = parry_body.get_parent().position - self.position
+                       parry_tween.tween_property(summon_item, "position", between, 0.2)
+                       parry_tween.set_trans(Tween.TRANS_EXPO)
+                       parry_tween.tween_property(summon_item, "rotation", TAU * 10, 0.3)
+                       parry_tween.set_parallel(false)
+                       var old_x = cos(-summon_item_angle) * SUMMON_ITEM_DIST
+                       var old_y = sin(-summon_item_angle) * SUMMON_ITEM_DIST + SUMMON_ITEM_Y_OFFSET
+                       parry_tween.set_trans(Tween.TRANS_EXPO)
+                       parry_tween.tween_property(summon_item, "position", Vector2(old_x, old_y), 0.4)
+                       parry_tween.tween_callback(func():
+                               parry_tween = null
+                               summon_item.rotation = 0.0
+                       )
+       elif attack_target != null and summon_item != null:
+               attack_tween = get_tree().create_tween()
+               var prev_x = cos(-summon_item_angle) * SUMMON_ITEM_DIST
+               var prev_y = sin(-summon_item_angle) * SUMMON_ITEM_DIST + SUMMON_ITEM_Y_OFFSET
+               attack_tween.set_parallel()
+               attack_tween.tween_property(summon_item, "position", attack_target.position - self.position, 0.2)
+               attack_tween.tween_property(summon_item, "rotation", -PI / 2.0, 0.2)
+               attack_tween.set_parallel(false)
+               var parent = get_parent()
+               attack_tween.tween_callback(func():
+                       match attack_type:
+                               AttackType.SWORD:
+                                       parent.play_attack_sfx_type = "sword"
+                               AttackType.HAMMER:
+                                       parent.play_attack_sfx_type = "hammer"
+               )
+               attack_tween.set_parallel()
+               attack_tween.tween_property(summon_item, "position", Vector2(prev_x, prev_y), 0.4)
+               attack_tween.tween_property(summon_item, "rotation", 0.0, 0.4)
+               attack_tween.set_parallel(false)
+               attack_tween.tween_callback(func():
+                       attack_target = null
+               )
+               
+       if summon_item_summoned:
+               summon_item_summoned = false
+               var summon_overlay = Sprite2D.new()
+               summon_overlay.set_name(&"SummonOverlay")
+               summon_overlay.set_texture(summon_item.get_texture())
+               add_child(summon_overlay, true)
+               summon_overlay.set_owner(self)
+               summon_overlay.set_scale(Vector2(5.0, 5.0))
+               summon_overlay.self_modulate = Color(1.0, 1.0, 1.0, 0.4)
+               summon_tween = get_tree().create_tween()
+               summon_tween.set_parallel()
+               summon_tween.set_trans(Tween.TRANS_EXPO)
+               summon_tween.tween_property(summon_overlay, "rotation", -TAU * 50.0, 2.0)
+               summon_tween.set_trans(Tween.TRANS_QUAD)
+               summon_tween.tween_property(summon_overlay, "self_modulate", Color(1.0, 1.0, 1.0, 0.0), 2.0)
+               summon_tween.set_parallel(false)
+               summon_tween.tween_callback(func(): self.remove_child(summon_overlay))
+       if health_dirty:
+               health_dirty = false
+               var hp_label = find_child("PlayerHPLabel")
+               if hp_label == null:
+                       hp_label = Label.new()
+                       hp_label.set_name(&"PlayerHPLabel")
+                       add_child(hp_label, true)
+                       hp_label.set_owner(self)
+                       hp_label.position.y += 10.0
+                       hp_label.position.x -= 18.0
+               hp_label.text = "%d HP" % health
 
 func _physics_process(delta):
        var vec2 = Vector2()
index 4deb67f65864d8d0400187e59d7506a819ec38e9..f71ba98170774602da1d5cf8a1c9d9ddf256e7e2 100644 (file)
@@ -1,4 +1,4 @@
-[gd_scene load_steps=21 format=3 uid="uid://ktxqc7xtqkex"]
+[gd_scene load_steps=23 format=3 uid="uid://ktxqc7xtqkex"]
 
 [ext_resource type="Script" path="res://gander_schwartz.gd" id="1_6ob4s"]
 [ext_resource type="Texture2D" uid="uid://cv0qw4j1q78r8" path="res://gimp/GanderSchwartz_spritesheet.png" id="1_n7bds"]
@@ -162,6 +162,14 @@ animations = [{
 radius = 15.0
 height = 80.0
 
+[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_sy0be"]
+radius = 32.0
+height = 126.0
+
+[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_58op3"]
+radius = 13.0
+height = 76.0
+
 [node name="GanderSchwartz" type="CharacterBody2D"]
 motion_mode = 1
 script = ExtResource("1_6ob4s")
@@ -178,3 +186,25 @@ frame_progress = 0.126405
 [node name="CollisionShape2D" type="CollisionShape2D" parent="."]
 position = Vector2(0, -40)
 shape = SubResource("CapsuleShape2D_u3k2h")
+
+[node name="ParryArea2D" type="Area2D" parent="."]
+collision_layer = 4
+collision_mask = 2
+input_pickable = false
+monitorable = false
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="ParryArea2D"]
+position = Vector2(0, -40)
+shape = SubResource("CapsuleShape2D_sy0be")
+debug_color = Color(0.631373, 0.352941, 0.984314, 0.419608)
+
+[node name="HitArea2D" type="Area2D" parent="."]
+collision_layer = 4
+collision_mask = 2
+input_pickable = false
+monitorable = false
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="HitArea2D"]
+position = Vector2(0, -40)
+shape = SubResource("CapsuleShape2D_58op3")
+debug_color = Color(0.988235, 0, 0.290196, 0.419608)