Added graphedit node highlighting and scrolling

pull/5/head
Yan Wittmann 2025-01-13 19:30:46 +01:00
parent fcbd269f68
commit b187b96507
7 changed files with 177 additions and 124 deletions

View File

@ -216,8 +216,8 @@ size_flags_vertical = 8
[node name="TreeVisualizer" type="Window" parent="Camera2D/CanvasLayer"]
unique_name_in_owner = true
position = Vector2i(0, 36)
size = Vector2i(1075, 225)
position = Vector2i(520, 41)
size = Vector2i(615, 595)
script = ExtResource("5_ecfvx")
[node name="GraphEdit" type="GraphEdit" parent="Camera2D/CanvasLayer/TreeVisualizer"]
@ -229,6 +229,7 @@ grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
scroll_offset = Vector2(-77, -25)
show_menu = false
show_zoom_buttons = false
show_grid_buttons = false

View File

@ -24,157 +24,159 @@ var waiting_for_input: bool = true
func _ready() -> void:
tilemap_navigation.world = world
tilemap_navigation.player = player
player.game_manager = self
world.camp_manager.game_manager = self
world.step_visualizer.game_manager = self
world.step_visualizer.world = world
update_bars()
call_deferred("defer_ready")
tilemap_navigation.world = world
tilemap_navigation.player = player
player.game_manager = self
world.camp_manager.game_manager = self
world.step_visualizer.game_manager = self
world.step_visualizer.world = world
update_bars()
call_deferred("defer_ready")
func defer_ready() -> void:
tree_visualizer.behavior_tree = player.behavior_tree
tree_visualizer.build_tree()
tree_visualizer.behavior_tree = player.behavior_tree
tree_visualizer.build_tree()
intro_image.visible = true
await wait_for_key_press()
get_tree().create_tween().tween_method(set_intro_opacity, 1.0, 0.0, 1.0)
intro_image.visible = true
await wait_for_key_press()
get_tree().create_tween().tween_method(set_intro_opacity, 1.0, 0.0, 1.0)
# game_ticker.start()
func _process(delta: float) -> void:
if Input.is_action_just_pressed("force_game_tick"):
Task.print_behavior_tree_evaluation = true
_on_game_tick_timeout()
Task.print_behavior_tree_evaluation = false
if Input.is_action_pressed("force_game_tick_fast"):
_on_game_tick_timeout()
if Input.is_action_just_pressed("key_2"):
toggle_temperature_layer()
camera.print_config()
if Input.is_action_just_pressed("auto_tick"):
if game_ticker.is_stopped():
game_ticker.start()
else:
game_ticker.stop()
if Input.is_action_just_pressed("force_game_tick"):
Task.print_behavior_tree_evaluation = true
_on_game_tick_timeout()
Task.print_behavior_tree_evaluation = false
if Input.is_action_pressed("force_game_tick_fast"):
_on_game_tick_timeout()
if Input.is_action_just_pressed("key_2"):
toggle_temperature_layer()
camera.print_config()
if Input.is_action_just_pressed("auto_tick"):
if game_ticker.is_stopped():
game_ticker.start()
else:
game_ticker.stop()
if intro_image.is_visible():
intro_image.set_scale(calculate_scale(intro_image.texture.get_size()))
if intro_image.is_visible():
intro_image.set_scale(calculate_scale(intro_image.texture.get_size()))
func calculate_scale(image_size: Vector2) -> Vector2:
var viewport_size: Vector2 = world.get_viewport_rect().size
var scale: float = viewport_size.x / image_size.x
return Vector2(scale, scale)
var viewport_size: Vector2 = world.get_viewport_rect().size
var scale: float = viewport_size.x / image_size.x
return Vector2(scale, scale)
# SECTION: intro
func set_intro_opacity(opacity: float) -> void:
intro_image.set_modulate(Color(1, 1, 1, opacity))
intro_image.set_modulate(Color(1, 1, 1, opacity))
# SECTION: game tick
func player_health_depleted():
# TODO
pass
# TODO
pass
func _on_game_tick_timeout() -> void:
var timer_on_game_tick_timeout: PerformanceTimer = PerformanceTimer.new()
timer_on_game_tick_timeout.display_name = "frame"
var timer_on_game_tick_timeout: PerformanceTimer = PerformanceTimer.new()
timer_on_game_tick_timeout.display_name = "frame"
tilemap_navigation.game_tick_start()
world.game_tick_start()
tilemap_navigation.game_tick_start()
world.game_tick_start()
player.game_tick()
player.game_tick()
tilemap_navigation.game_tick_end()
world.game_tick_end()
EventsTracker.populate_visual_log(%RecentEventsLog, self)
tree_visualizer.update_task_statuses(player.behavior_tree.blackboard)
update_bars()
handle_result_game_state(player.behavior_tree.blackboard)
tilemap_navigation.game_tick_end()
world.game_tick_end()
EventsTracker.populate_visual_log(%RecentEventsLog, self)
if not game_ticker.is_stopped():
camera_follow_player()
update_bars()
handle_result_game_state(player.behavior_tree.blackboard)
timer_on_game_tick_timeout.stop()
if not game_ticker.is_stopped():
camera_follow_player()
timer_on_game_tick_timeout.stop()
func camera_follow_player() -> void:
var player_position: Vector2 = world.tilemap_player.cell_to_local(player.board_position)
var targeted_position = null
var player_position: Vector2 = world.tilemap_player.cell_to_local(player.board_position)
var targeted_position = null
if player.behavior_tree.blackboard.has("path"):
var path: Array = player.behavior_tree.blackboard["path"]
if path.size() > 0:
targeted_position = world.tilemap_player.cell_to_local(path[path.size() - 1])
if player.behavior_tree.blackboard.has("path"):
var path: Array = player.behavior_tree.blackboard["path"]
if path.size() > 0:
targeted_position = world.tilemap_player.cell_to_local(path[path.size() - 1])
if not targeted_position:
camera.go_to(player_position)
return
if not targeted_position:
camera.go_to(player_position)
return
var avg_position = (player_position + targeted_position) / 2
var distance: float = player_position.distance_to(targeted_position)
if distance < 200:
camera.go_to_zooming(avg_position, distance_to_zoom_level(200))
else:
var zoom_level: float = distance_to_zoom_level(distance)
camera.go_to_zooming(avg_position, zoom_level)
var avg_position = (player_position + targeted_position) / 2
var distance: float = player_position.distance_to(targeted_position)
if distance < 200:
camera.go_to_zooming(avg_position, distance_to_zoom_level(200))
else:
var zoom_level: float = distance_to_zoom_level(distance)
camera.go_to_zooming(avg_position, zoom_level)
func distance_to_zoom_level(distance: float) -> float:
var a: float = 862.08
var b: float = 274.13
return a / (distance + b)
var a: float = 862.08
var b: float = 274.13
return a / (distance + b)
func handle_result_game_state(blackboard: Dictionary) -> void:
if blackboard.has("game_state_win"):
EventsTracker.track(EventsTracker.Event.GAME_STATE_WIN)
game_ticker.stop()
if blackboard.has("game_state_win"):
EventsTracker.track(EventsTracker.Event.GAME_STATE_WIN)
game_ticker.stop()
func update_bars() -> void:
if health_bar != null:
health_bar.max_value = player.max_health
health_bar.value = clamp(player.health, 0, player.max_health)
%HealthLabel.text = str(health_bar.value) + "/" + str(player.max_health)
%HealthLabel.add_theme_color_override("font_color", Color(1, 1, 1))
if health_bar != null:
health_bar.max_value = player.max_health
health_bar.value = clamp(player.health, 0, player.max_health)
%HealthLabel.text = str(health_bar.value) + "/" + str(player.max_health)
%HealthLabel.add_theme_color_override("font_color", Color(1, 1, 1))
if food_bar != null:
food_bar.max_value = player.max_food
food_bar.value = clamp(player.food, 0, player.max_food)
%FoodLabel.text = str(food_bar.value) + "/" + str(player.max_food)
if food_bar != null:
food_bar.max_value = player.max_food
food_bar.value = clamp(player.food, 0, player.max_food)
%FoodLabel.text = str(food_bar.value) + "/" + str(player.max_food)
if temperature_resistance_bar != null:
temperature_resistance_bar.max_value = player.temperature_set_buff_value
temperature_resistance_bar.value = clamp(player.temperature_buff_timer, 0, player.temperature_set_buff_value)
%TemperatureResistanceLabel.text = str(temperature_resistance_bar.value) + "/" + str(player.temperature_set_buff_value)
if temperature_resistance_bar != null:
temperature_resistance_bar.max_value = player.temperature_set_buff_value
temperature_resistance_bar.value = clamp(player.temperature_buff_timer, 0, player.temperature_set_buff_value)
%TemperatureResistanceLabel.text = str(temperature_resistance_bar.value) + "/" + str(player.temperature_set_buff_value)
if temperature_bar != null:
temperature_bar.max_value = player.temperature_endure
# invert the value to show the time left
var countdown: int = player.temperature_endure - player.temperature_timer
temperature_bar.value = clamp(countdown, 0, player.temperature_endure)
%TemperatureLabel.text = str(temperature_bar.value) + "/" + str(player.temperature_endure)
if temperature_bar != null:
temperature_bar.max_value = player.temperature_endure
# invert the value to show the time left
var countdown: int = player.temperature_endure - player.temperature_timer
temperature_bar.value = clamp(countdown, 0, player.temperature_endure)
%TemperatureLabel.text = str(temperature_bar.value) + "/" + str(player.temperature_endure)
func toggle_temperature_layer() -> void:
world.tilemap_temperature.tilemap.visible = not world.tilemap_temperature.tilemap.visible
world.tilemap_temperature.tilemap.visible = not world.tilemap_temperature.tilemap.visible
func wait_for_key_press():
waiting_for_input = true
while waiting_for_input:
await get_tree().process_frame
waiting_for_input = true
while waiting_for_input:
await get_tree().process_frame
func _input(event):
if event is InputEventKey and event.pressed:
waiting_for_input = false
if event is InputEventKey and event.pressed:
waiting_for_input = false

View File

@ -70,15 +70,7 @@ func get_first_child() -> 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"
elif status == SUCCESS_STOP:
clear_status = "SUCCESS_STOP"
var clear_status: String = clear_status()
var ret: String = name;
if addon != "":
@ -91,6 +83,18 @@ func human_readable(addon: String = "") -> String:
return ret
func clear_status() -> String:
if status == FAILURE:
return "FAILURE"
elif status == SUCCESS:
return "SUCCESS"
elif status == RUNNING:
return "RUNNING"
elif status == SUCCESS_STOP:
return "SUCCESS_STOP"
return "UNKNOWN"
# SECTION: utility
func find_closest_item(blackboard: Dictionary, item_types: Array[Vector2i], memory_key: String, max_distance: int = -1) -> Dictionary:

View File

@ -13,11 +13,11 @@ func run(blackboard: Dictionary) -> void:
if c.status == SUCCESS_STOP:
c.status = SUCCESS
status = SUCCESS
status_reason = "stopping at child " + c.name + ", as it returned SUCCESS_STOP"
status_reason = "stopping at " + c.name + " (STOP)"
return
if c.status != FAILURE:
status = c.status
status_reason = "stopped at child " + c.name
status_reason = "stopped at " + c.name
return
status = FAILURE
status_reason = "all children failed"

View File

@ -13,11 +13,11 @@ func run(blackboard: Dictionary) -> void:
if c.status == SUCCESS_STOP:
c.status = SUCCESS
status = SUCCESS
status_reason = "stopping at child " + c.name + ", as it returned SUCCESS_STOP"
status_reason = "stopping at " + c.name + " (STOP)"
return
if c.status != SUCCESS:
status = c.status
status_reason = "stopped at child " + c.name
status_reason = "stopped at " + c.name
return
status = SUCCESS
status_reason = "all children succeeded"

View File

@ -7,10 +7,13 @@ const D_TREE_NODE: PackedScene = preload("res://scripts/visualization/d_tree_nod
#
var behavior_tree: BehaviorTree
#
var x_spacing: int = 400
var x_spacing: int = 430
var y_spacing: int = 100
#
var all_nodes: Array[DTreeNode] = []
# Dictionary[Task, DTreeNode]
var task_to_node: Dictionary = {}
var parent_nodes: Dictionary = {}
#
var current_lowest_node_pos: Vector2 = Vector2(0, 0)
@ -46,13 +49,15 @@ func build_tree_from_task(task: Task, depth: int) -> DTreeNode:
var current_node: DTreeNode = D_TREE_NODE.instantiate()
graph_edit.add_child(current_node)
task_to_node[task] = current_node
current_node.name = task.get_name() + str(randf())
current_node.title = transform_string(task.get_name())
current_node.title = human_readable_task_name(task.get_name())
current_node.add_label("status", true, true)
all_nodes.append(current_node)
for child in child_nodes:
graph_edit.connect_node(current_node.name, 0, child.name, 0)
parent_nodes[child] = current_node
if child_node_positions.size() > 0:
var average_position: Vector2 = Vector2(0, 0)
@ -72,7 +77,43 @@ func build_tree_from_task(task: Task, depth: int) -> DTreeNode:
return current_node
func transform_string(input: String) -> String:
func update_task_statuses(blackboard: Dictionary) -> void:
for t in task_to_node.keys():
var task: Task = t as Task
var node: DTreeNode = task_to_node[task]
var status: int = task.status
var clear_status: String = task.clear_status()
var status_reason: String = task.status_reason
if status_reason != "":
node.set_label_text(0, status_reason)
else:
node.set_label_text(0, clear_status)
node.set_body_color(node.color_normal)
if status == Task.RUNNING or status == Task.SUCCESS or status == Task.SUCCESS_STOP:
node.set_body_color(node.color_success)
if blackboard.has("current_task"):
var selected_node = task_to_node[blackboard["current_task"]]
if selected_node:
center_view_on_position(selected_node.position_offset)
selected_node.set_body_color(selected_node.color_executed)
while parent_nodes.has(selected_node):
selected_node = parent_nodes[selected_node]
selected_node.set_body_color(selected_node.color_checked)
func center_view_on_position(target_position: Vector2) -> void:
var graph_edit_size: Vector2 = graph_edit.size
var zoom: float = graph_edit.zoom
var offset_x: float = target_position.x * zoom - graph_edit_size.x / 2
var offset_y: float = target_position.y * zoom - graph_edit_size.y / 2
graph_edit.scroll_offset = Vector2(offset_x, offset_y)
func human_readable_task_name(input: String) -> String:
var prefixes: Dictionary = {"sl_": "Selector: ", "sq_": "Sequence: ", "Task": ""}
var selected_prefix: String = ""
@ -86,7 +127,7 @@ func transform_string(input: String) -> String:
var current_word: String = ""
for i in range(input.length()):
var character = input[i]
var character: String = input[i]
if character.to_upper() == character and current_word.length() > 0:
words.append(current_word)
current_word = "" + character.to_lower()

View File

@ -1,15 +1,19 @@
class_name DTreeNode
extends GraphNode
var left_slots: Array[String] = []
var right_slots: Array[String] = []
var color_highlighted: StyleBoxFlat = StyleBoxFlat.new()
var color_normal: StyleBoxFlat = StyleBoxFlat.new()
var left_slots: Array[String] = []
var right_slots: Array[String] = []
var color_normal: StyleBoxFlat = StyleBoxFlat.new()
var color_success: StyleBoxFlat = StyleBoxFlat.new()
var color_executed: StyleBoxFlat = StyleBoxFlat.new()
var color_checked: StyleBoxFlat = StyleBoxFlat.new()
func _ready() -> void:
color_highlighted.bg_color = Color(0.67058825, 1.0, 0.5411765)
color_normal.bg_color = Color(1.0, 1.0, 1.0)
color_success.bg_color = Color(0.25490198, 0.78431374, 0.9529412)
color_executed.bg_color = Color(0.5058824, 0.9529412, 0.30588236)
color_checked.bg_color = Color(0.6509804, 0.9254902, 0.5372549)
func add_label(label_text: String, left: bool, right: bool) -> Vector3i:
@ -33,8 +37,9 @@ func add_label(label_text: String, left: bool, right: bool) -> Vector3i:
return Vector3i(child_index, left_slots.size() - 1, right_slots.size() - 1)
func set_highlighted(highlight: bool) -> void:
if highlight:
self.add_theme_stylebox_override("panel", color_highlighted)
else:
self.add_theme_stylebox_override("panel", color_normal)
func set_body_color(color: StyleBoxFlat) -> void:
self.add_theme_stylebox_override("panel", color)
func set_label_text(label_index: int, text: String) -> void:
var label: Label = self.get_child(label_index) as Label
label.text = text