From 6eb7554254407d95fb2341cd76716cad06f2bd58 Mon Sep 17 00:00:00 2001 From: Adam Johnston Date: Mon, 3 Feb 2025 02:37:34 -0800 Subject: [PATCH] Add node started/finished signals for animation state machines --- .../AnimationNodeStateMachinePlayback.xml | 14 ++++++ .../animation_node_state_machine.cpp | 46 +++++++++++++++++-- .../animation/animation_node_state_machine.h | 1 + scene/scene_string_names.h | 2 + 4 files changed, 59 insertions(+), 4 deletions(-) diff --git a/doc/classes/AnimationNodeStateMachinePlayback.xml b/doc/classes/AnimationNodeStateMachinePlayback.xml index 891dfa9f7549..b12f0fc34344 100644 --- a/doc/classes/AnimationNodeStateMachinePlayback.xml +++ b/doc/classes/AnimationNodeStateMachinePlayback.xml @@ -93,4 +93,18 @@ + + + + + Emitted when the [param node] finishes playback. If [param node] is a state machine set to grouped mode, its signals are passed through with its name prefixed. + + + + + + Emitted when the [param node] starts playback. If [param node] is a state machine set to grouped mode, its signals are passed through with its name prefixed. + + + diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index 19acf8c1b161..4905e160a0d6 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -204,6 +204,7 @@ void AnimationNodeStateMachinePlayback::_set_current(AnimationNodeStateMachine * if (anodesm.is_null()) { group_start_transition = Ref(); group_end_transition = Ref(); + emit_signal(SceneStringName(node_started), current); return; } @@ -223,8 +224,16 @@ void AnimationNodeStateMachinePlayback::_set_current(AnimationNodeStateMachine * group_end_transition = Ref(); } - // Validation. + // Validate grouped child state machine and ensure signals are connected for propagation. if (anodesm->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) { + Ref playback = p_state_machine->get_animation_tree()->get(base_path + current + "/playback"); + Callable callable = callable_mp(this, &AnimationNodeStateMachinePlayback::_propagate_grouped_child_signal); + if (!playback->is_connected(SceneStringName(node_started), callable)) { + String concatenated = vformat("%s/", current); + playback->connect(SceneStringName(node_started), callable.bind(concatenated, true)); + playback->connect(SceneStringName(node_finished), callable.bind(concatenated, false)); + } + indices = anodesm->find_transition_from(SceneStringName(Start)); int anodesm_start_size = indices.size(); indices = anodesm->find_transition_to(SceneStringName(End)); @@ -247,6 +256,8 @@ void AnimationNodeStateMachinePlayback::_set_current(AnimationNodeStateMachine * if (anodesm_end_size != group_end_size) { ERR_PRINT_ED("There is a mismatch in the number of end transitions in and out of the Grouped AnimationNodeStateMachine on AnimationNodeStateMachine: " + base_path + current + "."); } + } else { + emit_signal(SceneStringName(node_started), current); } } @@ -346,12 +357,27 @@ float AnimationNodeStateMachinePlayback::get_fading_pos() const { return fading_pos; } +bool _is_grouped_state_machine(const Ref p_node) { + return p_node.is_valid() && p_node->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED; +} + +void AnimationNodeStateMachinePlayback::_propagate_grouped_child_signal(const StringName p_child_current, String p_child_name, bool p_started) { + if (p_child_current == SceneStringName(Start) || p_child_current == SceneStringName(End)) { + return; + } + if (p_started) { + emit_signal(SceneStringName(node_started), p_child_name + p_child_current); + } else { + emit_signal(SceneStringName(node_finished), p_child_name + p_child_current); + } +} + void AnimationNodeStateMachinePlayback::_clear_path_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_test_only) { List child_nodes; p_state_machine->get_child_nodes(&child_nodes); for (const AnimationNode::ChildNode &child_node : child_nodes) { Ref anodesm = child_node.node; - if (anodesm.is_valid() && anodesm->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) { + if (_is_grouped_state_machine(anodesm)) { Ref playback = p_tree->get(base_path + child_node.name + "/playback"); ERR_FAIL_COND(playback.is_null()); playback->_set_base_path(base_path + child_node.name + "/"); @@ -789,7 +815,7 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St if (teleport_request) { teleport_request = false; - // Clear fadeing on teleport. + // Clear fading on teleport. fading_from = StringName(); fadeing_from_nti = AnimationNode::NodeTimeInfo(); fading_pos = 0; @@ -871,6 +897,9 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St if (Animation::is_greater_or_equal_approx(fading_pos, fading_time)) { // Finish fading. + if (!fading_from.is_empty() && !_is_grouped_state_machine(p_state_machine->get_node(fading_from))) { + emit_signal(SceneStringName(node_finished), fading_from); + } fading_from = StringName(); fadeing_from_nti = AnimationNode::NodeTimeInfo(); } @@ -932,6 +961,9 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT pi.weight = 0; p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); } + if (!current.is_empty() && !_is_grouped_state_machine(p_state_machine->get_node(current))) { + emit_signal(SceneStringName(node_finished), current); + } fading_from = StringName(); fadeing_from_nti = AnimationNode::NodeTimeInfo(); fading_time = 0; @@ -998,7 +1030,10 @@ bool AnimationNodeStateMachinePlayback::_can_transition_to_next(AnimationTree *p playback = playback->duplicate(); } playback->_next_main(); - // Then, fadeing should be end. + // Then, fading should end. + if (!fading_from.is_empty() && !_is_grouped_state_machine(p_state_machine->get_node(fading_from))) { + emit_signal(SceneStringName(node_finished), fading_from); + } fading_from = StringName(); fadeing_from_nti = AnimationNode::NodeTimeInfo(); fading_pos = 0; @@ -1194,6 +1229,9 @@ void AnimationNodeStateMachinePlayback::_bind_methods() { ClassDB::bind_method(D_METHOD("get_current_length"), &AnimationNodeStateMachinePlayback::get_current_length); ClassDB::bind_method(D_METHOD("get_fading_from_node"), &AnimationNodeStateMachinePlayback::get_fading_from_node); ClassDB::bind_method(D_METHOD("get_travel_path"), &AnimationNodeStateMachinePlayback::_get_travel_path); + + ADD_SIGNAL(MethodInfo(SceneStringName(node_started), PropertyInfo(Variant::STRING_NAME, "node"))); + ADD_SIGNAL(MethodInfo(SceneStringName(node_finished), PropertyInfo(Variant::STRING_NAME, "node"))); } AnimationNodeStateMachinePlayback::AnimationNodeStateMachinePlayback() { diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h index ae28c126b3a3..633ece61dfa4 100644 --- a/scene/animation/animation_node_state_machine.h +++ b/scene/animation/animation_node_state_machine.h @@ -288,6 +288,7 @@ class AnimationNodeStateMachinePlayback : public Resource { bool is_grouped = false; + void _propagate_grouped_child_signal(const StringName p_child_current, String p_child_name, bool p_started); void _travel_main(const StringName &p_state, bool p_reset_on_teleport = true); void _start_main(const StringName &p_state, bool p_reset = true); void _next_main(); diff --git a/scene/scene_string_names.h b/scene/scene_string_names.h index 068487e45786..3da0888443d6 100644 --- a/scene/scene_string_names.h +++ b/scene/scene_string_names.h @@ -154,6 +154,8 @@ class SceneStringNames { const StringName Start = StaticCString::create("Start"); const StringName End = StaticCString::create("End"); + const StringName node_started = StaticCString::create("node_started"); + const StringName node_finished = StaticCString::create("node_finished"); const StringName FlatButton = StaticCString::create("FlatButton"); };