diff --git a/demo/demo/agents/agent_base.tscn b/demo/demo/agents/agent_base.tscn index 2cfb32e..c497596 100644 --- a/demo/demo/agents/agent_base.tscn +++ b/demo/demo/agents/agent_base.tscn @@ -270,10 +270,22 @@ tracks/20/keys = { "update": 0, "values": [Vector2(1, 1)] } +tracks/21/type = "value" +tracks/21/imported = false +tracks/21/enabled = true +tracks/21/path = NodePath("Root/Hitbox/CollisionShape2D:disabled") +tracks/21/interp = 1 +tracks/21/loop_wrap = true +tracks/21/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 1, +"values": [true] +} [sub_resource type="Animation" id="Animation_g7a0r"] resource_name = "attack" -length = 0.3 +length = 0.35 step = 0.05 tracks/0/type = "value" tracks/0/imported = false @@ -503,6 +515,18 @@ tracks/18/keys = { "update": 1, "values": [false] } +tracks/19/type = "value" +tracks/19/imported = false +tracks/19/enabled = true +tracks/19/path = NodePath("Root/Hitbox/CollisionShape2D:disabled") +tracks/19/interp = 1 +tracks/19/loop_wrap = true +tracks/19/keys = { +"times": PackedFloat32Array(0, 0.15, 0.3), +"transitions": PackedFloat32Array(1, 1, 1), +"update": 1, +"values": [true, false, true] +} [sub_resource type="Animation" id="Animation_b0ub6"] resource_name = "attack_ranged" @@ -996,7 +1020,7 @@ tracks/20/keys = { [sub_resource type="Animation" id="Animation_gowr5"] resource_name = "hurt" -length = 0.15 +length = 0.25 step = 0.05 tracks/0/type = "value" tracks/0/imported = false @@ -1018,7 +1042,7 @@ tracks/1/interp = 2 tracks/1/loop_wrap = true tracks/1/keys = { "times": PackedFloat32Array(0, 0.1), -"transitions": PackedFloat32Array(1, 1), +"transitions": PackedFloat32Array(1, 2), "update": 0, "values": [0.0, -0.459832] } @@ -1036,7 +1060,7 @@ tracks/2/keys = { } tracks/3/type = "value" tracks/3/imported = false -tracks/3/enabled = true +tracks/3/enabled = false tracks/3/path = NodePath("Root/Rig/Body/Hat:position") tracks/3/interp = 2 tracks/3/loop_wrap = true @@ -1226,6 +1250,18 @@ tracks/18/keys = { "update": 1, "values": [false] } +tracks/19/type = "value" +tracks/19/imported = false +tracks/19/enabled = true +tracks/19/path = NodePath("Root/Hitbox/CollisionShape2D:disabled") +tracks/19/interp = 1 +tracks/19/loop_wrap = true +tracks/19/keys = { +"times": PackedFloat32Array(0, 0.25), +"transitions": PackedFloat32Array(1, 1), +"update": 1, +"values": [true, true] +} [sub_resource type="Animation" id="Animation_gnqgt"] resource_name = "idle" @@ -2214,6 +2250,7 @@ script = ExtResource("5_taq6b") [node name="CollisionShape2D" type="CollisionShape2D" parent="Root/Hitbox"] shape = SubResource("RectangleShape2D_2k81i") +disabled = true debug_color = Color(0.933131, 0.0801983, 0.605982, 0.42) metadata/_edit_lock_ = true diff --git a/demo/demo/agents/agent_base_enemy.gd b/demo/demo/agents/agent_base_enemy.gd new file mode 100644 index 0000000..7b704cb --- /dev/null +++ b/demo/demo/agents/agent_base_enemy.gd @@ -0,0 +1,2 @@ +extends "res://demo/agents/scripts/agent_base.gd" + diff --git a/demo/demo/agents/agent_base_enemy.tscn b/demo/demo/agents/agent_base_enemy.tscn new file mode 100644 index 0000000..04a4409 --- /dev/null +++ b/demo/demo/agents/agent_base_enemy.tscn @@ -0,0 +1,38 @@ +[gd_scene load_steps=6 format=3 uid="uid://sjans5psq6pg"] + +[ext_resource type="PackedScene" uid="uid://ooigbfhfy4wa" path="res://demo/agents/agent_base.tscn" id="1_uffd7"] +[ext_resource type="BehaviorTree" uid="uid://bpdm5jnegi38" path="res://demo/ai/trees/enemy_melee_simple.tres" id="2_tmu02"] +[ext_resource type="Script" path="res://demo/agents/agent_base_enemy.gd" id="2_w7f5y"] +[ext_resource type="Texture2D" uid="uid://cw8s50856x8ct" path="res://demo/assets/agent_melee_simple.png" id="3_c30et"] + +[sub_resource type="BlackboardPlan" id="BlackboardPlan_4e1gs"] +var/speed/name = "speed" +var/speed/type = 3 +var/speed/value = 400.0 +var/speed/hint = 0 +var/speed/hint_string = "" + +[node name="AgentEnemy" instance=ExtResource("1_uffd7")] +script = ExtResource("2_w7f5y") + +[node name="LegL" parent="Root/Rig" index="0"] +texture = ExtResource("3_c30et") + +[node name="LegR" parent="Root/Rig" index="1"] +texture = ExtResource("3_c30et") + +[node name="Body" parent="Root/Rig" index="2"] +texture = ExtResource("3_c30et") + +[node name="Hat" parent="Root/Rig/Body" index="0"] +texture = ExtResource("3_c30et") + +[node name="HandL" parent="Root/Rig/Body" index="1"] +texture = ExtResource("3_c30et") + +[node name="HandR" parent="Root/Rig/Body" index="2"] +texture = ExtResource("3_c30et") + +[node name="BTPlayer" type="BTPlayer" parent="." index="4"] +behavior_tree = ExtResource("2_tmu02") +blackboard_plan = SubResource("BlackboardPlan_4e1gs") diff --git a/demo/demo/agents/player/player.gd b/demo/demo/agents/player/player.gd index fa6480e..270cef1 100644 --- a/demo/demo/agents/player/player.gd +++ b/demo/demo/agents/player/player.gd @@ -2,17 +2,26 @@ extends "res://demo/agents/scripts/agent_base.gd" ## Player. -@onready var limbo_hsm: LimboHSM = $LimboHSM +@onready var hsm: LimboHSM = $LimboHSM @onready var idle_state: LimboState = $LimboHSM/IdleState @onready var move_state: LimboState = $LimboHSM/MoveState +@onready var attack_state: LimboState = $LimboHSM/AttackState func _ready() -> void: + super._ready() _init_state_machine() +func _unhandled_input(event: InputEvent) -> void: + if event.is_action_pressed("attack") and event.is_echo() == false: + hsm.dispatch("attack!") + + func _init_state_machine() -> void: - limbo_hsm.add_transition(idle_state, move_state, idle_state.EVENT_FINISHED) - limbo_hsm.add_transition(move_state, idle_state, move_state.EVENT_FINISHED) - limbo_hsm.initialize(self) - limbo_hsm.set_active(true) + hsm.add_transition(idle_state, move_state, idle_state.EVENT_FINISHED) + hsm.add_transition(move_state, idle_state, move_state.EVENT_FINISHED) + hsm.add_transition(hsm.ANYSTATE, attack_state, "attack!") + hsm.add_transition(attack_state, move_state, attack_state.EVENT_FINISHED) + hsm.initialize(self) + hsm.set_active(true) diff --git a/demo/demo/agents/player/player.tscn b/demo/demo/agents/player/player.tscn index 3103f31..1dff6ca 100644 --- a/demo/demo/agents/player/player.tscn +++ b/demo/demo/agents/player/player.tscn @@ -1,21 +1,27 @@ -[gd_scene load_steps=5 format=3 uid="uid://d07ag5dcje13i"] +[gd_scene load_steps=6 format=3 uid="uid://d07ag5dcje13i"] [ext_resource type="PackedScene" uid="uid://ooigbfhfy4wa" path="res://demo/agents/agent_base.tscn" id="1_mswd4"] [ext_resource type="Script" path="res://demo/agents/player/player.gd" id="2_24nyi"] -[ext_resource type="Script" path="res://demo/agents/player/idle_state.gd" id="2_moi60"] -[ext_resource type="Script" path="res://demo/agents/player/move_state.gd" id="3_bxpc0"] +[ext_resource type="Script" path="res://demo/agents/player/states/idle_state.gd" id="3_ekb12"] +[ext_resource type="Script" path="res://demo/agents/player/states/move_state.gd" id="4_paikn"] +[ext_resource type="Script" path="res://demo/agents/player/states/attack_state.gd" id="5_mpgu6"] -[node name="Player" instance=ExtResource("1_mswd4")] +[node name="Player" groups=["player"] instance=ExtResource("1_mswd4")] script = ExtResource("2_24nyi") [node name="LimboHSM" type="LimboHSM" parent="." index="4"] [node name="IdleState" type="LimboState" parent="LimboHSM" index="0" node_paths=PackedStringArray("animation_player")] -script = ExtResource("2_moi60") +script = ExtResource("3_ekb12") animation_player = NodePath("../../AnimationPlayer") animation = &"idle" [node name="MoveState" type="LimboState" parent="LimboHSM" index="1" node_paths=PackedStringArray("animation_player")] -script = ExtResource("3_bxpc0") +script = ExtResource("4_paikn") animation_player = NodePath("../../AnimationPlayer") animation = &"walk" + +[node name="AttackState" type="LimboState" parent="LimboHSM" index="2" node_paths=PackedStringArray("animation_player")] +script = ExtResource("5_mpgu6") +animation_player = NodePath("../../AnimationPlayer") +animation = &"attack" diff --git a/demo/demo/agents/player/states/attack_state.gd b/demo/demo/agents/player/states/attack_state.gd new file mode 100644 index 0000000..5f6f295 --- /dev/null +++ b/demo/demo/agents/player/states/attack_state.gd @@ -0,0 +1,13 @@ +extends LimboState + +## Idle state. + +@export var animation_player: AnimationPlayer +@export var animation: StringName + + +func _enter() -> void: + animation_player.play(animation) + await animation_player.animation_finished + if is_active(): + get_root().dispatch(EVENT_FINISHED) diff --git a/demo/demo/agents/player/idle_state.gd b/demo/demo/agents/player/states/idle_state.gd similarity index 91% rename from demo/demo/agents/player/idle_state.gd rename to demo/demo/agents/player/states/idle_state.gd index 65f7e63..fdbe8ab 100644 --- a/demo/demo/agents/player/idle_state.gd +++ b/demo/demo/agents/player/states/idle_state.gd @@ -7,7 +7,7 @@ extends LimboState func _enter() -> void: - animation_player.play(animation) + animation_player.play(animation, 0.1) func _update(_delta: float) -> void: diff --git a/demo/demo/agents/player/move_state.gd b/demo/demo/agents/player/states/move_state.gd similarity index 94% rename from demo/demo/agents/player/move_state.gd rename to demo/demo/agents/player/states/move_state.gd index e422c86..78dd23e 100644 --- a/demo/demo/agents/player/move_state.gd +++ b/demo/demo/agents/player/states/move_state.gd @@ -9,7 +9,7 @@ const VERTICAL_FACTOR := 0.8 @export var speed: float = 500.0 func _enter() -> void: - animation_player.play(animation) + animation_player.play(animation, 0.1) func _update(_delta: float) -> void: diff --git a/demo/demo/agents/scripts/agent_base.gd b/demo/demo/agents/scripts/agent_base.gd index 70366fb..d7f444a 100644 --- a/demo/demo/agents/scripts/agent_base.gd +++ b/demo/demo/agents/scripts/agent_base.gd @@ -6,6 +6,7 @@ extends CharacterBody2D @onready var health: Health = $Health @onready var root: Node2D = $Root +var _frames_since_facing_update: int = 0 func _ready() -> void: health.damaged.connect(_damaged) @@ -17,14 +18,34 @@ func _physics_process(_delta: float) -> void: func _update_facing() -> void: - if velocity.x > 0.0 and root.scale.x < 0.0: + _frames_since_facing_update += 1 + if _frames_since_facing_update > 3: + face_dir(velocity.x) + + +func face_dir(dir: float) -> void: + if dir > 0.0 and root.scale.x < 0.0: root.scale.x = 1.0; - if velocity.x < 0.0 and root.scale.x > 0.0: + _frames_since_facing_update = 0 + if dir < 0.0 and root.scale.x > 0.0: root.scale.x = -1.0; + _frames_since_facing_update = 0 func _damaged(_amount: float) -> void: animation_player.play(&"hurt") + var btplayer := get_node_or_null(^"BTPlayer") as BTPlayer + if btplayer: + btplayer.set_active(false) + var hsm := get_node_or_null(^"LimboHSM") + if hsm: + hsm.set_active(false) + await animation_player.animation_finished + if btplayer: + btplayer.restart() + if hsm: + hsm.set_active(true) + func _die() -> void: diff --git a/demo/demo/ai/tasks/face_target.gd b/demo/demo/ai/tasks/face_target.gd new file mode 100644 index 0000000..f370312 --- /dev/null +++ b/demo/demo/ai/tasks/face_target.gd @@ -0,0 +1,20 @@ +@tool +extends BTAction +## FaceTarget + +@export var target_var: String = "target" + +# Display a customized name (requires @tool). +func _generate_name() -> String: + return "FaceTarget " + LimboUtility.decorate_var(target_var) + + +# Called each time this task is ticked (aka executed). +func _tick(delta: float) -> Status: + var target := blackboard.get_var(target_var) as CharacterBody2D + if not is_instance_valid(target): + return FAILURE + var dir: float = target.global_position.x - agent.global_position.x + agent.velocity = Vector2.ZERO + agent.face_dir(dir) + return SUCCESS diff --git a/demo/demo/ai/tasks/get_first_in_group.gd b/demo/demo/ai/tasks/get_first_in_group.gd new file mode 100644 index 0000000..bce79f2 --- /dev/null +++ b/demo/demo/ai/tasks/get_first_in_group.gd @@ -0,0 +1,19 @@ +@tool +extends BTAction + +## Get first node in group and save it to the blackboard. + +@export var group: StringName +@export var output_var: String = "target" + + +func _generate_name() -> String: + return "GetFirstNodeInGroup \"%s\" -> %s" % [ + group, + LimboUtility.decorate_var(output_var) + ] + +func _tick(_delta: float) -> Status: + var node = agent.get_tree().get_first_node_in_group(group) + blackboard.set_var(output_var, node) + return SUCCESS diff --git a/demo/demo/ai/tasks/pursue.gd b/demo/demo/ai/tasks/pursue.gd new file mode 100644 index 0000000..9058ece --- /dev/null +++ b/demo/demo/ai/tasks/pursue.gd @@ -0,0 +1,30 @@ +@tool +extends BTAction + +const TOLERANCE := 30.0 + +@export var target_var: String = "target" +@export var speed_var: String = "speed" +@export var distance: float = 100.0 + +func _generate_name() -> String: + return "Pursue %s" % [LimboUtility.decorate_var(target_var)] + + +func _tick(_delta: float) -> Status: + var target := blackboard.get_var(target_var, null) as CharacterBody2D + if not is_instance_valid(target): + return FAILURE + + var dir: Vector2 = target.global_position - agent.global_position + var target_pos := Vector2( + target.global_position.x - distance * signf(dir.x), + target.global_position.y) + + if agent.global_position.distance_to(target_pos) < TOLERANCE: + return SUCCESS + + var speed: float = blackboard.get_var(speed_var, 200.0) + agent.velocity = agent.global_position.direction_to(target_pos) * speed + agent.move_and_slide() + return RUNNING diff --git a/demo/demo/ai/trees/enemy_melee_simple.tres b/demo/demo/ai/trees/enemy_melee_simple.tres new file mode 100644 index 0000000..9dba3d2 --- /dev/null +++ b/demo/demo/ai/trees/enemy_melee_simple.tres @@ -0,0 +1,94 @@ +[gd_resource type="BehaviorTree" load_steps=24 format=3 uid="uid://bpdm5jnegi38"] + +[ext_resource type="Script" path="res://demo/ai/tasks/get_first_in_group.gd" id="1_2jpsu"] +[ext_resource type="Script" path="res://demo/ai/tasks/pursue.gd" id="2_h5db5"] +[ext_resource type="Script" path="res://demo/ai/tasks/face_target.gd" id="3_bpmfp"] + +[sub_resource type="BlackboardPlan" id="BlackboardPlan_46tbn"] +var/speed/name = "speed" +var/speed/type = 3 +var/speed/value = 400.0 +var/speed/hint = 0 +var/speed/hint_string = "" + +[sub_resource type="BBNode" id="BBNode_nrd4b"] +resource_name = "AnimationPlayer" +saved_value = NodePath("AnimationPlayer") + +[sub_resource type="BTPlayAnimation" id="BTPlayAnimation_qiw21"] +animation_player = SubResource("BBNode_nrd4b") +animation_name = &"idle" +blend = 0.1 + +[sub_resource type="BTRandomWait" id="BTRandomWait_xlud8"] +min_duration = 0.1 +max_duration = 0.8 + +[sub_resource type="BTSequence" id="BTSequence_yhjh1"] +custom_name = "Chilling" +children = [SubResource("BTPlayAnimation_qiw21"), SubResource("BTRandomWait_xlud8")] + +[sub_resource type="BTComment" id="BTComment_6tjy8"] +custom_name = "Approach player" + +[sub_resource type="BTAction" id="BTAction_ulbrf"] +script = ExtResource("1_2jpsu") +group = &"player" +output_var = "_target" + +[sub_resource type="BBNode" id="BBNode_wpj6d"] +resource_name = "AnimationPlayer" +saved_value = NodePath("AnimationPlayer") + +[sub_resource type="BTPlayAnimation" id="BTPlayAnimation_olf37"] +animation_player = SubResource("BBNode_wpj6d") +animation_name = &"walk" +blend = 0.1 + +[sub_resource type="BTAction" id="BTAction_a4jqi"] +script = ExtResource("2_h5db5") +target_var = "_target" +speed_var = "speed" +distance = 100.0 + +[sub_resource type="BTComment" id="BTComment_i1svx"] +custom_name = "Attack player" + +[sub_resource type="BTAction" id="BTAction_kidxn"] +script = ExtResource("3_bpmfp") +target_var = "_target" + +[sub_resource type="BTWait" id="BTWait_tadkc"] +duration = 0.1 + +[sub_resource type="BBNode" id="BBNode_s8evu"] +resource_name = "AnimationPlayer" +saved_value = NodePath("AnimationPlayer") + +[sub_resource type="BTPlayAnimation" id="BTPlayAnimation_ppmxd"] +await_completion = 2.0 +animation_player = SubResource("BBNode_s8evu") +animation_name = &"attack" + +[sub_resource type="BBNode" id="BBNode_4jcut"] +resource_name = "AnimationPlayer" +saved_value = NodePath("AnimationPlayer") + +[sub_resource type="BTPlayAnimation" id="BTPlayAnimation_wgf32"] +animation_player = SubResource("BBNode_4jcut") +animation_name = &"idle" +blend = 0.1 + +[sub_resource type="BTWait" id="BTWait_lcs3i"] +duration = 0.5 + +[sub_resource type="BTSequence" id="BTSequence_pxl2k"] +custom_name = "Melee attack" +children = [SubResource("BTSequence_yhjh1"), SubResource("BTComment_6tjy8"), SubResource("BTAction_ulbrf"), SubResource("BTPlayAnimation_olf37"), SubResource("BTAction_a4jqi"), SubResource("BTComment_i1svx"), SubResource("BTAction_kidxn"), SubResource("BTWait_tadkc"), SubResource("BTPlayAnimation_ppmxd"), SubResource("BTPlayAnimation_wgf32"), SubResource("BTWait_lcs3i")] + +[sub_resource type="BTSelector" id="BTSelector_mcdtk"] +children = [SubResource("BTSequence_pxl2k")] + +[resource] +blackboard_plan = SubResource("BlackboardPlan_46tbn") +root_task = SubResource("BTSelector_mcdtk") diff --git a/demo/demo/scenes/demo.tscn b/demo/demo/scenes/demo.tscn index 031f297..25f54b7 100644 --- a/demo/demo/scenes/demo.tscn +++ b/demo/demo/scenes/demo.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=11 format=3 uid="uid://bsig1usigbbuy"] +[gd_scene load_steps=12 format=3 uid="uid://bsig1usigbbuy"] [ext_resource type="Texture2D" uid="uid://b3g14elmg0m36" path="res://demo/assets/env_rocks.png" id="1_145kx"] [ext_resource type="PackedScene" uid="uid://dt2jlrqffpyw" path="res://demo/scenes/clouds.tscn" id="1_gsxmp"] @@ -6,6 +6,7 @@ [ext_resource type="Texture2D" uid="uid://4kw2ks8doc0w" path="res://demo/assets/env_plants.png" id="2_kesm7"] [ext_resource type="PackedScene" uid="uid://bpd1wmw2f7bvg" path="res://demo/props/gong.tscn" id="3_nbto3"] [ext_resource type="PackedScene" uid="uid://d07ag5dcje13i" path="res://demo/agents/player/player.tscn" id="5_cmgoj"] +[ext_resource type="PackedScene" uid="uid://sjans5psq6pg" path="res://demo/agents/agent_base_enemy.tscn" id="5_knyss"] [ext_resource type="PackedScene" uid="uid://comfxjrcylgb" path="res://demo/agents/agent_melee_simple.tscn" id="7_ruy6b"] [sub_resource type="Animation" id="Animation_gwtgs"] @@ -2875,6 +2876,9 @@ metadata/_edit_lock_ = true [node name="AgentMeleeSimple" parent="YSort/Agents" instance=ExtResource("7_ruy6b")] position = Vector2(1212, 333) +[node name="AgentEnemy" parent="YSort/Agents" instance=ExtResource("5_knyss")] +position = Vector2(1527.4, 690.665) + [node name="Player" parent="YSort/Agents" instance=ExtResource("5_cmgoj")] position = Vector2(633, 256) @@ -4910,5 +4914,6 @@ position = Vector2(1059, -29) metadata/_edit_lock_ = true [node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="Limits"] +visible = false polygon = PackedVector2Array(-814, 535, -380, 190, -306, -21, 87, -46, 346, -330, 870, -373, 1115, -418, 1363, -334, 1613, -324, 1854, -225, 2208, -197, 2675, 309, 2642, 741, 2244, 1140, 1618, 1271, 1360, 1193, 865, 1423, 706, 1377, -113, 1192, -452, 1035, -488, 902, -625, 803, -734, 680, -1660, 719, -1331, 2205, 3170, 2304, 3988, 404, 2999, -1433, 131, -1364, -1715, 264, -1659, 716, -737, 676) metadata/_edit_lock_ = true diff --git a/demo/project.godot b/demo/project.godot index 415634d..de72bcd 100644 --- a/demo/project.godot +++ b/demo/project.godot @@ -42,6 +42,12 @@ move_down={ "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":83,"key_label":0,"unicode":115,"echo":false,"script":null) ] } +attack={ +"deadzone": 0.5, +"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":false,"double_click":false,"script":null) +, 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,"echo":false,"script":null) +] +} [layer_names]