From f6ce51677151960ec8c4473c5830419f9f80ddee Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Thu, 1 Feb 2024 15:45:11 +0100 Subject: [PATCH] Demo: Improved AI for melee and ranged --- demo/demo/ai/tasks/face_target.gd | 2 +- demo/demo/ai/tasks/pursue.gd | 49 +++++++++----- demo/demo/ai/tasks/select_flanking_pos.gd | 2 +- .../demo/ai/tasks/select_random_nearby_pos.gd | 23 +++++++ demo/demo/ai/trees/enemy_melee_nuanced.tres | 16 +++-- demo/demo/ai/trees/enemy_ranged.tres | 64 ++++++++++++++++--- 6 files changed, 121 insertions(+), 35 deletions(-) create mode 100644 demo/demo/ai/tasks/select_random_nearby_pos.gd diff --git a/demo/demo/ai/tasks/face_target.gd b/demo/demo/ai/tasks/face_target.gd index 115a9e3..04fddfe 100644 --- a/demo/demo/ai/tasks/face_target.gd +++ b/demo/demo/ai/tasks/face_target.gd @@ -21,7 +21,7 @@ func _generate_name() -> String: # Called each time this task is ticked (aka executed). func _tick(_delta: float) -> Status: - var target := blackboard.get_var(target_var) as CharacterBody2D + var target: Node2D = blackboard.get_var(target_var) if not is_instance_valid(target): return FAILURE var dir: float = target.global_position.x - agent.global_position.x diff --git a/demo/demo/ai/tasks/pursue.gd b/demo/demo/ai/tasks/pursue.gd index 65d0b84..ede859f 100644 --- a/demo/demo/ai/tasks/pursue.gd +++ b/demo/demo/ai/tasks/pursue.gd @@ -10,11 +10,11 @@ #* @tool extends BTAction -## Pursue target. +## Pursue: Move towards target until agent is flanking it. ## -## Returns RUNNING, while moving towards target but not yet at the desired distance. -## Returns SUCCESS, when at the desired distance from target. -## Returns FAILURE, if target is not a valid instance. +## Returns RUNNING, while moving towards target but not yet at the desired position. +## Returns SUCCESS, when at the desired position from target (flanking it). +## Returns FAILURE, if target is not a valid Node2D instance. const TOLERANCE := 30.0 @@ -23,33 +23,48 @@ const TOLERANCE := 30.0 @export var speed_var: String = "speed" @export var approach_distance: float = 100.0 -var _side: float +#var _side: float +var _waypoint: Vector2 # Display a customized name (requires @tool). func _generate_name() -> String: return "Pursue %s" % [LimboUtility.decorate_var(target_var)] -# Called each time this task is entered. + func _enter() -> void: - _side = 0.0 + var target: Node2D = blackboard.get_var(target_var, null) + if is_instance_valid(target): + _select_new_waypoint(_get_desired_position(target)) + # Called each time this task is ticked (aka executed). func _tick(_delta: float) -> Status: - var target := blackboard.get_var(target_var, null) as CharacterBody2D + var target: Node2D = blackboard.get_var(target_var, null) if not is_instance_valid(target): return FAILURE - if _side == 0: - var dir: Vector2 = agent.global_position - target.global_position - _side = signf(dir.x) - var target_pos := Vector2( - target.global_position.x + approach_distance * _side, - target.global_position.y) - - if agent.global_position.distance_to(target_pos) < TOLERANCE: + var desired_pos: Vector2 = _get_desired_position(target) + if agent.global_position.distance_to(desired_pos) < TOLERANCE: return SUCCESS var speed: float = blackboard.get_var(speed_var, 200.0) - agent.velocity = agent.global_position.direction_to(target_pos) * speed + if agent.global_position.distance_to(_waypoint) < TOLERANCE: + _select_new_waypoint(desired_pos) + agent.velocity = agent.global_position.direction_to(_waypoint) * speed agent.move_and_slide() return RUNNING + + +## Get closest flanking position to target. +func _get_desired_position(target: Node2D) -> Vector2: + var side: float = signf(agent.global_position.x - target.global_position.x) + var desired_pos: Vector2 = target.global_position + desired_pos.x += approach_distance * side + return desired_pos + + +## Select an intermidiate waypoint towards the desired position. +func _select_new_waypoint(desired_position: Vector2) -> void: + var distance_vector: Vector2 = desired_position - agent.global_position + var angle_variation: float = randf_range(-0.2, 0.2) + _waypoint = agent.global_position + distance_vector.limit_length(150.0).rotated(angle_variation) diff --git a/demo/demo/ai/tasks/select_flanking_pos.gd b/demo/demo/ai/tasks/select_flanking_pos.gd index 37186a6..f0ed70e 100644 --- a/demo/demo/ai/tasks/select_flanking_pos.gd +++ b/demo/demo/ai/tasks/select_flanking_pos.gd @@ -30,7 +30,7 @@ extends BTAction # 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] closest: %s ➜%s" % [ LimboUtility.decorate_var(target_var), range_min, range_max, diff --git a/demo/demo/ai/tasks/select_random_nearby_pos.gd b/demo/demo/ai/tasks/select_random_nearby_pos.gd new file mode 100644 index 0000000..ab2d195 --- /dev/null +++ b/demo/demo/ai/tasks/select_random_nearby_pos.gd @@ -0,0 +1,23 @@ +@tool +extends BTAction +## SelectRandomNearbyPos: Select a position nearby within specified range. +## Returns SUCCESS. + +## Maximum distance to the desired position. +@export var range_min: float = 300.0 +@export var range_max: float = 500.0 +@export var position_var: String = "_pos" + +# Display a customized name (requires @tool). +func _generate_name() -> String: + return "SelectRandomNearbyPos range: [%s, %s] ➜%s" % [ + range_min, range_max, + LimboUtility.decorate_var(position_var)] + +# Called each time this task is ticked (aka executed). +func _tick(_delta: float) -> Status: + var angle: float = randf() * TAU + var rand_distance: float = randf_range(range_min, range_max) + var pos: Vector2 = agent.global_position + Vector2(sin(angle), cos(angle)) * rand_distance + blackboard.set_var(position_var, pos) + return SUCCESS diff --git a/demo/demo/ai/trees/enemy_melee_nuanced.tres b/demo/demo/ai/trees/enemy_melee_nuanced.tres index def8f9d..45e4223 100644 --- a/demo/demo/ai/trees/enemy_melee_nuanced.tres +++ b/demo/demo/ai/trees/enemy_melee_nuanced.tres @@ -1,4 +1,4 @@ -[gd_resource type="BehaviorTree" load_steps=40 format=3 uid="uid://c2u6sljqkim0n"] +[gd_resource type="BehaviorTree" load_steps=41 format=3 uid="uid://c2u6sljqkim0n"] [ext_resource type="Script" path="res://demo/ai/tasks/get_first_in_group.gd" id="1_uvue5"] [ext_resource type="Script" path="res://demo/ai/tasks/pursue.gd" id="2_aanv5"] @@ -28,8 +28,7 @@ animation_name = &"idle" blend = 0.1 [sub_resource type="BTRandomWait" id="BTRandomWait_xlud8"] -min_duration = 0.7 -max_duration = 1.5 +max_duration = 3.0 [sub_resource type="BTAction" id="BTAction_c4cxo"] script = ExtResource("1_uvue5") @@ -54,8 +53,8 @@ speed = 1.5 script = ExtResource("2_fl3fr") target_var = "_target" closest_side = false -range_min = 400.0 -range_max = 600.0 +range_min = 400 +range_max = 600 position_var = "_flank_pos" [sub_resource type="BTAction" id="BTAction_66hsk"] @@ -133,8 +132,10 @@ resource_name = "AnimationPlayer" animation_player = SubResource("BBNode_s6vt4") animation_name = &"throw_prepare" blend = 0.1 +speed = 0.7 [sub_resource type="BTWait" id="BTWait_gbcyb"] +duration = 1.3 [sub_resource type="BBNode" id="BBNode_qkfqt"] saved_value = NodePath("AnimationPlayer") @@ -154,9 +155,12 @@ resource_name = "." node = SubResource("BBNode_1yxc5") method = &"throw_ninja_star" +[sub_resource type="BTRandomWait" id="BTRandomWait_2pmoe"] +min_duration = 1.5 + [sub_resource type="BTSequence" id="BTSequence_rgbq3"] custom_name = "Throw ninja star" -children = [SubResource("BTAction_8q20y"), SubResource("BTPlayAnimation_qa8jy"), SubResource("BTWait_gbcyb"), SubResource("BTPlayAnimation_0ktds"), SubResource("BTCallMethod_yx4fk")] +children = [SubResource("BTAction_8q20y"), SubResource("BTPlayAnimation_qa8jy"), SubResource("BTWait_gbcyb"), SubResource("BTPlayAnimation_0ktds"), SubResource("BTCallMethod_yx4fk"), SubResource("BTRandomWait_2pmoe")] metadata/_weight_ = 1.0 [sub_resource type="BTProbabilitySelector" id="BTProbabilitySelector_rjsiq"] diff --git a/demo/demo/ai/trees/enemy_ranged.tres b/demo/demo/ai/trees/enemy_ranged.tres index 9d82ecf..017f4ad 100644 --- a/demo/demo/ai/trees/enemy_ranged.tres +++ b/demo/demo/ai/trees/enemy_ranged.tres @@ -1,9 +1,10 @@ -[gd_resource type="BehaviorTree" load_steps=29 format=3 uid="uid://cqluon1y1hnn5"] +[gd_resource type="BehaviorTree" load_steps=40 format=3 uid="uid://cqluon1y1hnn5"] [ext_resource type="Script" path="res://demo/ai/tasks/get_first_in_group.gd" id="1_3j1v8"] [ext_resource type="Script" path="res://demo/ai/tasks/select_flanking_pos.gd" id="2_vbh52"] [ext_resource type="Script" path="res://demo/ai/tasks/arrive_pos.gd" id="3_20ffh"] [ext_resource type="Script" path="res://demo/ai/tasks/face_target.gd" id="4_x8yor"] +[ext_resource type="Script" path="res://demo/ai/tasks/select_random_nearby_pos.gd" id="5_mexvv"] [sub_resource type="BlackboardPlan" id="BlackboardPlan_46tbn"] var/speed/name = "speed" @@ -28,19 +29,55 @@ max_duration = 2.5 [sub_resource type="BTAction" id="BTAction_c4cxo"] script = ExtResource("1_3j1v8") group = &"player" -output_var = "_target" +output_var = "target" [sub_resource type="BTSequence" id="BTSequence_yhjh1"] custom_name = "Take a break" children = [SubResource("BTPlayAnimation_qiw21"), SubResource("BTRandomWait_xlud8"), SubResource("BTAction_c4cxo")] +[sub_resource type="BTComment" id="BTComment_qhsko"] +custom_name = "He is bored attacking player all the time ;)" + +[sub_resource type="BTAction" id="BTAction_lpk67"] +script = ExtResource("5_mexvv") +range_min = 300.0 +range_max = 500.0 +position_var = "pos" + +[sub_resource type="BBNode" id="BBNode_ok0r5"] +saved_value = NodePath("AnimationPlayer") +resource_name = "AnimationPlayer" + +[sub_resource type="BTPlayAnimation" id="BTPlayAnimation_unftu"] +animation_player = SubResource("BBNode_ok0r5") +animation_name = &"walk" +blend = 0.1 + +[sub_resource type="BTAction" id="BTAction_apfsn"] +script = ExtResource("3_20ffh") +target_position_var = "pos" +speed_var = "speed" +tolerance = 50.0 + +[sub_resource type="BTTimeLimit" id="BTTimeLimit_baob7"] +time_limit = 3.0 +children = [SubResource("BTAction_apfsn")] + +[sub_resource type="BTSequence" id="BTSequence_0gdqn"] +custom_name = "Random Walk" +children = [SubResource("BTComment_qhsko"), SubResource("BTAction_lpk67"), SubResource("BTPlayAnimation_unftu"), SubResource("BTTimeLimit_baob7")] + +[sub_resource type="BTProbability" id="BTProbability_sat88"] +run_chance = 0.3 +children = [SubResource("BTSequence_0gdqn")] + [sub_resource type="BTAction" id="BTAction_kuuw2"] script = ExtResource("2_vbh52") -target_var = "_target" +target_var = "target" closest_side = true -range_min = 400.0 -range_max = 1000.0 -position_var = "_shoot_pos" +range_min = 400 +range_max = 1000 +position_var = "shoot_pos" [sub_resource type="BBNode" id="BBNode_kc64r"] saved_value = NodePath("AnimationPlayer") @@ -53,7 +90,7 @@ blend = 0.1 [sub_resource type="BTAction" id="BTAction_66hsk"] script = ExtResource("3_20ffh") -target_position_var = "_shoot_pos" +target_position_var = "shoot_pos" speed_var = "speed" tolerance = 50.0 @@ -62,10 +99,10 @@ children = [SubResource("BTAction_66hsk")] [sub_resource type="BTAction" id="BTAction_enw2m"] script = ExtResource("4_x8yor") -target_var = "_target" +target_var = "target" [sub_resource type="BTSequence" id="BTSequence_lhg7f"] -custom_name = "Reposition" +custom_name = "Get into position" children = [SubResource("BTAction_kuuw2"), SubResource("BTPlayAnimation_panch"), SubResource("BTTimeLimit_24ath"), SubResource("BTAction_enw2m")] metadata/_weight_ = 1.0 @@ -110,9 +147,16 @@ metadata/_weight_ = 1.0 times = 3 children = [SubResource("BTSequence_rgbq3")] +[sub_resource type="BTSequence" id="BTSequence_h2tm0"] +custom_name = "Align and shoot" +children = [SubResource("BTSequence_lhg7f"), SubResource("BTRepeat_g08ia")] + +[sub_resource type="BTSelector" id="BTSelector_1rrya"] +children = [SubResource("BTProbability_sat88"), SubResource("BTSequence_h2tm0")] + [sub_resource type="BTSequence" id="BTSequence_pxl2k"] custom_name = "Main" -children = [SubResource("BTSequence_yhjh1"), SubResource("BTSequence_lhg7f"), SubResource("BTRepeat_g08ia")] +children = [SubResource("BTSequence_yhjh1"), SubResource("BTSelector_1rrya")] [resource] blackboard_plan = SubResource("BlackboardPlan_46tbn")