From 3016e63b23ceda78d53a7289c708689b2662742d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Mart=C3=ADnez=20Palacio?= Date: Wed, 11 Sep 2024 01:02:09 +0200 Subject: [PATCH] Finish Hiro AI state machine --- Hiro/hiro_ai.tscn | 122 ++++++++++++++++++++++++++++++++++++----- Hiro/hiro_player.tscn | 2 +- Scripts/hiro_ai.gd | 103 +++++++++++++++++++++++++++++++++- Scripts/hiro_player.gd | 1 + world/world.tscn | 8 +-- 5 files changed, 213 insertions(+), 23 deletions(-) diff --git a/Hiro/hiro_ai.tscn b/Hiro/hiro_ai.tscn index 78f2f4a..a28808e 100644 --- a/Hiro/hiro_ai.tscn +++ b/Hiro/hiro_ai.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=76 format=3 uid="uid://dd2km6qhpdicx"] +[gd_scene load_steps=80 format=3 uid="uid://dd2km6qhpdicx"] [ext_resource type="Script" path="res://Scripts/hiro_ai.gd" id="1_inmen"] [ext_resource type="Texture2D" uid="uid://cm7tgomrcvq30" path="res://Hiro/Hiro Walk Sprite Cycle Line Up.png" id="2_ctdbh"] @@ -7,6 +7,7 @@ [ext_resource type="Script" path="res://addons/godot_state_charts/state_chart.gd" id="6_i4c04"] [ext_resource type="Script" path="res://addons/godot_state_charts/compound_state.gd" id="7_j4kqa"] [ext_resource type="Script" path="res://addons/godot_state_charts/atomic_state.gd" id="8_pqyhf"] +[ext_resource type="Script" path="res://addons/godot_state_charts/expression_guard.gd" id="9_ak1rc"] [ext_resource type="Script" path="res://addons/godot_state_charts/transition.gd" id="9_la16b"] [sub_resource type="Animation" id="Animation_04piu"] @@ -519,7 +520,6 @@ tracks/3/keys = { [sub_resource type="Animation" id="Animation_ji461"] resource_name = "kick_east" length = 0.266673 -loop_mode = 1 step = 0.0666667 tracks/0/type = "value" tracks/0/imported = false @@ -609,7 +609,6 @@ tracks/6/keys = { [sub_resource type="Animation" id="Animation_cuaji"] resource_name = "kick_north" length = 0.266673 -loop_mode = 1 step = 0.0666667 tracks/0/type = "value" tracks/0/imported = false @@ -675,7 +674,6 @@ tracks/4/keys = { [sub_resource type="Animation" id="Animation_hqga6"] resource_name = "kick_south" length = 0.266673 -loop_mode = 1 step = 0.0666667 tracks/0/type = "value" tracks/0/imported = false @@ -741,7 +739,6 @@ tracks/4/keys = { [sub_resource type="Animation" id="Animation_s8xim"] resource_name = "kick_west" length = 0.266673 -loop_mode = 1 step = 0.0666667 tracks/0/type = "value" tracks/0/imported = false @@ -807,7 +804,6 @@ tracks/4/keys = { [sub_resource type="Animation" id="Animation_o0diw"] resource_name = "punch_east_left" length = 0.266673 -loop_mode = 1 step = 0.0666667 tracks/0/type = "value" tracks/0/imported = false @@ -873,7 +869,6 @@ tracks/4/keys = { [sub_resource type="Animation" id="Animation_ar6r3"] resource_name = "punch_east_rigth" length = 0.266673 -loop_mode = 1 step = 0.0666667 tracks/0/type = "value" tracks/0/imported = false @@ -939,7 +934,6 @@ tracks/4/keys = { [sub_resource type="Animation" id="Animation_1e0v6"] resource_name = "punch_north_left" length = 0.266673 -loop_mode = 1 step = 0.0666667 tracks/0/type = "value" tracks/0/imported = false @@ -1005,7 +999,6 @@ tracks/4/keys = { [sub_resource type="Animation" id="Animation_bwm5c"] resource_name = "punch_north_rigth" length = 0.266673 -loop_mode = 1 step = 0.0666667 tracks/0/type = "value" tracks/0/imported = false @@ -1071,7 +1064,6 @@ tracks/4/keys = { [sub_resource type="Animation" id="Animation_qmuik"] resource_name = "punch_south_left" length = 0.266673 -loop_mode = 1 step = 0.0666667 tracks/0/type = "value" tracks/0/imported = false @@ -1137,7 +1129,6 @@ tracks/4/keys = { [sub_resource type="Animation" id="Animation_bw220"] resource_name = "punch_south_rigth" length = 0.266673 -loop_mode = 1 step = 0.0666667 tracks/0/type = "value" tracks/0/imported = false @@ -1203,7 +1194,6 @@ tracks/4/keys = { [sub_resource type="Animation" id="Animation_wjl2t"] resource_name = "punch_west_left" length = 0.266673 -loop_mode = 1 step = 0.0666667 tracks/0/type = "value" tracks/0/imported = false @@ -1269,7 +1259,6 @@ tracks/4/keys = { [sub_resource type="Animation" id="Animation_e4rxs"] resource_name = "punch_west_rigth" length = 0.266673 -loop_mode = 1 step = 0.0666667 tracks/0/type = "value" tracks/0/imported = false @@ -1740,8 +1729,8 @@ advance_mode = 2 advance_condition = &"melee" [sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_l17k0"] +switch_mode = 2 advance_mode = 2 -advance_expression = "get(\"parameters/conditions/melee\") == false" [sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_ln3pc"] switch_mode = 2 @@ -1759,7 +1748,7 @@ states/melee/position = Vector2(329, 224) states/walk/node = SubResource("AnimationNodeBlendSpace2D_vunsg") states/walk/position = Vector2(520, 108) transitions = ["Start", "idle", SubResource("AnimationNodeStateMachineTransition_akmuq"), "idle", "walk", SubResource("AnimationNodeStateMachineTransition_sn1tm"), "walk", "idle", SubResource("AnimationNodeStateMachineTransition_5i3fu"), "idle", "melee", SubResource("AnimationNodeStateMachineTransition_y8p74"), "melee", "idle", SubResource("AnimationNodeStateMachineTransition_l17k0"), "hurt", "idle", SubResource("AnimationNodeStateMachineTransition_ln3pc")] -graph_offset = Vector2(-222, -37) +graph_offset = Vector2(-228, 51) [sub_resource type="CapsuleShape2D" id="CapsuleShape2D_vm050"] radius = 8.0 @@ -1770,6 +1759,18 @@ size = Vector2(13, 8) [sub_resource type="CircleShape2D" id="CircleShape2D_md645"] +[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_hxosv"] +radius = 12.0 +height = 44.0 + +[sub_resource type="Resource" id="Resource_ri8lv"] +script = ExtResource("9_ak1rc") +expression = "melee_stage == 0" + +[sub_resource type="Resource" id="Resource_gtfv8"] +script = ExtResource("9_ak1rc") +expression = "melee_stage == 1" + [node name="HiroAI" type="CharacterBody2D"] y_sort_enabled = true collision_layer = 5 @@ -1830,6 +1831,14 @@ shape = SubResource("CircleShape2D_md645") disabled = true debug_color = Color(0.745098, 0.462745, 0, 0.419608) +[node name="Meleebox" type="Area2D" parent="."] +collision_layer = 4 + +[node name="DetectionArea" type="CollisionShape2D" parent="Meleebox"] +position = Vector2(0, -13) +shape = SubResource("CapsuleShape2D_hxosv") +debug_color = Color(0.784314, 0.423529, 0.27451, 0.419608) + [node name="StateMachine" type="Node" parent="."] script = ExtResource("6_i4c04") @@ -1846,15 +1855,83 @@ to = NodePath("../../Hurt") event = &"been_hurt" delay_in_seconds = "0.0" +[node name="ToMelee" type="Node" parent="StateMachine/Root/Think"] +script = ExtResource("9_la16b") +to = NodePath("../../Melee") +event = &"will_melee" +delay_in_seconds = "0.0" + +[node name="ToApproach" type="Node" parent="StateMachine/Root/Think"] +script = ExtResource("9_la16b") +to = NodePath("../../Approach") +event = &"wants_tomelee" +guard = SubResource("Resource_ri8lv") +delay_in_seconds = "0.0" + +[node name="ToChase" type="Node" parent="StateMachine/Root/Think"] +script = ExtResource("9_la16b") +to = NodePath("../../Chase") +event = &"wants_tomelee" +guard = SubResource("Resource_gtfv8") +delay_in_seconds = "0.0" + [node name="Approach" type="Node" parent="StateMachine/Root"] script = ExtResource("8_pqyhf") +[node name="ToHurt" type="Node" parent="StateMachine/Root/Approach"] +script = ExtResource("9_la16b") +to = NodePath("../../Hurt") +event = &"been_hurt" +delay_in_seconds = "0.0" + +[node name="ToMelee" type="Node" parent="StateMachine/Root/Approach"] +script = ExtResource("9_la16b") +to = NodePath("../../Melee") +event = &"will_melee" +delay_in_seconds = "0.0" + +[node name="ToThink" type="Node" parent="StateMachine/Root/Approach"] +script = ExtResource("9_la16b") +to = NodePath("../../Think") +event = &"approach_complete" +delay_in_seconds = "0.0" + [node name="Chase" type="Node" parent="StateMachine/Root"] script = ExtResource("8_pqyhf") +[node name="ToHurt" type="Node" parent="StateMachine/Root/Chase"] +script = ExtResource("9_la16b") +to = NodePath("../../Hurt") +event = &"been_hurt" +delay_in_seconds = "0.0" + +[node name="ToMelee" type="Node" parent="StateMachine/Root/Chase"] +script = ExtResource("9_la16b") +to = NodePath("../../Melee") +event = &"will_melee" +delay_in_seconds = "0.0" + +[node name="ToThink" type="Node" parent="StateMachine/Root/Chase"] +script = ExtResource("9_la16b") +to = NodePath("../../Think") +event = &"melee_aborted" +delay_in_seconds = "0.0" + [node name="Melee" type="Node" parent="StateMachine/Root"] script = ExtResource("8_pqyhf") +[node name="ToHurt" type="Node" parent="StateMachine/Root/Melee"] +script = ExtResource("9_la16b") +to = NodePath("../../Hurt") +event = &"been_hurt" +delay_in_seconds = "0.0" + +[node name="ToThink" type="Node" parent="StateMachine/Root/Melee"] +script = ExtResource("9_la16b") +to = NodePath("../../Think") +event = &"melee_complete" +delay_in_seconds = "0.0" + [node name="Hurt" type="Node" parent="StateMachine/Root"] script = ExtResource("8_pqyhf") @@ -1889,6 +1966,21 @@ delay_in_seconds = "0.0" script = ExtResource("8_pqyhf") [connection signal="animation_finished" from="AnimationTree" to="." method="_on_animation_tree_animation_finished"] +[connection signal="body_entered" from="Meleebox" to="." method="_on_meleebox_body_entered"] +[connection signal="body_exited" from="Meleebox" to="." method="_on_meleebox_body_exited"] +[connection signal="state_entered" from="StateMachine/Root/Think" to="." method="_on_think_state_entered"] +[connection signal="state_processing" from="StateMachine/Root/Think" to="." method="_on_think_state_processing"] +[connection signal="state_entered" from="StateMachine/Root/Approach" to="." method="_on_approach_state_entered"] +[connection signal="state_exited" from="StateMachine/Root/Approach" to="." method="_on_approach_state_exited"] +[connection signal="state_processing" from="StateMachine/Root/Approach" to="." method="_on_approach_state_processing"] +[connection signal="taken" from="StateMachine/Root/Approach/ToThink" to="." method="_on_approach_to_think_taken"] +[connection signal="state_entered" from="StateMachine/Root/Chase" to="." method="_on_chase_state_entered"] +[connection signal="state_exited" from="StateMachine/Root/Chase" to="." method="_on_chase_state_exited"] +[connection signal="state_processing" from="StateMachine/Root/Chase" to="." method="_on_chase_state_processing"] +[connection signal="taken" from="StateMachine/Root/Chase/ToThink" to="." method="_on_chase_to_think_taken"] +[connection signal="state_entered" from="StateMachine/Root/Melee" to="." method="_on_melee_state_entered"] +[connection signal="state_exited" from="StateMachine/Root/Melee" to="." method="_on_melee_state_exited"] +[connection signal="state_processing" from="StateMachine/Root/Melee" to="." method="_on_melee_state_processing"] [connection signal="state_entered" from="StateMachine/Root/Hurt" to="." method="_on_hurt_state_entered"] [connection signal="state_exited" from="StateMachine/Root/Hurt" to="." method="_on_hurt_state_exited"] [connection signal="state_entered" from="StateMachine/Root/HurtReposition" to="." method="_on_hurt_reposition_state_entered"] diff --git a/Hiro/hiro_player.tscn b/Hiro/hiro_player.tscn index f9987a2..681e872 100644 --- a/Hiro/hiro_player.tscn +++ b/Hiro/hiro_player.tscn @@ -2019,7 +2019,7 @@ states/melee/position = Vector2(329, 224) states/walk/node = SubResource("AnimationNodeBlendSpace2D_vunsg") states/walk/position = Vector2(520, 108) transitions = ["Start", "idle", SubResource("AnimationNodeStateMachineTransition_akmuq"), "idle", "walk", SubResource("AnimationNodeStateMachineTransition_sn1tm"), "walk", "idle", SubResource("AnimationNodeStateMachineTransition_5i3fu"), "idle", "melee", SubResource("AnimationNodeStateMachineTransition_y8p74"), "melee", "idle", SubResource("AnimationNodeStateMachineTransition_l17k0"), "hurt", "idle", SubResource("AnimationNodeStateMachineTransition_ln3pc")] -graph_offset = Vector2(-222, -37) +graph_offset = Vector2(-254, -71) [sub_resource type="CapsuleShape2D" id="CapsuleShape2D_vm050"] radius = 8.0 diff --git a/Scripts/hiro_ai.gd b/Scripts/hiro_ai.gd index 44c11cb..182b642 100644 --- a/Scripts/hiro_ai.gd +++ b/Scripts/hiro_ai.gd @@ -1,20 +1,31 @@ +class_name AICharacter extends CharacterBody2D +@export var player_character: PlayableCharacter @export var health = 100 @export var KNOCKBACK_POWER = 50 @export var KNOCKBACK_REPOSITION_POWER = 75 @export var KNOCKBACK_RANDOMNESS = 50 +@export var MELEE_RADIUS = 75 @onready var anim_tree: AnimationTree = $AnimationTree @onready var anim_playback: AnimationNodeStateMachinePlayback = anim_tree["parameters/playback"] @onready var state_machine: StateChart = $StateMachine +var timer = 0 + +# Getting hurt variables var attacked_from = Vector2.ZERO var received_damage = 0 var hit_counter = 0 +var wants_to_melee = false + signal _got_hurt(damage: int, attack_direction: Vector2) #signal defeated +func _ready(): + state_machine.set_expression_property("melee_stage", 0) + func set_animation_direction(facing_direction: Vector2): anim_tree.set("parameters/idle/blend_position", facing_direction) anim_tree.set("parameters/walk/blend_position", facing_direction) @@ -22,6 +33,11 @@ func set_animation_direction(facing_direction: Vector2): anim_tree.set("parameters/melee/1/blend_position", facing_direction) anim_tree.set("parameters/melee/2/blend_position", facing_direction) anim_tree.set("parameters/hurt/blend_position", facing_direction) + +func follow_player(speed: float): + var approach_direction = position.direction_to(player_character.position) + velocity = speed * approach_direction + set_animation_direction(approach_direction) func _physics_process(_delta): move_and_slide() @@ -36,6 +52,27 @@ func _on_ai_got_hurt(attack_power: int, attack_direction: Vector2): received_damage = attack_power state_machine.send_event("been_hurt") +## Thinking state +## Once some time passes out, do_thinking is called +func _on_think_state_entered(): + timer = randf_range(3.0/60.0, 1.5) + if timer <= 88.0/90.0: + timer = timer * 0.75 + +func _on_think_state_processing(delta): + timer -= delta + if timer <= 0: + do_thinking() + +## This function actually determines what to do +func do_thinking(): + var distance = position.distance_to(player_character.position) + if distance > MELEE_RADIUS: + state_machine.set_expression_property("melee_stage", 0) + # melee attacks here + else: + state_machine.send_event("wants_tomelee") + func _on_hurt_state_entered(): var facing_direction = attacked_from * -1 health = health - received_damage @@ -72,7 +109,8 @@ func _on_hurt_state_entered(): elif attacked_from.y != 0: knockback_variance.x = randf_range(-1, 1) * KNOCKBACK_RANDOMNESS velocity = attacked_from * KNOCKBACK_POWER + knockback_variance - print(velocity, knockback_variance) + + state_machine.set_expression_property("melee_stage", 0) func _on_hurt_state_exited(): velocity = Vector2.ZERO @@ -92,7 +130,68 @@ func _on_hurt_reposition_state_exited(): func _on_animation_tree_animation_finished(anim_name): if anim_name.contains("hurt"): state_machine.send_event("hurt_complete") - + elif (anim_name.contains("kick") or anim_name.contains("punch")): + if wants_to_melee: + anim_tree.set("parameters/conditions/melee", true) + else: + state_machine.send_event("melee_complete") func _on_defeat_state_entered(): queue_free() + +## Aproaching +func _on_approach_state_entered(): + follow_player(25) + anim_tree.set("parameters/conditions/is_moving", true) + timer = 1 + +func _on_approach_state_processing(delta): + timer -= delta + if timer < 0: + state_machine.send_event("approach_complete") + + +func _on_approach_state_exited(): + anim_tree.set("parameters/conditions/is_moving", false) + velocity = Vector2.ZERO + + +func _on_approach_to_think_taken(): + state_machine.set_expression_property("melee_stage", 1) + +## Chasing +func _on_chase_state_entered(): + anim_tree.set("parameters/conditions/is_moving", true) + +func _on_chase_state_processing(_delta): + follow_player(65) + var distance = position.distance_to(player_character.position) + if distance >= MELEE_RADIUS: + state_machine.send_event("melee_aborted") + +func _on_chase_state_exited(): + anim_tree.set("parameters/conditions/is_moving", false) + velocity = Vector2.ZERO + +func _on_chase_to_think_taken(): + state_machine.set_expression_property("melee_stage", 0) + +## Melee +func _on_meleebox_body_entered(body): + if body == player_character: + wants_to_melee = true + state_machine.send_event("will_melee") + +func _on_melee_state_entered(): + state_machine.set_expression_property("melee_stage", 0) + anim_tree.set("parameters/conditions/melee", true) + +func _on_meleebox_body_exited(body): + if body == player_character: + wants_to_melee = false + +func _on_melee_state_processing(_delta): + follow_player(0) + +func _on_melee_state_exited(): + anim_tree.set("parameters/conditions/melee", false) diff --git a/Scripts/hiro_player.gd b/Scripts/hiro_player.gd index 446f9e8..701143a 100644 --- a/Scripts/hiro_player.gd +++ b/Scripts/hiro_player.gd @@ -1,3 +1,4 @@ +class_name PlayableCharacter extends CharacterBody2D @export var speed = 100 diff --git a/world/world.tscn b/world/world.tscn index d48df3c..ee92800 100644 --- a/world/world.tscn +++ b/world/world.tscn @@ -41,10 +41,8 @@ offset_bottom = 1099.0 scale = Vector2(0.25, 0.25) initial_node_to_watch = NodePath("../HiroAI") -[node name="HiroAI" parent="." instance=ExtResource("5_r7nw4")] -position = Vector2(135, 216) -health = 500 -KNOCKBACK_POWER = 40 -KNOCKBACK_RANDOMNESS = 40 +[node name="HiroAI" parent="." node_paths=PackedStringArray("player_character") instance=ExtResource("5_r7nw4")] +position = Vector2(135, 215) +player_character = NodePath("../HiroPlayer") [connection signal="attack_did_hit" from="HiroPlayer" to="HiroAI" method="_on_player_attack_did_hit"]