From 9d9f29dbcf28224e9593917bedf34ccb6b0e24a0 Mon Sep 17 00:00:00 2001 From: Yan Wittmann Date: Sat, 4 Jan 2025 15:42:12 +0100 Subject: [PATCH 1/5] Base behavior tree implementation --- doc/planning.md | 4 ++- project/assets/tilemap/tilemaps/sprite.json | 22 +++++++------- project/main-scenes/island.tscn | 10 ++++++- project/project.godot | 5 ++++ project/scripts/global/GameManager.gd | 5 ++-- project/scripts/player/PlayerManager.gd | 29 ++++++++++++++----- project/scripts/player/tree/BehaviorTree.gd | 29 +++++++++++++++++++ project/scripts/player/tree/Task.gd | 20 +++++++++++++ .../player/tree/impl/base/TaskSelector.gd | 13 +++++++++ .../player/tree/impl/base/TaskSequence.gd | 12 ++++++++ .../scripts/player/tree/impl/context/.gitkeep | 0 11 files changed, 126 insertions(+), 23 deletions(-) create mode 100644 project/scripts/player/tree/BehaviorTree.gd create mode 100644 project/scripts/player/tree/Task.gd create mode 100644 project/scripts/player/tree/impl/base/TaskSelector.gd create mode 100644 project/scripts/player/tree/impl/base/TaskSequence.gd create mode 100644 project/scripts/player/tree/impl/context/.gitkeep diff --git a/doc/planning.md b/doc/planning.md index dda4a31..5c80dde 100644 --- a/doc/planning.md +++ b/doc/planning.md @@ -9,6 +9,8 @@ - Temperature layer (normal and different levels of coldness, transparent solid color) - Design a tilemap for the game - Player (Dome) + - As a layer in the tilemap + - Implemented in the PlayerManager - Stats (see document) - Inventory - with one slot @@ -18,7 +20,7 @@ - Use tilemap layers to compute route - Support obstacles - Result must be an array of tile coordinates, length (array length) and the total cost (sum of weights) -- Decision Tree (Classes, etc.) (Luca, Cool in) +- Decision Tree (Classes, etc.) (Yan) - Reference Food Gatherer for implementation - Child of player, serves as controller - Script needs access to the scene, player and other objects/data diff --git a/project/assets/tilemap/tilemaps/sprite.json b/project/assets/tilemap/tilemaps/sprite.json index 8c162bc..c06eee1 100644 --- a/project/assets/tilemap/tilemaps/sprite.json +++ b/project/assets/tilemap/tilemaps/sprite.json @@ -1,48 +1,48 @@ { + "filename": "tilemaps.aseprite", "height": 320, + "width": 320, "layers": [ { + "name": "ground", "cels": [ { "image": "tilemaps\\tilemap_ground.png", "frame": 0 } - ], - "name": "ground" + ] }, { + "name": "objects", "cels": [ { "image": "tilemaps\\tilemap_objects.png", "frame": 0 } - ], - "name": "objects" + ] }, { + "name": "temperature", "cels": [ { "image": "tilemaps\\tilemap_temperature.png", "frame": 0 } - ], - "name": "temperature" + ] }, { + "name": "player", "cels": [ { "image": "tilemaps\\tilemap_player.png", "frame": 0 } - ], - "name": "player" + ] } ], - "width": 320, "frames": [ { "duration": 0.1 } - ], - "filename": "tilemaps.aseprite" + ] } \ No newline at end of file diff --git a/project/main-scenes/island.tscn b/project/main-scenes/island.tscn index ee4505e..56ae467 100644 --- a/project/main-scenes/island.tscn +++ b/project/main-scenes/island.tscn @@ -1,10 +1,12 @@ -[gd_scene load_steps=6 format=4 uid="uid://b88asko1ugyd2"] +[gd_scene load_steps=8 format=4 uid="uid://b88asko1ugyd2"] [ext_resource type="Script" path="res://scripts/global/GameManager.gd" id="1_eeg2d"] [ext_resource type="Script" path="res://scripts/tilemap/World.gd" id="1_k0rw8"] [ext_resource type="TileSet" uid="uid://bi836ygcmyvhb" path="res://assets/tilemap/tileset.tres" id="1_vlccq"] [ext_resource type="Script" path="res://scripts/global/Camera.gd" id="2_1vbjl"] [ext_resource type="Script" path="res://scripts/player/PlayerManager.gd" id="4_1xqo1"] +[ext_resource type="Script" path="res://scripts/player/tree/BehaviorTree.gd" id="6_efs30"] +[ext_resource type="Script" path="res://scripts/player/tree/impl/base/TaskSelector.gd" id="7_1jajd"] [node name="Island-scene" type="Node2D"] script = ExtResource("1_eeg2d") @@ -33,3 +35,9 @@ tile_set = ExtResource("1_vlccq") [node name="PlayerManager" type="Node" parent="."] script = ExtResource("4_1xqo1") + +[node name="BehaviorTree" type="Node" parent="PlayerManager"] +script = ExtResource("6_efs30") + +[node name="sl_Root" type="Node" parent="PlayerManager/BehaviorTree"] +script = ExtResource("7_1jajd") diff --git a/project/project.godot b/project/project.godot index 154e68d..cd658d0 100644 --- a/project/project.godot +++ b/project/project.godot @@ -76,6 +76,11 @@ key_9={ "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":57,"key_label":0,"unicode":57,"location":0,"echo":false,"script":null) ] } +force_game_tick={ +"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":70,"key_label":0,"unicode":102,"location":0,"echo":false,"script":null) +] +} [rendering] diff --git a/project/scripts/global/GameManager.gd b/project/scripts/global/GameManager.gd index 735f63e..cf54b03 100644 --- a/project/scripts/global/GameManager.gd +++ b/project/scripts/global/GameManager.gd @@ -6,8 +6,7 @@ extends Node @onready var camera: Camera2D = $Camera2D func _ready() -> void: - player.world = world - player.camera = camera + player.game_manager = self func _process(delta: float) -> void: if Input.is_action_just_pressed("key_1"): @@ -16,3 +15,5 @@ func _process(delta: float) -> void: camera.go_to_zooming(Vector2(789.883972167969, 450.102813720703), 0.56015348434448) if Input.is_action_just_pressed("key_9"): camera.print_config() + if Input.is_action_just_pressed("force_game_tick"): + player.game_tick() diff --git a/project/scripts/player/PlayerManager.gd b/project/scripts/player/PlayerManager.gd index 495544e..4f1e95d 100644 --- a/project/scripts/player/PlayerManager.gd +++ b/project/scripts/player/PlayerManager.gd @@ -1,19 +1,32 @@ class_name PlayerManager extends Node -var board_position: Vector2 = Vector2(0, 0) -var world: World = null -var camera: Camera2D = null -# var tilemap_types: TileMapTileTypes = TileMapTileTypes.new() +# +var game_manager: GameManager = null +var board_position: Vector2 = Vector2(0, 0) + +@onready var behavior_tree: BehaviorTree = $BehaviorTree + func _ready() -> void: - call_deferred("update_board") + call_deferred("defer_ready") + + +func defer_ready() -> void: + behavior_tree.game_manager = game_manager + update_board() + func _process(delta: float) -> void: if Input.is_action_just_pressed("key_3"): - camera.go_to_zooming(world.tilemap_player.cell_to_local(board_position), 2) + game_manager.camera.go_to_zooming(game_manager.world.tilemap_player.cell_to_local(board_position), 2) + func update_board() -> void: - world.tilemap_player.clear_cells() - world.tilemap_player.set_cell(board_position, tilemap_types.PLAYER) + game_manager.world.tilemap_player.clear_cells() + game_manager.world.tilemap_player.set_cell(board_position, tilemap_types.PLAYER) + + +func game_tick() -> void: + behavior_tree.game_tick() diff --git a/project/scripts/player/tree/BehaviorTree.gd b/project/scripts/player/tree/BehaviorTree.gd new file mode 100644 index 0000000..8164a84 --- /dev/null +++ b/project/scripts/player/tree/BehaviorTree.gd @@ -0,0 +1,29 @@ +class_name BehaviorTree +extends Node + +var game_manager: GameManager = null +# +var blackboard: Dictionary = {} +var behavior_tree: Task = null + + +func _ready() -> void: + if get_child_count() == 0 or get_child_count() > 1: + push_error("This controller needs exactly one Task child, got " + str(get_child_count())) + + var child: Node = get_child(0) + if not (child is Task): + push_error("Child is not a task: " + child.name) + + behavior_tree = child as Task + + +func populate_blackboard(): + blackboard["world"] = game_manager.world + blackboard["player"] = game_manager.player + blackboard["camera"] = game_manager.camera + + +func game_tick() -> void: + populate_blackboard() + behavior_tree.run(blackboard) diff --git a/project/scripts/player/tree/Task.gd b/project/scripts/player/tree/Task.gd new file mode 100644 index 0000000..4603ea9 --- /dev/null +++ b/project/scripts/player/tree/Task.gd @@ -0,0 +1,20 @@ +class_name Task +extends Node + +enum {FAILURE = -1, SUCCESS = 1, RUNNING = 0} +var status: int = FAILURE + + +func run(p_blackboard: Dictionary) -> void: + pass + + +func cancel(p_blackboard: Dictionary): + pass + + +func get_first_child() -> Task: + if get_child_count() == 0: + push_error("Task does not have any children: " + name) + return null + return get_children()[0] as Task diff --git a/project/scripts/player/tree/impl/base/TaskSelector.gd b/project/scripts/player/tree/impl/base/TaskSelector.gd new file mode 100644 index 0000000..0619e00 --- /dev/null +++ b/project/scripts/player/tree/impl/base/TaskSelector.gd @@ -0,0 +1,13 @@ +class_name TaskSelector +extends Task + +func run(blackboard: Dictionary) -> void: + for c in self.get_children(): + if status == RUNNING and not c.status == RUNNING: + continue + c.run(blackboard) + if c.status != FAILURE: + status = c.status + return + status = FAILURE + diff --git a/project/scripts/player/tree/impl/base/TaskSequence.gd b/project/scripts/player/tree/impl/base/TaskSequence.gd new file mode 100644 index 0000000..ea3e1f9 --- /dev/null +++ b/project/scripts/player/tree/impl/base/TaskSequence.gd @@ -0,0 +1,12 @@ +class_name TaskSequence +extends Task + +func run(blackboard: Dictionary) -> void: + for c in self.get_children(): + if status == RUNNING and not c.status == RUNNING: + continue + c.run(blackboard) + if c.status != SUCCESS: + status = c.status + return + status = SUCCESS diff --git a/project/scripts/player/tree/impl/context/.gitkeep b/project/scripts/player/tree/impl/context/.gitkeep new file mode 100644 index 0000000..e69de29 -- 2.43.0 From db933cd62003a41e73399b50c0d8b1bbb7a947f0 Mon Sep 17 00:00:00 2001 From: Yan Wittmann Date: Sat, 4 Jan 2025 16:42:32 +0100 Subject: [PATCH 2/5] Started to introduce RUNNING concept --- project/scripts/player/tree/BehaviorTree.gd | 2 +- project/scripts/player/tree/Task.gd | 15 +++++++++++++++ .../scripts/player/tree/impl/base/TaskSelector.gd | 4 +--- .../scripts/player/tree/impl/base/TaskSequence.gd | 4 +--- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/project/scripts/player/tree/BehaviorTree.gd b/project/scripts/player/tree/BehaviorTree.gd index 8164a84..b565371 100644 --- a/project/scripts/player/tree/BehaviorTree.gd +++ b/project/scripts/player/tree/BehaviorTree.gd @@ -26,4 +26,4 @@ func populate_blackboard(): func game_tick() -> void: populate_blackboard() - behavior_tree.run(blackboard) + behavior_tree.internal_run(blackboard) diff --git a/project/scripts/player/tree/Task.gd b/project/scripts/player/tree/Task.gd index 4603ea9..c45079b 100644 --- a/project/scripts/player/tree/Task.gd +++ b/project/scripts/player/tree/Task.gd @@ -5,6 +5,21 @@ enum {FAILURE = -1, SUCCESS = 1, RUNNING = 0} var status: int = FAILURE +func internal_run(p_blackboard: Dictionary) -> void: + p_blackboard["current_task"] = self + + if status == RUNNING: + for c in self.get_children(): + if not c.status == RUNNING: + continue + c.internal_run(p_blackboard) + if c.status != SUCCESS: + status = c.status + return + + run(p_blackboard) + + func run(p_blackboard: Dictionary) -> void: pass diff --git a/project/scripts/player/tree/impl/base/TaskSelector.gd b/project/scripts/player/tree/impl/base/TaskSelector.gd index 0619e00..a71f8a8 100644 --- a/project/scripts/player/tree/impl/base/TaskSelector.gd +++ b/project/scripts/player/tree/impl/base/TaskSelector.gd @@ -3,9 +3,7 @@ extends Task func run(blackboard: Dictionary) -> void: for c in self.get_children(): - if status == RUNNING and not c.status == RUNNING: - continue - c.run(blackboard) + c.internal_run(blackboard) if c.status != FAILURE: status = c.status return diff --git a/project/scripts/player/tree/impl/base/TaskSequence.gd b/project/scripts/player/tree/impl/base/TaskSequence.gd index ea3e1f9..39f847b 100644 --- a/project/scripts/player/tree/impl/base/TaskSequence.gd +++ b/project/scripts/player/tree/impl/base/TaskSequence.gd @@ -3,9 +3,7 @@ extends Task func run(blackboard: Dictionary) -> void: for c in self.get_children(): - if status == RUNNING and not c.status == RUNNING: - continue - c.run(blackboard) + c.internal_run(blackboard) if c.status != SUCCESS: status = c.status return -- 2.43.0 From e9fba43aec59d3fc6565631df1bf810b18b5893a Mon Sep 17 00:00:00 2001 From: Yan Wittmann Date: Sat, 4 Jan 2025 17:09:43 +0100 Subject: [PATCH 3/5] Added demo for behavior tree --- project/main-scenes/island.tscn | 10 ++++- project/scripts/player/tree/BehaviorTree.gd | 2 + project/scripts/player/tree/Task.gd | 37 +++++++++++++++---- .../player/tree/impl/base/TaskSelector.gd | 2 +- .../player/tree/impl/base/TaskSequence.gd | 2 +- .../tree/impl/context/Task5050Running.gd | 9 +++++ .../tree/impl/context/Task5050Success.gd | 9 +++++ 7 files changed, 60 insertions(+), 11 deletions(-) create mode 100644 project/scripts/player/tree/impl/context/Task5050Running.gd create mode 100644 project/scripts/player/tree/impl/context/Task5050Success.gd diff --git a/project/main-scenes/island.tscn b/project/main-scenes/island.tscn index 56ae467..924b5b4 100644 --- a/project/main-scenes/island.tscn +++ b/project/main-scenes/island.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=8 format=4 uid="uid://b88asko1ugyd2"] +[gd_scene load_steps=10 format=4 uid="uid://b88asko1ugyd2"] [ext_resource type="Script" path="res://scripts/global/GameManager.gd" id="1_eeg2d"] [ext_resource type="Script" path="res://scripts/tilemap/World.gd" id="1_k0rw8"] @@ -7,6 +7,8 @@ [ext_resource type="Script" path="res://scripts/player/PlayerManager.gd" id="4_1xqo1"] [ext_resource type="Script" path="res://scripts/player/tree/BehaviorTree.gd" id="6_efs30"] [ext_resource type="Script" path="res://scripts/player/tree/impl/base/TaskSelector.gd" id="7_1jajd"] +[ext_resource type="Script" path="res://scripts/player/tree/impl/context/Task5050Success.gd" id="8_xrswf"] +[ext_resource type="Script" path="res://scripts/player/tree/impl/context/Task5050Running.gd" id="9_r13cd"] [node name="Island-scene" type="Node2D"] script = ExtResource("1_eeg2d") @@ -41,3 +43,9 @@ script = ExtResource("6_efs30") [node name="sl_Root" type="Node" parent="PlayerManager/BehaviorTree"] script = ExtResource("7_1jajd") + +[node name="5050Success" type="Node" parent="PlayerManager/BehaviorTree/sl_Root"] +script = ExtResource("8_xrswf") + +[node name="5050Running" type="Node" parent="PlayerManager/BehaviorTree/sl_Root"] +script = ExtResource("9_r13cd") diff --git a/project/scripts/player/tree/BehaviorTree.gd b/project/scripts/player/tree/BehaviorTree.gd index b565371..536c007 100644 --- a/project/scripts/player/tree/BehaviorTree.gd +++ b/project/scripts/player/tree/BehaviorTree.gd @@ -25,5 +25,7 @@ func populate_blackboard(): func game_tick() -> void: + print("game_tick:") populate_blackboard() behavior_tree.internal_run(blackboard) + print(" ==> [active state=", blackboard["current_task"], "] [status=", behavior_tree.status, "]") diff --git a/project/scripts/player/tree/Task.gd b/project/scripts/player/tree/Task.gd index c45079b..ee43085 100644 --- a/project/scripts/player/tree/Task.gd +++ b/project/scripts/player/tree/Task.gd @@ -5,19 +5,40 @@ enum {FAILURE = -1, SUCCESS = 1, RUNNING = 0} var status: int = FAILURE +func _ready() -> void: + for c in get_children(): + if not c is Task: + push_error("Child is not a task: " + c.name + " in " + name) + return + + func internal_run(p_blackboard: Dictionary) -> void: p_blackboard["current_task"] = self if status == RUNNING: - for c in self.get_children(): - if not c.status == RUNNING: - continue - c.internal_run(p_blackboard) - if c.status != SUCCESS: - status = c.status - return + var running_child: Task = find_running_child() + if running_child != null: + running_child.internal_run(p_blackboard) + status = running_child.status + return + else: + run(p_blackboard) + else: + run(p_blackboard) + print(" - ", name, " ", status) - run(p_blackboard) + +func find_running_child() -> Task: + for c in get_children(): + if c.status == RUNNING: + return c + return null + + +func run_child(p_blackboard: Dictionary, p_child: Task) -> void: + p_child.internal_run(p_blackboard) + if p_child.status != RUNNING: + status = RUNNING func run(p_blackboard: Dictionary) -> void: diff --git a/project/scripts/player/tree/impl/base/TaskSelector.gd b/project/scripts/player/tree/impl/base/TaskSelector.gd index a71f8a8..07b705e 100644 --- a/project/scripts/player/tree/impl/base/TaskSelector.gd +++ b/project/scripts/player/tree/impl/base/TaskSelector.gd @@ -3,7 +3,7 @@ extends Task func run(blackboard: Dictionary) -> void: for c in self.get_children(): - c.internal_run(blackboard) + run_child(blackboard, c) if c.status != FAILURE: status = c.status return diff --git a/project/scripts/player/tree/impl/base/TaskSequence.gd b/project/scripts/player/tree/impl/base/TaskSequence.gd index 39f847b..f7a7b80 100644 --- a/project/scripts/player/tree/impl/base/TaskSequence.gd +++ b/project/scripts/player/tree/impl/base/TaskSequence.gd @@ -3,7 +3,7 @@ extends Task func run(blackboard: Dictionary) -> void: for c in self.get_children(): - c.internal_run(blackboard) + run_child(blackboard, c) if c.status != SUCCESS: status = c.status return diff --git a/project/scripts/player/tree/impl/context/Task5050Running.gd b/project/scripts/player/tree/impl/context/Task5050Running.gd new file mode 100644 index 0000000..17ca558 --- /dev/null +++ b/project/scripts/player/tree/impl/context/Task5050Running.gd @@ -0,0 +1,9 @@ +class_name Task5050Running +extends Task + +func run(blackboard: Dictionary) -> void: + var random: int = randi() % 2 + if random == 0: + status = RUNNING + else: + status = SUCCESS diff --git a/project/scripts/player/tree/impl/context/Task5050Success.gd b/project/scripts/player/tree/impl/context/Task5050Success.gd new file mode 100644 index 0000000..94ea92b --- /dev/null +++ b/project/scripts/player/tree/impl/context/Task5050Success.gd @@ -0,0 +1,9 @@ +class_name Task5050Success +extends Task + +func run(blackboard: Dictionary) -> void: + var random: int = randi() % 2 + if random == 0: + status = SUCCESS + else: + status = FAILURE -- 2.43.0 From 42b8f56c2ba4caf636337448028e6d55daafc4ab Mon Sep 17 00:00:00 2001 From: Yan Wittmann Date: Sat, 4 Jan 2025 17:12:29 +0100 Subject: [PATCH 4/5] Removed irrelevant .gitkeep --- project/scripts/player/tree/impl/context/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 project/scripts/player/tree/impl/context/.gitkeep diff --git a/project/scripts/player/tree/impl/context/.gitkeep b/project/scripts/player/tree/impl/context/.gitkeep deleted file mode 100644 index e69de29..0000000 -- 2.43.0 From cb9cc1f8b1bee81b874de91c5b8c3e35eaa09acc Mon Sep 17 00:00:00 2001 From: Yan Wittmann Date: Mon, 6 Jan 2025 11:06:17 +0100 Subject: [PATCH 5/5] Minor changes to tree layout --- project/main-scenes/island.tscn | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/project/main-scenes/island.tscn b/project/main-scenes/island.tscn index 924b5b4..e248ef9 100644 --- a/project/main-scenes/island.tscn +++ b/project/main-scenes/island.tscn @@ -44,8 +44,11 @@ script = ExtResource("6_efs30") [node name="sl_Root" type="Node" parent="PlayerManager/BehaviorTree"] script = ExtResource("7_1jajd") -[node name="5050Success" type="Node" parent="PlayerManager/BehaviorTree/sl_Root"] +[node name="Node" type="Node" parent="PlayerManager/BehaviorTree/sl_Root"] +script = ExtResource("7_1jajd") + +[node name="5050Success" type="Node" parent="PlayerManager/BehaviorTree/sl_Root/Node"] script = ExtResource("8_xrswf") -[node name="5050Running" type="Node" parent="PlayerManager/BehaviorTree/sl_Root"] +[node name="5050Running" type="Node" parent="PlayerManager/BehaviorTree/sl_Root/Node"] script = ExtResource("9_r13cd") -- 2.43.0