Play ball with the enemy
Enemy can return the ball to the player
This commit is contained in:
parent
332c0fb0e1
commit
e0633efcbe
|
@ -162,4 +162,16 @@ libraries = {
|
||||||
position = Vector2(-0.5, -5)
|
position = Vector2(-0.5, -5)
|
||||||
shape = SubResource("RectangleShape2D_rlijp")
|
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")]
|
[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"]
|
||||||
|
|
|
@ -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/beehave_tree.gd" id="1_b2pc4"]
|
||||||
[ext_resource type="Script" path="res://addons/beehave/nodes/composites/sequence.gd" id="2_80fm4"]
|
[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/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/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/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/can_go_to_ball_condition.gd" id="10_3puvl"]
|
||||||
[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/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/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"]
|
[node name="EnemyBehaviorTree" type="Node"]
|
||||||
script = ExtResource("1_b2pc4")
|
script = ExtResource("1_b2pc4")
|
||||||
|
@ -44,14 +46,26 @@ script = ExtResource("8_y68xp")
|
||||||
[node name="ThrowBall" type="Node" parent="MainSelector/ThrowBallSequence"]
|
[node name="ThrowBall" type="Node" parent="MainSelector/ThrowBallSequence"]
|
||||||
script = ExtResource("8_wytqf")
|
script = ExtResource("8_wytqf")
|
||||||
|
|
||||||
[node name="ReturnBallSequence" type="Node" parent="MainSelector"]
|
[node name="GoToBallSequence" type="Node" parent="MainSelector"]
|
||||||
script = ExtResource("2_80fm4")
|
script = ExtResource("2_80fm4")
|
||||||
|
|
||||||
[node name="IsIdle" type="Node" parent="MainSelector/ReturnBallSequence"]
|
[node name="CanGoToBall" type="Node" parent="MainSelector/GoToBallSequence"]
|
||||||
script = ExtResource("9_vboat")
|
script = ExtResource("10_3puvl")
|
||||||
|
|
||||||
[node name="GetRandomDestination" type="Node" parent="MainSelector/ReturnBallSequence"]
|
[node name="GetBallDestination" type="Node" parent="MainSelector/GoToBallSequence"]
|
||||||
script = ExtResource("10_f4jrw")
|
script = ExtResource("11_bvy6m")
|
||||||
|
|
||||||
[node name="MoveToDestination" type="Node" parent="MainSelector/ReturnBallSequence"]
|
[node name="MoveToDestination" type="Node" parent="MainSelector/GoToBallSequence"]
|
||||||
script = ExtResource("11_tjc85")
|
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")
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
class_name Ball
|
class_name Ball
|
||||||
extends CharacterBody2D
|
extends CharacterBody2D
|
||||||
|
|
||||||
|
signal notify_enemy
|
||||||
const Y_OFFSET = -10
|
const Y_OFFSET = -10
|
||||||
var speed = 100
|
var speed = 100
|
||||||
var target = Vector2.ZERO
|
var target = Vector2.ZERO
|
||||||
|
@ -19,4 +20,7 @@ func _on_player_hit():
|
||||||
var rand_cell: Vector2i = tile_map.get_random_top_cell()
|
var rand_cell: Vector2i = tile_map.get_random_top_cell()
|
||||||
tile_map.reset_and_set_target_cell(rand_cell)
|
tile_map.reset_and_set_target_cell(rand_cell)
|
||||||
target = tile_map.map_to_local(rand_cell) + Vector2(0, Y_OFFSET)
|
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)
|
||||||
|
|
7
scripts/enemy/behavior_tree/can_go_to_ball_condition.gd
Normal file
7
scripts/enemy/behavior_tree/can_go_to_ball_condition.gd
Normal file
|
@ -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
|
12
scripts/enemy/behavior_tree/can_return_ball_condition.gd
Normal file
12
scripts/enemy/behavior_tree/can_return_ball_condition.gd
Normal file
|
@ -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
|
|
@ -1,8 +1,11 @@
|
||||||
class_name CanWaitCodition
|
class_name CanWaitCodition
|
||||||
extends ConditionLeaf
|
extends ConditionLeaf
|
||||||
|
|
||||||
func tick(_actor, _blackboard):
|
func tick(actor, _blackboard):
|
||||||
|
if actor.next_target != null:
|
||||||
|
return FAILURE
|
||||||
|
|
||||||
var num = randi_range(0, 1)
|
var num = randi_range(0, 1)
|
||||||
if num == 0:
|
if num == 1:
|
||||||
return SUCCESS
|
return FAILURE
|
||||||
return FAILURE
|
return SUCCESS
|
||||||
|
|
13
scripts/enemy/behavior_tree/get_ball_destination_action.gd
Normal file
13
scripts/enemy/behavior_tree/get_ball_destination_action.gd
Normal file
|
@ -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
|
|
@ -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
|
|
|
@ -1,5 +0,0 @@
|
||||||
class_name IsIdleCondition
|
|
||||||
extends ConditionLeaf
|
|
||||||
|
|
||||||
func tick(_actor, _blackboard):
|
|
||||||
return SUCCESS
|
|
17
scripts/enemy/behavior_tree/return_ball_action.gd
Normal file
17
scripts/enemy/behavior_tree/return_ball_action.gd
Normal file
|
@ -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")
|
|
@ -1,16 +1,11 @@
|
||||||
class_name ThrowBallAction
|
class_name ThrowBallAction
|
||||||
extends ActionLeaf
|
extends ActionLeaf
|
||||||
|
|
||||||
var is_animation_finished = false
|
|
||||||
|
|
||||||
func before_run(actor, _blackboard):
|
func before_run(actor, _blackboard):
|
||||||
actor.animation_player.animation_finished.connect(_on_animation_finished)
|
actor.play_throw_animation()
|
||||||
actor.animation_player.speed_scale = 0.8
|
|
||||||
|
|
||||||
func tick(actor, blackboard):
|
func tick(actor, blackboard):
|
||||||
actor.animation_player.play("throw")
|
if !actor.is_throw_animation_finished:
|
||||||
|
|
||||||
if !is_animation_finished:
|
|
||||||
return RUNNING
|
return RUNNING
|
||||||
else:
|
else:
|
||||||
var target = blackboard.get_value("target")
|
var target = blackboard.get_value("target")
|
||||||
|
@ -19,8 +14,4 @@ func tick(actor, blackboard):
|
||||||
return SUCCESS
|
return SUCCESS
|
||||||
|
|
||||||
func after_run(actor, _blackboard):
|
func after_run(actor, _blackboard):
|
||||||
actor.animation_player.speed_scale = 1
|
|
||||||
actor.animation_player.play("idle")
|
actor.animation_player.play("idle")
|
||||||
|
|
||||||
func _on_animation_finished(_anim_name):
|
|
||||||
is_animation_finished = true
|
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
class_name Enemy
|
class_name Enemy
|
||||||
extends CharacterBody2D
|
extends CharacterBody2D
|
||||||
|
|
||||||
|
signal go_to_ball
|
||||||
const Y_SPAWN_OFFSET = -8
|
const Y_SPAWN_OFFSET = -8
|
||||||
@export var speed = 80
|
@export var speed = 80
|
||||||
var has_thrown_ball = false
|
var has_thrown_ball = false
|
||||||
var ball_scene = preload("res://scenes/ball.tscn")
|
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 tile_map: TileMap = get_parent()
|
||||||
@onready var animation_player = $AnimationPlayer
|
@onready var animation_player = $AnimationPlayer
|
||||||
@onready var sprite = $Sprite2D
|
@onready var sprite = $Sprite2D
|
||||||
|
@ -13,9 +18,33 @@ func _ready():
|
||||||
var spawn_cell: Vector2i = tile_map.get_top_spawn_cell()
|
var spawn_cell: Vector2i = tile_map.get_top_spawn_cell()
|
||||||
position = tile_map.map_to_local(spawn_cell)
|
position = tile_map.map_to_local(spawn_cell)
|
||||||
position.y += Y_SPAWN_OFFSET
|
position.y += Y_SPAWN_OFFSET
|
||||||
|
animation_player.animation_finished.connect(_on_animation_finished)
|
||||||
|
|
||||||
func throw_ball(target: Vector2):
|
func throw_ball(target: Vector2):
|
||||||
var ball = ball_scene.instantiate()
|
var ball = ball_scene.instantiate()
|
||||||
ball.position = position
|
ball.position = position
|
||||||
ball.target = target
|
ball.target = target
|
||||||
tile_map.add_child(ball)
|
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
|
||||||
|
|
|
@ -15,7 +15,7 @@ func _ready():
|
||||||
position.y += y_spawn_offset
|
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
|
# As player’s Area2D only collide with balls
|
||||||
# We only enter this function after colliding with a ball
|
# We only enter this function after colliding with a ball
|
||||||
collide_with_ball.emit()
|
collide_with_ball.emit()
|
||||||
|
|
|
@ -49,6 +49,16 @@ func get_random_bottom_cell() -> Vector2i:
|
||||||
|
|
||||||
return Vector2i(rand_width, rand_height)
|
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
|
# Debug helper functions
|
||||||
func reset_and_set_target_cell(cell: Vector2i):
|
func reset_and_set_target_cell(cell: Vector2i):
|
||||||
reset_and_set_cell(current_target_cell, cell, target_tile_source_id)
|
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, current_cell, ground_tile_source_id, Vector2i(0, 0), 0)
|
||||||
set_cell(0, cell, tile_source_id, Vector2i(0, 0), 0)
|
set_cell(0, cell, tile_source_id, Vector2i(0, 0), 0)
|
||||||
current_cell = cell
|
current_cell = cell
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue