diff --git a/doc/classes/AnimationNode.xml b/doc/classes/AnimationNode.xml index 8204b456d970..533c3c57275c 100644 --- a/doc/classes/AnimationNode.xml +++ b/doc/classes/AnimationNode.xml @@ -170,6 +170,34 @@ This function should return the time left for the current animation to finish (if unsure, pass the value from the main blend being called). + + + + + + + Called when a custom node begins processing. The [code]time[/code] parameter is the time remaining in the node's current animation, which is the same value returned by [method process]. + When an animation starts, [code]time[/code] should be the same as the length of the animation. + + + + + + + + + Called when the custom node is processed by [AnimationTree]. The [code]delta[/code] parameter is the time (in seconds) since the last advance callback. It should be the same delta value provided to [method process] when [code]seek[/code] is [code]false[/code]. + + + + + + + + + Called when a custom node stops processing. The [code]time[/code] parameter is the time remaining in the node's current animation, which is the same value returned by [method process]. + + @@ -206,6 +234,12 @@ If [code]true[/code], filtering is enabled. + + Returns [code]true[/code] when [method process] is called on the current [AnimationTree] frame. + + + Returns all child nodes as a [code]name: node[/code] dictionary. Custom nodes can change this behavior by overriding [method get_child_nodes]. + diff --git a/scene/animation/animation_blend_space_1d.cpp b/scene/animation/animation_blend_space_1d.cpp index e426e98def57..9d02eb328e9b 100644 --- a/scene/animation/animation_blend_space_1d.cpp +++ b/scene/animation/animation_blend_space_1d.cpp @@ -91,13 +91,18 @@ void AnimationNodeBlendSpace1D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING, "value_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_value_label", "get_value_label"); } -void AnimationNodeBlendSpace1D::get_child_nodes(List *r_child_nodes) { - for (int i = 0; i < blend_points_used; i++) { - ChildNode cn; - cn.name = itos(i); - cn.node = blend_points[i].node; - r_child_nodes->push_back(cn); +int AnimationNodeBlendSpace1D::get_child_nodes(List *r_child_nodes) { + int child_count = AnimationNode::get_child_nodes(r_child_nodes); + if (child_count == 0) { + child_count = blend_points_used; + for (int i = 0; i < blend_points_used; i++) { + ChildNode cn; + cn.name = itos(i); + cn.node = blend_points[i].node; + r_child_nodes->push_back(cn); + } } + return child_count; } void AnimationNodeBlendSpace1D::add_blend_point(const Ref &p_node, float p_position, int p_at_index) { diff --git a/scene/animation/animation_blend_space_1d.h b/scene/animation/animation_blend_space_1d.h index 816d3c9d4e5f..882135a4c159 100644 --- a/scene/animation/animation_blend_space_1d.h +++ b/scene/animation/animation_blend_space_1d.h @@ -70,7 +70,7 @@ class AnimationNodeBlendSpace1D : public AnimationRootNode { virtual void get_parameter_list(List *r_list) const override; virtual Variant get_parameter_default_value(const StringName &p_parameter) const override; - virtual void get_child_nodes(List *r_child_nodes) override; + virtual int get_child_nodes(List *r_child_nodes) override; void add_blend_point(const Ref &p_node, float p_position, int p_at_index = -1); void set_blend_point_position(int p_point, float p_position); diff --git a/scene/animation/animation_blend_space_2d.cpp b/scene/animation/animation_blend_space_2d.cpp index 5a42e2af7a2e..ef151242a05c 100644 --- a/scene/animation/animation_blend_space_2d.cpp +++ b/scene/animation/animation_blend_space_2d.cpp @@ -48,13 +48,20 @@ Variant AnimationNodeBlendSpace2D::get_parameter_default_value(const StringName } } -void AnimationNodeBlendSpace2D::get_child_nodes(List *r_child_nodes) { - for (int i = 0; i < blend_points_used; i++) { - ChildNode cn; - cn.name = itos(i); - cn.node = blend_points[i].node; - r_child_nodes->push_back(cn); +int AnimationNodeBlendSpace2D::get_child_nodes(List *r_child_nodes) { + int child_count = AnimationNode::get_child_nodes(r_child_nodes); + + if (child_count == 0) { + child_count = blend_points_used; + for (int i = 0; i < blend_points_used; i++) { + ChildNode cn; + cn.name = itos(i); + cn.node = blend_points[i].node; + r_child_nodes->push_back(cn); + } } + + return child_count; } void AnimationNodeBlendSpace2D::add_blend_point(const Ref &p_node, const Vector2 &p_position, int p_at_index) { diff --git a/scene/animation/animation_blend_space_2d.h b/scene/animation/animation_blend_space_2d.h index 2aff678aad2b..1197b05abbed 100644 --- a/scene/animation/animation_blend_space_2d.h +++ b/scene/animation/animation_blend_space_2d.h @@ -95,7 +95,7 @@ class AnimationNodeBlendSpace2D : public AnimationRootNode { virtual void get_parameter_list(List *r_list) const override; virtual Variant get_parameter_default_value(const StringName &p_parameter) const override; - virtual void get_child_nodes(List *r_child_nodes) override; + virtual int get_child_nodes(List *r_child_nodes) override; void add_blend_point(const Ref &p_node, const Vector2 &p_position, int p_at_index = -1); void set_blend_point_position(int p_point, const Vector2 &p_position); diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp index 56995c0c132b..10b0c75e1ffd 100644 --- a/scene/animation/animation_blend_tree.cpp +++ b/scene/animation/animation_blend_tree.cpp @@ -885,21 +885,26 @@ Vector2 AnimationNodeBlendTree::get_node_position(const StringName &p_node) cons return nodes[p_node].position; } -void AnimationNodeBlendTree::get_child_nodes(List *r_child_nodes) { - Vector ns; - - for (Map::Element *E = nodes.front(); E; E = E->next()) { - ns.push_back(E->key()); - } +int AnimationNodeBlendTree::get_child_nodes(List *r_child_nodes) { + int child_count = AnimationNode::get_child_nodes(r_child_nodes); + if (child_count == 0) { + Vector ns; + child_count = nodes.size(); + + for (Map::Element *E = nodes.front(); E; E = E->next()) { + ns.push_back(E->key()); + } - ns.sort_custom(); + ns.sort_custom(); - for (int i = 0; i < ns.size(); i++) { - ChildNode cn; - cn.name = ns[i]; - cn.node = nodes[cn.name].node; - r_child_nodes->push_back(cn); + for (int i = 0; i < ns.size(); i++) { + ChildNode cn; + cn.name = ns[i]; + cn.node = nodes[cn.name].node; + r_child_nodes->push_back(cn); + } } + return child_count; } bool AnimationNodeBlendTree::has_node(const StringName &p_name) const { diff --git a/scene/animation/animation_blend_tree.h b/scene/animation/animation_blend_tree.h index 7241a6bc13b3..2fcead4b4ec9 100644 --- a/scene/animation/animation_blend_tree.h +++ b/scene/animation/animation_blend_tree.h @@ -374,7 +374,7 @@ class AnimationNodeBlendTree : public AnimationRootNode { void set_node_position(const StringName &p_node, const Vector2 &p_position); Vector2 get_node_position(const StringName &p_node) const; - virtual void get_child_nodes(List *r_child_nodes) override; + virtual int get_child_nodes(List *r_child_nodes) override; void connect_node(const StringName &p_input_node, int p_input_index, const StringName &p_output_node); void disconnect_node(const StringName &p_node, int p_input_index); diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index 17ce05f1303d..963166468b97 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -593,21 +593,27 @@ StringName AnimationNodeStateMachine::get_node_name(const Ref &p_ ERR_FAIL_V(StringName()); } -void AnimationNodeStateMachine::get_child_nodes(List *r_child_nodes) { - Vector nodes; +int AnimationNodeStateMachine::get_child_nodes(List *r_child_nodes) { + int child_count = AnimationNode::get_child_nodes(r_child_nodes); - for (Map::Element *E = states.front(); E; E = E->next()) { - nodes.push_back(E->key()); - } + if (child_count == 0) { + Vector nodes; - nodes.sort_custom(); + for (Map::Element *E = states.front(); E; E = E->next()) { + nodes.push_back(E->key()); + } + + nodes.sort_custom(); - for (int i = 0; i < nodes.size(); i++) { - ChildNode cn; - cn.name = nodes[i]; - cn.node = states[cn.name].node; - r_child_nodes->push_back(cn); + for (int i = 0; i < nodes.size(); i++) { + ChildNode cn; + cn.name = nodes[i]; + cn.node = states[cn.name].node; + r_child_nodes->push_back(cn); + } + child_count = nodes.size(); } + return child_count; } bool AnimationNodeStateMachine::has_node(const StringName &p_name) const { diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h index ae8975e9405b..5a438f51ec24 100644 --- a/scene/animation/animation_node_state_machine.h +++ b/scene/animation/animation_node_state_machine.h @@ -187,7 +187,7 @@ class AnimationNodeStateMachine : public AnimationRootNode { void set_node_position(const StringName &p_name, const Vector2 &p_position); Vector2 get_node_position(const StringName &p_name) const; - virtual void get_child_nodes(List *r_child_nodes) override; + virtual int get_child_nodes(List *r_child_nodes) override; bool has_transition(const StringName &p_from, const StringName &p_to) const; int find_transition(const StringName &p_from, const StringName &p_to) const; diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index 466536db1075..0a36bf5084b8 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -72,18 +72,40 @@ Variant AnimationNode::get_parameter(const StringName &p_name) const { return state->tree->property_map[path]; } -void AnimationNode::get_child_nodes(List *r_child_nodes) { +int AnimationNode::get_child_nodes(List *r_child_nodes) { if (get_script_instance()) { Dictionary cn = get_script_instance()->call("get_child_nodes"); List keys; cn.get_key_list(&keys); for (List::Element *E = keys.front(); E; E = E->next()) { ChildNode child; + Variant node = cn[E->get()]; + + // Some rudimentary type safety + ERR_CONTINUE_MSG(node.get_type() != Variant::OBJECT, "Expected 'AnimationNode' type values from get_child_nodes. Got '" + Variant::get_type_name(node.get_type()) + "' instead."); + child.name = E->get(); child.node = cn[E->get()]; r_child_nodes->push_back(child); } + return keys.size(); + } + return 0; +} + +Dictionary AnimationNode::_get_child_nodes_bind() { + // Exposes get_child_nodes to all scripts extending AnimationNode, including native subclasses such as AnimationNodeStateMachine + + List nodes; + get_child_nodes(&nodes); + + Dictionary ret; + for (const List::Element *iter = nodes.front(); iter; iter = iter->next()) { + ChildNode child = iter->get(); + ret[child.name] = child.node; } + + return ret; } void AnimationNode::blend_animation(const StringName &p_animation, float p_time, float p_delta, bool p_seeked, float p_blend) { @@ -116,6 +138,22 @@ void AnimationNode::blend_animation(const StringName &p_animation, float p_time, state->animation_states.push_back(anim_state); } +void AnimationNode::on_play(float p_time) { + if (get_script_instance()) { + get_script_instance()->call("_on_play", p_time); + } +} + +void AnimationNode::on_stop(float p_time) { + if (get_script_instance()) { + get_script_instance()->call("_on_stop", p_time); + } +} + +bool AnimationNode::is_processing() const { + return last_process_time > 0.f; +} + float AnimationNode::_pre_process(const StringName &p_base_path, AnimationNode *p_parent, State *p_state, float p_time, bool p_seek, const Vector &p_connections) { base_path = p_base_path; parent = p_parent; @@ -123,6 +161,9 @@ float AnimationNode::_pre_process(const StringName &p_base_path, AnimationNode * state = p_state; float t = process(p_time, p_seek); + if (!p_seek) { + process_time = t; + } state = nullptr; parent = nullptr; @@ -337,6 +378,12 @@ float AnimationNode::process(float p_time, bool p_seek) { return 0; } +void AnimationNode::advance(float p_delta) { + if (get_script_instance()) { + get_script_instance()->call("_advance", p_delta); + } +} + void AnimationNode::set_filter_path(const NodePath &p_path, bool p_enable) { if (p_enable) { filter[p_path] = true; @@ -416,9 +463,19 @@ void AnimationNode::_bind_methods() { ClassDB::bind_method(D_METHOD("set_parameter", "name", "value"), &AnimationNode::set_parameter); ClassDB::bind_method(D_METHOD("get_parameter", "name"), &AnimationNode::get_parameter); + ClassDB::bind_method(D_METHOD("_get_child_nodes"), &AnimationNode::_get_child_nodes_bind); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "child_nodes", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "", "_get_child_nodes"); + + ClassDB::bind_method(D_METHOD("_get_is_processing"), &AnimationNode::is_processing); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "is_processing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "", "_get_is_processing"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter_enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_filter_enabled", "is_filter_enabled"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "filters", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_filters", "_get_filters"); + BIND_VMETHOD(MethodInfo("_on_play", PropertyInfo(Variant::FLOAT, "time"))); + BIND_VMETHOD(MethodInfo("_on_stop", PropertyInfo(Variant::FLOAT, "time"))); + BIND_VMETHOD(MethodInfo("_advance", PropertyInfo(Variant::FLOAT, "delta"))); + BIND_VMETHOD(MethodInfo(Variant::DICTIONARY, "get_child_nodes")); BIND_VMETHOD(MethodInfo(Variant::ARRAY, "get_parameter_list")); BIND_VMETHOD(MethodInfo(Variant::OBJECT, "get_child_by_name", PropertyInfo(Variant::STRING, "name"))); @@ -445,6 +502,8 @@ AnimationNode::AnimationNode() { state = nullptr; parent = nullptr; filter_enabled = false; + process_time = -1.f; + last_process_time = 0.f; } //////////////////// @@ -797,6 +856,11 @@ void AnimationTree::_process_graph(float p_delta) { //process + // Reset process_time count on every node in tree + for (List::Element *E = all_nodes.front(); E; E = E->next()) { + E->get().node->process_time = -1.f; + } + { if (started) { //if started, seek @@ -807,6 +871,24 @@ void AnimationTree::_process_graph(float p_delta) { root->_pre_process(SceneStringNames::get_singleton()->parameters_base_path, nullptr, &state, p_delta, false, Vector()); } + // Check if processing occurred on each node + for (List::Element *E = all_nodes.front(); E; E = E->next()) { + Ref node = E->get().node; + // If there was a change in processing state, call the relevant script function + if (node->last_process_time < 0.f && node->process_time > 0.f) { + node->on_play(node->process_time); // node entered + } + + if (node->is_processing()) { + node->advance(p_delta); + } + + if (node->last_process_time >= 0.f && node->process_time < 0.f) { + node->on_stop(node->last_process_time); // node exited + } + node->last_process_time = node->process_time; + } + if (!state.valid) { return; //state is not valid. do nothing. } @@ -1381,11 +1463,22 @@ void AnimationTree::_update_properties_for_node(const String &p_base_path, Ref children; - node->get_child_nodes(&children); + // Accumulate all child nodes into one list + List::Element *E = all_nodes.back(); + int start = all_nodes.size(); + node->get_child_nodes(&all_nodes); + + if (!E) { + E = all_nodes.front(); + } else { + E = E->next(); + } + int end = all_nodes.size(); - for (List::Element *E = children.front(); E; E = E->next()) { + // Update properties for each child node + for (int i = start; E && i < end; i++) { _update_properties_for_node(p_base_path + E->get().name + "/", E->get().node); + E = E->next(); } } @@ -1398,6 +1491,7 @@ void AnimationTree::_update_properties() { property_parent_map.clear(); input_activity_map.clear(); input_activity_map_get.clear(); + all_nodes.clear(); if (root.is_valid()) { _update_properties_for_node(SceneStringNames::get_singleton()->parameters_base_path, root); diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h index 166ca04f402f..563995f2846c 100644 --- a/scene/animation/animation_tree.h +++ b/scene/animation/animation_tree.h @@ -124,8 +124,9 @@ class AnimationNode : public Resource { Ref node; }; - virtual void get_child_nodes(List *r_child_nodes); + virtual int get_child_nodes(List *r_child_nodes); + bool is_processing() const; virtual float process(float p_time, bool p_seek); virtual String get_caption() const; @@ -147,6 +148,16 @@ class AnimationNode : public Resource { virtual Ref get_child_by_name(const StringName &p_name); AnimationNode(); + +private: + float process_time; + float last_process_time; + + void on_play(float p_time); + void advance(float p_delta); + void on_stop(float p_time); + + Dictionary _get_child_nodes_bind(); }; VARIANT_ENUM_CAST(AnimationNode::FilterAction) @@ -249,6 +260,7 @@ class AnimationTree : public Node { Set playing_caches; Ref root; + List all_nodes; AnimationProcessMode process_mode; bool active;