Merge pull request 'behavior-tree-implementation' (#2) from behavior-tree-implementation into main

Reviewed-on: #2
pull/3/head
Yan Wittmann 2025-01-06 11:08:13 +01:00
commit 208dfd20d5
12 changed files with 189 additions and 23 deletions

View File

@ -9,6 +9,8 @@
- Temperature layer (normal and different levels of coldness, transparent solid color) - Temperature layer (normal and different levels of coldness, transparent solid color)
- Design a tilemap for the game - Design a tilemap for the game
- Player (Dome) - Player (Dome)
- As a layer in the tilemap
- Implemented in the PlayerManager
- Stats (see document) - Stats (see document)
- Inventory - Inventory
- with one slot - with one slot
@ -18,7 +20,7 @@
- Use tilemap layers to compute route - Use tilemap layers to compute route
- Support obstacles - Support obstacles
- Result must be an array of tile coordinates, length (array length) and the total cost (sum of weights) - 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 - Reference Food Gatherer for implementation
- Child of player, serves as controller - Child of player, serves as controller
- Script needs access to the scene, player and other objects/data - Script needs access to the scene, player and other objects/data

View File

@ -1,48 +1,48 @@
{ {
"filename": "tilemaps.aseprite",
"height": 320, "height": 320,
"width": 320,
"layers": [ "layers": [
{ {
"name": "ground",
"cels": [ "cels": [
{ {
"image": "tilemaps\\tilemap_ground.png", "image": "tilemaps\\tilemap_ground.png",
"frame": 0 "frame": 0
} }
], ]
"name": "ground"
}, },
{ {
"name": "objects",
"cels": [ "cels": [
{ {
"image": "tilemaps\\tilemap_objects.png", "image": "tilemaps\\tilemap_objects.png",
"frame": 0 "frame": 0
} }
], ]
"name": "objects"
}, },
{ {
"name": "temperature",
"cels": [ "cels": [
{ {
"image": "tilemaps\\tilemap_temperature.png", "image": "tilemaps\\tilemap_temperature.png",
"frame": 0 "frame": 0
} }
], ]
"name": "temperature"
}, },
{ {
"name": "player",
"cels": [ "cels": [
{ {
"image": "tilemaps\\tilemap_player.png", "image": "tilemaps\\tilemap_player.png",
"frame": 0 "frame": 0
} }
], ]
"name": "player"
} }
], ],
"width": 320,
"frames": [ "frames": [
{ {
"duration": 0.1 "duration": 0.1
} }
], ]
"filename": "tilemaps.aseprite"
} }

View File

@ -1,10 +1,14 @@
[gd_scene load_steps=6 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/global/GameManager.gd" id="1_eeg2d"]
[ext_resource type="Script" path="res://scripts/tilemap/World.gd" id="1_k0rw8"] [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="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/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/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"] [node name="Island-scene" type="Node2D"]
script = ExtResource("1_eeg2d") script = ExtResource("1_eeg2d")
@ -33,3 +37,18 @@ tile_set = ExtResource("1_vlccq")
[node name="PlayerManager" type="Node" parent="."] [node name="PlayerManager" type="Node" parent="."]
script = ExtResource("4_1xqo1") 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")
[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"]
script = ExtResource("9_r13cd")

View File

@ -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) "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] [rendering]

View File

@ -6,8 +6,7 @@ extends Node
@onready var camera: Camera2D = $Camera2D @onready var camera: Camera2D = $Camera2D
func _ready() -> void: func _ready() -> void:
player.world = world player.game_manager = self
player.camera = camera
func _process(delta: float) -> void: func _process(delta: float) -> void:
if Input.is_action_just_pressed("key_1"): 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) camera.go_to_zooming(Vector2(789.883972167969, 450.102813720703), 0.56015348434448)
if Input.is_action_just_pressed("key_9"): if Input.is_action_just_pressed("key_9"):
camera.print_config() camera.print_config()
if Input.is_action_just_pressed("force_game_tick"):
player.game_tick()

View File

@ -1,19 +1,32 @@
class_name PlayerManager class_name PlayerManager
extends Node extends Node
var board_position: Vector2 = Vector2(0, 0)
var world: World = null
var camera: Camera2D = null
#
var tilemap_types: TileMapTileTypes = TileMapTileTypes.new() 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: 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: func _process(delta: float) -> void:
if Input.is_action_just_pressed("key_3"): 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: func update_board() -> void:
world.tilemap_player.clear_cells() game_manager.world.tilemap_player.clear_cells()
world.tilemap_player.set_cell(board_position, tilemap_types.PLAYER) game_manager.world.tilemap_player.set_cell(board_position, tilemap_types.PLAYER)
func game_tick() -> void:
behavior_tree.game_tick()

View File

@ -0,0 +1,31 @@
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:
print("game_tick:")
populate_blackboard()
behavior_tree.internal_run(blackboard)
print(" ==> [active state=", blackboard["current_task"], "] [status=", behavior_tree.status, "]")

View File

@ -0,0 +1,56 @@
class_name Task
extends Node
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:
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)
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:
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

View File

@ -0,0 +1,11 @@
class_name TaskSelector
extends Task
func run(blackboard: Dictionary) -> void:
for c in self.get_children():
run_child(blackboard, c)
if c.status != FAILURE:
status = c.status
return
status = FAILURE

View File

@ -0,0 +1,10 @@
class_name TaskSequence
extends Task
func run(blackboard: Dictionary) -> void:
for c in self.get_children():
run_child(blackboard, c)
if c.status != SUCCESS:
status = c.status
return
status = SUCCESS

View File

@ -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

View File

@ -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