diff --git a/project.godot b/project.godot index afce394..1ae755c 100644 --- a/project.godot +++ b/project.godot @@ -56,8 +56,15 @@ move_down={ , 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":4194322,"key_label":0,"unicode":0,"echo":false,"script":null) ] } +hit={ +"deadzone": 0.5, +"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":32,"key_label":0,"unicode":32,"echo":false,"script":null) +] +} [layer_names] -2d_physics/layer_1="walls" -2d_physics/layer_2="player" +2d_physics/layer_1="player" +2d_physics/layer_2="enemy" +2d_physics/layer_3="wall" +2d_physics/layer_4="ball" diff --git a/scenes/ball.tscn b/scenes/ball.tscn index 421142c..f7ebddb 100644 --- a/scenes/ball.tscn +++ b/scenes/ball.tscn @@ -1,12 +1,16 @@ -[gd_scene load_steps=3 format=3 uid="uid://cy0ko2cawudia"] +[gd_scene load_steps=4 format=3 uid="uid://cy0ko2cawudia"] +[ext_resource type="Script" path="res://scripts/ball/ball.gd" id="1_8tu7d"] [ext_resource type="Texture2D" uid="uid://dl8qfp3u18nkx" path="res://art/objects/ball.png" id="1_un30v"] [sub_resource type="CircleShape2D" id="CircleShape2D_srlvs"] -radius = 5.0 +radius = 6.0 -[node name="Ball" type="Area2D"] +[node name="Ball" type="CharacterBody2D"] z_index = 1 +collision_layer = 8 +collision_mask = 7 +script = ExtResource("1_8tu7d") [node name="Sprite2D" type="Sprite2D" parent="."] texture = ExtResource("1_un30v") diff --git a/scenes/enemy.tscn b/scenes/enemy.tscn index dbb2760..8908d82 100644 --- a/scenes/enemy.tscn +++ b/scenes/enemy.tscn @@ -143,6 +143,8 @@ _data = { size = Vector2(17, 10) [node name="Enemy" type="CharacterBody2D"] +collision_layer = 2 +collision_mask = 13 script = ExtResource("1_2uhn6") [node name="Sprite2D" type="Sprite2D" parent="."] diff --git a/scenes/enemy_behavior_tree.tscn b/scenes/enemy_behavior_tree.tscn index d7abf79..6dd5392 100644 --- a/scenes/enemy_behavior_tree.tscn +++ b/scenes/enemy_behavior_tree.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=12 format=3 uid="uid://b51tdt5kunai"] +[gd_scene load_steps=13 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"] @@ -8,6 +8,7 @@ [ext_resource type="Script" path="res://scripts/enemy/behavior_tree/wait_action.gd" id="6_eyknc"] [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/move_to_destination_action.gd" id="11_tjc85"] @@ -37,6 +38,9 @@ script = ExtResource("2_80fm4") [node name="CanThrowBall" type="Node" parent="MainSelector/ThrowBallSequence"] script = ExtResource("7_k5qlq") +[node name="GetRandomTarget" type="Node" parent="MainSelector/ThrowBallSequence"] +script = ExtResource("8_y68xp") + [node name="ThrowBall" type="Node" parent="MainSelector/ThrowBallSequence"] script = ExtResource("8_wytqf") diff --git a/scenes/player.tscn b/scenes/player.tscn index 8297e0d..2bdd068 100644 --- a/scenes/player.tscn +++ b/scenes/player.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=16 format=3 uid="uid://ba1t35yyot4jl"] +[gd_scene load_steps=17 format=3 uid="uid://ba1t35yyot4jl"] [ext_resource type="Texture2D" uid="uid://bqkvmyqo86dtj" path="res://art/characters/Owlet_Monster_Walk_6.png" id="1_e23l4"] [ext_resource type="Script" path="res://scripts/player/player.gd" id="1_fmx2p"] @@ -8,6 +8,7 @@ [ext_resource type="Script" path="res://scripts/state_machine/state_machine.gd" id="6_hl63m"] [ext_resource type="Script" path="res://scripts/player/states/player_idle_state.gd" id="7_0nfrc"] [ext_resource type="Script" path="res://scripts/player/states/player_walk_state.gd" id="7_gcd3q"] +[ext_resource type="Script" path="res://scripts/player/states/player_throw_state.gd" id="9_atm3r"] [sub_resource type="Animation" id="Animation_rh7n7"] length = 0.001 @@ -97,7 +98,6 @@ tracks/2/keys = { [sub_resource type="Animation" id="Animation_gswgu"] resource_name = "throw" length = 0.4 -loop_mode = 1 tracks/0/type = "value" tracks/0/imported = false tracks/0/enabled = true @@ -186,14 +186,15 @@ _data = { } [sub_resource type="RectangleShape2D" id="RectangleShape2D_hocsq"] -size = Vector2(21, 7.5) +size = Vector2(21, 10) [node name="Player" type="CharacterBody2D"] +collision_mask = 14 script = ExtResource("1_fmx2p") [node name="Sprite2D" type="Sprite2D" parent="."] position = Vector2(0, -16) -texture = ExtResource("4_orj3a") +texture = ExtResource("5_2s1mg") hframes = 4 [node name="AnimationPlayer" type="AnimationPlayer" parent="."] @@ -203,7 +204,15 @@ libraries = { [node name="CollisionShape2D" type="CollisionShape2D" parent="."] y_sort_enabled = true -position = Vector2(-2.5, -3.75) +position = Vector2(-2.5, -5) +shape = SubResource("RectangleShape2D_hocsq") + +[node name="Area2D" type="Area2D" parent="."] +collision_mask = 8 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"] +y_sort_enabled = true +position = Vector2(-2.5, -5) shape = SubResource("RectangleShape2D_hocsq") [node name="StateMachine" type="Node" parent="." node_paths=PackedStringArray("state")] @@ -215,3 +224,10 @@ script = ExtResource("7_0nfrc") [node name="Walk" type="Node" parent="StateMachine"] script = ExtResource("7_gcd3q") + +[node name="Throw" type="Node" parent="StateMachine"] +script = ExtResource("9_atm3r") + +[connection signal="collide_with_ball" from="." to="StateMachine/Idle" method="_on_player_collide_with_ball"] +[connection signal="animation_finished" from="AnimationPlayer" to="StateMachine/Throw" method="_on_animation_player_animation_finished"] +[connection signal="body_entered" from="Area2D" to="." method="_on_area_2d_body_entered"] diff --git a/scripts/ball/ball.gd b/scripts/ball/ball.gd new file mode 100644 index 0000000..6f425ff --- /dev/null +++ b/scripts/ball/ball.gd @@ -0,0 +1,22 @@ +class_name Ball +extends CharacterBody2D + +const Y_OFFSET = -10 +var speed = 100 +var target = Vector2.ZERO +@onready var tile_map: TileMap = get_parent() + +func _ready(): + target.y += Y_OFFSET + var player = get_node("/root/Main/TileMap/Player") + assert(player) + player.hit.connect(_on_player_hit) + +func _physics_process(delta): + position = position.move_toward(target, delta * speed) + +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) + diff --git a/scripts/enemy/behavior_tree/get_random_destination_action.gd b/scripts/enemy/behavior_tree/get_random_destination_action.gd index 7d10b45..21f0dff 100644 --- a/scripts/enemy/behavior_tree/get_random_destination_action.gd +++ b/scripts/enemy/behavior_tree/get_random_destination_action.gd @@ -3,7 +3,7 @@ extends ActionLeaf func tick(actor: Node, blackboard: Blackboard): var rand_cell: Vector2i = actor.tile_map.get_random_top_cell() - actor.tile_map.set_cell(0, Vector2i(rand_cell.x, rand_cell.y), 2, Vector2i(0, 0), 0) # debug purpose + 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 diff --git a/scripts/enemy/behavior_tree/get_random_target_action.gd b/scripts/enemy/behavior_tree/get_random_target_action.gd new file mode 100644 index 0000000..77b4712 --- /dev/null +++ b/scripts/enemy/behavior_tree/get_random_target_action.gd @@ -0,0 +1,12 @@ +class_name GetRandomTargetAction +extends ActionLeaf + +func tick(actor, blackboard): + var rand_cell: Vector2i = actor.tile_map.get_random_bottom_cell() + actor.tile_map.reset_and_set_target_cell(rand_cell) + + var target = actor.tile_map.map_to_local(rand_cell) + + blackboard.set_value("target", target) + + return SUCCESS diff --git a/scripts/enemy/behavior_tree/move_to_destination_action.gd b/scripts/enemy/behavior_tree/move_to_destination_action.gd index 48d9c69..62d6d6d 100644 --- a/scripts/enemy/behavior_tree/move_to_destination_action.gd +++ b/scripts/enemy/behavior_tree/move_to_destination_action.gd @@ -14,3 +14,9 @@ func tick(actor: Node, blackboard: Blackboard): if actor.position == destination: return SUCCESS return RUNNING + +func after_run(actor, blackboard): + var destination = blackboard.get_value("destination") + destination.y -= actor.Y_SPAWN_OFFSET + var cell = actor.tile_map.local_to_map(destination) + actor.tile_map.set_cell(0, cell, 0, Vector2i(0, 0), 0) # debug purpose diff --git a/scripts/enemy/behavior_tree/throw_ball_action.gd b/scripts/enemy/behavior_tree/throw_ball_action.gd index 868fc15..670115d 100644 --- a/scripts/enemy/behavior_tree/throw_ball_action.gd +++ b/scripts/enemy/behavior_tree/throw_ball_action.gd @@ -1,25 +1,26 @@ class_name ThrowBallAction extends ActionLeaf -var has_animation_finished = false +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 -func tick(actor, _blackboard): +func tick(actor, blackboard): actor.animation_player.play("throw") - if !has_animation_finished: + if !is_animation_finished: return RUNNING else: - actor.throw_ball() + var target = blackboard.get_value("target") + actor.throw_ball(target) actor.has_thrown_ball = true return SUCCESS func after_run(actor, _blackboard): - actor.animation_player.play("idle") actor.animation_player.speed_scale = 1 + actor.animation_player.play("idle") func _on_animation_finished(_anim_name): - has_animation_finished = true + is_animation_finished = true diff --git a/scripts/enemy/enemy.gd b/scripts/enemy/enemy.gd index 2e2867b..a7f6c39 100644 --- a/scripts/enemy/enemy.gd +++ b/scripts/enemy/enemy.gd @@ -14,7 +14,8 @@ func _ready(): position = tile_map.map_to_local(spawn_cell) position.y += Y_SPAWN_OFFSET -func throw_ball(): +func throw_ball(target: Vector2): var ball = ball_scene.instantiate() ball.position = position + ball.target = target tile_map.add_child(ball) diff --git a/scripts/player/player.gd b/scripts/player/player.gd index 6c40b44..a699096 100644 --- a/scripts/player/player.gd +++ b/scripts/player/player.gd @@ -1,6 +1,8 @@ class_name Player extends CharacterBody2D +signal hit +signal collide_with_ball @export var speed = 120 var y_spawn_offset = -8 @onready var animation_player = $AnimationPlayer @@ -11,3 +13,9 @@ func _ready(): var spawn_cell: Vector2i = tile_map.get_bottom_spawn_cell() position = tile_map.map_to_local(spawn_cell) position.y += y_spawn_offset + + +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/player/states/player_idle_state.gd b/scripts/player/states/player_idle_state.gd index 2e86f14..c8f8fbb 100644 --- a/scripts/player/states/player_idle_state.gd +++ b/scripts/player/states/player_idle_state.gd @@ -1,13 +1,23 @@ class_name PlayerIdleState extends PlayerState +var collide_with_ball = false + func enter(_msg := {}): player.velocity = Vector2.ZERO player.animation_player.play("idle") func update(_delta): + if collide_with_ball && Input.is_action_pressed("hit"): + collide_with_ball = false + state_machine.transition_to("Throw") + if get_input_direction() != Vector2.ZERO: state_machine.transition_to("Walk") func get_input_direction(): return Input.get_vector("move_left", "move_right", "move_up", "move_down") + + +func _on_player_collide_with_ball(): + collide_with_ball = true diff --git a/scripts/player/states/player_throw_state.gd b/scripts/player/states/player_throw_state.gd new file mode 100644 index 0000000..2f4723d --- /dev/null +++ b/scripts/player/states/player_throw_state.gd @@ -0,0 +1,20 @@ +class_name PlayerThrowState +extends PlayerState + +var is_animation_finished = false + +func enter(_msg := {}): + player.animation_player.speed_scale = 0.8 + player.animation_player.play("throw") + player.hit.emit() + +func update(_delta: float): + if is_animation_finished: + state_machine.transition_to("Idle") + +func exit(): + is_animation_finished = false + player.animation_player.speed_scale = 1 + +func _on_animation_player_animation_finished(_anim_name): + is_animation_finished = true diff --git a/scripts/player/states/player_walk_state.gd b/scripts/player/states/player_walk_state.gd index dc96beb..c091ea3 100644 --- a/scripts/player/states/player_walk_state.gd +++ b/scripts/player/states/player_walk_state.gd @@ -4,13 +4,15 @@ extends PlayerState func enter(_msg := {}): player.animation_player.play("walk") -func physics_update(_delta): +func physics_update(delta): var direction = get_input_direction() - player.velocity = direction * player.speed + var velocity = direction * player.speed + + var collision = player.move_and_collide(velocity * delta) + if collision and Input.is_action_pressed("hit"): + state_machine.transition_to("Throw") - player.move_and_slide() # This method calculate with delta, we don't need to do it. - - if player.velocity == Vector2.ZERO: + if velocity == Vector2.ZERO: state_machine.transition_to("Idle") func get_input_direction(): diff --git a/scripts/tile_map/tile_map.gd b/scripts/tile_map/tile_map.gd index 815beab..0f1eefb 100644 --- a/scripts/tile_map/tile_map.gd +++ b/scripts/tile_map/tile_map.gd @@ -1,7 +1,12 @@ extends TileMap +@export var ground_tile_source_id = 0 +@export var destination_tile_source_id = 2 +@export var target_tile_source_id = 3 @export var map_width = 13 # keep to a odd value @export var map_height = 19 # keep to a odd value +var current_destination_cell: Vector2i +var current_target_cell: Vector2i func _ready(): draw_map() @@ -34,5 +39,25 @@ func get_random_top_cell() -> Vector2i: var middle_height = floor(map_height / 2.0) var rand_width = randi_range(0, map_width - 1) var rand_height = randi_range(0, middle_height - 1) - #set_cell(0, Vector2i(rand_width, rand_height), 1, Vector2i(0, 0), 0) + return Vector2i(rand_width, rand_height) + +func get_random_bottom_cell() -> Vector2i: + var middle_height = floor(map_height / 2.0) + var rand_width = randi_range(0, map_width - 1) + var rand_height = randi_range(middle_height + 1, map_height - 1) + + return Vector2i(rand_width, rand_height) + +# Debug helper functions +func reset_and_set_target_cell(cell: Vector2i): + reset_and_set_cell(current_target_cell, cell, target_tile_source_id) + +func reset_and_set_destination_cell(cell: Vector2i): + reset_and_set_cell(current_destination_cell, cell, destination_tile_source_id) + +func reset_and_set_cell(current_cell: Vector2i, cell: Vector2i, tile_source_id: int): + if current_cell != null: + 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