From d60102dd9b86576ec524840f1190a0e8444c66b3 Mon Sep 17 00:00:00 2001 From: Adam Johnston Date: Mon, 3 Feb 2025 15:50:06 -0800 Subject: [PATCH] Add node_notify signal for animation trees --- doc/classes/AnimationNodeOneShot.xml | 12 +++++++ doc/classes/AnimationNodeStateMachine.xml | 6 ++++ doc/classes/AnimationNodeTransition.xml | 8 +++++ doc/classes/AnimationTree.xml | 7 ++++ scene/animation/animation_blend_tree.cpp | 33 +++++++++++++++++-- scene/animation/animation_blend_tree.h | 14 ++++++++ .../animation_node_state_machine.cpp | 33 ++++++++++++++----- .../animation/animation_node_state_machine.h | 7 +++- scene/animation/animation_tree.cpp | 7 ++++ scene/animation/animation_tree.h | 2 ++ scene/scene_string_names.h | 1 + 11 files changed, 118 insertions(+), 12 deletions(-) diff --git a/doc/classes/AnimationNodeOneShot.xml b/doc/classes/AnimationNodeOneShot.xml index 28bea47e219..d16b692b7d3 100644 --- a/doc/classes/AnimationNodeOneShot.xml +++ b/doc/classes/AnimationNodeOneShot.xml @@ -106,5 +106,17 @@ Blends two animations additively. See also [AnimationNodeAdd2]. + + The oneshot node has started playback. + + + The oneshot node has finished playback. + + + The oneshot node has finished fading in. Only emitted if fade-in is enabled. + + + The oneshot node has started fading out. Only emitted if fade-out is enabled. + diff --git a/doc/classes/AnimationNodeStateMachine.xml b/doc/classes/AnimationNodeStateMachine.xml index e80b1f00b02..269d2a55c00 100644 --- a/doc/classes/AnimationNodeStateMachine.xml +++ b/doc/classes/AnimationNodeStateMachine.xml @@ -183,5 +183,11 @@ This is a grouped state machine that can be controlled from a parent state machine. It does not work independently. There must be a state machine with [member state_machine_type] of [constant STATE_MACHINE_TYPE_ROOT] or [constant STATE_MACHINE_TYPE_NESTED] in the parent or ancestor. + + The state machine has started playback. + + + The state machine has finished playback. This is emitted if the terminal state is reached or if playback is manually stopped, but not if the state machine is stuck in a non-terminal state. + diff --git a/doc/classes/AnimationNodeTransition.xml b/doc/classes/AnimationNodeTransition.xml index af80fef73a2..f62b0536de4 100644 --- a/doc/classes/AnimationNodeTransition.xml +++ b/doc/classes/AnimationNodeTransition.xml @@ -103,4 +103,12 @@ [b]Note:[/b] [AnimationNodeTransition] transitions the current state immediately after the start of the fading. The precise remaining time can only be inferred from the main animation. When [AnimationNodeOutput] is considered as the most upstream, so the [member xfade_time] is not scaled depending on the downstream delta. See also [member AnimationNodeOneShot.fadeout_time]. + + + The node has started transitioning to the next state. This is emitted immediately before the current state changes to the requested state. + + + The node has finished transitioning to the next state. + + diff --git a/doc/classes/AnimationTree.xml b/doc/classes/AnimationTree.xml index 4a63b35ba05..1a15977a693 100644 --- a/doc/classes/AnimationTree.xml +++ b/doc/classes/AnimationTree.xml @@ -40,6 +40,13 @@ + + + + + Emitted when the animation node located at [param animation_node_path] state changes during processing or playback. + + Emitted when the [member anim_player] is changed. diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp index 019a14e556c..144f4c3e152 100644 --- a/scene/animation/animation_blend_tree.cpp +++ b/scene/animation/animation_blend_tree.cpp @@ -535,6 +535,18 @@ bool AnimationNodeOneShot::has_filter() const { return true; } +void AnimationNodeOneShot::_check_and_notify_state_changes(bool p_active, bool p_internal_active, double p_fade_in_remaining) { + if (Animation::is_greater_approx(p_fade_in_remaining, 0) && Math::is_zero_approx((double)get_parameter(fade_in_remaining))) { + _animation_tree_notify(ANIMATION_NODE_NOTIFICATION_ONESHOT_FADEIN_FINISHED); + } + if (get_fade_out_time() > 0 && p_internal_active && p_internal_active != (bool)get_parameter(internal_active)) { + _animation_tree_notify(ANIMATION_NODE_NOTIFICATION_ONESHOT_FADEOUT_STARTED); + } + if (p_active != (bool)get_parameter(active)) { + _animation_tree_notify(p_active ? ANIMATION_NODE_NOTIFICATION_ONESHOT_FINISHED : ANIMATION_NODE_NOTIFICATION_ONESHOT_STARTED); + } +} + AnimationNode::NodeTimeInfo AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { OneShotRequest cur_request = static_cast((int)get_parameter(request)); bool cur_active = get_parameter(active); @@ -555,6 +567,7 @@ AnimationNode::NodeTimeInfo AnimationNodeOneShot::_process(const AnimationMixer: double abs_delta = Math::abs(p_delta); bool p_seek = p_playback_info.seeked; bool p_is_external_seeking = p_playback_info.is_external_seeking; + double prev_fade_in_remaining = cur_fade_in_remaining; if (Math::is_zero_approx(p_time) && p_seek && !p_is_external_seeking) { clear_remaining_fade = true; // Reset occurs. @@ -605,6 +618,7 @@ AnimationNode::NodeTimeInfo AnimationNodeOneShot::_process(const AnimationMixer: } if (!is_shooting) { + _check_and_notify_state_changes(cur_active, cur_internal_active, prev_fade_in_remaining); AnimationMixer::PlaybackInfo pi = p_playback_info; pi.weight = 1.0; return blend_input(0, pi, FILTER_IGNORE, sync, p_test_only); @@ -696,6 +710,7 @@ AnimationNode::NodeTimeInfo AnimationNodeOneShot::_process(const AnimationMixer: set_parameter(fade_in_remaining, cur_fade_in_remaining); set_parameter(fade_out_remaining, cur_fade_out_remaining); + _check_and_notify_state_changes(cur_active, cur_internal_active, prev_fade_in_remaining); return cur_internal_active ? os_nti : main_nti; } @@ -748,6 +763,11 @@ void AnimationNodeOneShot::_bind_methods() { BIND_ENUM_CONSTANT(MIX_MODE_BLEND); BIND_ENUM_CONSTANT(MIX_MODE_ADD); + + BIND_CONSTANT(ANIMATION_NODE_NOTIFICATION_ONESHOT_STARTED); + BIND_CONSTANT(ANIMATION_NODE_NOTIFICATION_ONESHOT_FINISHED); + BIND_CONSTANT(ANIMATION_NODE_NOTIFICATION_ONESHOT_FADEIN_FINISHED); + BIND_CONSTANT(ANIMATION_NODE_NOTIFICATION_ONESHOT_FADEOUT_STARTED); } AnimationNodeOneShot::AnimationNodeOneShot() { @@ -1297,6 +1317,7 @@ AnimationNode::NodeTimeInfo AnimationNodeTransition::_process(const AnimationMix clear_remaining_fade = true; } } else { + _animation_tree_notify(ANIMATION_NODE_NOTIFICATION_TRANSITION_STARTED); switched = true; cur_prev_index = cur_current_index; set_parameter(prev_index, cur_current_index); @@ -1309,6 +1330,9 @@ AnimationNode::NodeTimeInfo AnimationNodeTransition::_process(const AnimationMix } cur_transition_request = String(); set_parameter(transition_request, cur_transition_request); + if (switched && Animation::is_less_or_equal_approx(xfade_time, 0)) { + _animation_tree_notify(ANIMATION_NODE_NOTIFICATION_TRANSITION_FINISHED); + } } if (clear_remaining_fade) { @@ -1381,6 +1405,9 @@ AnimationNode::NodeTimeInfo AnimationNodeTransition::_process(const AnimationMix blend_input(cur_prev_index, pi, FILTER_IGNORE, true, p_test_only); if (!p_seek) { if (Animation::is_less_or_equal_approx(cur_prev_xfading, 0)) { + if (xfade_time > 0) { + _animation_tree_notify(ANIMATION_NODE_NOTIFICATION_TRANSITION_FINISHED); + } set_parameter(prev_index, -1); } cur_prev_xfading -= Math::abs(p_playback_info.delta); @@ -1426,6 +1453,9 @@ void AnimationNodeTransition::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "xfade_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_xfade_curve", "get_xfade_curve"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_transition_to_self"), "set_allow_transition_to_self", "is_allow_transition_to_self"); ADD_PROPERTY(PropertyInfo(Variant::INT, "input_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED, "Inputs,input_"), "set_input_count", "get_input_count"); + + BIND_CONSTANT(ANIMATION_NODE_NOTIFICATION_TRANSITION_STARTED); + BIND_CONSTANT(ANIMATION_NODE_NOTIFICATION_TRANSITION_FINISHED); } AnimationNodeTransition::AnimationNodeTransition() { @@ -1554,7 +1584,6 @@ void AnimationNodeBlendTree::rename_node(const StringName &p_name, const StringN ERR_FAIL_COND(p_new_name == SceneStringName(output)); nodes[p_name].node->disconnect_changed(callable_mp(this, &AnimationNodeBlendTree::_node_changed)); - nodes[p_new_name] = nodes[p_name]; nodes.erase(p_name); @@ -1853,7 +1882,7 @@ void AnimationNodeBlendTree::_initialize_node_tree() { n.node = output; n.position = Vector2(300, 150); n.connections.resize(1); - nodes["output"] = n; + nodes[SceneStringName(output)] = n; } AnimationNodeBlendTree::AnimationNodeBlendTree() { diff --git a/scene/animation/animation_blend_tree.h b/scene/animation/animation_blend_tree.h index 36a391ebdbd..76f30c2fcfe 100644 --- a/scene/animation/animation_blend_tree.h +++ b/scene/animation/animation_blend_tree.h @@ -138,6 +138,13 @@ public: MIX_MODE_ADD }; + enum { + ANIMATION_NODE_NOTIFICATION_ONESHOT_STARTED = 1000, + ANIMATION_NODE_NOTIFICATION_ONESHOT_FINISHED = 1001, + ANIMATION_NODE_NOTIFICATION_ONESHOT_FADEIN_FINISHED = 1002, + ANIMATION_NODE_NOTIFICATION_ONESHOT_FADEOUT_STARTED = 1003 + }; + private: double fade_in = 0.0; Ref fade_in_curve; @@ -157,6 +164,8 @@ private: StringName fade_out_remaining = "fade_out_remaining"; StringName time_to_restart = "time_to_restart"; + void _check_and_notify_state_changes(bool p_active, bool p_internal_active, double p_fade_in_remaining); + protected: static void _bind_methods(); @@ -355,6 +364,11 @@ protected: void _get_property_list(List *p_list) const; public: + enum { + ANIMATION_NODE_NOTIFICATION_TRANSITION_STARTED = 3000, + ANIMATION_NODE_NOTIFICATION_TRANSITION_FINISHED = 3001 + }; + virtual void get_parameter_list(List *r_list) const override; virtual Variant get_parameter_default_value(const StringName &p_parameter) const override; virtual bool is_parameter_read_only(const StringName &p_parameter) const override; diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index 1c4d489d687..a0d4f981aab 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -194,6 +194,7 @@ AnimationNodeStateMachineTransition::AnimationNodeStateMachineTransition() { void AnimationNodeStateMachinePlayback::_set_current(AnimationNodeStateMachine *p_state_machine, const StringName &p_state) { current = p_state; + if (current == StringName()) { group_start_transition = Ref(); group_end_transition = Ref(); @@ -361,7 +362,7 @@ void AnimationNodeStateMachinePlayback::_clear_path_children(AnimationTree *p_tr playback->path.clear(); playback->_clear_path_children(p_tree, anodesm.ptr(), p_test_only); if (current != child_node.name) { - playback->_start(anodesm.ptr()); // Can restart. + playback->_start(anodesm.ptr(), p_test_only); // Can restart. } } } @@ -421,7 +422,7 @@ bool AnimationNodeStateMachinePlayback::_travel_children(AnimationTree *p_tree, playback = playback->duplicate(); } if (!playback->is_playing()) { - playback->_start(anodesm.ptr()); + playback->_start(anodesm.ptr(), p_test_only); } ChildStateMachineInfo child_info; child_info.playback = playback; @@ -448,7 +449,7 @@ bool AnimationNodeStateMachinePlayback::_travel_children(AnimationTree *p_tree, child_path += "/" + child_playback->get_current_node(); } // Force restart target state machine. - playback->_start(anodesm.ptr()); + playback->_start(anodesm.ptr(), p_test_only); } is_parent_same_state = is_current_same_state; @@ -484,12 +485,16 @@ bool AnimationNodeStateMachinePlayback::_travel_children(AnimationTree *p_tree, return found_route; } -void AnimationNodeStateMachinePlayback::_start(AnimationNodeStateMachine *p_state_machine) { +void AnimationNodeStateMachinePlayback::_start(AnimationNodeStateMachine *p_state_machine, bool p_test_only) { playing = true; _set_current(p_state_machine, start_request != StringName() ? start_request : SceneStringName(Start)); teleport_request = true; stop_request = false; start_request = StringName(); + // Only emit the started signal if currently configured for playback + if (!p_test_only && p_state_machine->process_state) { + p_state_machine->_animation_tree_notify(AnimationNodeStateMachine::ANIMATION_NODE_NOTIFICATION_STATE_MACHINE_STARTED); + } } bool AnimationNodeStateMachinePlayback::_travel(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_is_allow_transition_to_self, bool p_test_only) { @@ -519,7 +524,7 @@ bool AnimationNodeStateMachinePlayback::_make_travel_path(AnimationTree *p_tree, travel_request = StringName(); if (!playing) { - _start(p_state_machine); + _start(p_state_machine, p_test_only); } ERR_FAIL_COND_V(!p_state_machine->states.has(travel), false); @@ -651,7 +656,7 @@ bool AnimationNodeStateMachinePlayback::_make_travel_path(AnimationTree *p_tree, playback = playback->duplicate(); } if (i > 0) { - playback->_start(anodesm.ptr()); + playback->_start(anodesm.ptr(), p_test_only); } if (i >= new_path.size()) { break; // Tracing has been finished, needs to break. @@ -705,7 +710,8 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St path.clear(); _clear_path_children(tree, p_state_machine, p_test_only); } - _start(p_state_machine); + playing = false; + _start(p_state_machine, p_test_only); reset_request = true; } else { // Reset current state. @@ -718,7 +724,7 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St start_request = StringName(); travel_request = StringName(); path.clear(); - playing = false; + p_state_machine->_animation_tree_notify(AnimationNodeStateMachine::ANIMATION_NODE_NOTIFICATION_STATE_MACHINE_FINISHED); return AnimationNode::NodeTimeInfo(); } @@ -743,7 +749,7 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St } // Teleport to start. if (p_state_machine->states.has(start_request)) { - _start(p_state_machine); + _start(p_state_machine, p_test_only); } else { StringName node = start_request; ERR_FAIL_V_MSG(AnimationNode::NodeTimeInfo(), "No such node: '" + node + "'"); @@ -883,8 +889,14 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St if (will_end || ((p_state_machine->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_NESTED) && !p_state_machine->has_transition_from(current))) { // There is no next transition. if (fading_from != StringName()) { + if (MAX(current_nti.get_remain(), fadeing_from_nti.get_remain()) <= 0) { + p_state_machine->_animation_tree_notify(AnimationNodeStateMachine::ANIMATION_NODE_NOTIFICATION_STATE_MACHINE_FINISHED); + } return Animation::is_greater_approx(current_nti.get_remain(), fadeing_from_nti.get_remain()) ? current_nti : fadeing_from_nti; } + if (current_nti.get_remain() <= 0) { + p_state_machine->_animation_tree_notify(AnimationNodeStateMachine::ANIMATION_NODE_NOTIFICATION_STATE_MACHINE_FINISHED); + } return current_nti; } @@ -1828,6 +1840,9 @@ void AnimationNodeStateMachine::_bind_methods() { BIND_ENUM_CONSTANT(STATE_MACHINE_TYPE_ROOT); BIND_ENUM_CONSTANT(STATE_MACHINE_TYPE_NESTED); BIND_ENUM_CONSTANT(STATE_MACHINE_TYPE_GROUPED); + + BIND_CONSTANT(ANIMATION_NODE_NOTIFICATION_STATE_MACHINE_STARTED); + BIND_CONSTANT(ANIMATION_NODE_NOTIFICATION_STATE_MACHINE_FINISHED); } Vector AnimationNodeStateMachine::get_nodes_with_transitions_from(const StringName &p_node) const { diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h index ae28c126b3a..7ef630d3de7 100644 --- a/scene/animation/animation_node_state_machine.h +++ b/scene/animation/animation_node_state_machine.h @@ -116,6 +116,11 @@ public: STATE_MACHINE_TYPE_GROUPED, }; + enum { + ANIMATION_NODE_NOTIFICATION_STATE_MACHINE_STARTED = 2000, + ANIMATION_NODE_NOTIFICATION_STATE_MACHINE_FINISHED = 2001 + }; + private: friend class AnimationNodeStateMachinePlayback; @@ -296,7 +301,7 @@ class AnimationNodeStateMachinePlayback : public Resource { bool _make_travel_path(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_is_allow_transition_to_self, Vector &r_path, bool p_test_only); String _validate_path(AnimationNodeStateMachine *p_state_machine, const String &p_path); bool _travel(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_is_allow_transition_to_self, bool p_test_only); - void _start(AnimationNodeStateMachine *p_state_machine); + void _start(AnimationNodeStateMachine *p_state_machine, bool p_test_only); void _clear_path_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_test_only); bool _travel_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const String &p_path, bool p_is_allow_transition_to_self, bool p_is_parent_same_state, bool p_test_only); diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index f1c01b1e807..1b9010138a3 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -589,6 +589,12 @@ void AnimationNode::_bind_methods() { BIND_ENUM_CONSTANT(FILTER_BLEND); } +void AnimationNode::_animation_tree_notify(int p_what) { + ERR_FAIL_NULL(process_state); + ERR_FAIL_NULL(process_state->tree); + process_state->tree->emit_signal(SceneStringName(animation_node_notification), process_state->tree->property_reference_map[get_instance_id()], p_what); +} + AnimationNode::AnimationNode() { } @@ -991,6 +997,7 @@ void AnimationTree::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "anim_player", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "AnimationPlayer"), "set_animation_player", "get_animation_player"); ADD_SIGNAL(MethodInfo(SNAME("animation_player_changed"))); + ADD_SIGNAL(MethodInfo(SceneStringName(animation_node_notification), PropertyInfo(Variant::STRING_NAME, "animation_node_path"), PropertyInfo(Variant::INT, "what"))); } AnimationTree::AnimationTree() { diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h index 5ac24b49857..f99508dd4eb 100644 --- a/scene/animation/animation_tree.h +++ b/scene/animation/animation_tree.h @@ -181,6 +181,8 @@ protected: void _validate_property(PropertyInfo &p_property) const; + void _animation_tree_notify(int p_what); + GDVIRTUAL0RC(Dictionary, _get_child_nodes) GDVIRTUAL0RC(Array, _get_parameter_list) GDVIRTUAL1RC(Ref, _get_child_by_name, StringName) diff --git a/scene/scene_string_names.h b/scene/scene_string_names.h index 068487e4578..faf47364273 100644 --- a/scene/scene_string_names.h +++ b/scene/scene_string_names.h @@ -85,6 +85,7 @@ public: const StringName sort_children = StaticCString::create("sort_children"); const StringName finished = StaticCString::create("finished"); + const StringName animation_node_notification = StaticCString::create("animation_node_notification"); const StringName animation_finished = StaticCString::create("animation_finished"); const StringName animation_changed = StaticCString::create("animation_changed"); const StringName animation_started = StaticCString::create("animation_started");