From bfca1ff23f74d44611a62f1205efb27110876b59 Mon Sep 17 00:00:00 2001 From: Yan Wittmann Date: Thu, 9 Jan 2025 13:47:40 +0100 Subject: [PATCH] Started implementing behaviors --- project/assets/tilemap/tilemaps.aseprite | Bin 9030 -> 9051 bytes project/assets/tilemap/tilemaps/sprite.json | 2 +- .../tilemap/tilemaps/tilemap_temperature.png | Bin 163 -> 201 bytes project/assets/tilemap/tileset.tres | 1 + project/main-scenes/island.tscn | 17 +++- project/scripts/global/GameManager.gd | 2 + project/scripts/player/tree/BehaviorTree.gd | 2 +- project/scripts/player/tree/Task.gd | 94 +++++++++++------- .../player/tree/impl/base/GoToRunning.gd | 26 +++++ .../player/tree/impl/base/GoToSuccess.gd | 26 +++++ .../player/tree/impl/base/TaskSelector.gd | 13 ++- .../TaskCheckFoodCritical.gd | 30 +++++- .../player/tree/impl/testing/WalkToMouse.gd | 8 +- .../player/tree/impl/testing/WalkUpToMouse.gd | 8 +- project/scripts/tilemap/TileMapLayerAccess.gd | 11 +- project/scripts/tilemap/TileMapTileTypes.gd | 1 + project/scripts/tilemap/TilemapNavigation.gd | 22 +++- 17 files changed, 204 insertions(+), 59 deletions(-) create mode 100644 project/scripts/player/tree/impl/base/GoToRunning.gd create mode 100644 project/scripts/player/tree/impl/base/GoToSuccess.gd diff --git a/project/assets/tilemap/tilemaps.aseprite b/project/assets/tilemap/tilemaps.aseprite index 6287ad6b8927025a39e4b9c3a501d53107f84cf3..81f69c68be7d78340ae79b7cc0df0dd894061114 100644 GIT binary patch delta 166 zcmX@+cH50HdLm;z?`4O7*ZG*7`!(2ZkOWE0kV`87=a`k5VKGIBQ5FT0Ay9n zd3#t(Q#t#)TOr0~jG5Sy5s3gkF!oW27fuf2~k^@7ULK2&8B!P+7(6%jZkOUu2C|eD7=a`k2v6phk+e1kvMT1h zJ!~j=KtX`TA!IK@RmhSqwJ4EGbrU&u-kmAS?D6$Y{;G}rvv%g|nihXu-S$q)^b3Eq z%j@Zr7f8Bok~z#K?h7=7Re=deGBEsS22((SdGakKb^Zp1G=((Aq%Z}bl^YlYCJQNh F0ssloFVg@3 diff --git a/project/assets/tilemap/tilemaps/sprite.json b/project/assets/tilemap/tilemaps/sprite.json index 23734c9..d5b7165 100644 --- a/project/assets/tilemap/tilemaps/sprite.json +++ b/project/assets/tilemap/tilemaps/sprite.json @@ -1 +1 @@ -{"width":320,"height":320,"layers":[{"name":"ground","cels":[{"image":"tilemaps\\tilemap_ground.png","frame":0}]},{"name":"objects","cels":[{"image":"tilemaps\\tilemap_objects.png","frame":0}]},{"name":"temperature","cels":[{"image":"tilemaps\\tilemap_temperature.png","frame":0}]},{"name":"player","cels":[{"image":"tilemaps\\tilemap_player.png","frame":0}]}],"frames":[{"duration":0.1}],"filename":"tilemaps.aseprite"} \ No newline at end of file +{"layers":[{"cels":[{"frame":0,"image":"tilemaps\\tilemap_ground.png"}],"name":"ground"},{"cels":[{"frame":0,"image":"tilemaps\\tilemap_objects.png"}],"name":"objects"},{"cels":[{"frame":0,"image":"tilemaps\\tilemap_temperature.png"}],"name":"temperature"},{"cels":[{"frame":0,"image":"tilemaps\\tilemap_player.png"}],"name":"player"}],"height":320,"filename":"tilemaps.aseprite","frames":[{"duration":0.1}],"width":320} \ No newline at end of file diff --git a/project/assets/tilemap/tilemaps/tilemap_temperature.png b/project/assets/tilemap/tilemaps/tilemap_temperature.png index e779996c9bf0f18629957c9322e330ef0c1f818c..868855175e0f7560ecc79b307272a9c0333e54bc 100644 GIT binary patch literal 201 zcmeAS@N?(olHy`uVBq!ia0vp^4nVBH!3HE3&8=$zQWHE~978f1-%eW1+u$I=Qfc92 zsWPLu!ph;}%9kt)S$8>H;t^wXT_Yh>_UGPgAH8K?Uvl*ZO6Gg>JgV)SWb=&cC6nPF6R-d%qjD7%D3Ws*{T13%RgY)!qCfnfyso=f%}35 fgRaA4WPv@gkGYl~c8yX8x{blp)z4*}Q$iB}_-sTV literal 163 zcmeAS@N?(olHy`uVBq!ia0vp^20*O9!3HEluKbn(QdyoZjv*1PZ!a41IXFnLCax}) z>vUUQ&MaD}RVcIcbcU$RBT;9g`2S}AQ%-NPb)GtN^2dGCG;dx%`GcEhfuw_RLmI void: if Input.is_action_just_pressed("key_2"): camera.go_to_zooming(Vector2(789.883972167969, 450.102813720703), 0.56015348434448) if Input.is_action_just_pressed("key_9"): + world.camp_manager.campfire_light() world.camp_manager.sleep_effect() + world.camp_manager.campfire_extinguish() if Input.is_action_just_pressed("force_game_tick"): player.game_tick() diff --git a/project/scripts/player/tree/BehaviorTree.gd b/project/scripts/player/tree/BehaviorTree.gd index e1a7f8d..56945af 100644 --- a/project/scripts/player/tree/BehaviorTree.gd +++ b/project/scripts/player/tree/BehaviorTree.gd @@ -29,4 +29,4 @@ func game_tick() -> void: print("game_tick:") populate_blackboard() behavior_tree.internal_run(blackboard) - print(" ==> [active state=", blackboard["current_task"], "] [status=", behavior_tree.status, "]") + print(" ==> [active state=", blackboard["current_task"], "] [status=", behavior_tree.status, "] [blackboard=", blackboard, "]") diff --git a/project/scripts/player/tree/Task.gd b/project/scripts/player/tree/Task.gd index 8593f24..7ed7455 100644 --- a/project/scripts/player/tree/Task.gd +++ b/project/scripts/player/tree/Task.gd @@ -2,56 +2,82 @@ class_name Task extends Node enum {FAILURE = -1, SUCCESS = 1, RUNNING = 0} -var status: int = FAILURE +var status: int = FAILURE +var status_reason: String = "" var tilemap_types: TileMapTileTypes = TileMapTileTypes.new() 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 + 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 +func internal_run(blackboard: Dictionary) -> void: + 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) - + if status == RUNNING: + var running_child: Task = find_running_child() + if running_child != null: + print(" -> ", human_readable(running_child.name)) + running_child.internal_run(blackboard) + print(" <- ", human_readable(running_child.name)) + status = running_child.status + return + else: + print(" -> ", human_readable()) + run(blackboard) + print(" <- ", human_readable()) + else: + print(" -> ", human_readable()) + run(blackboard) + print(" <- ", human_readable()) func find_running_child() -> Task: - for c in get_children(): - if c.status == RUNNING: - return c - return null + 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_child(blackboard: Dictionary, p_child: Task) -> void: + p_child.internal_run(blackboard) + if p_child.status != RUNNING: + status = RUNNING -func run(p_blackboard: Dictionary) -> void: - pass +func run(blackboard: Dictionary) -> void: + pass -func cancel(p_blackboard: Dictionary): - pass +func cancel(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 + if get_child_count() == 0: + push_error("Task does not have any children: " + name) + return null + return get_children()[0] as Task + + +func human_readable(addon: String = "") -> String: + var clear_status: String = "UNKNOWN" + if status == FAILURE: + clear_status = "FAILURE" + elif status == SUCCESS: + clear_status = "SUCCESS" + elif status == RUNNING: + clear_status = "RUNNING" + + var ret: String = name; + if addon != "": + ret += " " + addon + if status_reason != "": + ret += " [" + clear_status + ", " + status_reason + "]" + else: + ret += " [" + clear_status + "]" + + return ret diff --git a/project/scripts/player/tree/impl/base/GoToRunning.gd b/project/scripts/player/tree/impl/base/GoToRunning.gd new file mode 100644 index 0000000..29c2e85 --- /dev/null +++ b/project/scripts/player/tree/impl/base/GoToRunning.gd @@ -0,0 +1,26 @@ +class_name GoToRunning +extends Task + +func run(blackboard: Dictionary) -> void: + var player: PlayerManager = blackboard["player"] + var navigation: TilemapNavigation = blackboard["navigation"] + + if not blackboard.has("path"): + status = FAILURE + status_reason = "blackboard did not have path" + return + + var path: Array[Vector2i] = blackboard["path"] + if path.size() == 0: + status = FAILURE + status_reason = "path was empty" + return + + if navigation.has_arrived(player.board_position, path): + status = SUCCESS + status_reason = "already arrived at destination" + return + + player.walk_along(path) + status = RUNNING + status_reason = "walked along path, now at " + str(player.board_position) diff --git a/project/scripts/player/tree/impl/base/GoToSuccess.gd b/project/scripts/player/tree/impl/base/GoToSuccess.gd new file mode 100644 index 0000000..221d821 --- /dev/null +++ b/project/scripts/player/tree/impl/base/GoToSuccess.gd @@ -0,0 +1,26 @@ +class_name GoToSuccess +extends Task + +func run(blackboard: Dictionary) -> void: + var player: PlayerManager = blackboard["player"] + var navigation: TilemapNavigation = blackboard["navigation"] + + if not blackboard.has("path"): + status = FAILURE + status_reason = "blackboard did not have path" + return + + var path: Array[Vector2i] = blackboard["path"] + if path.size() == 0: + status = FAILURE + status_reason = "path was empty" + return + + if navigation.has_arrived(player.board_position, path): + status = SUCCESS + status_reason = "already arrived at destination" + return + + player.walk_along(path) + status = SUCCESS + status_reason = "walked along path, now at " + str(player.board_position) diff --git a/project/scripts/player/tree/impl/base/TaskSelector.gd b/project/scripts/player/tree/impl/base/TaskSelector.gd index 5fca651..2cdd883 100644 --- a/project/scripts/player/tree/impl/base/TaskSelector.gd +++ b/project/scripts/player/tree/impl/base/TaskSelector.gd @@ -2,10 +2,9 @@ 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 - + for c in self.get_children(): + run_child(blackboard, c) + if c.status != FAILURE: + status = c.status + return + status = FAILURE diff --git a/project/scripts/player/tree/impl/game/critical_survival/TaskCheckFoodCritical.gd b/project/scripts/player/tree/impl/game/critical_survival/TaskCheckFoodCritical.gd index 3c657d5..9d16af7 100644 --- a/project/scripts/player/tree/impl/game/critical_survival/TaskCheckFoodCritical.gd +++ b/project/scripts/player/tree/impl/game/critical_survival/TaskCheckFoodCritical.gd @@ -1,6 +1,30 @@ class_name TaskCheckFoodCritical extends Task -func run(p_blackboard: Dictionary) -> void: - var world: World = p_blackboard["world"] - var player: PlayerManager = p_blackboard["player"] +func run(blackboard: Dictionary) -> void: + var world: World = blackboard["world"] + var player: PlayerManager = blackboard["player"] + var navigation: TilemapNavigation = blackboard["navigation"] + + var active_foods: Array[Vector2i] = world.tilemap_interactive.get_cells_by_type_collection( + tilemap_types.OBJECT_COLLECTION_BERRY_SOURCE, player.board_position, player.view_distance) + if len(active_foods) == 0: + status = FAILURE + status_reason = "No active foods found" + return + + var closest_berry: Vector2i = navigation.manhattan_distance_closest(active_foods, player.board_position) + if closest_berry == tilemap_types.NO_TILE_FOUND: + status = FAILURE + status_reason = "No closest berry found" + return + + var path: Array[Vector2i] = navigation.find_path_allow_neighbors(player.board_position, closest_berry, player.view_distance) + if path.size() > 0: + blackboard["path"] = path + status_reason = "Found path to closest berry" + status = SUCCESS + return + + status = FAILURE + status_reason = "No path found to closest berry " + str(closest_berry) diff --git a/project/scripts/player/tree/impl/testing/WalkToMouse.gd b/project/scripts/player/tree/impl/testing/WalkToMouse.gd index 9dd44c8..1a466c4 100644 --- a/project/scripts/player/tree/impl/testing/WalkToMouse.gd +++ b/project/scripts/player/tree/impl/testing/WalkToMouse.gd @@ -1,10 +1,10 @@ class_name WalkToMouse extends Task -func run(p_blackboard: Dictionary) -> void: - var world: World = p_blackboard["world"] - var player: PlayerManager = p_blackboard["player"] - var tilemap_navigation: TilemapNavigation = p_blackboard["navigation"] +func run(blackboard: Dictionary) -> void: + var world: World = blackboard["world"] + var player: PlayerManager = blackboard["player"] + var tilemap_navigation: TilemapNavigation = blackboard["navigation"] world.tilemap_temperature.clear_cells() var path: Array[Vector2i] = tilemap_navigation.find_path(player.board_position, world.tilemap_mouse_position(), player.view_distance) diff --git a/project/scripts/player/tree/impl/testing/WalkUpToMouse.gd b/project/scripts/player/tree/impl/testing/WalkUpToMouse.gd index 35f4c56..251db88 100644 --- a/project/scripts/player/tree/impl/testing/WalkUpToMouse.gd +++ b/project/scripts/player/tree/impl/testing/WalkUpToMouse.gd @@ -1,10 +1,10 @@ class_name WalkUpToMouse extends Task -func run(p_blackboard: Dictionary) -> void: - var world: World = p_blackboard["world"] - var player: PlayerManager = p_blackboard["player"] - var tilemap_navigation: TilemapNavigation = p_blackboard["navigation"] +func run(blackboard: Dictionary) -> void: + var world: World = blackboard["world"] + var player: PlayerManager = blackboard["player"] + var tilemap_navigation: TilemapNavigation = blackboard["navigation"] world.tilemap_temperature.clear_cells() var path: Array[Vector2i] = tilemap_navigation.find_path_allow_neighbors(player.board_position, world.tilemap_mouse_position(), player.view_distance) diff --git a/project/scripts/tilemap/TileMapLayerAccess.gd b/project/scripts/tilemap/TileMapLayerAccess.gd index ca1dab2..42f42fa 100644 --- a/project/scripts/tilemap/TileMapLayerAccess.gd +++ b/project/scripts/tilemap/TileMapLayerAccess.gd @@ -13,10 +13,19 @@ func get_cells_by_type(atlas_coords: Vector2i) -> Array[Vector2i]: return tilemap.get_used_cells_by_id(sid, atlas_coords) -func get_cells_by_type_collection(atlas_coords: Array[Vector2i]) -> Array[Vector2i]: +func get_cells_by_type_collection( + atlas_coords: Array[Vector2i], + center: Vector2i = Vector2i(-1, -1), max_distance: int = 99999999 +) -> Array[Vector2i]: var tiles_with_type: Array[Vector2i] = [] for coords in atlas_coords: tiles_with_type.append_array(get_cells_by_type(coords)) + if max_distance < 99999999: + var filtered_tiles: Array[Vector2i] = [] + for tile in tiles_with_type: + if TilemapNavigation.manhattan_distance(center, tile) <= max_distance: + filtered_tiles.append(tile) + return filtered_tiles return tiles_with_type diff --git a/project/scripts/tilemap/TileMapTileTypes.gd b/project/scripts/tilemap/TileMapTileTypes.gd index 43902fc..1b2346a 100644 --- a/project/scripts/tilemap/TileMapTileTypes.gd +++ b/project/scripts/tilemap/TileMapTileTypes.gd @@ -58,6 +58,7 @@ const TEMPERATURE_COLD_1: Vector2i = Vector2i(0, 0) const TEMPERATURE_COLD_2: Vector2i = Vector2i(1, 0) # const NAVIGATION_CHECKED: Vector2i = Vector2i(0, 1) +const NAVIGATION_TARGET: Vector2i = Vector2i(3, 1) const NAVIGATION_CHOSEN: Vector2i = Vector2i(1, 1) const NAVIGATION_FAILED: Vector2i = Vector2i(2, 1) # diff --git a/project/scripts/tilemap/TilemapNavigation.gd b/project/scripts/tilemap/TilemapNavigation.gd index 9e50c2b..5f90687 100644 --- a/project/scripts/tilemap/TilemapNavigation.gd +++ b/project/scripts/tilemap/TilemapNavigation.gd @@ -20,7 +20,6 @@ func game_tick_start() -> void: func game_tick_end() -> void: world.tilemap_nav_vis.clear_cells() - # use tilemap_types.NAVIGATION_CHECKED, tilemap_types.NAVIGATION_FAILED and tilemap_types.NAVIGATION_CHOSEN for path in found_paths.values(): for pos in path: world.tilemap_nav_vis.set_cell(pos, tilemap_types.NAVIGATION_CHECKED) @@ -28,16 +27,29 @@ func game_tick_end() -> void: world.tilemap_nav_vis.set_cell(pos, tilemap_types.NAVIGATION_FAILED) for pos in chosen_path: world.tilemap_nav_vis.set_cell(pos, tilemap_types.NAVIGATION_CHOSEN) + # mark last in chosen path as NAVIGATION_TARGET + if chosen_path.size() > 0: + world.tilemap_nav_vis.set_cell(chosen_path[chosen_path.size() - 1], tilemap_types.NAVIGATION_TARGET) func is_within_radius(position: Vector2i, center: Vector2i, radius: int) -> bool: - return manhattan_distance(position, center) <= radius + return TilemapNavigation.manhattan_distance(position, center) <= radius -func manhattan_distance(a: Vector2i, b: Vector2i) -> int: +static func manhattan_distance(a: Vector2i, b: Vector2i) -> int: return abs(a.x - b.x) + abs(a.y - b.y) +func manhattan_distance_closest(options: Array[Vector2i], target: Vector2i) -> Vector2i: + var closest: Vector2i = tilemap_types.NO_TILE_FOUND + var shortest: int = 9999999999 + for option in options: + var distance: int = manhattan_distance(option, target) + if distance < shortest: + closest = option + shortest = distance + return closest + var walking_directions: Array[Vector2i] = [Vector2i(0, -1), Vector2i(0, 1), Vector2i(-1, 0), Vector2i(1, 0)] var f_score: Dictionary = {} @@ -59,6 +71,10 @@ func find_path_allow_neighbors(start_position: Vector2i, end_position: Vector2i, return [] +func has_arrived(position: Vector2i, path: Array[Vector2i]) -> bool: + return path.size() > 0 and path[path.size() - 1] == position + + func find_path(start_position: Vector2i, end_position: Vector2i, max_radius: int = -1) -> Array[Vector2i]: var path: Array[Vector2i] = _find_path_internal(start_position, end_position, max_radius) if path.size() > 0: