From bd3277655abf500d0cd4aae04481fc96f75230a6 Mon Sep 17 00:00:00 2001 From: Iceball457 Date: Wed, 12 Feb 2025 10:17:10 -0800 Subject: [PATCH 01/17] Relocate "default_transition" and use it when teleporting in an animation state machine. --- .../animation_node_state_machine.cpp | 62 ++++++++++++------- .../animation/animation_node_state_machine.h | 5 +- 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index 1c4d489d687..94726c5b749 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -774,10 +774,8 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St // Can't travel, then teleport. if (p_state_machine->states.has(temp_travel_request)) { path.clear(); - if (current != temp_travel_request || reset_request_on_teleport) { - _set_current(p_state_machine, temp_travel_request); - reset_request = reset_request_on_teleport; - teleport_request = true; + if (current != temp_travel_request) { + path.push_back(temp_travel_request); } } else { ERR_FAIL_V_MSG(AnimationNode::NodeTimeInfo(), "No such node: '" + temp_travel_request + "'"); @@ -789,18 +787,20 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St if (teleport_request) { teleport_request = false; - // Clear fadeing on teleport. - fading_from = StringName(); - fadeing_from_nti = AnimationNode::NodeTimeInfo(); - fading_pos = 0; - // Init current length. - pi.time = 0; - pi.seeked = true; - pi.is_external_seeking = false; - pi.weight = 0; - current_nti = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true); - // Don't process first node if not necessary, instead process next node. - _transition_to_next_recursive(tree, p_state_machine, p_delta, p_test_only); + // // Clear fadeing on teleport. + // fading_from = StringName(); + // fadeing_from_nti = AnimationNode::NodeTimeInfo(); + // fading_pos = 0; + // // Init current length. + // pi.time = 0; + // pi.seeked = true; + // pi.is_external_seeking = false; + // pi.weight = 0; + // current_nti = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true); + // // Don't process first node if not necessary, instead process next node. + // _transition_to_next_recursive(tree, p_state_machine, p_delta, p_test_only); + + // path.push_back(travel_request); } // Check current node existence. @@ -1058,8 +1058,17 @@ AnimationNodeStateMachinePlayback::NextInfo AnimationNodeStateMachinePlayback::_ next.switch_mode = ref_transition->get_switch_mode(); next.is_reset = ref_transition->is_reset(); next.break_loop_at_end = ref_transition->is_loop_broken_at_end(); + return next; // Once we have the information we need, we can actually just return that info } } + // There is no transition that ends at the next state in the state machine. We can use the default transition + next.node = path[0]; + next.xfade = p_state_machine->default_transition->get_xfade_time(); + next.curve = p_state_machine->default_transition->get_xfade_curve(); + next.switch_mode = p_state_machine->default_transition->get_switch_mode(); + next.is_reset = p_state_machine->default_transition->is_reset(); + next.break_loop_at_end = p_state_machine->default_transition->is_loop_broken_at_end(); + return next; // Once we have the information we need, we can actually just return that info } else { int auto_advance_to = -1; float priority_best = 1e20; @@ -1198,11 +1207,6 @@ void AnimationNodeStateMachinePlayback::_bind_methods() { AnimationNodeStateMachinePlayback::AnimationNodeStateMachinePlayback() { set_local_to_scene(true); // Only one per instantiated scene. - default_transition.instantiate(); - default_transition->set_xfade_time(0); - default_transition->set_reset(true); - default_transition->set_advance_mode(AnimationNodeStateMachineTransition::ADVANCE_MODE_AUTO); - default_transition->set_switch_mode(AnimationNodeStateMachineTransition::SWITCH_MODE_IMMEDIATE); } /////////////////////////////////////////////////////// @@ -1320,6 +1324,18 @@ bool AnimationNodeStateMachine::are_ends_reset() const { return reset_ends; } +void AnimationNodeStateMachine::set_default_transition(Ref p_transition) { + default_transition.instantiate(); + default_transition->set_xfade_time(0); + default_transition->set_reset(true); + default_transition->set_advance_mode(AnimationNodeStateMachineTransition::ADVANCE_MODE_AUTO); + default_transition->set_switch_mode(AnimationNodeStateMachineTransition::SWITCH_MODE_IMMEDIATE); +} + +Ref AnimationNodeStateMachine::get_default_transition() { + return default_transition; +} + bool AnimationNodeStateMachine::can_edit_node(const StringName &p_name) const { if (states.has(p_name)) { const AnimationNode *anode = states[p_name].node.ptr(); @@ -1789,6 +1805,9 @@ void AnimationNodeStateMachine::get_argument_options(const StringName &p_functio #endif void AnimationNodeStateMachine::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_default_transition", "transition"), &AnimationNodeStateMachine::set_default_transition); + ClassDB::bind_method(D_METHOD("get_default_transition"), &AnimationNodeStateMachine::get_default_transition); + ClassDB::bind_method(D_METHOD("add_node", "name", "node", "position"), &AnimationNodeStateMachine::add_node, DEFVAL(Vector2())); ClassDB::bind_method(D_METHOD("replace_node", "name", "node"), &AnimationNodeStateMachine::replace_node); ClassDB::bind_method(D_METHOD("get_node", "name"), &AnimationNodeStateMachine::get_node); @@ -1824,6 +1843,7 @@ void AnimationNodeStateMachine::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "state_machine_type", PROPERTY_HINT_ENUM, "Root,Nested,Grouped"), "set_state_machine_type", "get_state_machine_type"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_transition_to_self"), "set_allow_transition_to_self", "is_allow_transition_to_self"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reset_ends"), "set_reset_ends", "are_ends_reset"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "default_transition", PROPERTY_HINT_RESOURCE_TYPE, ""), "set_default_transition", "get_default_transition"); BIND_ENUM_CONSTANT(STATE_MACHINE_TYPE_ROOT); BIND_ENUM_CONSTANT(STATE_MACHINE_TYPE_NESTED); diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h index ae28c126b3a..04c0634a872 100644 --- a/scene/animation/animation_node_state_machine.h +++ b/scene/animation/animation_node_state_machine.h @@ -136,6 +136,7 @@ private: Ref transition; }; + Ref default_transition; Vector transitions; StringName playback = "playback"; @@ -206,6 +207,9 @@ public: void set_reset_ends(bool p_enable); bool are_ends_reset() const; + void set_default_transition(Ref p_transition); + Ref get_default_transition(); + bool can_edit_node(const StringName &p_name) const; void set_graph_offset(const Vector2 &p_offset); @@ -259,7 +263,6 @@ class AnimationNodeStateMachinePlayback : public Resource { bool is_reset = false; }; - Ref default_transition; String base_path; AnimationNode::NodeTimeInfo current_nti; From 5447afca50d53702f17e52b7f5888bef1c0e8b65 Mon Sep 17 00:00:00 2001 From: Iceball457 Date: Wed, 12 Feb 2025 19:40:13 -0800 Subject: [PATCH 02/17] Update documentation. --- doc/classes/AnimationNodeStateMachine.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/classes/AnimationNodeStateMachine.xml b/doc/classes/AnimationNodeStateMachine.xml index e80b1f00b02..2fe59ac6b25 100644 --- a/doc/classes/AnimationNodeStateMachine.xml +++ b/doc/classes/AnimationNodeStateMachine.xml @@ -165,6 +165,9 @@ If [code]true[/code], allows teleport to the self state with [method AnimationNodeStateMachinePlayback.travel]. When the reset option is enabled in [method AnimationNodeStateMachinePlayback.travel], the animation is restarted. If [code]false[/code], nothing happens on the teleportation to the self state. + + Transition used whenever the state machine teleports. + If [code]true[/code], treat the cross-fade to the start and end nodes as a blend with the RESET animation. In most cases, when additional cross-fades are performed in the parent [AnimationNode] of the state machine, setting this property to [code]false[/code] and matching the cross-fade time of the parent [AnimationNode] and the state machine's start node and end node gives good results. From 883597e25357033848282a989f3e011482573d8b Mon Sep 17 00:00:00 2001 From: Iceball457 Date: Thu, 13 Feb 2025 15:16:25 -0800 Subject: [PATCH 03/17] Re-added missing property hint type. --- scene/animation/animation_node_state_machine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index 94726c5b749..d8cb2e2ac3d 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -1843,7 +1843,7 @@ void AnimationNodeStateMachine::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "state_machine_type", PROPERTY_HINT_ENUM, "Root,Nested,Grouped"), "set_state_machine_type", "get_state_machine_type"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_transition_to_self"), "set_allow_transition_to_self", "is_allow_transition_to_self"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reset_ends"), "set_reset_ends", "are_ends_reset"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "default_transition", PROPERTY_HINT_RESOURCE_TYPE, ""), "set_default_transition", "get_default_transition"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "default_transition", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNodeStateMachineTransition"), "set_default_transition", "get_default_transition"); BIND_ENUM_CONSTANT(STATE_MACHINE_TYPE_ROOT); BIND_ENUM_CONSTANT(STATE_MACHINE_TYPE_NESTED); From 9b4fbdc4d26aa849897b389a692fb26560677cd7 Mon Sep 17 00:00:00 2001 From: Iceball457 Date: Sun, 16 Feb 2025 05:33:23 -0800 Subject: [PATCH 04/17] Allow AnimationNodeStateMachine nodes to transition to themselves during a teleport if allow_transition_to_self flag is set. && Update state machine children on teleport. --- .../animation/animation_node_state_machine.cpp | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index d8cb2e2ac3d..deb87fb89a1 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -774,7 +774,7 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St // Can't travel, then teleport. if (p_state_machine->states.has(temp_travel_request)) { path.clear(); - if (current != temp_travel_request) { + if (p_state_machine->is_allow_transition_to_self() || current != temp_travel_request) { path.push_back(temp_travel_request); } } else { @@ -786,21 +786,8 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St AnimationMixer::PlaybackInfo pi = p_playback_info; if (teleport_request) { + _transition_to_next_recursive(tree, p_state_machine, p_delta, p_test_only); teleport_request = false; - // // Clear fadeing on teleport. - // fading_from = StringName(); - // fadeing_from_nti = AnimationNode::NodeTimeInfo(); - // fading_pos = 0; - // // Init current length. - // pi.time = 0; - // pi.seeked = true; - // pi.is_external_seeking = false; - // pi.weight = 0; - // current_nti = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true); - // // Don't process first node if not necessary, instead process next node. - // _transition_to_next_recursive(tree, p_state_machine, p_delta, p_test_only); - - // path.push_back(travel_request); } // Check current node existence. From fdef20dce6fe9a66d21b6f46e7f058fe018112b9 Mon Sep 17 00:00:00 2001 From: Iceball457 Date: Sun, 16 Feb 2025 07:16:24 -0800 Subject: [PATCH 05/17] Fix save/load & null case for teleport_transition. --- scene/animation/animation_node_state_machine.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index deb87fb89a1..a1ce93fa925 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -1312,11 +1312,9 @@ bool AnimationNodeStateMachine::are_ends_reset() const { } void AnimationNodeStateMachine::set_default_transition(Ref p_transition) { - default_transition.instantiate(); - default_transition->set_xfade_time(0); - default_transition->set_reset(true); - default_transition->set_advance_mode(AnimationNodeStateMachineTransition::ADVANCE_MODE_AUTO); - default_transition->set_switch_mode(AnimationNodeStateMachineTransition::SWITCH_MODE_IMMEDIATE); + if (p_transition != NULL) { + default_transition = p_transition->duplicate(); + } } Ref AnimationNodeStateMachine::get_default_transition() { @@ -1871,4 +1869,7 @@ AnimationNodeStateMachine::AnimationNodeStateMachine() { end.node = e; end.position = Vector2(900, 100); states[SceneStringName(End)] = end; + + Ref t; + set_teleport_transition(t); } From 0135d8fdc17ed1d58e4fa2ebe5c353f9ca6362f9 Mon Sep 17 00:00:00 2001 From: Iceball457 Date: Sun, 16 Feb 2025 07:22:58 -0800 Subject: [PATCH 06/17] Allow teleport block in _process to access information about the most recent travel request. --- .../animation_node_state_machine.cpp | 53 ++++++++++--------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index a1ce93fa925..dae1b319c9b 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -751,45 +751,50 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St } if (travel_request != StringName()) { - // Fix path. String travel_target = _validate_path(p_state_machine, travel_request); Vector travel_path = travel_target.split("/"); travel_request = travel_path[0]; StringName temp_travel_request = travel_request; // For the case that can't travel. - // Process children. - Vector new_path; - bool can_travel = _make_travel_path(tree, p_state_machine, travel_path.size() <= 1 ? p_state_machine->is_allow_transition_to_self() : false, new_path, p_test_only); - if (travel_path.size()) { + + // If we are already planning to teleport, we can skip the expensive A* attempt. + if (!teleport_request) { + // Fix path. + // Process children. + Vector new_path; + bool can_travel = _make_travel_path(tree, p_state_machine, travel_path.size() <= 1 ? p_state_machine->is_allow_transition_to_self() : false, new_path, p_test_only); + if (travel_path.size()) { + if (can_travel) { + can_travel = _travel_children(tree, p_state_machine, travel_target, p_state_machine->is_allow_transition_to_self(), travel_path[0] == current, p_test_only); + } else { + _start_children(tree, p_state_machine, travel_target, p_test_only); + } + } + + // Process to travel. if (can_travel) { - can_travel = _travel_children(tree, p_state_machine, travel_target, p_state_machine->is_allow_transition_to_self(), travel_path[0] == current, p_test_only); + path = new_path; } else { - _start_children(tree, p_state_machine, travel_target, p_test_only); + // Can't travel, then teleport. + if (p_state_machine->states.has(temp_travel_request)) { + teleport_request = true; + } else { + ERR_FAIL_V_MSG(AnimationNode::NodeTimeInfo(), "No such node: '" + temp_travel_request + "'"); + } } } - // Process to travel. - if (can_travel) { - path = new_path; - } else { - // Can't travel, then teleport. - if (p_state_machine->states.has(temp_travel_request)) { - path.clear(); - if (p_state_machine->is_allow_transition_to_self() || current != temp_travel_request) { - path.push_back(temp_travel_request); - } - } else { - ERR_FAIL_V_MSG(AnimationNode::NodeTimeInfo(), "No such node: '" + temp_travel_request + "'"); + if (teleport_request) { + path.clear(); + if (p_state_machine->is_allow_transition_to_self() || current != temp_travel_request) { + path.push_back(temp_travel_request); } + _transition_to_next_recursive(tree, p_state_machine, p_delta, p_test_only); + teleport_request = false; } } AnimationMixer::PlaybackInfo pi = p_playback_info; - if (teleport_request) { - _transition_to_next_recursive(tree, p_state_machine, p_delta, p_test_only); - teleport_request = false; - } - // Check current node existence. if (!p_state_machine->states.has(current)) { playing = false; // Current does not exist. From 87e4adf5328c9bfb6a33d3c1fe0e4902566a9afb Mon Sep 17 00:00:00 2001 From: Iceball457 Date: Sun, 16 Feb 2025 08:32:12 -0800 Subject: [PATCH 07/17] Move teleport_transition nullptr handling from setter to body. --- .../animation_node_state_machine.cpp | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index dae1b319c9b..2cc6adbf173 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -1055,11 +1055,18 @@ AnimationNodeStateMachinePlayback::NextInfo AnimationNodeStateMachinePlayback::_ } // There is no transition that ends at the next state in the state machine. We can use the default transition next.node = path[0]; - next.xfade = p_state_machine->default_transition->get_xfade_time(); - next.curve = p_state_machine->default_transition->get_xfade_curve(); - next.switch_mode = p_state_machine->default_transition->get_switch_mode(); - next.is_reset = p_state_machine->default_transition->is_reset(); - next.break_loop_at_end = p_state_machine->default_transition->is_loop_broken_at_end(); + if (p_state_machine->default_transition == nullptr) { + next.xfade = 0; + next.switch_mode = AnimationNodeStateMachineTransition::SWITCH_MODE_IMMEDIATE; + next.is_reset = false; + next.break_loop_at_end = false; + } else { + next.xfade = p_state_machine->default_transition->get_xfade_time(); + next.curve = p_state_machine->default_transition->get_xfade_curve(); + next.switch_mode = p_state_machine->default_transition->get_switch_mode(); + next.is_reset = p_state_machine->default_transition->is_reset(); + next.break_loop_at_end = p_state_machine->default_transition->is_loop_broken_at_end(); + } return next; // Once we have the information we need, we can actually just return that info } else { int auto_advance_to = -1; @@ -1317,9 +1324,7 @@ bool AnimationNodeStateMachine::are_ends_reset() const { } void AnimationNodeStateMachine::set_default_transition(Ref p_transition) { - if (p_transition != NULL) { - default_transition = p_transition->duplicate(); - } + default_transition = p_transition; } Ref AnimationNodeStateMachine::get_default_transition() { @@ -1874,7 +1879,4 @@ AnimationNodeStateMachine::AnimationNodeStateMachine() { end.node = e; end.position = Vector2(900, 100); states[SceneStringName(End)] = end; - - Ref t; - set_teleport_transition(t); } From 99668870dbd0a4083861d22e5cd3778f9d8ee7b3 Mon Sep 17 00:00:00 2001 From: Iceball457 Date: Mon, 17 Feb 2025 02:02:06 -0800 Subject: [PATCH 08/17] Teleports are now the same as before. Default transition is a separate logical element and is not a teleport anymore. --- .../animation_node_state_machine.cpp | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index 2cc6adbf173..7546aa5fe76 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -750,6 +750,8 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St } } + AnimationMixer::PlaybackInfo pi = p_playback_info; + if (travel_request != StringName()) { String travel_target = _validate_path(p_state_machine, travel_request); Vector travel_path = travel_target.split("/"); @@ -774,9 +776,12 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St if (can_travel) { path = new_path; } else { - // Can't travel, then teleport. + // Can't travel via explicit transitions, then travel directly (will use default_transition). if (p_state_machine->states.has(temp_travel_request)) { - teleport_request = true; + path.clear(); + if (p_state_machine->is_allow_transition_to_self() || current != temp_travel_request) { + path.push_back(temp_travel_request); + } } else { ERR_FAIL_V_MSG(AnimationNode::NodeTimeInfo(), "No such node: '" + temp_travel_request + "'"); } @@ -784,17 +789,22 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St } if (teleport_request) { - path.clear(); - if (p_state_machine->is_allow_transition_to_self() || current != temp_travel_request) { - path.push_back(temp_travel_request); - } - _transition_to_next_recursive(tree, p_state_machine, p_delta, p_test_only); teleport_request = false; + // Clear fadeing on teleport. + fading_from = StringName(); + fadeing_from_nti = AnimationNode::NodeTimeInfo(); + fading_pos = 0; + // Init current length. + pi.time = 0; + pi.seeked = true; + pi.is_external_seeking = false; + pi.weight = 0; + current_nti = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true); + // Don't process first node if not necessary, instead process next node. + _transition_to_next_recursive(tree, p_state_machine, p_delta, p_test_only); } } - AnimationMixer::PlaybackInfo pi = p_playback_info; - // Check current node existence. if (!p_state_machine->states.has(current)) { playing = false; // Current does not exist. From a5bd0d7678ebf5616e880051b672ebf795e52ccb Mon Sep 17 00:00:00 2001 From: Iceball457 Date: Mon, 17 Feb 2025 02:14:27 -0800 Subject: [PATCH 09/17] Add jump_request flag for users to be allowed to force a single transition to the desired state. --- .../AnimationNodeStateMachinePlayback.xml | 2 ++ .../animation_node_state_machine.cpp | 29 +++++++++++-------- .../animation/animation_node_state_machine.h | 5 ++-- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/doc/classes/AnimationNodeStateMachinePlayback.xml b/doc/classes/AnimationNodeStateMachinePlayback.xml index 891dfa9f754..a89556ea0b8 100644 --- a/doc/classes/AnimationNodeStateMachinePlayback.xml +++ b/doc/classes/AnimationNodeStateMachinePlayback.xml @@ -83,10 +83,12 @@ + Transitions from the current state to another one, following the shortest path. If the path does not connect from the current state, the animation will play after the state teleports. If [param reset_on_teleport] is [code]true[/code], the animation is played from the beginning when the travel cause a teleportation. + If [param force_jump] is [code]true[/code], the state machine will use its [code]default_transition[/code] to move directly to the requested state. diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index 7546aa5fe76..e77d05055d1 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -254,10 +254,10 @@ void AnimationNodeStateMachinePlayback::_set_grouped(bool p_is_grouped) { is_grouped = p_is_grouped; } -void AnimationNodeStateMachinePlayback::travel(const StringName &p_state, bool p_reset_on_teleport) { +void AnimationNodeStateMachinePlayback::travel(const StringName &p_state, bool p_reset_on_teleport, bool p_force_jump) { ERR_FAIL_COND_EDMSG(is_grouped, "Grouped AnimationNodeStateMachinePlayback must be handled by parent AnimationNodeStateMachinePlayback. You need to retrieve the parent Root/Nested AnimationNodeStateMachine."); ERR_FAIL_COND_EDMSG(String(p_state).contains("/Start") || String(p_state).contains("/End"), "Grouped AnimationNodeStateMachinePlayback doesn't allow to play Start/End directly. Instead, play the prev or next state of group in the parent AnimationNodeStateMachine."); - _travel_main(p_state, p_reset_on_teleport); + _travel_main(p_state, p_reset_on_teleport, p_force_jump); } void AnimationNodeStateMachinePlayback::start(const StringName &p_state, bool p_reset) { @@ -276,10 +276,11 @@ void AnimationNodeStateMachinePlayback::stop() { _stop_main(); } -void AnimationNodeStateMachinePlayback::_travel_main(const StringName &p_state, bool p_reset_on_teleport) { +void AnimationNodeStateMachinePlayback::_travel_main(const StringName &p_state, bool p_reset_on_teleport, bool p_force_jump) { travel_request = p_state; reset_request_on_teleport = p_reset_on_teleport; stop_request = false; + jump_request = p_force_jump; } void AnimationNodeStateMachinePlayback::_start_main(const StringName &p_state, bool p_reset) { @@ -758,8 +759,8 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St travel_request = travel_path[0]; StringName temp_travel_request = travel_request; // For the case that can't travel. - // If we are already planning to teleport, we can skip the expensive A* attempt. - if (!teleport_request) { + // If we are already not planning to use the pathfinder, we can skip the expensive A* attempt. + if (!teleport_request && !jump_request) { // Fix path. // Process children. Vector new_path; @@ -777,14 +778,18 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St path = new_path; } else { // Can't travel via explicit transitions, then travel directly (will use default_transition). - if (p_state_machine->states.has(temp_travel_request)) { - path.clear(); - if (p_state_machine->is_allow_transition_to_self() || current != temp_travel_request) { - path.push_back(temp_travel_request); - } - } else { - ERR_FAIL_V_MSG(AnimationNode::NodeTimeInfo(), "No such node: '" + temp_travel_request + "'"); + jump_request = true; + } + } + + if (jump_request) { + if (p_state_machine->states.has(temp_travel_request)) { + path.clear(); + if (p_state_machine->is_allow_transition_to_self() || current != temp_travel_request) { + path.push_back(temp_travel_request); } + } else { + ERR_FAIL_V_MSG(AnimationNode::NodeTimeInfo(), "No such node: '" + temp_travel_request + "'"); } } diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h index 04c0634a872..a1497484cf7 100644 --- a/scene/animation/animation_node_state_machine.h +++ b/scene/animation/animation_node_state_machine.h @@ -288,10 +288,11 @@ class AnimationNodeStateMachinePlayback : public Resource { bool next_request = false; bool stop_request = false; bool teleport_request = false; + bool jump_request = false; bool is_grouped = false; - void _travel_main(const StringName &p_state, bool p_reset_on_teleport = true); + void _travel_main(const StringName &p_state, bool p_reset_on_teleport = true, bool p_force_jump = false); void _start_main(const StringName &p_state, bool p_reset = true); void _next_main(); void _stop_main(); @@ -328,7 +329,7 @@ protected: static void _bind_methods(); public: - void travel(const StringName &p_state, bool p_reset_on_teleport = true); + void travel(const StringName &p_state, bool p_reset_on_teleport = true, bool p_force_jump = false); void start(const StringName &p_state, bool p_reset = true); void next(); void stop(); From 4d9ea68b85ec67c985148bc04e6b9ceb9521c34f Mon Sep 17 00:00:00 2001 From: Iceball457 Date: Mon, 17 Feb 2025 21:54:44 -0800 Subject: [PATCH 10/17] Make null checking for the default transition safer. --- scene/animation/animation_node_state_machine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index e77d05055d1..4e494379bb2 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -1070,7 +1070,7 @@ AnimationNodeStateMachinePlayback::NextInfo AnimationNodeStateMachinePlayback::_ } // There is no transition that ends at the next state in the state machine. We can use the default transition next.node = path[0]; - if (p_state_machine->default_transition == nullptr) { + if (p_state_machine->default_transition.is_null()) { next.xfade = 0; next.switch_mode = AnimationNodeStateMachineTransition::SWITCH_MODE_IMMEDIATE; next.is_reset = false; From fcd7a06821bb2576fa1c5812fab957e3359c6acc Mon Sep 17 00:00:00 2001 From: Iceball457 Date: Tue, 18 Feb 2025 01:07:49 -0800 Subject: [PATCH 11/17] Change user-facing API to remove extraneous parameters. --- doc/classes/AnimationNodeStateMachinePlayback.xml | 13 ++++++++----- scene/animation/animation_node_state_machine.cpp | 10 ++++++++-- scene/animation/animation_node_state_machine.h | 3 ++- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/doc/classes/AnimationNodeStateMachinePlayback.xml b/doc/classes/AnimationNodeStateMachinePlayback.xml index a89556ea0b8..e6e4698af96 100644 --- a/doc/classes/AnimationNodeStateMachinePlayback.xml +++ b/doc/classes/AnimationNodeStateMachinePlayback.xml @@ -82,13 +82,16 @@ - - Transitions from the current state to another one, following the shortest path. - If the path does not connect from the current state, the animation will play after the state teleports. - If [param reset_on_teleport] is [code]true[/code], the animation is played from the beginning when the travel cause a teleportation. - If [param force_jump] is [code]true[/code], the state machine will use its [code]default_transition[/code] to move directly to the requested state. + If the path does not connect from the current state, the state machine's default transition will be used to jump to the destination state. + + + + + + + Transitions from the current state to another one, using the state machine's default transition to jump to the destination state. diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index 4e494379bb2..bec0a1aa7e4 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -254,10 +254,16 @@ void AnimationNodeStateMachinePlayback::_set_grouped(bool p_is_grouped) { is_grouped = p_is_grouped; } -void AnimationNodeStateMachinePlayback::travel(const StringName &p_state, bool p_reset_on_teleport, bool p_force_jump) { +void AnimationNodeStateMachinePlayback::travel(const StringName &p_state) { ERR_FAIL_COND_EDMSG(is_grouped, "Grouped AnimationNodeStateMachinePlayback must be handled by parent AnimationNodeStateMachinePlayback. You need to retrieve the parent Root/Nested AnimationNodeStateMachine."); ERR_FAIL_COND_EDMSG(String(p_state).contains("/Start") || String(p_state).contains("/End"), "Grouped AnimationNodeStateMachinePlayback doesn't allow to play Start/End directly. Instead, play the prev or next state of group in the parent AnimationNodeStateMachine."); - _travel_main(p_state, p_reset_on_teleport, p_force_jump); + _travel_main(p_state, false, false); +} + +void AnimationNodeStateMachinePlayback::jump(const StringName &p_state) { + ERR_FAIL_COND_EDMSG(is_grouped, "Grouped AnimationNodeStateMachinePlayback must be handled by parent AnimationNodeStateMachinePlayback. You need to retrieve the parent Root/Nested AnimationNodeStateMachine."); + ERR_FAIL_COND_EDMSG(String(p_state).contains("/Start") || String(p_state).contains("/End"), "Grouped AnimationNodeStateMachinePlayback doesn't allow to play Start/End directly. Instead, play the prev or next state of group in the parent AnimationNodeStateMachine."); + _travel_main(p_state, false, true); } void AnimationNodeStateMachinePlayback::start(const StringName &p_state, bool p_reset) { diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h index a1497484cf7..5dc5419d48d 100644 --- a/scene/animation/animation_node_state_machine.h +++ b/scene/animation/animation_node_state_machine.h @@ -329,7 +329,8 @@ protected: static void _bind_methods(); public: - void travel(const StringName &p_state, bool p_reset_on_teleport = true, bool p_force_jump = false); + void travel(const StringName &p_state); + void jump(const StringName &p_state); void start(const StringName &p_state, bool p_reset = true); void next(); void stop(); From 74a16145b4843c0eb451db8b1596d6b191b50d46 Mon Sep 17 00:00:00 2001 From: Iceball457 Date: Tue, 18 Feb 2025 01:08:18 -0800 Subject: [PATCH 12/17] Do not use transitions to move to and from start and end states. --- scene/animation/animation_node_state_machine.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index bec0a1aa7e4..61fe2ca8783 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -760,11 +760,16 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St AnimationMixer::PlaybackInfo pi = p_playback_info; if (travel_request != StringName()) { - String travel_target = _validate_path(p_state_machine, travel_request); + String travel_target = _validate_path(p_state_machine, travel_request); Vector travel_path = travel_target.split("/"); travel_request = travel_path[0]; StringName temp_travel_request = travel_request; // For the case that can't travel. + // Do not use transitions to move to and from special states: + if (String(current).contains("/Start") || String(current).contains("/End") || String(temp_travel_request).contains("/Start") || String(temp_travel_request).contains("/End")) { + teleport_request = true; + } + // If we are already not planning to use the pathfinder, we can skip the expensive A* attempt. if (!teleport_request && !jump_request) { // Fix path. From b5928914d466ab106123f182e3635e1a72d9cd3a Mon Sep 17 00:00:00 2001 From: Iceball457 Date: Tue, 18 Feb 2025 02:08:05 -0800 Subject: [PATCH 13/17] Formatting fix. --- scene/animation/animation_node_state_machine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index 61fe2ca8783..567c75cfeb4 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -760,7 +760,7 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St AnimationMixer::PlaybackInfo pi = p_playback_info; if (travel_request != StringName()) { - String travel_target = _validate_path(p_state_machine, travel_request); + String travel_target = _validate_path(p_state_machine, travel_request); Vector travel_path = travel_target.split("/"); travel_request = travel_path[0]; StringName temp_travel_request = travel_request; // For the case that can't travel. From ee6cdb6d926b845deda9c8cb714420f9c8d06df5 Mon Sep 17 00:00:00 2001 From: Iceball457 Date: Tue, 18 Feb 2025 02:21:26 -0800 Subject: [PATCH 14/17] Add "queue_" versions of state machine APIs for more flexible state machine control. --- .../AnimationNodeStateMachinePlayback.xml | 17 +++++++++- .../animation_node_state_machine.cpp | 31 ++++++++++++++++--- .../animation/animation_node_state_machine.h | 5 ++- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/doc/classes/AnimationNodeStateMachinePlayback.xml b/doc/classes/AnimationNodeStateMachinePlayback.xml index e6e4698af96..c79a59dfc07 100644 --- a/doc/classes/AnimationNodeStateMachinePlayback.xml +++ b/doc/classes/AnimationNodeStateMachinePlayback.xml @@ -91,7 +91,22 @@ - Transitions from the current state to another one, using the state machine's default transition to jump to the destination state. + Transitions from the current state directly to another one, using the state machine's default transition if a direct connection is not available. + + + + + + + Appends a series of transitions from the last state in the current travel path to another one, following the shortest path. + If the path does not connect, the state machine's default transition will be used to jump to the destination state. + + + + + + + Appends a single transition to the requested state to the end of the current state machine travel path, using the state machine's default transition if a direct connection is not available. diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index 567c75cfeb4..1a2d5a450cb 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -257,13 +257,24 @@ void AnimationNodeStateMachinePlayback::_set_grouped(bool p_is_grouped) { void AnimationNodeStateMachinePlayback::travel(const StringName &p_state) { ERR_FAIL_COND_EDMSG(is_grouped, "Grouped AnimationNodeStateMachinePlayback must be handled by parent AnimationNodeStateMachinePlayback. You need to retrieve the parent Root/Nested AnimationNodeStateMachine."); ERR_FAIL_COND_EDMSG(String(p_state).contains("/Start") || String(p_state).contains("/End"), "Grouped AnimationNodeStateMachinePlayback doesn't allow to play Start/End directly. Instead, play the prev or next state of group in the parent AnimationNodeStateMachine."); - _travel_main(p_state, false, false); + _travel_main(p_state, false, false, true); } void AnimationNodeStateMachinePlayback::jump(const StringName &p_state) { ERR_FAIL_COND_EDMSG(is_grouped, "Grouped AnimationNodeStateMachinePlayback must be handled by parent AnimationNodeStateMachinePlayback. You need to retrieve the parent Root/Nested AnimationNodeStateMachine."); ERR_FAIL_COND_EDMSG(String(p_state).contains("/Start") || String(p_state).contains("/End"), "Grouped AnimationNodeStateMachinePlayback doesn't allow to play Start/End directly. Instead, play the prev or next state of group in the parent AnimationNodeStateMachine."); - _travel_main(p_state, false, true); + _travel_main(p_state, false, true, true); +} +void AnimationNodeStateMachinePlayback::queue_travel(const StringName &p_state) { + ERR_FAIL_COND_EDMSG(is_grouped, "Grouped AnimationNodeStateMachinePlayback must be handled by parent AnimationNodeStateMachinePlayback. You need to retrieve the parent Root/Nested AnimationNodeStateMachine."); + ERR_FAIL_COND_EDMSG(String(p_state).contains("/Start") || String(p_state).contains("/End"), "Grouped AnimationNodeStateMachinePlayback doesn't allow to play Start/End directly. Instead, play the prev or next state of group in the parent AnimationNodeStateMachine."); + _travel_main(p_state, false, false, false); +} + +void AnimationNodeStateMachinePlayback::queue_jump(const StringName &p_state) { + ERR_FAIL_COND_EDMSG(is_grouped, "Grouped AnimationNodeStateMachinePlayback must be handled by parent AnimationNodeStateMachinePlayback. You need to retrieve the parent Root/Nested AnimationNodeStateMachine."); + ERR_FAIL_COND_EDMSG(String(p_state).contains("/Start") || String(p_state).contains("/End"), "Grouped AnimationNodeStateMachinePlayback doesn't allow to play Start/End directly. Instead, play the prev or next state of group in the parent AnimationNodeStateMachine."); + _travel_main(p_state, false, true, false); } void AnimationNodeStateMachinePlayback::start(const StringName &p_state, bool p_reset) { @@ -282,11 +293,12 @@ void AnimationNodeStateMachinePlayback::stop() { _stop_main(); } -void AnimationNodeStateMachinePlayback::_travel_main(const StringName &p_state, bool p_reset_on_teleport, bool p_force_jump) { +void AnimationNodeStateMachinePlayback::_travel_main(const StringName &p_state, bool p_reset_on_teleport, bool p_force_jump, bool p_retain_path) { travel_request = p_state; reset_request_on_teleport = p_reset_on_teleport; stop_request = false; jump_request = p_force_jump; + retain_path_request = p_retain_path; } void AnimationNodeStateMachinePlayback::_start_main(const StringName &p_state, bool p_reset) { @@ -786,7 +798,12 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St // Process to travel. if (can_travel) { - path = new_path; + if (retain_path_request) { + retain_path_request = false; + path.append_array(new_path); + } else { + path = new_path; + } } else { // Can't travel via explicit transitions, then travel directly (will use default_transition). jump_request = true; @@ -795,7 +812,11 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St if (jump_request) { if (p_state_machine->states.has(temp_travel_request)) { - path.clear(); + if (retain_path_request) { + retain_path_request = false; + } else { + path.clear(); + } if (p_state_machine->is_allow_transition_to_self() || current != temp_travel_request) { path.push_back(temp_travel_request); } diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h index 5dc5419d48d..edae62a4085 100644 --- a/scene/animation/animation_node_state_machine.h +++ b/scene/animation/animation_node_state_machine.h @@ -289,10 +289,11 @@ class AnimationNodeStateMachinePlayback : public Resource { bool stop_request = false; bool teleport_request = false; bool jump_request = false; + bool retain_path_request = false; bool is_grouped = false; - void _travel_main(const StringName &p_state, bool p_reset_on_teleport = true, bool p_force_jump = false); + void _travel_main(const StringName &p_state, bool p_reset_on_teleport = true, bool p_force_jump = false, bool p_retain_path = false); void _start_main(const StringName &p_state, bool p_reset = true); void _next_main(); void _stop_main(); @@ -331,6 +332,8 @@ protected: public: void travel(const StringName &p_state); void jump(const StringName &p_state); + void queue_travel(const StringName &p_state); + void queue_jump(const StringName &p_state); void start(const StringName &p_state, bool p_reset = true); void next(); void stop(); From ed53ce769808eda0256a571f40d5ed00dedeb4a2 Mon Sep 17 00:00:00 2001 From: Iceball457 Date: Tue, 18 Feb 2025 03:04:25 -0800 Subject: [PATCH 15/17] Rename `queue_jump` to `queue`. --- doc/classes/AnimationNodeStateMachinePlayback.xml | 4 ++-- scene/animation/animation_node_state_machine.cpp | 2 +- scene/animation/animation_node_state_machine.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/classes/AnimationNodeStateMachinePlayback.xml b/doc/classes/AnimationNodeStateMachinePlayback.xml index c79a59dfc07..653d08bd67f 100644 --- a/doc/classes/AnimationNodeStateMachinePlayback.xml +++ b/doc/classes/AnimationNodeStateMachinePlayback.xml @@ -102,11 +102,11 @@ If the path does not connect, the state machine's default transition will be used to jump to the destination state. - + - Appends a single transition to the requested state to the end of the current state machine travel path, using the state machine's default transition if a direct connection is not available. + Queues the requested state, using the state machine's default transition if a direct connection is not available. diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index 1a2d5a450cb..3ecb324836a 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -271,7 +271,7 @@ void AnimationNodeStateMachinePlayback::queue_travel(const StringName &p_state) _travel_main(p_state, false, false, false); } -void AnimationNodeStateMachinePlayback::queue_jump(const StringName &p_state) { +void AnimationNodeStateMachinePlayback::queue(const StringName &p_state) { ERR_FAIL_COND_EDMSG(is_grouped, "Grouped AnimationNodeStateMachinePlayback must be handled by parent AnimationNodeStateMachinePlayback. You need to retrieve the parent Root/Nested AnimationNodeStateMachine."); ERR_FAIL_COND_EDMSG(String(p_state).contains("/Start") || String(p_state).contains("/End"), "Grouped AnimationNodeStateMachinePlayback doesn't allow to play Start/End directly. Instead, play the prev or next state of group in the parent AnimationNodeStateMachine."); _travel_main(p_state, false, true, false); diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h index edae62a4085..3b8cc716a0a 100644 --- a/scene/animation/animation_node_state_machine.h +++ b/scene/animation/animation_node_state_machine.h @@ -333,7 +333,7 @@ public: void travel(const StringName &p_state); void jump(const StringName &p_state); void queue_travel(const StringName &p_state); - void queue_jump(const StringName &p_state); + void queue(const StringName &p_state); void start(const StringName &p_state, bool p_reset = true); void next(); void stop(); From 52b61d5322991600cde97bf77bdd7eb2d22ae27e Mon Sep 17 00:00:00 2001 From: Iceball457 Date: Tue, 18 Feb 2025 03:27:58 -0800 Subject: [PATCH 16/17] Remove `jump` as it exposes duplicate behavior. --- doc/classes/AnimationNodeStateMachinePlayback.xml | 9 +-------- scene/animation/animation_node_state_machine.cpp | 5 ----- scene/animation/animation_node_state_machine.h | 1 - 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/doc/classes/AnimationNodeStateMachinePlayback.xml b/doc/classes/AnimationNodeStateMachinePlayback.xml index 653d08bd67f..7439a42026d 100644 --- a/doc/classes/AnimationNodeStateMachinePlayback.xml +++ b/doc/classes/AnimationNodeStateMachinePlayback.xml @@ -84,14 +84,7 @@ Transitions from the current state to another one, following the shortest path. - If the path does not connect from the current state, the state machine's default transition will be used to jump to the destination state. - - - - - - - Transitions from the current state directly to another one, using the state machine's default transition if a direct connection is not available. + If the path does not connect from the current state, the state machine's default transition will be used to jump directly to the destination state. diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index 3ecb324836a..8b5aa9349b6 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -260,11 +260,6 @@ void AnimationNodeStateMachinePlayback::travel(const StringName &p_state) { _travel_main(p_state, false, false, true); } -void AnimationNodeStateMachinePlayback::jump(const StringName &p_state) { - ERR_FAIL_COND_EDMSG(is_grouped, "Grouped AnimationNodeStateMachinePlayback must be handled by parent AnimationNodeStateMachinePlayback. You need to retrieve the parent Root/Nested AnimationNodeStateMachine."); - ERR_FAIL_COND_EDMSG(String(p_state).contains("/Start") || String(p_state).contains("/End"), "Grouped AnimationNodeStateMachinePlayback doesn't allow to play Start/End directly. Instead, play the prev or next state of group in the parent AnimationNodeStateMachine."); - _travel_main(p_state, false, true, true); -} void AnimationNodeStateMachinePlayback::queue_travel(const StringName &p_state) { ERR_FAIL_COND_EDMSG(is_grouped, "Grouped AnimationNodeStateMachinePlayback must be handled by parent AnimationNodeStateMachinePlayback. You need to retrieve the parent Root/Nested AnimationNodeStateMachine."); ERR_FAIL_COND_EDMSG(String(p_state).contains("/Start") || String(p_state).contains("/End"), "Grouped AnimationNodeStateMachinePlayback doesn't allow to play Start/End directly. Instead, play the prev or next state of group in the parent AnimationNodeStateMachine."); diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h index 3b8cc716a0a..de2cf79809b 100644 --- a/scene/animation/animation_node_state_machine.h +++ b/scene/animation/animation_node_state_machine.h @@ -331,7 +331,6 @@ protected: public: void travel(const StringName &p_state); - void jump(const StringName &p_state); void queue_travel(const StringName &p_state); void queue(const StringName &p_state); void start(const StringName &p_state, bool p_reset = true); From 321fca7e988dfe6babbe2dd569676e7cc8ac1dfc Mon Sep 17 00:00:00 2001 From: Iceball457 Date: Tue, 18 Feb 2025 06:01:02 -0800 Subject: [PATCH 17/17] Minimize API changes. Travel paths for queue_travel are now calculated from the end of the travel path. --- .../AnimationNodeStateMachinePlayback.xml | 2 + .../animation_node_state_machine.cpp | 64 +++++++++++++------ .../animation/animation_node_state_machine.h | 3 +- 3 files changed, 48 insertions(+), 21 deletions(-) diff --git a/doc/classes/AnimationNodeStateMachinePlayback.xml b/doc/classes/AnimationNodeStateMachinePlayback.xml index 7439a42026d..9a31dca5e55 100644 --- a/doc/classes/AnimationNodeStateMachinePlayback.xml +++ b/doc/classes/AnimationNodeStateMachinePlayback.xml @@ -100,6 +100,8 @@ Queues the requested state, using the state machine's default transition if a direct connection is not available. + + You can emulate traveling with a transition by calling [code]clear_path()[/code] beforehand. diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index 8b5aa9349b6..28727a8d7d3 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -254,22 +254,30 @@ void AnimationNodeStateMachinePlayback::_set_grouped(bool p_is_grouped) { is_grouped = p_is_grouped; } -void AnimationNodeStateMachinePlayback::travel(const StringName &p_state) { +void AnimationNodeStateMachinePlayback::travel(const StringName &p_state, bool p_reset_on_teleport) { ERR_FAIL_COND_EDMSG(is_grouped, "Grouped AnimationNodeStateMachinePlayback must be handled by parent AnimationNodeStateMachinePlayback. You need to retrieve the parent Root/Nested AnimationNodeStateMachine."); ERR_FAIL_COND_EDMSG(String(p_state).contains("/Start") || String(p_state).contains("/End"), "Grouped AnimationNodeStateMachinePlayback doesn't allow to play Start/End directly. Instead, play the prev or next state of group in the parent AnimationNodeStateMachine."); - _travel_main(p_state, false, false, true); + + reset_request_on_teleport = p_reset_on_teleport; + + _travel_main(p_state, p_reset_on_teleport, false, false); } void AnimationNodeStateMachinePlayback::queue_travel(const StringName &p_state) { ERR_FAIL_COND_EDMSG(is_grouped, "Grouped AnimationNodeStateMachinePlayback must be handled by parent AnimationNodeStateMachinePlayback. You need to retrieve the parent Root/Nested AnimationNodeStateMachine."); ERR_FAIL_COND_EDMSG(String(p_state).contains("/Start") || String(p_state).contains("/End"), "Grouped AnimationNodeStateMachinePlayback doesn't allow to play Start/End directly. Instead, play the prev or next state of group in the parent AnimationNodeStateMachine."); - _travel_main(p_state, false, false, false); + + // This might be able to be a parameter on both 'travel' + // It cannot be false in 'queue_travel' otherwise the teleport would delete the queue anyway... + request_jump_instead = true; + + _travel_main(p_state, false, false, true); } void AnimationNodeStateMachinePlayback::queue(const StringName &p_state) { ERR_FAIL_COND_EDMSG(is_grouped, "Grouped AnimationNodeStateMachinePlayback must be handled by parent AnimationNodeStateMachinePlayback. You need to retrieve the parent Root/Nested AnimationNodeStateMachine."); ERR_FAIL_COND_EDMSG(String(p_state).contains("/Start") || String(p_state).contains("/End"), "Grouped AnimationNodeStateMachinePlayback doesn't allow to play Start/End directly. Instead, play the prev or next state of group in the parent AnimationNodeStateMachine."); - _travel_main(p_state, false, true, false); + _travel_main(p_state, false, true, true); } void AnimationNodeStateMachinePlayback::start(const StringName &p_state, bool p_reset) { @@ -529,24 +537,31 @@ String AnimationNodeStateMachinePlayback::_validate_path(AnimationNodeStateMachi } bool AnimationNodeStateMachinePlayback::_make_travel_path(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_is_allow_transition_to_self, Vector &r_path, bool p_test_only) { - StringName travel = travel_request; + StringName travel_from; + if (retain_path_request) { + travel_from = r_path[r_path.size() - 1]; + } else { + travel_from = current; + } + + StringName travel_to = travel_request; travel_request = StringName(); if (!playing) { _start(p_state_machine); } - ERR_FAIL_COND_V(!p_state_machine->states.has(travel), false); - ERR_FAIL_COND_V(!p_state_machine->states.has(current), false); + ERR_FAIL_COND_V(!p_state_machine->states.has(travel_to), false); + ERR_FAIL_COND_V(!p_state_machine->states.has(travel_from), false); - if (current == travel) { + if (travel_from == travel_to) { return !p_is_allow_transition_to_self; } Vector new_path; - Vector2 current_pos = p_state_machine->states[current].position; - Vector2 target_pos = p_state_machine->states[travel].position; + Vector2 current_pos = p_state_machine->states[travel_from].position; + Vector2 target_pos = p_state_machine->states[travel_to].position; bool found_route = false; HashMap cost_map; @@ -559,16 +574,16 @@ bool AnimationNodeStateMachinePlayback::_make_travel_path(AnimationTree *p_tree, continue; } - if (p_state_machine->transitions[i].from == current) { + if (p_state_machine->transitions[i].from == travel_from) { open_list.push_back(i); float cost = p_state_machine->states[p_state_machine->transitions[i].to].position.distance_to(current_pos); cost *= p_state_machine->transitions[i].transition->get_priority(); AStarCost ap; - ap.prev = current; + ap.prev = travel_from; ap.distance = cost; cost_map[p_state_machine->transitions[i].to] = ap; - if (p_state_machine->transitions[i].to == travel) { // Prematurely found it! :D + if (p_state_machine->transitions[i].to == travel_to) { // Prematurely found it! :D found_route = true; break; } @@ -581,7 +596,7 @@ bool AnimationNodeStateMachinePlayback::_make_travel_path(AnimationTree *p_tree, break; // No path found. } - // Find the last cost transition. + // Find the least cost transition. List::Element *least_cost_transition = nullptr; float least_cost = 1e20; @@ -626,7 +641,7 @@ bool AnimationNodeStateMachinePlayback::_make_travel_path(AnimationTree *p_tree, open_list.push_back(i); - if (p_state_machine->transitions[i].to == travel) { + if (p_state_machine->transitions[i].to == travel_to) { found_route = true; break; } @@ -643,8 +658,8 @@ bool AnimationNodeStateMachinePlayback::_make_travel_path(AnimationTree *p_tree, // Check child grouped state machine. if (found_route) { // Make path. - StringName at = travel; - while (at != current) { + StringName at = travel_to; + while (at != travel_from) { new_path.push_back(at); at = cost_map[at].prev; } @@ -653,7 +668,7 @@ bool AnimationNodeStateMachinePlayback::_make_travel_path(AnimationTree *p_tree, // Check internal paths of child grouped state machine. // For example: // [current - End] - [Start - End] - [Start - End] - [Start - target] - String current_path = current; + String current_path = travel_from; int len = new_path.size() + 1; for (int i = 0; i < len; i++) { Ref anodesm = p_state_machine->find_node_by_path(current_path); @@ -800,8 +815,17 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St path = new_path; } } else { - // Can't travel via explicit transitions, then travel directly (will use default_transition). - jump_request = true; + // Can't travel via explicit transitions, then travel directly... + if (request_jump_instead) { + // using 'default_transition'. + request_jump_instead = false; + jump_request = true; + } else { + // by teleporting immediately. + _set_current(p_state_machine, temp_travel_request); + reset_request = reset_request_on_teleport; + teleport_request = true; + } } } diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h index de2cf79809b..8f218f82b57 100644 --- a/scene/animation/animation_node_state_machine.h +++ b/scene/animation/animation_node_state_machine.h @@ -287,6 +287,7 @@ class AnimationNodeStateMachinePlayback : public Resource { bool _reset_request_for_fading_from = false; bool next_request = false; bool stop_request = false; + bool request_jump_instead = false; bool teleport_request = false; bool jump_request = false; bool retain_path_request = false; @@ -330,7 +331,7 @@ protected: static void _bind_methods(); public: - void travel(const StringName &p_state); + void travel(const StringName &p_state, bool p_reset_on_teleport = true); void queue_travel(const StringName &p_state); void queue(const StringName &p_state); void start(const StringName &p_state, bool p_reset = true);