From e0633efcbe063f48faefbc2472f33b3932adc760 Mon Sep 17 00:00:00 2001 From: Mathilde Grapin Date: Tue, 27 Jun 2023 21:25:32 +0200 Subject: [PATCH] Play ball with the enemy Enemy can return the ball to the player --- scenes/enemy.tscn | 12 +++++++ scenes/enemy_behavior_tree.tscn | 32 +++++++++++++------ scripts/ball/ball.gd | 6 +++- .../behavior_tree/can_go_to_ball_condition.gd | 7 ++++ .../can_return_ball_condition.gd | 12 +++++++ .../enemy/behavior_tree/can_wait_condition.gd | 11 ++++--- .../get_ball_destination_action.gd | 13 ++++++++ .../get_random_destination_action.gd | 13 -------- .../enemy/behavior_tree/is_idle_condition.gd | 5 --- .../enemy/behavior_tree/return_ball_action.gd | 17 ++++++++++ .../enemy/behavior_tree/throw_ball_action.gd | 13 ++------ scripts/enemy/enemy.gd | 29 +++++++++++++++++ scripts/player/player.gd | 2 +- scripts/tile_map/tile_map.gd | 11 +++++++ 14 files changed, 139 insertions(+), 44 deletions(-) create mode 100644 scripts/enemy/behavior_tree/can_go_to_ball_condition.gd create mode 100644 scripts/enemy/behavior_tree/can_return_ball_condition.gd create mode 100644 scripts/enemy/behavior_tree/get_ball_destination_action.gd delete mode 100644 scripts/enemy/behavior_tree/get_random_destination_action.gd delete mode 100644 scripts/enemy/behavior_tree/is_idle_condition.gd create mode 100644 scripts/enemy/behavior_tree/return_ball_action.gd diff --git a/scenes/enemy.tscn b/scenes/enemy.tscn index 8908d82..ca4640a 100644 --- a/scenes/enemy.tscn +++ b/scenes/enemy.tscn @@ -162,4 +162,16 @@ libraries = { position = Vector2(-0.5, -5) shape = SubResource("RectangleShape2D_rlijp") +[node name="Area2D" type="Area2D" parent="."] +collision_layer = 2 +collision_mask = 8 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"] +position = Vector2(-0.5, -5) +shape = SubResource("RectangleShape2D_rlijp") + [node name="EnemyBehaviorTree" parent="." instance=ExtResource("3_jk76t")] + +[connection signal="go_to_ball" from="." to="EnemyBehaviorTree" method="_on_enemy_go_to_ball"] +[connection signal="body_entered" from="Area2D" to="." method="_on_area_2d_body_entered"] +[connection signal="body_exited" from="Area2D" to="." method="_on_area_2d_body_exited"] diff --git a/scenes/enemy_behavior_tree.tscn b/scenes/enemy_behavior_tree.tscn index 6dd5392..3554575 100644 --- a/scenes/enemy_behavior_tree.tscn +++ b/scenes/enemy_behavior_tree.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=13 format=3 uid="uid://b51tdt5kunai"] +[gd_scene load_steps=15 format=3 uid="uid://b51tdt5kunai"] [ext_resource type="Script" path="res://addons/beehave/nodes/beehave_tree.gd" id="1_b2pc4"] [ext_resource type="Script" path="res://addons/beehave/nodes/composites/sequence.gd" id="2_80fm4"] @@ -9,9 +9,11 @@ [ext_resource type="Script" path="res://scripts/enemy/behavior_tree/can_throw_ball_condition.gd" id="7_k5qlq"] [ext_resource type="Script" path="res://scripts/enemy/behavior_tree/throw_ball_action.gd" id="8_wytqf"] [ext_resource type="Script" path="res://scripts/enemy/behavior_tree/get_random_target_action.gd" id="8_y68xp"] -[ext_resource type="Script" path="res://scripts/enemy/behavior_tree/is_idle_condition.gd" id="9_vboat"] -[ext_resource type="Script" path="res://scripts/enemy/behavior_tree/get_random_destination_action.gd" id="10_f4jrw"] +[ext_resource type="Script" path="res://scripts/enemy/behavior_tree/can_go_to_ball_condition.gd" id="10_3puvl"] +[ext_resource type="Script" path="res://scripts/enemy/behavior_tree/get_ball_destination_action.gd" id="11_bvy6m"] [ext_resource type="Script" path="res://scripts/enemy/behavior_tree/move_to_destination_action.gd" id="11_tjc85"] +[ext_resource type="Script" path="res://scripts/enemy/behavior_tree/can_return_ball_condition.gd" id="13_lrd2w"] +[ext_resource type="Script" path="res://scripts/enemy/behavior_tree/return_ball_action.gd" id="14_qbh47"] [node name="EnemyBehaviorTree" type="Node"] script = ExtResource("1_b2pc4") @@ -44,14 +46,26 @@ script = ExtResource("8_y68xp") [node name="ThrowBall" type="Node" parent="MainSelector/ThrowBallSequence"] script = ExtResource("8_wytqf") -[node name="ReturnBallSequence" type="Node" parent="MainSelector"] +[node name="GoToBallSequence" type="Node" parent="MainSelector"] script = ExtResource("2_80fm4") -[node name="IsIdle" type="Node" parent="MainSelector/ReturnBallSequence"] -script = ExtResource("9_vboat") +[node name="CanGoToBall" type="Node" parent="MainSelector/GoToBallSequence"] +script = ExtResource("10_3puvl") -[node name="GetRandomDestination" type="Node" parent="MainSelector/ReturnBallSequence"] -script = ExtResource("10_f4jrw") +[node name="GetBallDestination" type="Node" parent="MainSelector/GoToBallSequence"] +script = ExtResource("11_bvy6m") -[node name="MoveToDestination" type="Node" parent="MainSelector/ReturnBallSequence"] +[node name="MoveToDestination" type="Node" parent="MainSelector/GoToBallSequence"] script = ExtResource("11_tjc85") + +[node name="SequenceComposite" type="Node" parent="MainSelector"] +script = ExtResource("2_80fm4") + +[node name="CanReturnBall" type="Node" parent="MainSelector/SequenceComposite"] +script = ExtResource("13_lrd2w") + +[node name="GetRandomTarget2" type="Node" parent="MainSelector/SequenceComposite"] +script = ExtResource("8_y68xp") + +[node name="ReturnBall" type="Node" parent="MainSelector/SequenceComposite"] +script = ExtResource("14_qbh47") diff --git a/scripts/ball/ball.gd b/scripts/ball/ball.gd index 6f425ff..bdd1208 100644 --- a/scripts/ball/ball.gd +++ b/scripts/ball/ball.gd @@ -1,6 +1,7 @@ class_name Ball extends CharacterBody2D +signal notify_enemy const Y_OFFSET = -10 var speed = 100 var target = Vector2.ZERO @@ -19,4 +20,7 @@ func _on_player_hit(): var rand_cell: Vector2i = tile_map.get_random_top_cell() tile_map.reset_and_set_target_cell(rand_cell) target = tile_map.map_to_local(rand_cell) + Vector2(0, Y_OFFSET) - + notify_enemy.emit() + +func aim_to_bottom() -> bool: + return tile_map.is_in_bottom_area(target) diff --git a/scripts/enemy/behavior_tree/can_go_to_ball_condition.gd b/scripts/enemy/behavior_tree/can_go_to_ball_condition.gd new file mode 100644 index 0000000..2a34c61 --- /dev/null +++ b/scripts/enemy/behavior_tree/can_go_to_ball_condition.gd @@ -0,0 +1,7 @@ +class_name CanGoToBallCondition +extends ConditionLeaf + +func tick(actor, _blackboard): + if actor.next_target != null && actor.has_thrown_ball && !actor.collide_with_ball: + return SUCCESS + return FAILURE diff --git a/scripts/enemy/behavior_tree/can_return_ball_condition.gd b/scripts/enemy/behavior_tree/can_return_ball_condition.gd new file mode 100644 index 0000000..c04632a --- /dev/null +++ b/scripts/enemy/behavior_tree/can_return_ball_condition.gd @@ -0,0 +1,12 @@ +class_name CanReturnBallCondition +extends ConditionLeaf + +func tick(actor, _blackboard): + if ( + actor.has_thrown_ball + && actor.next_target != null + && actor.collide_with_ball + && !actor.current_ball.aim_to_bottom() + ): + return SUCCESS + return FAILURE diff --git a/scripts/enemy/behavior_tree/can_wait_condition.gd b/scripts/enemy/behavior_tree/can_wait_condition.gd index 565cdc7..ac9b8e0 100644 --- a/scripts/enemy/behavior_tree/can_wait_condition.gd +++ b/scripts/enemy/behavior_tree/can_wait_condition.gd @@ -1,8 +1,11 @@ class_name CanWaitCodition extends ConditionLeaf -func tick(_actor, _blackboard): +func tick(actor, _blackboard): + if actor.next_target != null: + return FAILURE + var num = randi_range(0, 1) - if num == 0: - return SUCCESS - return FAILURE + if num == 1: + return FAILURE + return SUCCESS diff --git a/scripts/enemy/behavior_tree/get_ball_destination_action.gd b/scripts/enemy/behavior_tree/get_ball_destination_action.gd new file mode 100644 index 0000000..9937487 --- /dev/null +++ b/scripts/enemy/behavior_tree/get_ball_destination_action.gd @@ -0,0 +1,13 @@ +class_name GetBallDestinationAction +extends ActionLeaf + +func tick(actor: Node, blackboard: Blackboard): +# var rand_cell: Vector2i = actor.tile_map.get_random_top_cell() +# actor.tile_map.reset_and_set_destination_cell(rand_cell) +# +# var destination = actor.tile_map.map_to_local(rand_cell) +# destination.y += actor.Y_SPAWN_OFFSET +# + blackboard.set_value("destination", actor.next_target) + + return SUCCESS diff --git a/scripts/enemy/behavior_tree/get_random_destination_action.gd b/scripts/enemy/behavior_tree/get_random_destination_action.gd deleted file mode 100644 index 21f0dff..0000000 --- a/scripts/enemy/behavior_tree/get_random_destination_action.gd +++ /dev/null @@ -1,13 +0,0 @@ -class_name GetRandomDestinationAction -extends ActionLeaf - -func tick(actor: Node, blackboard: Blackboard): - var rand_cell: Vector2i = actor.tile_map.get_random_top_cell() - actor.tile_map.reset_and_set_destination_cell(rand_cell) - - var destination = actor.tile_map.map_to_local(rand_cell) - destination.y += actor.Y_SPAWN_OFFSET - - blackboard.set_value("destination", destination) - - return SUCCESS diff --git a/scripts/enemy/behavior_tree/is_idle_condition.gd b/scripts/enemy/behavior_tree/is_idle_condition.gd deleted file mode 100644 index ea20247..0000000 --- a/scripts/enemy/behavior_tree/is_idle_condition.gd +++ /dev/null @@ -1,5 +0,0 @@ -class_name IsIdleCondition -extends ConditionLeaf - -func tick(_actor, _blackboard): - return SUCCESS diff --git a/scripts/enemy/behavior_tree/return_ball_action.gd b/scripts/enemy/behavior_tree/return_ball_action.gd new file mode 100644 index 0000000..1d883e4 --- /dev/null +++ b/scripts/enemy/behavior_tree/return_ball_action.gd @@ -0,0 +1,17 @@ +class_name ReturnBallAction +extends ActionLeaf + +func before_run(actor, _blackboard): + actor.play_throw_animation() + +func tick(actor, blackboard): + if !actor.is_throw_animation_finished: + return RUNNING + else: + print("enemy return ball!") + var target = blackboard.get_value("target") + actor.return_ball(target) + return SUCCESS + +func after_run(actor, _blackboard): + actor.animation_player.play("idle") diff --git a/scripts/enemy/behavior_tree/throw_ball_action.gd b/scripts/enemy/behavior_tree/throw_ball_action.gd index 670115d..fb62b87 100644 --- a/scripts/enemy/behavior_tree/throw_ball_action.gd +++ b/scripts/enemy/behavior_tree/throw_ball_action.gd @@ -1,16 +1,11 @@ class_name ThrowBallAction extends ActionLeaf -var is_animation_finished = false - func before_run(actor, _blackboard): - actor.animation_player.animation_finished.connect(_on_animation_finished) - actor.animation_player.speed_scale = 0.8 + actor.play_throw_animation() func tick(actor, blackboard): - actor.animation_player.play("throw") - - if !is_animation_finished: + if !actor.is_throw_animation_finished: return RUNNING else: var target = blackboard.get_value("target") @@ -19,8 +14,4 @@ func tick(actor, blackboard): return SUCCESS func after_run(actor, _blackboard): - actor.animation_player.speed_scale = 1 actor.animation_player.play("idle") - -func _on_animation_finished(_anim_name): - is_animation_finished = true diff --git a/scripts/enemy/enemy.gd b/scripts/enemy/enemy.gd index a7f6c39..4336ceb 100644 --- a/scripts/enemy/enemy.gd +++ b/scripts/enemy/enemy.gd @@ -1,10 +1,15 @@ class_name Enemy extends CharacterBody2D +signal go_to_ball const Y_SPAWN_OFFSET = -8 @export var speed = 80 var has_thrown_ball = false var ball_scene = preload("res://scenes/ball.tscn") +var current_ball: Ball +var next_target +var collide_with_ball = false +var is_throw_animation_finished = false @onready var tile_map: TileMap = get_parent() @onready var animation_player = $AnimationPlayer @onready var sprite = $Sprite2D @@ -13,9 +18,33 @@ func _ready(): var spawn_cell: Vector2i = tile_map.get_top_spawn_cell() position = tile_map.map_to_local(spawn_cell) position.y += Y_SPAWN_OFFSET + animation_player.animation_finished.connect(_on_animation_finished) func throw_ball(target: Vector2): var ball = ball_scene.instantiate() ball.position = position ball.target = target tile_map.add_child(ball) + current_ball = ball + current_ball.notify_enemy.connect(_on_notify_enemy) + +func play_throw_animation(): + is_throw_animation_finished = false + animation_player.speed_scale = 0.8 + animation_player.play("throw") + animation_player.speed_scale = 1 + +func return_ball(target: Vector2): + current_ball.target = target + +func _on_animation_finished(_anim_name): + is_throw_animation_finished = true + +func _on_notify_enemy(): + next_target = current_ball.target + +func _on_area_2d_body_entered(_body): + collide_with_ball = true + +func _on_area_2d_body_exited(_body): + collide_with_ball = false diff --git a/scripts/player/player.gd b/scripts/player/player.gd index a699096..7fa2cc0 100644 --- a/scripts/player/player.gd +++ b/scripts/player/player.gd @@ -15,7 +15,7 @@ func _ready(): position.y += y_spawn_offset -func _on_area_2d_body_entered(body: Node2D): +func _on_area_2d_body_entered(_body: Node2D): # As player’s Area2D only collide with balls # We only enter this function after colliding with a ball collide_with_ball.emit() diff --git a/scripts/tile_map/tile_map.gd b/scripts/tile_map/tile_map.gd index 0f1eefb..1f589c0 100644 --- a/scripts/tile_map/tile_map.gd +++ b/scripts/tile_map/tile_map.gd @@ -49,6 +49,16 @@ func get_random_bottom_cell() -> Vector2i: return Vector2i(rand_width, rand_height) +func is_in_bottom_area(local_position: Vector2) -> bool: + var map_position = local_to_map(local_position) + + var middle_height = floor(map_height / 2.0) + var bottom_min_height = middle_height + 1 + var bottom_max_height = map_height - 1 + + return map_position.y >= bottom_min_height && map_position.y <= map_height + + # Debug helper functions func reset_and_set_target_cell(cell: Vector2i): reset_and_set_cell(current_target_cell, cell, target_tile_source_id) @@ -61,3 +71,4 @@ func reset_and_set_cell(current_cell: Vector2i, cell: Vector2i, tile_source_id: set_cell(0, current_cell, ground_tile_source_id, Vector2i(0, 0), 0) set_cell(0, cell, tile_source_id, Vector2i(0, 0), 0) current_cell = cell +