From dc8aaa60980d66d804308c5835d7a7c46a070f09 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Fri, 2 Feb 2024 16:13:11 +0100 Subject: [PATCH] Demo: Implement skirmisher AI --- demo/demo/agents/agent_skirmisher.tscn | 32 +++- demo/demo/agents/player/player.tscn | 2 - demo/demo/ai/tasks/arrive_pos.gd | 2 +- demo/demo/ai/tasks/back_away.gd | 35 ++++ demo/demo/ai/tasks/in_range.gd | 49 ++++++ demo/demo/ai/tasks/select_flanking_pos.gd | 30 +++- demo/demo/ai/trees/enemy_melee_combo.tres | 1 + demo/demo/ai/trees/enemy_melee_nuanced.tres | 1 + demo/demo/ai/trees/enemy_melee_simple.tres | 2 +- .../demo/ai/trees/enemy_melee_skirmisher.tres | 156 ++++++++++++++++++ demo/demo/ai/trees/enemy_ranged.tres | 1 + 11 files changed, 296 insertions(+), 15 deletions(-) create mode 100644 demo/demo/ai/tasks/back_away.gd create mode 100644 demo/demo/ai/tasks/in_range.gd create mode 100644 demo/demo/ai/trees/enemy_melee_skirmisher.tres diff --git a/demo/demo/agents/agent_skirmisher.tscn b/demo/demo/agents/agent_skirmisher.tscn index 80552ac..e40d0e6 100644 --- a/demo/demo/agents/agent_skirmisher.tscn +++ b/demo/demo/agents/agent_skirmisher.tscn @@ -1,17 +1,35 @@ -[gd_scene load_steps=3 format=3 uid="uid://co6yeafaljbq0"] +[gd_scene load_steps=5 format=3 uid="uid://co6yeafaljbq0"] [ext_resource type="PackedScene" uid="uid://ooigbfhfy4wa" path="res://demo/agents/agent_base.tscn" id="1_2ir76"] [ext_resource type="Texture2D" uid="uid://l042ovqqsy3l" path="res://demo/assets/agent_skirmisher.png" id="2_w8tqw"] +[ext_resource type="BehaviorTree" uid="uid://qqmjvbeibatn" path="res://demo/ai/trees/enemy_melee_skirmisher.tres" id="3_bhfkv"] -[node name="Bobby" instance=ExtResource("1_2ir76")] +[sub_resource type="BlackboardPlan" id="BlackboardPlan_vjbry"] +var/speed/name = "speed" +var/speed/type = 3 +var/speed/value = 400.0 +var/speed/hint = 1 +var/speed/hint_string = "10,1000,10" +var/fast_speed/name = "fast_speed" +var/fast_speed/type = 3 +var/fast_speed/value = 600.0 +var/fast_speed/hint = 1 +var/fast_speed/hint_string = "10,1000,10" +var/slow_speed/name = "slow_speed" +var/slow_speed/type = 3 +var/slow_speed/value = 300.0 +var/slow_speed/hint = 1 +var/slow_speed/hint_string = "10,1000,10" -[node name="LegL" parent="Root/Rig" index="0"] +[node name="AgentSkirmisher" instance=ExtResource("1_2ir76")] + +[node name="LegL" parent="Root/Rig" index="1"] texture = ExtResource("2_w8tqw") -[node name="LegR" parent="Root/Rig" index="1"] +[node name="LegR" parent="Root/Rig" index="2"] texture = ExtResource("2_w8tqw") -[node name="Body" parent="Root/Rig" index="2"] +[node name="Body" parent="Root/Rig" index="3"] texture = ExtResource("2_w8tqw") [node name="Hat" parent="Root/Rig/Body" index="0"] @@ -22,3 +40,7 @@ texture = ExtResource("2_w8tqw") [node name="HandR" parent="Root/Rig/Body" index="2"] texture = ExtResource("2_w8tqw") + +[node name="BTPlayer" type="BTPlayer" parent="." index="4"] +behavior_tree = ExtResource("3_bhfkv") +blackboard_plan = SubResource("BlackboardPlan_vjbry") diff --git a/demo/demo/agents/player/player.tscn b/demo/demo/agents/player/player.tscn index 3ae1d03..638d104 100644 --- a/demo/demo/agents/player/player.tscn +++ b/demo/demo/agents/player/player.tscn @@ -12,8 +12,6 @@ collision_mask = 1 script = ExtResource("2_24nyi") [node name="WeaponNinjaStar" parent="Root" index="2"] -position = Vector2(-55, -76) -rotation = 0.0 scale = Vector2(0.999983, 0.999976) [node name="Hitbox" parent="Root" index="3"] diff --git a/demo/demo/ai/tasks/arrive_pos.gd b/demo/demo/ai/tasks/arrive_pos.gd index 63b7394..f9ce775 100644 --- a/demo/demo/ai/tasks/arrive_pos.gd +++ b/demo/demo/ai/tasks/arrive_pos.gd @@ -13,7 +13,7 @@ extends BTAction ## Arrive to a position, with a bias to horizontal movement. -@export var target_position_var := "target_position" +@export var target_position_var := "pos" @export var speed_var := "speed" @export var tolerance := 50.0 diff --git a/demo/demo/ai/tasks/back_away.gd b/demo/demo/ai/tasks/back_away.gd new file mode 100644 index 0000000..8128cd6 --- /dev/null +++ b/demo/demo/ai/tasks/back_away.gd @@ -0,0 +1,35 @@ +#* +#* back_away.gd +#* ============================================================================= +#* Copyright 2021-2024 Serhii Snitsaruk +#* +#* Use of this source code is governed by an MIT-style +#* license that can be found in the LICENSE file or at +#* https://opensource.org/licenses/MIT. +#* ============================================================================= +#* +@tool +extends BTAction +## BackAway +## Returns RUNNING always. + +@export var speed_var: String = "speed" +@export var max_angle_deviation: float = 0.7 + +var _dir: Vector2 +var _desired_velocity: Vector2 + +# Called each time this task is entered. +func _enter() -> void: + _dir = Vector2.LEFT * agent.get_facing() + var speed: float = blackboard.get_var(speed_var, 200.0) + var rand_angle = randf_range(-max_angle_deviation, max_angle_deviation) + _desired_velocity = _dir.rotated(rand_angle) * speed + + +# Called each time this task is ticked (aka executed). +func _tick(_delta: float) -> Status: + agent.velocity = lerp(agent.velocity, _desired_velocity, 0.2) + agent.move_and_slide() + agent.face_dir(-signf(_dir.x)) + return RUNNING diff --git a/demo/demo/ai/tasks/in_range.gd b/demo/demo/ai/tasks/in_range.gd new file mode 100644 index 0000000..d01a1c5 --- /dev/null +++ b/demo/demo/ai/tasks/in_range.gd @@ -0,0 +1,49 @@ +#* +#* in_range.gd +#* ============================================================================= +#* Copyright 2021-2024 Serhii Snitsaruk +#* +#* Use of this source code is governed by an MIT-style +#* license that can be found in the LICENSE file or at +#* https://opensource.org/licenses/MIT. +#* ============================================================================= +#* +@tool +extends BTCondition + +## InRange condition checks if the agent is within a range of target, +## defined by distance_min and distance_max. +## Returns SUCCESS if the agent is within the defined range; +## otherwise, returns FAILURE. + +@export var distance_min: float +@export var distance_max: float +@export var target_var := "target" + +var _min_distance_squared: float +var _max_distance_squared: float + + +# Called to generate a display name for the task. +func _generate_name() -> String: + return "InRange (%d, %d) of %s" % [distance_min, distance_max, + LimboUtility.decorate_var(target_var)] + + +# Called to initialize the task. +func _setup() -> void: + _min_distance_squared = distance_min * distance_min + _max_distance_squared = distance_max * distance_max + + +# Called when the task is executed. +func _tick(_delta: float) -> Status: + var target: Node2D = blackboard.get_var(target_var, null) + if not is_instance_valid(target): + return FAILURE + + var dist_sq: float = agent.global_position.distance_squared_to(target.global_position) + if dist_sq >= _min_distance_squared and dist_sq <= _max_distance_squared: + return SUCCESS + else: + return FAILURE diff --git a/demo/demo/ai/tasks/select_flanking_pos.gd b/demo/demo/ai/tasks/select_flanking_pos.gd index 021086b..4c6729c 100644 --- a/demo/demo/ai/tasks/select_flanking_pos.gd +++ b/demo/demo/ai/tasks/select_flanking_pos.gd @@ -13,12 +13,21 @@ extends BTAction ## SelectFlankingPos on the side of a target, and return SUCCESS. ## Returns FAILURE, if the target is not valid. +enum AgentSide { + CLOSEST, + FARTHEST, + BACK, + FRONT, +} + ## Blackboard variable that holds current target (should be a Node2D instance). @export var target_var: String = "target" ## Should closest side be selected? @export var closest_side: bool = false +@export var flank_side: AgentSide = AgentSide.CLOSEST + ## Minimum range relative to the target. @export var range_min: int = 300 @@ -26,15 +35,15 @@ extends BTAction @export var range_max: int = 400 ## Blackboard variable that will be used to store selected position. -@export var position_var: String = "flank_pos" +@export var position_var: String = "pos" # Display a customized name (requires @tool). func _generate_name() -> String: - return "SelectFlankingPos target: %s range: [%s, %s] closest: %s ➜%s" % [ + return "SelectFlankingPos target: %s range: [%s, %s] side: %s ➜%s" % [ LimboUtility.decorate_var(target_var), range_min, range_max, - closest_side, + AgentSide.keys()[flank_side], LimboUtility.decorate_var(position_var)] # Called each time this task is ticked (aka executed). @@ -43,10 +52,19 @@ func _tick(_delta: float) -> Status: if not is_instance_valid(target): return FAILURE - var dir: float = signf(target.global_position.x - agent.global_position.x) - if closest_side: - dir *= -1.0 + var dir: float + match flank_side: + AgentSide.FARTHEST: + dir = signf(target.global_position.x - agent.global_position.x) + AgentSide.CLOSEST: + dir = -signf(target.global_position.x - agent.global_position.x) + AgentSide.BACK: + dir = -target.get_facing() + AgentSide.FRONT: + dir = target.get_facing() + var flank_pos: Vector2 = target.global_position flank_pos.x += dir * randf_range(range_min, range_max) blackboard.set_var(position_var, flank_pos) return SUCCESS + diff --git a/demo/demo/ai/trees/enemy_melee_combo.tres b/demo/demo/ai/trees/enemy_melee_combo.tres index f6a2694..99ee626 100644 --- a/demo/demo/ai/trees/enemy_melee_combo.tres +++ b/demo/demo/ai/trees/enemy_melee_combo.tres @@ -39,6 +39,7 @@ children = [SubResource("BTPlayAnimation_qiw21"), SubResource("BTRandomWait_xlud script = ExtResource("2_ifiw3") target_var = "target" closest_side = true +flank_side = 0 range_min = 300 range_max = 400 position_var = "pos" diff --git a/demo/demo/ai/trees/enemy_melee_nuanced.tres b/demo/demo/ai/trees/enemy_melee_nuanced.tres index 45e4223..1038005 100644 --- a/demo/demo/ai/trees/enemy_melee_nuanced.tres +++ b/demo/demo/ai/trees/enemy_melee_nuanced.tres @@ -53,6 +53,7 @@ speed = 1.5 script = ExtResource("2_fl3fr") target_var = "_target" closest_side = false +flank_side = 1 range_min = 400 range_max = 600 position_var = "_flank_pos" diff --git a/demo/demo/ai/trees/enemy_melee_simple.tres b/demo/demo/ai/trees/enemy_melee_simple.tres index 26330b1..38c46d0 100644 --- a/demo/demo/ai/trees/enemy_melee_simple.tres +++ b/demo/demo/ai/trees/enemy_melee_simple.tres @@ -70,7 +70,7 @@ resource_name = "AnimationPlayer" [sub_resource type="BTPlayAnimation" id="BTPlayAnimation_ppmxd"] await_completion = 2.0 animation_player = SubResource("BBNode_s8evu") -animation_name = &"attack_3" +animation_name = &"attack_1" [sub_resource type="BTSequence" id="BTSequence_ww5v2"] custom_name = "Melee attack" diff --git a/demo/demo/ai/trees/enemy_melee_skirmisher.tres b/demo/demo/ai/trees/enemy_melee_skirmisher.tres new file mode 100644 index 0000000..f2999e3 --- /dev/null +++ b/demo/demo/ai/trees/enemy_melee_skirmisher.tres @@ -0,0 +1,156 @@ +[gd_resource type="BehaviorTree" load_steps=36 format=3 uid="uid://qqmjvbeibatn"] + +[ext_resource type="Script" path="res://demo/ai/tasks/get_first_in_group.gd" id="1_oiqfa"] +[ext_resource type="Script" path="res://demo/ai/tasks/select_flanking_pos.gd" id="2_b4wcf"] +[ext_resource type="Script" path="res://demo/ai/tasks/face_target.gd" id="3_hwbvn"] +[ext_resource type="Script" path="res://demo/ai/tasks/arrive_pos.gd" id="3_pn7sq"] +[ext_resource type="Script" path="res://demo/ai/tasks/in_range.gd" id="4_330a2"] +[ext_resource type="Script" path="res://demo/ai/tasks/back_away.gd" id="5_morrs"] + +[sub_resource type="BlackboardPlan" id="BlackboardPlan_46tbn"] +var/speed/name = "speed" +var/speed/type = 3 +var/speed/value = 400.0 +var/speed/hint = 1 +var/speed/hint_string = "10,1000,10" +var/fast_speed/name = "fast_speed" +var/fast_speed/type = 3 +var/fast_speed/value = 600.0 +var/fast_speed/hint = 1 +var/fast_speed/hint_string = "10,1000,10" +var/slow_speed/name = "slow_speed" +var/slow_speed/type = 3 +var/slow_speed/value = 300.0 +var/slow_speed/hint = 1 +var/slow_speed/hint_string = "10,1000,10" + +[sub_resource type="BBNode" id="BBNode_nrd4b"] +saved_value = NodePath("AnimationPlayer") +resource_name = "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 = 1.5 +max_duration = 3.0 + +[sub_resource type="BTAction" id="BTAction_ulbrf"] +script = ExtResource("1_oiqfa") +group = &"player" +output_var = "target" + +[sub_resource type="BTSequence" id="BTSequence_yhjh1"] +custom_name = "Pause before action" +children = [SubResource("BTPlayAnimation_qiw21"), SubResource("BTRandomWait_xlud8"), SubResource("BTAction_ulbrf")] + +[sub_resource type="BBNode" id="BBNode_wpj6d"] +saved_value = NodePath("AnimationPlayer") +resource_name = "AnimationPlayer" + +[sub_resource type="BTPlayAnimation" id="BTPlayAnimation_olf37"] +animation_player = SubResource("BBNode_wpj6d") +animation_name = &"walk" +blend = 0.1 +speed = 1.2 + +[sub_resource type="BTAction" id="BTAction_g5ayy"] +script = ExtResource("2_b4wcf") +target_var = "target" +closest_side = false +flank_side = 2 +range_min = 100 +range_max = 100 +position_var = "flank_pos" + +[sub_resource type="BTAction" id="BTAction_tv4lt"] +script = ExtResource("3_pn7sq") +target_position_var = "flank_pos" +speed_var = "fast_speed" +tolerance = 50.0 + +[sub_resource type="BTSequence" id="BTSequence_rgwic"] +children = [SubResource("BTAction_g5ayy"), SubResource("BTAction_tv4lt")] + +[sub_resource type="BTTimeLimit" id="BTTimeLimit_xek5v"] +time_limit = 4.0 +children = [SubResource("BTSequence_rgwic")] + +[sub_resource type="BTAction" id="BTAction_kidxn"] +script = ExtResource("3_hwbvn") +target_var = "target" + +[sub_resource type="BTCondition" id="BTCondition_uk4dg"] +script = ExtResource("4_330a2") +distance_min = 50.0 +distance_max = 170.0 +target_var = "target" + +[sub_resource type="BTWait" id="BTWait_tadkc"] +duration = 0.1 + +[sub_resource type="BBNode" id="BBNode_s8evu"] +saved_value = NodePath("AnimationPlayer") +resource_name = "AnimationPlayer" + +[sub_resource type="BTPlayAnimation" id="BTPlayAnimation_ppmxd"] +await_completion = 2.0 +animation_player = SubResource("BBNode_s8evu") +animation_name = &"attack_3" + +[sub_resource type="BTSequence" id="BTSequence_ww5v2"] +custom_name = "Melee attack" +children = [SubResource("BTCondition_uk4dg"), SubResource("BTWait_tadkc"), SubResource("BTPlayAnimation_ppmxd")] + +[sub_resource type="BBNode" id="BBNode_wtnf2"] +saved_value = NodePath("AnimationPlayer") +resource_name = "AnimationPlayer" + +[sub_resource type="BTPlayAnimation" id="BTPlayAnimation_k86qc"] +animation_player = SubResource("BBNode_wtnf2") +animation_name = &"idle" +blend = 0.1 + +[sub_resource type="BTRandomWait" id="BTRandomWait_m447o"] +min_duration = 0.3 +max_duration = 0.6 + +[sub_resource type="BTSequence" id="BTSequence_b00uw"] +custom_name = "Short break" +children = [SubResource("BTPlayAnimation_k86qc"), SubResource("BTRandomWait_m447o")] + +[sub_resource type="BBNode" id="BBNode_3iqcf"] +saved_value = NodePath("AnimationPlayer") +resource_name = "AnimationPlayer" + +[sub_resource type="BTPlayAnimation" id="BTPlayAnimation_ee0ff"] +animation_player = SubResource("BBNode_3iqcf") +animation_name = &"walk" +blend = 0.1 +speed = -0.7 + +[sub_resource type="BTAction" id="BTAction_4ye2y"] +script = ExtResource("5_morrs") +speed_var = "slow_speed" +max_angle_deviation = 0.7 + +[sub_resource type="BTTimeLimit" id="BTTimeLimit_cns1i"] +time_limit = 1.5 +children = [SubResource("BTAction_4ye2y")] + +[sub_resource type="BTSequence" id="BTSequence_y12eg"] +custom_name = "Back away" +children = [SubResource("BTPlayAnimation_ee0ff"), SubResource("BTTimeLimit_cns1i")] + +[sub_resource type="BTSequence" id="BTSequence_1xfnq"] +custom_name = "Skirmish" +children = [SubResource("BTPlayAnimation_olf37"), SubResource("BTTimeLimit_xek5v"), SubResource("BTAction_kidxn"), SubResource("BTSequence_ww5v2"), SubResource("BTSequence_b00uw"), SubResource("BTSequence_y12eg")] + +[sub_resource type="BTSequence" id="BTSequence_pxl2k"] +children = [SubResource("BTSequence_yhjh1"), SubResource("BTSequence_1xfnq")] + +[resource] +blackboard_plan = SubResource("BlackboardPlan_46tbn") +root_task = SubResource("BTSequence_pxl2k") diff --git a/demo/demo/ai/trees/enemy_ranged.tres b/demo/demo/ai/trees/enemy_ranged.tres index 017f4ad..8e764f8 100644 --- a/demo/demo/ai/trees/enemy_ranged.tres +++ b/demo/demo/ai/trees/enemy_ranged.tres @@ -75,6 +75,7 @@ children = [SubResource("BTSequence_0gdqn")] script = ExtResource("2_vbh52") target_var = "target" closest_side = true +flank_side = 0 range_min = 400 range_max = 1000 position_var = "shoot_pos"