Proper generation of the node graph for target selectors, refactored carrying logic

main
Yan Wittmann 2024-12-13 20:43:56 +01:00
parent 7879bb4656
commit b688b5c8f5
6 changed files with 92 additions and 25 deletions

View File

@ -201,7 +201,8 @@
"ignore": [
"RechargeBattery",
"GoToBattery"
]
],
"restore_transfer_variables": true
},
"conditions": [
{

View File

@ -14,8 +14,10 @@ const ROTATION_SPEED: float = 5.0 # adians per second
signal waste_detected(waste)
#
var battery_charge: float = 100.0
const battery_charge_drain_per_second: float = 4.0
const battery_charge_recharge_per_second: float = 27.0
const battery_charge_drain_per_second: float = 5.0
const battery_charge_recharge_per_second: float = 30.0
# carry item
var carry_item: Node2D = null
func _ready() -> void:
@ -43,7 +45,11 @@ func _draw() -> void:
func _process(delta: float) -> void:
battery_charge -= battery_charge_drain_per_second * delta
%StateMachineInfoPanel.values["battery_charge"] = battery_charge
if carry_item:
# put the carry_item on the character
carry_item.position = position
func _physics_process(delta: float) -> void:
@ -51,8 +57,6 @@ func _physics_process(delta: float) -> void:
detect_waste()
queue_redraw()
battery_charge -= battery_charge_drain_per_second * delta
func detect_waste() -> void:
var overlapping_bodies: Array[Node2D] = detection_area.get_overlapping_bodies()

View File

@ -17,7 +17,7 @@ func state_process(delta: float) -> void:
pass
func state_exit() -> void:
func state_exit(new_state: State) -> void:
pass

View File

@ -10,14 +10,13 @@ var state_machine_data: Dictionary = {}
var received_signals: Array[Dictionary] = []
# state change parameters ("transfer")
var state_transfer_variables: Dictionary = {}
var state_history: Array = []
var current_state: State:
set(value):
if current_state:
current_state.state_exit()
state_history.append(value)
current_state.state_exit(value)
state_history.append({"state": value, "transfer": state_transfer_variables})
current_state = value
current_state.state_enter()
@ -95,16 +94,30 @@ func check_transitions() -> void:
%StateMachineInfoPanel.values["transitioned to"] = transition["target"]
# evaluate transfer parameters if any
state_transfer_variables = {}
if transition.has("copy_transfer_variables") and transition["copy_transfer_variables"]:
pass
else:
state_transfer_variables = {}
if "transfer" in transition and transition["transfer"]:
for key in transition["transfer"]:
state_transfer_variables[key] = transition_resolve_parameter(transition["transfer"][key])
%StateMachineInfoPanel.values["transfer [" + key + "]"] = state_transfer_variables[key]
# allow current state to contribute to transfer variables
current_state.contribute_transfer_variables(state_transfer_variables)
print("All criteria were met for switching to state ", transition["target"], " with transfer variables ", state_transfer_variables)
print("All criteria were met for switching to state ", transition["target"])
if transition["target"] is Dictionary:
if transition["target"].has("restore_transfer_variables") and transition["target"]["restore_transfer_variables"]:
var transfer_variables: Dictionary = find_state_by_selector(transition["target"])["transfer"]
for key in transfer_variables:
state_transfer_variables[key] = transfer_variables[key]
print("Transfer variables: ", state_transfer_variables)
for key in state_transfer_variables:
%StateMachineInfoPanel.values["transfer [" + str(key) + "]"] = state_transfer_variables[key]
current_state = find_state(transition["target"])
@ -233,7 +246,7 @@ func find_detected_signal(signal_name: String) -> Dictionary:
# UTILITY FUNCTIONS
func find_state(name) -> State:
if name is Dictionary:
return find_state_by_selector(name)
return find_state_by_selector(name)["state"]
return find_state_by_name(name)
@ -242,10 +255,11 @@ func find_state_by_name(name: String) -> State:
if child is State:
if child.get_name() == name:
return child
return null
# return the default state as a fallback
return self.get_child(state_machine_data["start"]["state"])
func find_state_by_selector(name: Dictionary) -> State:
func find_state_by_selector(name: Dictionary) -> Dictionary:
# {
# "type": "history_first",
# "ignore": [
@ -256,10 +270,12 @@ func find_state_by_selector(name: Dictionary) -> State:
if name["type"] == "history_first":
var index: int = state_history.size() - 1
while index >= 0:
if not state_history[index].get_name() in name["ignore"]:
if not state_history[index]["state"].get_name() in name["ignore"]:
return state_history[index]
index -= 1
return null
# return the default state as a fallback
return {"state": self.get_child(state_machine_data["start"]["state"]), "transfer": {}}
func was_signal_received(signal_name: String) -> bool:

View File

@ -6,20 +6,23 @@ var carry_trash: Node2D = null
func state_enter():
carry_trash = state_machine.state_transfer_variables["waste"]
# disable it's collision to avoid it from colliding with the character
carry_trash.get_node("CollisionShape2D").disabled = true
if state_machine.state_transfer_variables.has("waste"):
carry_trash = state_machine.state_transfer_variables["waste"]
character.carry_item = carry_trash
# disable it's collision to avoid it from colliding with the character
carry_trash.get_node("CollisionShape2D").disabled = true
print("Carry ", carry_trash, " to ", trash_bin.position)
func state_process(delta: float) -> void:
# put the trash on the character
carry_trash.position = character.position
# carry the trash to the trash bin
var target_position: Vector2 = trash_bin.position
character.move_towards(target_position, delta)
character.move_and_slide()
func state_exit() -> void:
carry_trash.queue_free()
func state_exit(new_state: State) -> void:
if new_state.get_name() != "GoToBattery":
character.carry_item = null
carry_trash.queue_free()

View File

@ -59,12 +59,16 @@ func build_tree():
# Set up labels (input and output)
populate_node_labels(graph_node, state_name, state_data, all_node_names_to_nodes)
# Arrange nodes
var idx: int = 0
for graph_node in all_nodes:
@warning_ignore("integer_division")
var y_pos: int = randi() % (y_spacing * 2) - y_spacing
var x_pos: int = x_spacing * i
var x_pos: int = x_spacing * idx
y_pos += randi() % 200 - 100
graph_node.position_offset = Vector2(x_pos, y_pos)
node_positions.append(graph_node)
idx += 1
func _on_graph_edit_connection_request(from_node: StringName, from_port: int, to_node: StringName, to_port: int) -> void:
@ -82,7 +86,10 @@ func populate_node_labels(node: DTreeNode, state_name: String, state_data: Dicti
if not transition.has("target"):
continue
if not transition['target'] is String:
if transition['target'] is Dictionary:
populate_node_labels_state_dict(node, state_name, node_names_to_nodes, transition, port_out)
continue
var target_state_name: String = transition['target']
@ -100,3 +107,39 @@ func populate_node_labels(node: DTreeNode, state_name: String, state_data: Dicti
)
print("Connecting %s [%d] to %s [%d]" % [node.get_name(), port_out, target_node.get_name(), port_in])
func populate_node_labels_state_dict(node: DTreeNode, state_name: String, node_names_to_nodes: Dictionary, transition: Dictionary, port_out: Vector3i) -> void:
# {
# "type": "history_first",
# "ignore": [
# "RechargeBattery",
# "GoToBattery"
# ],
# "restore_transfer_variables": true
# }
# display a new node for the target state with it's information
var target_state_name: String = node.get_name() + "_" + transition['target']['type']
var target_node: DTreeNode = null
if node_names_to_nodes.has(target_state_name):
target_node = node_names_to_nodes[target_state_name]
else:
target_node = D_TREE_NODE.instantiate()
graph_edit.add_child(target_node)
target_node.title = transition['target']['type']
all_nodes.append(target_node)
all_node_names_to_nodes[target_state_name] = target_node
# add the "ignore" states as input ports
if transition['target'].has("ignore"):
target_node.add_label("ignore:", false, false)
for ignore_state in transition['target']['ignore']:
target_node.add_label("- %s" % [ignore_state], false, false)
var port_in: Vector3i = target_node.add_label("%s" % [state_name], true, false)
# connect the nodes
graph_edit.connect_node(
node.get_name(), port_out.z,
target_node.get_name(), port_in.y
)
print("Connecting %s [%d] to %s [%d]" % [node.get_name(), port_out, target_node.get_name(), port_in])