diff --git a/addons/imjp94.yafsm/scenes/condition_editors/TimeoutConditionEditor.gd b/addons/imjp94.yafsm/scenes/condition_editors/TimeoutConditionEditor.gd new file mode 100644 index 0000000..2816901 --- /dev/null +++ b/addons/imjp94.yafsm/scenes/condition_editors/TimeoutConditionEditor.gd @@ -0,0 +1,58 @@ +tool +extends HBoxContainer + +onready var timeout_edit = $Timeout +onready var remove = $Remove + +var undo_redo + +var condition setget set_condition +var _old_timeout = 0.0 + +func _ready(): + timeout_edit.connect("value_changed", self, "_on_timeout_value_changed") + timeout_edit.connect("focus_entered", self, "_on_timeout_focus_entered") + timeout_edit.connect("focus_exited", self, "_on_timeout_focus_exited") + set_process_input(false) + +func _input(event): + if event is InputEventMouseButton: + if event.pressed: + if get_focus_owner() == timeout_edit: + var local_event = timeout_edit.make_input_local(event) + if not timeout_edit.get_rect().has_point(local_event.position): + timeout_edit.release_focus() + +func set_timeout(v): + if condition.timeout != v: + condition.timeout = v + timeout_edit.value = v + +func change_timeout_action(from, to): + if from == to: + return + undo_redo.create_action("Change Condition Timeout") + undo_redo.add_do_method(self, "set_timeout", to) + undo_redo.add_undo_method(self, "set_timeout", from) + undo_redo.commit_action() + +func _on_timeout_value_changed(timeout): + change_timeout_action(_old_timeout, timeout) + timeout_edit.release_focus() + +func _on_timeout_focus_entered(): + set_process_input(true) + _old_timeout = timeout_edit.value + +func _on_timeout_focus_exited(): + set_process_input(false) + change_timeout_action(_old_timeout, timeout_edit.value) + +func _on_condition_changed(new_condition): + if new_condition: + timeout_edit.value = new_condition.timeout + +func set_condition(c): + if condition != c: + condition = c + _on_condition_changed(c) diff --git a/addons/imjp94.yafsm/scenes/condition_editors/TimeoutConditionEditor.tscn b/addons/imjp94.yafsm/scenes/condition_editors/TimeoutConditionEditor.tscn new file mode 100644 index 0000000..bdc037a --- /dev/null +++ b/addons/imjp94.yafsm/scenes/condition_editors/TimeoutConditionEditor.tscn @@ -0,0 +1,39 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://addons/imjp94.yafsm/scenes/condition_editors/TimeoutConditionEditor.gd" type="Script" id=1] +[ext_resource path="res://addons/imjp94.yafsm/assets/icons/close-white-18dp.svg" type="Texture" id=2] + +[node name="TimeoutConditionEditor" type="HBoxContainer"] +margin_right = 58.0 +margin_bottom = 24.0 +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Label" type="Label" parent="."] +margin_top = 5.0 +margin_right = 53.0 +margin_bottom = 19.0 +text = "Timeout" + +[node name="Timeout" type="SpinBox" parent="."] +margin_left = 57.0 +margin_right = 131.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 +step = 0.01 +allow_greater = true +align = 2 +suffix = "s" +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Remove" type="Button" parent="."] +margin_left = 135.0 +margin_right = 165.0 +margin_bottom = 24.0 +size_flags_horizontal = 9 +icon = ExtResource( 2 ) +flat = true diff --git a/addons/imjp94.yafsm/scenes/transition_editors/TransitionEditor.gd b/addons/imjp94.yafsm/scenes/transition_editors/TransitionEditor.gd index b5ee7e3..d1b2ef0 100644 --- a/addons/imjp94.yafsm/scenes/transition_editors/TransitionEditor.gd +++ b/addons/imjp94.yafsm/scenes/transition_editors/TransitionEditor.gd @@ -7,11 +7,13 @@ const BooleanCondition = preload("../../src/conditions/BooleanCondition.gd") const IntegerCondition = preload("../../src/conditions/IntegerCondition.gd") const FloatCondition = preload("../../src/conditions/FloatCondition.gd") const StringCondition = preload("../../src/conditions/StringCondition.gd") +const TimeoutCondition = preload("../../src/conditions/TimeoutCondition.gd") const ConditionEditor = preload("../condition_editors/ConditionEditor.tscn") const BoolConditionEditor = preload("../condition_editors/BoolConditionEditor.tscn") const IntegerConditionEditor = preload("../condition_editors/IntegerConditionEditor.tscn") const FloatConditionEditor = preload("../condition_editors/FloatConditionEditor.tscn") const StringConditionEditor = preload("../condition_editors/StringConditionEditor.tscn") +const TimeoutConditionEditor = preload("../condition_editors/TimeoutConditionEditor.tscn") onready var header = $HeaderContainer/Header onready var title = $HeaderContainer/Header/Title @@ -72,6 +74,8 @@ func _on_add_popup_menu_index_pressed(index): condition = FloatCondition.new() 4: # String condition = StringCondition.new() + 5: # String + condition = TimeoutCondition.new() _: push_error("Unexpected index(%d) from PopupMenu" % index) var editor = create_condition_editor(condition) @@ -148,6 +152,8 @@ func create_condition_editor(condition): editor = FloatConditionEditor.instance() elif condition is StringCondition: editor = StringConditionEditor.instance() + elif condition is TimeoutCondition: + editor = TimeoutConditionEditor.instance() else: editor = ConditionEditor.instance() return editor diff --git a/addons/imjp94.yafsm/scenes/transition_editors/TransitionEditor.tscn b/addons/imjp94.yafsm/scenes/transition_editors/TransitionEditor.tscn index 9641fac..25be311 100644 --- a/addons/imjp94.yafsm/scenes/transition_editors/TransitionEditor.tscn +++ b/addons/imjp94.yafsm/scenes/transition_editors/TransitionEditor.tscn @@ -155,7 +155,7 @@ flat = true [node name="PopupMenu" type="PopupMenu" parent="HeaderContainer/Header/HBoxContainer/Add"] margin_right = 20.0 margin_bottom = 20.0 -items = [ "Trigger", null, 0, false, false, 0, 0, null, "", false, "Boolean", null, 0, false, false, 1, 0, null, "", false, "Integer", null, 0, false, false, 2, 0, null, "", false, "Float", null, 0, false, false, 3, 0, null, "", false, "String", null, 0, false, false, 4, 0, null, "", false ] +items = [ "Trigger", null, 0, false, false, 0, 0, null, "", false, "Boolean", null, 0, false, false, 1, 0, null, "", false, "Integer", null, 0, false, false, 2, 0, null, "", false, "Float", null, 0, false, false, 3, 0, null, "", false, "String", null, 0, false, false, 4, 0, null, "", false, "Timeout", null, 0, false, false, 5, 0, null, "", false ] __meta__ = { "_edit_use_anchors_": false } diff --git a/addons/imjp94.yafsm/scenes/transition_editors/TransitionLine.gd b/addons/imjp94.yafsm/scenes/transition_editors/TransitionLine.gd index 1528bae..bc96f85 100644 --- a/addons/imjp94.yafsm/scenes/transition_editors/TransitionLine.gd +++ b/addons/imjp94.yafsm/scenes/transition_editors/TransitionLine.gd @@ -65,6 +65,8 @@ func update_label(): var override_template_var = _template_var.get(condition.name) if override_template_var: label.text = label.text.format(override_template_var) + elif "timeout" in condition: + label.text = condition.display_string() else: label.text = condition.name update() diff --git a/addons/imjp94.yafsm/src/StateMachinePlayer.gd b/addons/imjp94.yafsm/src/StateMachinePlayer.gd index b1bb5df..bf72cc4 100644 --- a/addons/imjp94.yafsm/src/StateMachinePlayer.gd +++ b/addons/imjp94.yafsm/src/StateMachinePlayer.gd @@ -25,6 +25,7 @@ var _local_parameters var _is_update_locked = true var _was_transited = false # If last transition was successful var _is_param_edited = false +var _time_in_state = 0.0 func _init(): @@ -83,7 +84,7 @@ func _transit(): var from = get_current() var local_params = _local_parameters.get(path_backward(from), {}) - var next_state = state_machine.transit(get_current(), _parameters, local_params) + var next_state = state_machine.transit(get_current(), _parameters, local_params, _time_in_state) if next_state: if stack.has(next_state): reset(stack.find(next_state)) @@ -116,6 +117,7 @@ func _on_state_changed(from, to): clear_param(state, false) # Clearing params internally, do not update emit_signal("exited", state) + _time_in_state = 0.0 emit_signal("transited", from, to) # Called internally if process_mode is PHYSICS/IDLE to unlock update() @@ -194,6 +196,7 @@ func update(delta=get_physics_process_delta_time()): if process_mode != ProcessMode.MANUAL: assert(not _is_update_locked, "Attempting to update manually with ProcessMode.%s" % ProcessMode.keys()[process_mode]) + _time_in_state += delta _transit() var current_state = get_current() _on_updated(current_state, delta) diff --git a/addons/imjp94.yafsm/src/conditions/TimeoutCondition.gd b/addons/imjp94.yafsm/src/conditions/TimeoutCondition.gd new file mode 100644 index 0000000..34ae006 --- /dev/null +++ b/addons/imjp94.yafsm/src/conditions/TimeoutCondition.gd @@ -0,0 +1,18 @@ +tool +extends "Condition.gd" + +signal timeout_changed(new_timeout) + +export(float) var timeout setget set_timeout + +func set_timeout(t): + if not is_equal_approx(timeout, t): + timeout = t + emit_signal("timeout_changed", t) + emit_signal("display_string_changed", display_string()) + +func display_string(): + return "Timeout: %.2fs" % timeout + +func has_timed_out(t): + return t >= timeout diff --git a/addons/imjp94.yafsm/src/states/StateMachine.gd b/addons/imjp94.yafsm/src/states/StateMachine.gd index 126169e..11d887a 100644 --- a/addons/imjp94.yafsm/src/states/StateMachine.gd +++ b/addons/imjp94.yafsm/src/states/StateMachine.gd @@ -16,7 +16,7 @@ func _init(p_name="", p_transitions={}, p_states={}): states = p_states # Attempt to transit with global/local parameters, where local_params override params -func transit(current_state, params={}, local_params={}): +func transit(current_state, params={}, local_params={}, time_in_state=0.0): var nested_states = current_state.split("/") var is_nested = nested_states.size() > 1 var end_state_machine = self @@ -40,7 +40,7 @@ func transit(current_state, params={}, local_params={}): end_state_machine_parent_path = join_path(end_state_machine_parent_path, [nested_states[i]]) var end_state_machine_parent = get_state(end_state_machine_parent_path) var normalized_current_state = end_state_machine.name - var next_state = end_state_machine_parent.transit(normalized_current_state, params) + var next_state = end_state_machine_parent.transit(normalized_current_state, params, time_in_state) if next_state: # Construct next state into absolute path next_state = join_path(end_state_machine_parent_path, [next_state]) @@ -53,7 +53,7 @@ func transit(current_state, params={}, local_params={}): from_transitions_array.sort_custom(Transition, "sort") for transition in from_transitions_array: - var next_state = transition.transit(params, local_params) + var next_state = transition.transit(params, local_params, time_in_state) if next_state: if "states" in end_state_machine.states[next_state]: # Next state is a StateMachine, return entry state of the state machine in absolute path diff --git a/addons/imjp94.yafsm/src/transitions/Transition.gd b/addons/imjp94.yafsm/src/transitions/Transition.gd index 546ff60..ede91f7 100644 --- a/addons/imjp94.yafsm/src/transitions/Transition.gd +++ b/addons/imjp94.yafsm/src/transitions/Transition.gd @@ -16,21 +16,24 @@ func _init(p_from="", p_to="", p_conditions={}): conditions = p_conditions # Attempt to transit with parameters given, return name of next state if succeeded else null -func transit(params={}, local_params={}): +func transit(params={}, local_params={}, time_in_state=0.0): var can_transit = conditions.size() > 0 for condition in conditions.values(): - var has_param = params.has(condition.name) - var has_local_param = local_params.has(condition.name) - if has_param or has_local_param: - # local_params > params - var value = local_params.get(condition.name) if has_local_param else params.get(condition.name) - if value == null: # null value is treated as trigger - can_transit = can_transit and true - else: - if "value" in condition: - can_transit = can_transit and condition.compare(value) + if condition.has_method("has_timed_out"): + can_transit = can_transit and condition.has_timed_out(time_in_state) else: - can_transit = false + var has_param = params.has(condition.name) + var has_local_param = local_params.has(condition.name) + if has_param or has_local_param: + # local_params > params + var value = local_params.get(condition.name) if has_local_param else params.get(condition.name) + if value == null: # null value is treated as trigger + can_transit = can_transit and true + else: + if "value" in condition: + can_transit = can_transit and condition.compare(value) + else: + can_transit = false if can_transit or conditions.size() == 0: return to return null