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;