diff options
Diffstat (limited to 'scene/animation')
-rw-r--r-- | scene/animation/animation_blend_space_1d.cpp | 200 | ||||
-rw-r--r-- | scene/animation/animation_blend_space_1d.h | 24 | ||||
-rw-r--r-- | scene/animation/animation_blend_space_2d.cpp | 26 | ||||
-rw-r--r-- | scene/animation/animation_blend_space_2d.h | 7 | ||||
-rw-r--r-- | scene/animation/animation_blend_tree.cpp | 382 | ||||
-rw-r--r-- | scene/animation/animation_blend_tree.h | 74 | ||||
-rw-r--r-- | scene/animation/animation_node_state_machine.cpp | 204 | ||||
-rw-r--r-- | scene/animation/animation_node_state_machine.h | 36 | ||||
-rw-r--r-- | scene/animation/animation_player.cpp | 439 | ||||
-rw-r--r-- | scene/animation/animation_player.h | 38 | ||||
-rw-r--r-- | scene/animation/animation_tree.cpp | 601 | ||||
-rw-r--r-- | scene/animation/animation_tree.h | 70 | ||||
-rw-r--r-- | scene/animation/root_motion_view.cpp | 10 | ||||
-rw-r--r-- | scene/animation/tween.cpp | 52 | ||||
-rw-r--r-- | scene/animation/tween.h | 5 |
15 files changed, 1498 insertions, 670 deletions
diff --git a/scene/animation/animation_blend_space_1d.cpp b/scene/animation/animation_blend_space_1d.cpp index a2028b8de8..0e9e02f247 100644 --- a/scene/animation/animation_blend_space_1d.cpp +++ b/scene/animation/animation_blend_space_1d.cpp @@ -30,12 +30,20 @@ #include "animation_blend_space_1d.h" +#include "animation_blend_tree.h" + void AnimationNodeBlendSpace1D::get_parameter_list(List<PropertyInfo> *r_list) const { r_list->push_back(PropertyInfo(Variant::FLOAT, blend_position)); + r_list->push_back(PropertyInfo(Variant::INT, closest, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); + r_list->push_back(PropertyInfo(Variant::FLOAT, length_internal, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); } Variant AnimationNodeBlendSpace1D::get_parameter_default_value(const StringName &p_parameter) const { - return 0; + if (p_parameter == closest) { + return -1; + } else { + return 0; + } } Ref<AnimationNode> AnimationNodeBlendSpace1D::get_child_by_name(const StringName &p_name) { @@ -53,7 +61,15 @@ void AnimationNodeBlendSpace1D::_validate_property(PropertyInfo &p_property) con } void AnimationNodeBlendSpace1D::_tree_changed() { - emit_signal(SNAME("tree_changed")); + AnimationRootNode::_tree_changed(); +} + +void AnimationNodeBlendSpace1D::_animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name) { + AnimationRootNode::_animation_node_renamed(p_oid, p_old_name, p_new_name); +} + +void AnimationNodeBlendSpace1D::_animation_node_removed(const ObjectID &p_oid, const StringName &p_node) { + AnimationRootNode::_animation_node_removed(p_oid, p_node); } void AnimationNodeBlendSpace1D::_bind_methods() { @@ -77,6 +93,9 @@ void AnimationNodeBlendSpace1D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_value_label", "text"), &AnimationNodeBlendSpace1D::set_value_label); ClassDB::bind_method(D_METHOD("get_value_label"), &AnimationNodeBlendSpace1D::get_value_label); + ClassDB::bind_method(D_METHOD("set_blend_mode", "mode"), &AnimationNodeBlendSpace1D::set_blend_mode); + ClassDB::bind_method(D_METHOD("get_blend_mode"), &AnimationNodeBlendSpace1D::get_blend_mode); + ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeBlendSpace1D::set_use_sync); ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeBlendSpace1D::is_using_sync); @@ -91,7 +110,12 @@ void AnimationNodeBlendSpace1D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_max_space", "get_max_space"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "snap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_snap", "get_snap"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "value_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_value_label", "get_value_label"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "blend_mode", PROPERTY_HINT_ENUM, "Interpolated,Discrete,Carry", PROPERTY_USAGE_NO_EDITOR), "set_blend_mode", "get_blend_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_use_sync", "is_using_sync"); + + BIND_ENUM_CONSTANT(BLEND_MODE_INTERPOLATED); + BIND_ENUM_CONSTANT(BLEND_MODE_DISCRETE); + BIND_ENUM_CONSTANT(BLEND_MODE_DISCRETE_CARRY); } void AnimationNodeBlendSpace1D::get_child_nodes(List<ChildNode> *r_child_nodes) { @@ -121,6 +145,8 @@ void AnimationNodeBlendSpace1D::add_blend_point(const Ref<AnimationRootNode> &p_ blend_points[p_at_index].position = p_position; blend_points[p_at_index].node->connect("tree_changed", callable_mp(this, &AnimationNodeBlendSpace1D::_tree_changed), CONNECT_REFERENCE_COUNTED); + blend_points[p_at_index].node->connect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_renamed), CONNECT_REFERENCE_COUNTED); + blend_points[p_at_index].node->connect("animation_node_removed", callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_removed), CONNECT_REFERENCE_COUNTED); blend_points_used++; emit_signal(SNAME("tree_changed")); @@ -138,10 +164,14 @@ void AnimationNodeBlendSpace1D::set_blend_point_node(int p_point, const Ref<Anim if (blend_points[p_point].node.is_valid()) { blend_points[p_point].node->disconnect("tree_changed", callable_mp(this, &AnimationNodeBlendSpace1D::_tree_changed)); + blend_points[p_point].node->disconnect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_renamed)); + blend_points[p_point].node->disconnect("animation_node_removed", callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_removed)); } blend_points[p_point].node = p_node; blend_points[p_point].node->connect("tree_changed", callable_mp(this, &AnimationNodeBlendSpace1D::_tree_changed), CONNECT_REFERENCE_COUNTED); + blend_points[p_point].node->connect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_renamed), CONNECT_REFERENCE_COUNTED); + blend_points[p_point].node->connect("animation_node_removed", callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_removed), CONNECT_REFERENCE_COUNTED); emit_signal(SNAME("tree_changed")); } @@ -161,12 +191,16 @@ void AnimationNodeBlendSpace1D::remove_blend_point(int p_point) { ERR_FAIL_COND(blend_points[p_point].node.is_null()); blend_points[p_point].node->disconnect("tree_changed", callable_mp(this, &AnimationNodeBlendSpace1D::_tree_changed)); + blend_points[p_point].node->disconnect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_renamed)); + blend_points[p_point].node->disconnect("animation_node_removed", callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_removed)); for (int i = p_point; i < blend_points_used - 1; i++) { blend_points[i] = blend_points[i + 1]; } blend_points_used--; + + emit_signal(SNAME("animation_node_removed"), get_instance_id(), itos(p_point)); emit_signal(SNAME("tree_changed")); } @@ -214,6 +248,14 @@ String AnimationNodeBlendSpace1D::get_value_label() const { return value_label; } +void AnimationNodeBlendSpace1D::set_blend_mode(BlendMode p_blend_mode) { + blend_mode = p_blend_mode; +} + +AnimationNodeBlendSpace1D::BlendMode AnimationNodeBlendSpace1D::get_blend_mode() const { + return blend_mode; +} + void AnimationNodeBlendSpace1D::set_use_sync(bool p_sync) { sync = p_sync; } @@ -241,79 +283,125 @@ double AnimationNodeBlendSpace1D::process(double p_time, bool p_seek, bool p_is_ } double blend_pos = get_parameter(blend_position); + int cur_closest = get_parameter(closest); + double cur_length_internal = get_parameter(length_internal); + double max_time_remaining = 0.0; - float weights[MAX_BLEND_POINTS] = {}; + if (blend_mode == BLEND_MODE_INTERPOLATED) { + float weights[MAX_BLEND_POINTS] = {}; + + int point_lower = -1; + float pos_lower = 0.0; + int point_higher = -1; + float pos_higher = 0.0; + + // find the closest two points to blend between + for (int i = 0; i < blend_points_used; i++) { + float pos = blend_points[i].position; + + if (pos <= blend_pos) { + if (point_lower == -1) { + point_lower = i; + pos_lower = pos; + } else if ((blend_pos - pos) < (blend_pos - pos_lower)) { + point_lower = i; + pos_lower = pos; + } + } else { + if (point_higher == -1) { + point_higher = i; + pos_higher = pos; + } else if ((pos - blend_pos) < (pos_higher - blend_pos)) { + point_higher = i; + pos_higher = pos; + } + } + } - int point_lower = -1; - float pos_lower = 0.0; - int point_higher = -1; - float pos_higher = 0.0; + // fill in weights - // find the closest two points to blend between - for (int i = 0; i < blend_points_used; i++) { - float pos = blend_points[i].position; - - if (pos <= blend_pos) { - if (point_lower == -1) { - point_lower = i; - pos_lower = pos; - } else if ((blend_pos - pos) < (blend_pos - pos_lower)) { - point_lower = i; - pos_lower = pos; - } + if (point_lower == -1 && point_higher != -1) { + // we are on the left side, no other point to the left + // we just play the next point. + + weights[point_higher] = 1.0; + } else if (point_higher == -1) { + // we are on the right side, no other point to the right + // we just play the previous point + + weights[point_lower] = 1.0; } else { - if (point_higher == -1) { - point_higher = i; - pos_higher = pos; - } else if ((pos - blend_pos) < (pos_higher - blend_pos)) { - point_higher = i; - pos_higher = pos; - } - } - } + // we are between two points. + // figure out weights, then blend the animations - // fill in weights + float distance_between_points = pos_higher - pos_lower; - if (point_lower == -1 && point_higher != -1) { - // we are on the left side, no other point to the left - // we just play the next point. + float current_pos_inbetween = blend_pos - pos_lower; - weights[point_higher] = 1.0; - } else if (point_higher == -1) { - // we are on the right side, no other point to the right - // we just play the previous point + float blend_percentage = current_pos_inbetween / distance_between_points; - weights[point_lower] = 1.0; - } else { - // we are between two points. - // figure out weights, then blend the animations + float blend_lower = 1.0 - blend_percentage; + float blend_higher = blend_percentage; - float distance_between_points = pos_higher - pos_lower; + weights[point_lower] = blend_lower; + weights[point_higher] = blend_higher; + } - float current_pos_inbetween = blend_pos - pos_lower; + // actually blend the animations now - float blend_percentage = current_pos_inbetween / distance_between_points; + for (int i = 0; i < blend_points_used; i++) { + if (i == point_lower || i == point_higher) { + double remaining = blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, weights[i], FILTER_IGNORE, true); + max_time_remaining = MAX(max_time_remaining, remaining); + } else if (sync) { + blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true); + } + } + } else { + int new_closest = -1; + double new_closest_dist = 1e20; + + for (int i = 0; i < blend_points_used; i++) { + double d = abs(blend_points[i].position - blend_pos); + if (d < new_closest_dist) { + new_closest = i; + new_closest_dist = d; + } + } - float blend_lower = 1.0 - blend_percentage; - float blend_higher = blend_percentage; + if (new_closest != cur_closest && new_closest != -1) { + double from = 0.0; + if (blend_mode == BLEND_MODE_DISCRETE_CARRY && cur_closest != -1) { + //for ping-pong loop + Ref<AnimationNodeAnimation> na_c = static_cast<Ref<AnimationNodeAnimation>>(blend_points[cur_closest].node); + Ref<AnimationNodeAnimation> na_n = static_cast<Ref<AnimationNodeAnimation>>(blend_points[new_closest].node); + if (!na_c.is_null() && !na_n.is_null()) { + na_n->set_backward(na_c->is_backward()); + } + //see how much animation remains + from = cur_length_internal - blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, false, p_is_external_seeking, 0.0, FILTER_IGNORE, true); + } - weights[point_lower] = blend_lower; - weights[point_higher] = blend_higher; - } + max_time_remaining = blend_node(blend_points[new_closest].name, blend_points[new_closest].node, from, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true); + cur_length_internal = from + max_time_remaining; - // actually blend the animations now + cur_closest = new_closest; - double max_time_remaining = 0.0; + } else { + max_time_remaining = blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true); + } - for (int i = 0; i < blend_points_used; i++) { - if (i == point_lower || i == point_higher) { - double remaining = blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, weights[i], FILTER_IGNORE, true); - max_time_remaining = MAX(max_time_remaining, remaining); - } else if (sync) { - blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true); + if (sync) { + for (int i = 0; i < blend_points_used; i++) { + if (i != cur_closest) { + blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true); + } + } } } + set_parameter(this->closest, cur_closest); + set_parameter(this->length_internal, cur_length_internal); return max_time_remaining; } diff --git a/scene/animation/animation_blend_space_1d.h b/scene/animation/animation_blend_space_1d.h index af93783c0d..4007df0ded 100644 --- a/scene/animation/animation_blend_space_1d.h +++ b/scene/animation/animation_blend_space_1d.h @@ -36,6 +36,14 @@ class AnimationNodeBlendSpace1D : public AnimationRootNode { GDCLASS(AnimationNodeBlendSpace1D, AnimationRootNode); +public: + enum BlendMode { + BLEND_MODE_INTERPOLATED, + BLEND_MODE_DISCRETE, + BLEND_MODE_DISCRETE_CARRY, + }; + +protected: enum { MAX_BLEND_POINTS = 64 }; @@ -58,16 +66,21 @@ class AnimationNodeBlendSpace1D : public AnimationRootNode { void _add_blend_point(int p_index, const Ref<AnimationRootNode> &p_node); - void _tree_changed(); - StringName blend_position = "blend_position"; + StringName closest = "closest"; + StringName length_internal = "length_internal"; + + BlendMode blend_mode = BLEND_MODE_INTERPOLATED; -protected: bool sync = false; void _validate_property(PropertyInfo &p_property) const; static void _bind_methods(); + virtual void _tree_changed() override; + virtual void _animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name) override; + virtual void _animation_node_removed(const ObjectID &p_oid, const StringName &p_node) override; + public: virtual void get_parameter_list(List<PropertyInfo> *r_list) const override; virtual Variant get_parameter_default_value(const StringName &p_parameter) const override; @@ -95,6 +108,9 @@ public: void set_value_label(const String &p_label); String get_value_label() const; + void set_blend_mode(BlendMode p_blend_mode); + BlendMode get_blend_mode() const; + void set_use_sync(bool p_sync); bool is_using_sync() const; @@ -107,4 +123,6 @@ public: ~AnimationNodeBlendSpace1D(); }; +VARIANT_ENUM_CAST(AnimationNodeBlendSpace1D::BlendMode) + #endif // ANIMATION_BLEND_SPACE_1D_H diff --git a/scene/animation/animation_blend_space_2d.cpp b/scene/animation/animation_blend_space_2d.cpp index c37d54961e..ae5b0d5779 100644 --- a/scene/animation/animation_blend_space_2d.cpp +++ b/scene/animation/animation_blend_space_2d.cpp @@ -81,6 +81,8 @@ void AnimationNodeBlendSpace2D::add_blend_point(const Ref<AnimationRootNode> &p_ blend_points[p_at_index].position = p_position; blend_points[p_at_index].node->connect("tree_changed", callable_mp(this, &AnimationNodeBlendSpace2D::_tree_changed), CONNECT_REFERENCE_COUNTED); + blend_points[p_at_index].node->connect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendSpace2D::_animation_node_renamed), CONNECT_REFERENCE_COUNTED); + blend_points[p_at_index].node->connect("animation_node_removed", callable_mp(this, &AnimationNodeBlendSpace2D::_animation_node_removed), CONNECT_REFERENCE_COUNTED); blend_points_used++; _queue_auto_triangles(); @@ -100,9 +102,13 @@ void AnimationNodeBlendSpace2D::set_blend_point_node(int p_point, const Ref<Anim if (blend_points[p_point].node.is_valid()) { blend_points[p_point].node->disconnect("tree_changed", callable_mp(this, &AnimationNodeBlendSpace2D::_tree_changed)); + blend_points[p_point].node->disconnect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendSpace2D::_animation_node_renamed)); + blend_points[p_point].node->disconnect("animation_node_removed", callable_mp(this, &AnimationNodeBlendSpace2D::_animation_node_removed)); } blend_points[p_point].node = p_node; blend_points[p_point].node->connect("tree_changed", callable_mp(this, &AnimationNodeBlendSpace2D::_tree_changed), CONNECT_REFERENCE_COUNTED); + blend_points[p_point].node->connect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendSpace2D::_animation_node_renamed), CONNECT_REFERENCE_COUNTED); + blend_points[p_point].node->connect("animation_node_removed", callable_mp(this, &AnimationNodeBlendSpace2D::_animation_node_removed), CONNECT_REFERENCE_COUNTED); emit_signal(SNAME("tree_changed")); } @@ -122,6 +128,8 @@ void AnimationNodeBlendSpace2D::remove_blend_point(int p_point) { ERR_FAIL_COND(blend_points[p_point].node.is_null()); blend_points[p_point].node->disconnect("tree_changed", callable_mp(this, &AnimationNodeBlendSpace2D::_tree_changed)); + blend_points[p_point].node->disconnect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendSpace2D::_animation_node_renamed)); + blend_points[p_point].node->disconnect("animation_node_removed", callable_mp(this, &AnimationNodeBlendSpace2D::_animation_node_removed)); for (int i = 0; i < triangles.size(); i++) { bool erase = false; @@ -144,6 +152,8 @@ void AnimationNodeBlendSpace2D::remove_blend_point(int p_point) { blend_points[i] = blend_points[i + 1]; } blend_points_used--; + + emit_signal(SNAME("animation_node_removed"), get_instance_id(), itos(p_point)); emit_signal(SNAME("tree_changed")); } @@ -598,10 +608,6 @@ Ref<AnimationNode> AnimationNodeBlendSpace2D::get_child_by_name(const StringName return get_blend_point_node(p_name.operator String().to_int()); } -void AnimationNodeBlendSpace2D::_tree_changed() { - emit_signal(SNAME("tree_changed")); -} - void AnimationNodeBlendSpace2D::set_blend_mode(BlendMode p_blend_mode) { blend_mode = p_blend_mode; } @@ -618,6 +624,18 @@ bool AnimationNodeBlendSpace2D::is_using_sync() const { return sync; } +void AnimationNodeBlendSpace2D::_tree_changed() { + AnimationRootNode::_tree_changed(); +} + +void AnimationNodeBlendSpace2D::_animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name) { + AnimationRootNode::_animation_node_renamed(p_oid, p_old_name, p_new_name); +} + +void AnimationNodeBlendSpace2D::_animation_node_removed(const ObjectID &p_oid, const StringName &p_node) { + AnimationRootNode::_animation_node_removed(p_oid, p_node); +} + void AnimationNodeBlendSpace2D::_bind_methods() { ClassDB::bind_method(D_METHOD("add_blend_point", "node", "pos", "at_index"), &AnimationNodeBlendSpace2D::add_blend_point, DEFVAL(-1)); ClassDB::bind_method(D_METHOD("set_blend_point_position", "point", "pos"), &AnimationNodeBlendSpace2D::set_blend_point_position); diff --git a/scene/animation/animation_blend_space_2d.h b/scene/animation/animation_blend_space_2d.h index 044c93d9f6..a770bf01ee 100644 --- a/scene/animation/animation_blend_space_2d.h +++ b/scene/animation/animation_blend_space_2d.h @@ -85,14 +85,15 @@ protected: void _update_triangles(); void _queue_auto_triangles(); - void _tree_changed(); - -protected: bool sync = false; void _validate_property(PropertyInfo &p_property) const; static void _bind_methods(); + virtual void _tree_changed() override; + virtual void _animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name) override; + virtual void _animation_node_removed(const ObjectID &p_oid, const StringName &p_node) override; + public: virtual void get_parameter_list(List<PropertyInfo> *r_list) const override; virtual Variant get_parameter_default_value(const StringName &p_parameter) const override; diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp index 1ef0774828..3567738366 100644 --- a/scene/animation/animation_blend_tree.cpp +++ b/scene/animation/animation_blend_tree.cpp @@ -229,15 +229,17 @@ AnimationNodeSync::AnimationNodeSync() { //////////////////////////////////////////////////////// void AnimationNodeOneShot::get_parameter_list(List<PropertyInfo> *r_list) const { - r_list->push_back(PropertyInfo(Variant::BOOL, active)); - r_list->push_back(PropertyInfo(Variant::BOOL, prev_active, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); + r_list->push_back(PropertyInfo(Variant::BOOL, active, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_READ_ONLY)); + r_list->push_back(PropertyInfo(Variant::INT, request, PROPERTY_HINT_ENUM, ",Fire,Abort")); r_list->push_back(PropertyInfo(Variant::FLOAT, time, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); r_list->push_back(PropertyInfo(Variant::FLOAT, remaining, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); r_list->push_back(PropertyInfo(Variant::FLOAT, time_to_restart, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); } Variant AnimationNodeOneShot::get_parameter_default_value(const StringName &p_parameter) const { - if (p_parameter == active || p_parameter == prev_active) { + if (p_parameter == request) { + return ONE_SHOT_REQUEST_NONE; + } else if (p_parameter == active) { return false; } else if (p_parameter == time_to_restart) { return -1; @@ -246,6 +248,13 @@ Variant AnimationNodeOneShot::get_parameter_default_value(const StringName &p_pa } } +bool AnimationNodeOneShot::is_parameter_read_only(const StringName &p_parameter) const { + if (p_parameter == active) { + return true; + } + return false; +} + void AnimationNodeOneShot::set_fadein_time(double p_time) { fade_in = p_time; } @@ -303,50 +312,51 @@ bool AnimationNodeOneShot::has_filter() const { } double AnimationNodeOneShot::process(double p_time, bool p_seek, bool p_is_external_seeking) { + OneShotRequest cur_request = static_cast<OneShotRequest>((int)get_parameter(request)); bool cur_active = get_parameter(active); - bool cur_prev_active = get_parameter(prev_active); double cur_time = get_parameter(time); double cur_remaining = get_parameter(remaining); double cur_time_to_restart = get_parameter(time_to_restart); - if (!cur_active) { - //make it as if this node doesn't exist, pass input 0 by. - if (cur_prev_active) { - set_parameter(prev_active, false); - } + set_parameter(request, ONE_SHOT_REQUEST_NONE); + + bool do_start = cur_request == ONE_SHOT_REQUEST_FIRE; + if (cur_request == ONE_SHOT_REQUEST_ABORT) { + set_parameter(active, false); + set_parameter(time_to_restart, -1); + return blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync); + } else if (!do_start && !cur_active) { if (cur_time_to_restart >= 0.0 && !p_seek) { cur_time_to_restart -= p_time; if (cur_time_to_restart < 0) { - //restart - set_parameter(active, true); - cur_active = true; + do_start = true; // Restart. } set_parameter(time_to_restart, cur_time_to_restart); } - - return blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync); + if (!do_start) { + return blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync); + } } bool os_seek = p_seek; - if (p_seek) { cur_time = p_time; } - bool do_start = !cur_prev_active; if (do_start) { cur_time = 0; os_seek = true; - set_parameter(prev_active, true); + set_parameter(request, ONE_SHOT_REQUEST_NONE); + set_parameter(active, true); } real_t blend; - + bool use_fade_in = fade_in > 0; if (cur_time < fade_in) { - if (fade_in > 0) { + if (use_fade_in) { blend = cur_time / fade_in; } else { - blend = 0; + blend = 0; // Should not happen. } } else if (!do_start && cur_remaining <= fade_out) { if (fade_out > 0) { @@ -358,10 +368,10 @@ double AnimationNodeOneShot::process(double p_time, bool p_seek, bool p_is_exter blend = 1.0; } - double main_rem; + double main_rem = 0.0; if (mix == MIX_MODE_ADD) { main_rem = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync); - } else { + } else if (use_fade_in) { main_rem = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0 - blend, FILTER_BLEND, sync); // Unlike below, processing this edge is a corner case. } double os_rem = blend_input(1, os_seek ? cur_time : p_time, os_seek, p_is_external_seeking, Math::is_zero_approx(blend) ? CMP_EPSILON : blend, FILTER_PASS, true); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. @@ -375,7 +385,6 @@ double AnimationNodeOneShot::process(double p_time, bool p_seek, bool p_is_exter cur_remaining = os_rem; if (cur_remaining <= 0) { set_parameter(active, false); - set_parameter(prev_active, false); if (autorestart) { double restart_sec = autorestart_delay + Math::randd() * autorestart_random_delay; set_parameter(time_to_restart, restart_sec); @@ -419,6 +428,10 @@ void AnimationNodeOneShot::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "autorestart_delay", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater,suffix:s"), "set_autorestart_delay", "get_autorestart_delay"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "autorestart_random_delay", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater,suffix:s"), "set_autorestart_random_delay", "get_autorestart_random_delay"); + BIND_ENUM_CONSTANT(ONE_SHOT_REQUEST_NONE); + BIND_ENUM_CONSTANT(ONE_SHOT_REQUEST_FIRE); + BIND_ENUM_CONSTANT(ONE_SHOT_REQUEST_ABORT); + BIND_ENUM_CONSTANT(MIX_MODE_BLEND); BIND_ENUM_CONSTANT(MIX_MODE_ADD); } @@ -505,7 +518,7 @@ void AnimationNodeBlend2::get_parameter_list(List<PropertyInfo> *r_list) const { } Variant AnimationNodeBlend2::get_parameter_default_value(const StringName &p_parameter) const { - return 0; //for blend amount + return 0; // For blend amount. } String AnimationNodeBlend2::get_caption() const { @@ -518,7 +531,7 @@ double AnimationNodeBlend2::process(double p_time, bool p_seek, bool p_is_extern double rem0 = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0 - amount, FILTER_BLEND, sync); double rem1 = blend_input(1, p_time, p_seek, p_is_external_seeking, amount, FILTER_PASS, sync); - return amount > 0.5 ? rem1 : rem0; //hacky but good enough + return amount > 0.5 ? rem1 : rem0; // Hacky but good enough. } bool AnimationNodeBlend2::has_filter() const { @@ -540,7 +553,7 @@ void AnimationNodeBlend3::get_parameter_list(List<PropertyInfo> *r_list) const { } Variant AnimationNodeBlend3::get_parameter_default_value(const StringName &p_parameter) const { - return 0; //for blend amount + return 0; // For blend amount. } String AnimationNodeBlend3::get_caption() const { @@ -553,7 +566,7 @@ double AnimationNodeBlend3::process(double p_time, bool p_seek, bool p_is_extern double rem1 = blend_input(1, p_time, p_seek, p_is_external_seeking, 1.0 - ABS(amount), FILTER_IGNORE, sync); double rem2 = blend_input(2, p_time, p_seek, p_is_external_seeking, MAX(0, amount), FILTER_IGNORE, sync); - return amount > 0.5 ? rem2 : (amount < -0.5 ? rem0 : rem1); //hacky but good enough + return amount > 0.5 ? rem2 : (amount < -0.5 ? rem0 : rem1); // Hacky but good enough. } void AnimationNodeBlend3::_bind_methods() { @@ -572,7 +585,7 @@ void AnimationNodeTimeScale::get_parameter_list(List<PropertyInfo> *r_list) cons } Variant AnimationNodeTimeScale::get_parameter_default_value(const StringName &p_parameter) const { - return 1.0; //initial timescale + return 1.0; // Initial timescale. } String AnimationNodeTimeScale::get_caption() const { @@ -598,24 +611,24 @@ AnimationNodeTimeScale::AnimationNodeTimeScale() { //////////////////////////////////// void AnimationNodeTimeSeek::get_parameter_list(List<PropertyInfo> *r_list) const { - r_list->push_back(PropertyInfo(Variant::FLOAT, seek_pos, PROPERTY_HINT_RANGE, "-1,3600,0.01,or_greater")); + r_list->push_back(PropertyInfo(Variant::FLOAT, seek_pos_request, PROPERTY_HINT_RANGE, "-1,3600,0.01,or_greater")); // It will be reset to -1 after seeking the position immediately. } Variant AnimationNodeTimeSeek::get_parameter_default_value(const StringName &p_parameter) const { - return 1.0; //initial timescale + return -1.0; // Initial seek request. } String AnimationNodeTimeSeek::get_caption() const { - return "Seek"; + return "TimeSeek"; } double AnimationNodeTimeSeek::process(double p_time, bool p_seek, bool p_is_external_seeking) { - double cur_seek_pos = get_parameter(seek_pos); + double cur_seek_pos = get_parameter(seek_pos_request); if (p_seek) { return blend_input(0, p_time, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true); } else if (cur_seek_pos >= 0) { double ret = blend_input(0, cur_seek_pos, true, true, 1.0, FILTER_IGNORE, true); - set_parameter(seek_pos, -1.0); //reset + set_parameter(seek_pos_request, -1.0); // Reset. return ret; } else { return blend_input(0, p_time, false, p_is_external_seeking, 1.0, FILTER_IGNORE, true); @@ -631,18 +644,76 @@ AnimationNodeTimeSeek::AnimationNodeTimeSeek() { ///////////////////////////////////////////////// +bool AnimationNodeTransition::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (!path.begins_with("input_")) { + return false; + } + + int which = path.get_slicec('/', 0).get_slicec('_', 1).to_int(); + String what = path.get_slicec('/', 1); + + if (which == get_input_count() && what == "name") { + if (add_input(p_value)) { + return true; + } + return false; + } + + ERR_FAIL_INDEX_V(which, get_input_count(), false); + + if (what == "name") { + set_input_name(which, p_value); + } else if (what == "auto_advance") { + set_input_as_auto_advance(which, p_value); + } else if (what == "reset") { + set_input_reset(which, p_value); + } else { + return false; + } + + return true; +} + +bool AnimationNodeTransition::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (!path.begins_with("input_")) { + return false; + } + + int which = path.get_slicec('/', 0).get_slicec('_', 1).to_int(); + String what = path.get_slicec('/', 1); + + ERR_FAIL_INDEX_V(which, get_input_count(), false); + + if (what == "name") { + r_ret = get_input_name(which); + } else if (what == "auto_advance") { + r_ret = is_input_set_as_auto_advance(which); + } else if (what == "reset") { + r_ret = is_input_reset(which); + } else { + return false; + } + + return true; +} + void AnimationNodeTransition::get_parameter_list(List<PropertyInfo> *r_list) const { String anims; - for (int i = 0; i < enabled_inputs; i++) { + for (int i = 0; i < get_input_count(); i++) { if (i > 0) { anims += ","; } anims += inputs[i].name; } - r_list->push_back(PropertyInfo(Variant::INT, current, PROPERTY_HINT_ENUM, anims)); - r_list->push_back(PropertyInfo(Variant::INT, prev_current, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); - r_list->push_back(PropertyInfo(Variant::INT, prev, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); + r_list->push_back(PropertyInfo(Variant::STRING, current_state, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY)); // For interface. + r_list->push_back(PropertyInfo(Variant::STRING, transition_request, PROPERTY_HINT_ENUM, anims)); // For transition request. It will be cleared after setting the value immediately. + r_list->push_back(PropertyInfo(Variant::INT, current_index, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_READ_ONLY)); // To avoid finding the index every frame, use this internally. + r_list->push_back(PropertyInfo(Variant::INT, prev_index, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); r_list->push_back(PropertyInfo(Variant::FLOAT, time, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); r_list->push_back(PropertyInfo(Variant::FLOAT, prev_xfading, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); } @@ -650,56 +721,74 @@ void AnimationNodeTransition::get_parameter_list(List<PropertyInfo> *r_list) con Variant AnimationNodeTransition::get_parameter_default_value(const StringName &p_parameter) const { if (p_parameter == time || p_parameter == prev_xfading) { return 0.0; - } else if (p_parameter == prev || p_parameter == prev_current) { + } else if (p_parameter == prev_index || p_parameter == current_index) { return -1; } else { - return 0; + return String(); } } +bool AnimationNodeTransition::is_parameter_read_only(const StringName &p_parameter) const { + if (p_parameter == current_state || p_parameter == current_index) { + return true; + } + return false; +} + String AnimationNodeTransition::get_caption() const { return "Transition"; } -void AnimationNodeTransition::_update_inputs() { - while (get_input_count() < enabled_inputs) { - add_input(inputs[get_input_count()].name); +void AnimationNodeTransition::set_input_count(int p_inputs) { + for (int i = get_input_count(); i < p_inputs; i++) { + add_input("state_" + itos(i)); } - - while (get_input_count() > enabled_inputs) { + while (get_input_count() > p_inputs) { remove_input(get_input_count() - 1); } + + pending_update = true; + + emit_signal(SNAME("tree_changed")); // For updating connect activity map. + notify_property_list_changed(); +} + +bool AnimationNodeTransition::add_input(const String &p_name) { + if (AnimationNode::add_input(p_name)) { + input_data.push_back(InputData()); + return true; + } + return false; } -void AnimationNodeTransition::set_enabled_inputs(int p_inputs) { - ERR_FAIL_INDEX(p_inputs, MAX_INPUTS); - enabled_inputs = p_inputs; - _update_inputs(); +void AnimationNodeTransition::remove_input(int p_index) { + input_data.remove_at(p_index); + AnimationNode::remove_input(p_index); } -int AnimationNodeTransition::get_enabled_inputs() { - return enabled_inputs; +bool AnimationNodeTransition::set_input_name(int p_input, const String &p_name) { + pending_update = true; + return AnimationNode::set_input_name(p_input, p_name); } void AnimationNodeTransition::set_input_as_auto_advance(int p_input, bool p_enable) { - ERR_FAIL_INDEX(p_input, MAX_INPUTS); - inputs[p_input].auto_advance = p_enable; + ERR_FAIL_INDEX(p_input, get_input_count()); + input_data.write[p_input].auto_advance = p_enable; } bool AnimationNodeTransition::is_input_set_as_auto_advance(int p_input) const { - ERR_FAIL_INDEX_V(p_input, MAX_INPUTS, false); - return inputs[p_input].auto_advance; + ERR_FAIL_INDEX_V(p_input, get_input_count(), false); + return input_data[p_input].auto_advance; } -void AnimationNodeTransition::set_input_caption(int p_input, const String &p_name) { - ERR_FAIL_INDEX(p_input, MAX_INPUTS); - inputs[p_input].name = p_name; - set_input_name(p_input, p_name); +void AnimationNodeTransition::set_input_reset(int p_input, bool p_enable) { + ERR_FAIL_INDEX(p_input, get_input_count()); + input_data.write[p_input].reset = p_enable; } -String AnimationNodeTransition::get_input_caption(int p_input) const { - ERR_FAIL_INDEX_V(p_input, MAX_INPUTS, String()); - return inputs[p_input].name; +bool AnimationNodeTransition::is_input_reset(int p_input) const { + ERR_FAIL_INDEX_V(p_input, get_input_count(), true); + return input_data[p_input].reset; } void AnimationNodeTransition::set_xfade_time(double p_fade) { @@ -718,51 +807,96 @@ Ref<Curve> AnimationNodeTransition::get_xfade_curve() const { return xfade_curve; } -void AnimationNodeTransition::set_from_start(bool p_from_start) { - from_start = p_from_start; +void AnimationNodeTransition::set_allow_transition_to_self(bool p_enable) { + allow_transition_to_self = p_enable; } -bool AnimationNodeTransition::is_from_start() const { - return from_start; +bool AnimationNodeTransition::is_allow_transition_to_self() const { + return allow_transition_to_self; } double AnimationNodeTransition::process(double p_time, bool p_seek, bool p_is_external_seeking) { - int cur_current = get_parameter(current); - int cur_prev = get_parameter(prev); - int cur_prev_current = get_parameter(prev_current); + String cur_transition_request = get_parameter(transition_request); + int cur_current_index = get_parameter(current_index); + int cur_prev_index = get_parameter(prev_index); double cur_time = get_parameter(time); double cur_prev_xfading = get_parameter(prev_xfading); - bool switched = cur_current != cur_prev_current; + bool switched = false; + bool restart = false; - if (switched) { - set_parameter(prev_current, cur_current); - set_parameter(prev, cur_prev_current); + if (pending_update) { + if (cur_current_index < 0 || cur_current_index >= get_input_count()) { + set_parameter(prev_index, -1); + if (get_input_count() > 0) { + set_parameter(current_index, 0); + set_parameter(current_state, get_input_name(0)); + } else { + set_parameter(current_index, -1); + set_parameter(current_state, StringName()); + } + } else { + set_parameter(current_state, get_input_name(cur_current_index)); + } + pending_update = false; + } - cur_prev = cur_prev_current; + if (!cur_transition_request.is_empty()) { + int new_idx = find_input(cur_transition_request); + if (new_idx >= 0) { + if (cur_current_index == new_idx) { + if (allow_transition_to_self) { + // Transition to same state. + restart = input_data[cur_current_index].reset; + cur_prev_xfading = 0; + set_parameter(prev_xfading, 0); + cur_prev_index = -1; + set_parameter(prev_index, -1); + } + } else { + switched = true; + cur_prev_index = cur_current_index; + set_parameter(prev_index, cur_current_index); + cur_current_index = new_idx; + set_parameter(current_index, cur_current_index); + set_parameter(current_state, cur_transition_request); + } + } else { + ERR_PRINT("No such input: '" + cur_transition_request + "'"); + } + cur_transition_request = String(); + set_parameter(transition_request, cur_transition_request); + } + + // Special case for restart. + if (restart) { + set_parameter(time, 0); + return blend_input(cur_current_index, 0, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true); + } + + if (switched) { cur_prev_xfading = xfade_time; cur_time = 0; - switched = true; } - if (cur_current < 0 || cur_current >= enabled_inputs || cur_prev >= enabled_inputs) { + if (cur_current_index < 0 || cur_current_index >= get_input_count() || cur_prev_index >= get_input_count()) { return 0; } double rem = 0.0; if (sync) { - for (int i = 0; i < enabled_inputs; i++) { - if (i != cur_current && i != cur_prev) { + for (int i = 0; i < get_input_count(); i++) { + if (i != cur_current_index && i != cur_prev_index) { blend_input(i, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true); } } } - if (cur_prev < 0) { // process current animation, check for transition + if (cur_prev_index < 0) { // Process current animation, check for transition. - rem = blend_input(cur_current, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true); + rem = blend_input(cur_current_index, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true); if (p_seek) { cur_time = p_time; @@ -770,34 +904,39 @@ double AnimationNodeTransition::process(double p_time, bool p_seek, bool p_is_ex cur_time += p_time; } - if (inputs[cur_current].auto_advance && rem <= xfade_time) { - set_parameter(current, (cur_current + 1) % enabled_inputs); + if (input_data[cur_current_index].auto_advance && rem <= xfade_time) { + set_parameter(transition_request, get_input_name((cur_current_index + 1) % get_input_count())); } - } else { // cross-fading from prev to current + } else { // Cross-fading from prev to current. - real_t blend = xfade_time == 0 ? 0 : (cur_prev_xfading / xfade_time); + bool use_blend = xfade_time > 0; + real_t blend = !use_blend ? 0 : (cur_prev_xfading / xfade_time); if (xfade_curve.is_valid()) { blend = xfade_curve->sample(blend); } // Blend values must be more than CMP_EPSILON to process discrete keys in edge. real_t blend_inv = 1.0 - blend; - if (from_start && !p_seek && switched) { //just switched, seek to start of current - rem = blend_input(cur_current, 0, true, p_is_external_seeking, Math::is_zero_approx(blend_inv) ? CMP_EPSILON : blend_inv, FILTER_IGNORE, true); + if (input_data[cur_current_index].reset && !p_seek && switched) { // Just switched, seek to start of current. + rem = blend_input(cur_current_index, 0, true, p_is_external_seeking, Math::is_zero_approx(blend_inv) ? CMP_EPSILON : blend_inv, FILTER_IGNORE, true); } else { - rem = blend_input(cur_current, p_time, p_seek, p_is_external_seeking, Math::is_zero_approx(blend_inv) ? CMP_EPSILON : blend_inv, FILTER_IGNORE, true); + rem = blend_input(cur_current_index, p_time, p_seek, p_is_external_seeking, Math::is_zero_approx(blend_inv) ? CMP_EPSILON : blend_inv, FILTER_IGNORE, true); } if (p_seek) { - blend_input(cur_prev, p_time, true, p_is_external_seeking, Math::is_zero_approx(blend) ? CMP_EPSILON : blend, FILTER_IGNORE, true); + if (use_blend) { + blend_input(cur_prev_index, p_time, true, p_is_external_seeking, Math::is_zero_approx(blend) ? CMP_EPSILON : blend, FILTER_IGNORE, true); + } cur_time = p_time; } else { - blend_input(cur_prev, p_time, false, p_is_external_seeking, Math::is_zero_approx(blend) ? CMP_EPSILON : blend, FILTER_IGNORE, true); + if (use_blend) { + blend_input(cur_prev_index, p_time, false, p_is_external_seeking, Math::is_zero_approx(blend) ? CMP_EPSILON : blend, FILTER_IGNORE, true); + } cur_time += p_time; cur_prev_xfading -= p_time; if (cur_prev_xfading < 0) { - set_parameter(prev, -1); + set_parameter(prev_index, -1); } } } @@ -808,27 +947,22 @@ double AnimationNodeTransition::process(double p_time, bool p_seek, bool p_is_ex return rem; } -void AnimationNodeTransition::_validate_property(PropertyInfo &p_property) const { - if (p_property.name.begins_with("input_")) { - String n = p_property.name.get_slicec('/', 0).get_slicec('_', 1); - if (n != "count") { - int idx = n.to_int(); - if (idx >= enabled_inputs) { - p_property.usage = PROPERTY_USAGE_NONE; - } - } +void AnimationNodeTransition::_get_property_list(List<PropertyInfo> *p_list) const { + for (int i = 0; i < get_input_count(); i++) { + p_list->push_back(PropertyInfo(Variant::STRING, "input_" + itos(i) + "/name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(Variant::BOOL, "input_" + itos(i) + "/auto_advance", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(Variant::BOOL, "input_" + itos(i) + "/reset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL)); } } void AnimationNodeTransition::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_enabled_inputs", "amount"), &AnimationNodeTransition::set_enabled_inputs); - ClassDB::bind_method(D_METHOD("get_enabled_inputs"), &AnimationNodeTransition::get_enabled_inputs); + ClassDB::bind_method(D_METHOD("set_input_count", "input_count"), &AnimationNodeTransition::set_input_count); ClassDB::bind_method(D_METHOD("set_input_as_auto_advance", "input", "enable"), &AnimationNodeTransition::set_input_as_auto_advance); ClassDB::bind_method(D_METHOD("is_input_set_as_auto_advance", "input"), &AnimationNodeTransition::is_input_set_as_auto_advance); - ClassDB::bind_method(D_METHOD("set_input_caption", "input", "caption"), &AnimationNodeTransition::set_input_caption); - ClassDB::bind_method(D_METHOD("get_input_caption", "input"), &AnimationNodeTransition::get_input_caption); + ClassDB::bind_method(D_METHOD("set_input_reset", "input", "enable"), &AnimationNodeTransition::set_input_reset); + ClassDB::bind_method(D_METHOD("is_input_reset", "input"), &AnimationNodeTransition::is_input_reset); ClassDB::bind_method(D_METHOD("set_xfade_time", "time"), &AnimationNodeTransition::set_xfade_time); ClassDB::bind_method(D_METHOD("get_xfade_time"), &AnimationNodeTransition::get_xfade_time); @@ -836,24 +970,16 @@ void AnimationNodeTransition::_bind_methods() { ClassDB::bind_method(D_METHOD("set_xfade_curve", "curve"), &AnimationNodeTransition::set_xfade_curve); ClassDB::bind_method(D_METHOD("get_xfade_curve"), &AnimationNodeTransition::get_xfade_curve); - ClassDB::bind_method(D_METHOD("set_from_start", "from_start"), &AnimationNodeTransition::set_from_start); - ClassDB::bind_method(D_METHOD("is_from_start"), &AnimationNodeTransition::is_from_start); + ClassDB::bind_method(D_METHOD("set_allow_transition_to_self", "enable"), &AnimationNodeTransition::set_allow_transition_to_self); + ClassDB::bind_method(D_METHOD("is_allow_transition_to_self"), &AnimationNodeTransition::is_allow_transition_to_self); - ADD_PROPERTY(PropertyInfo(Variant::INT, "enabled_inputs", PROPERTY_HINT_RANGE, "0,64,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_enabled_inputs", "get_enabled_inputs"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "xfade_time", PROPERTY_HINT_RANGE, "0,120,0.01,suffix:s"), "set_xfade_time", "get_xfade_time"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "xfade_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_xfade_curve", "get_xfade_curve"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "from_start"), "set_from_start", "is_from_start"); - - for (int i = 0; i < MAX_INPUTS; i++) { - ADD_PROPERTYI(PropertyInfo(Variant::STRING, "input_" + itos(i) + "/name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "set_input_caption", "get_input_caption", i); - ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "input_" + itos(i) + "/auto_advance", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "set_input_as_auto_advance", "is_input_set_as_auto_advance", i); - } + 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"); } AnimationNodeTransition::AnimationNodeTransition() { - for (int i = 0; i < MAX_INPUTS; i++) { - inputs[i].name = "state " + itos(i); - } } ///////////////////// @@ -887,6 +1013,8 @@ void AnimationNodeBlendTree::add_node(const StringName &p_name, Ref<AnimationNod emit_signal(SNAME("tree_changed")); p_node->connect("tree_changed", callable_mp(this, &AnimationNodeBlendTree::_tree_changed), CONNECT_REFERENCE_COUNTED); + p_node->connect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendTree::_animation_node_renamed), CONNECT_REFERENCE_COUNTED); + p_node->connect("animation_node_removed", callable_mp(this, &AnimationNodeBlendTree::_animation_node_removed), CONNECT_REFERENCE_COUNTED); p_node->connect("changed", callable_mp(this, &AnimationNodeBlendTree::_node_changed).bind(p_name), CONNECT_REFERENCE_COUNTED); } @@ -949,12 +1077,14 @@ void AnimationNodeBlendTree::remove_node(const StringName &p_name) { { Ref<AnimationNode> node = nodes[p_name].node; node->disconnect("tree_changed", callable_mp(this, &AnimationNodeBlendTree::_tree_changed)); + node->disconnect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendTree::_animation_node_renamed)); + node->disconnect("animation_node_removed", callable_mp(this, &AnimationNodeBlendTree::_animation_node_removed)); node->disconnect("changed", callable_mp(this, &AnimationNodeBlendTree::_node_changed)); } nodes.erase(p_name); - //erase connections to name + // Erase connections to name. for (KeyValue<StringName, Node> &E : nodes) { for (int i = 0; i < E.value.connections.size(); i++) { if (E.value.connections[i] == p_name) { @@ -963,6 +1093,7 @@ void AnimationNodeBlendTree::remove_node(const StringName &p_name) { } } + emit_signal(SNAME("animation_node_removed"), get_instance_id(), p_name); emit_changed(); emit_signal(SNAME("tree_changed")); } @@ -978,7 +1109,7 @@ void AnimationNodeBlendTree::rename_node(const StringName &p_name, const StringN nodes[p_new_name] = nodes[p_name]; nodes.erase(p_name); - //rename connections + // Rename connections. for (KeyValue<StringName, Node> &E : nodes) { for (int i = 0; i < E.value.connections.size(); i++) { if (E.value.connections[i] == p_name) { @@ -986,9 +1117,10 @@ void AnimationNodeBlendTree::rename_node(const StringName &p_name, const StringN } } } - //connection must be done with new name + // Connection must be done with new name. nodes[p_new_name].node->connect("changed", callable_mp(this, &AnimationNodeBlendTree::_node_changed).bind(p_new_name), CONNECT_REFERENCE_COUNTED); + emit_signal(SNAME("animation_node_renamed"), get_instance_id(), p_name, p_new_name); emit_signal(SNAME("tree_changed")); } @@ -1189,6 +1321,18 @@ void AnimationNodeBlendTree::_get_property_list(List<PropertyInfo> *p_list) cons p_list->push_back(PropertyInfo(Variant::ARRAY, "node_connections", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); } +void AnimationNodeBlendTree::_tree_changed() { + AnimationRootNode::_tree_changed(); +} + +void AnimationNodeBlendTree::_animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name) { + AnimationRootNode::_animation_node_renamed(p_oid, p_old_name, p_new_name); +} + +void AnimationNodeBlendTree::_animation_node_removed(const ObjectID &p_oid, const StringName &p_node) { + AnimationRootNode::_animation_node_removed(p_oid, p_node); +} + void AnimationNodeBlendTree::reset_state() { graph_offset = Vector2(); nodes.clear(); @@ -1197,10 +1341,6 @@ void AnimationNodeBlendTree::reset_state() { emit_signal(SNAME("tree_changed")); } -void AnimationNodeBlendTree::_tree_changed() { - emit_signal(SNAME("tree_changed")); -} - void AnimationNodeBlendTree::_node_changed(const StringName &p_node) { ERR_FAIL_COND(!nodes.has(p_node)); nodes[p_node].connections.resize(nodes[p_node].node->get_input_count()); diff --git a/scene/animation/animation_blend_tree.h b/scene/animation/animation_blend_tree.h index e72471202e..d4827180bb 100644 --- a/scene/animation/animation_blend_tree.h +++ b/scene/animation/animation_blend_tree.h @@ -96,6 +96,12 @@ class AnimationNodeOneShot : public AnimationNodeSync { GDCLASS(AnimationNodeOneShot, AnimationNodeSync); public: + enum OneShotRequest { + ONE_SHOT_REQUEST_NONE, + ONE_SHOT_REQUEST_FIRE, + ONE_SHOT_REQUEST_ABORT, + }; + enum MixMode { MIX_MODE_BLEND, MIX_MODE_ADD @@ -110,13 +116,8 @@ private: double autorestart_random_delay = 0.0; MixMode mix = MIX_MODE_BLEND; - /* bool active; - bool do_start; - double time; - double remaining;*/ - + StringName request = PNAME("request"); StringName active = PNAME("active"); - StringName prev_active = "prev_active"; StringName time = "time"; StringName remaining = "remaining"; StringName time_to_restart = "time_to_restart"; @@ -127,6 +128,7 @@ protected: public: virtual void get_parameter_list(List<PropertyInfo> *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; virtual String get_caption() const override; @@ -153,6 +155,7 @@ public: AnimationNodeOneShot(); }; +VARIANT_ENUM_CAST(AnimationNodeOneShot::OneShotRequest) VARIANT_ENUM_CAST(AnimationNodeOneShot::MixMode) class AnimationNodeAdd2 : public AnimationNodeSync { @@ -254,7 +257,7 @@ public: class AnimationNodeTimeSeek : public AnimationNode { GDCLASS(AnimationNodeTimeSeek, AnimationNode); - StringName seek_pos = PNAME("seek_position"); + StringName seek_pos_request = PNAME("seek_request"); protected: static void _bind_methods(); @@ -273,54 +276,52 @@ public: class AnimationNodeTransition : public AnimationNodeSync { GDCLASS(AnimationNodeTransition, AnimationNodeSync); - enum { - MAX_INPUTS = 32 - }; struct InputData { - String name; bool auto_advance = false; + bool reset = true; }; + Vector<InputData> input_data; - InputData inputs[MAX_INPUTS]; - int enabled_inputs = 0; - - /* - double prev_xfading; - int prev; - double time; - int current; - int prev_current; */ - - StringName prev_xfading = "prev_xfading"; - StringName prev = "prev"; StringName time = "time"; - StringName current = PNAME("current"); - StringName prev_current = "prev_current"; + StringName prev_xfading = "prev_xfading"; + StringName prev_index = "prev_index"; + StringName current_index = PNAME("current_index"); + StringName current_state = PNAME("current_state"); + StringName transition_request = PNAME("transition_request"); + + StringName prev_frame_current = "pf_current"; + StringName prev_frame_current_idx = "pf_current_idx"; double xfade_time = 0.0; Ref<Curve> xfade_curve; - bool from_start = true; + bool allow_transition_to_self = false; - void _update_inputs(); + bool pending_update = false; protected: + bool _get(const StringName &p_path, Variant &r_ret) const; + bool _set(const StringName &p_path, const Variant &p_value); static void _bind_methods(); - void _validate_property(PropertyInfo &p_property) const; + void _get_property_list(List<PropertyInfo> *p_list) const; public: virtual void get_parameter_list(List<PropertyInfo> *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; virtual String get_caption() const override; - void set_enabled_inputs(int p_inputs); - int get_enabled_inputs(); + void set_input_count(int p_inputs); + + virtual bool add_input(const String &p_name) override; + virtual void remove_input(int p_index) override; + virtual bool set_input_name(int p_input, const String &p_name) override; void set_input_as_auto_advance(int p_input, bool p_enable); bool is_input_set_as_auto_advance(int p_input) const; - void set_input_caption(int p_input, const String &p_name); - String get_input_caption(int p_input) const; + void set_input_reset(int p_input, bool p_enable); + bool is_input_reset(int p_input) const; void set_xfade_time(double p_fade); double get_xfade_time() const; @@ -328,8 +329,8 @@ public: void set_xfade_curve(const Ref<Curve> &p_curve); Ref<Curve> get_xfade_curve() const; - void set_from_start(bool p_from_start); - bool is_from_start() const; + void set_allow_transition_to_self(bool p_enable); + bool is_allow_transition_to_self() const; double process(double p_time, bool p_seek, bool p_is_external_seeking) override; @@ -360,7 +361,6 @@ class AnimationNodeBlendTree : public AnimationRootNode { Vector2 graph_offset; - void _tree_changed(); void _node_changed(const StringName &p_node); void _initialize_node_tree(); @@ -371,6 +371,10 @@ protected: bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List<PropertyInfo> *p_list) const; + virtual void _tree_changed() override; + virtual void _animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name) override; + virtual void _animation_node_removed(const ObjectID &p_oid, const StringName &p_node) override; + virtual void reset_state() override; public: diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index f5df64dbdd..d19d3cc7a3 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -107,6 +107,15 @@ Ref<Curve> AnimationNodeStateMachineTransition::get_xfade_curve() const { return xfade_curve; } +void AnimationNodeStateMachineTransition::set_reset(bool p_reset) { + reset = p_reset; + emit_changed(); +} + +bool AnimationNodeStateMachineTransition::is_reset() const { + return reset; +} + void AnimationNodeStateMachineTransition::set_priority(int p_priority) { priority = p_priority; emit_changed(); @@ -132,6 +141,9 @@ void AnimationNodeStateMachineTransition::_bind_methods() { ClassDB::bind_method(D_METHOD("set_xfade_curve", "curve"), &AnimationNodeStateMachineTransition::set_xfade_curve); ClassDB::bind_method(D_METHOD("get_xfade_curve"), &AnimationNodeStateMachineTransition::get_xfade_curve); + ClassDB::bind_method(D_METHOD("set_reset", "reset"), &AnimationNodeStateMachineTransition::set_reset); + ClassDB::bind_method(D_METHOD("is_reset"), &AnimationNodeStateMachineTransition::is_reset); + ClassDB::bind_method(D_METHOD("set_priority", "priority"), &AnimationNodeStateMachineTransition::set_priority); ClassDB::bind_method(D_METHOD("get_priority"), &AnimationNodeStateMachineTransition::get_priority); @@ -140,6 +152,9 @@ void AnimationNodeStateMachineTransition::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "xfade_time", PROPERTY_HINT_RANGE, "0,240,0.01,suffix:s"), "set_xfade_time", "get_xfade_time"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "xfade_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_xfade_curve", "get_xfade_curve"); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reset"), "set_reset", "is_reset"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "priority", PROPERTY_HINT_RANGE, "0,32,1"), "set_priority", "get_priority"); ADD_GROUP("Switch", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "switch_mode", PROPERTY_HINT_ENUM, "Immediate,Sync,At End"), "set_switch_mode", "get_switch_mode"); @@ -164,18 +179,27 @@ AnimationNodeStateMachineTransition::AnimationNodeStateMachineTransition() { //////////////////////////////////////////////////////// -void AnimationNodeStateMachinePlayback::travel(const StringName &p_state) { - start_request_travel = true; - start_request = p_state; +void AnimationNodeStateMachinePlayback::travel(const StringName &p_state, bool p_reset_on_teleport) { + travel_request = p_state; + reset_request_on_teleport = p_reset_on_teleport; stop_request = false; } -void AnimationNodeStateMachinePlayback::start(const StringName &p_state) { - start_request_travel = false; +void AnimationNodeStateMachinePlayback::start(const StringName &p_state, bool p_reset) { + travel_request = StringName(); + reset_request = p_reset; + _start(p_state); +} + +void AnimationNodeStateMachinePlayback::_start(const StringName &p_state) { start_request = p_state; stop_request = false; } +void AnimationNodeStateMachinePlayback::next() { + next_request = true; +} + void AnimationNodeStateMachinePlayback::stop() { stop_request = true; } @@ -188,7 +212,7 @@ StringName AnimationNodeStateMachinePlayback::get_current_node() const { return current; } -StringName AnimationNodeStateMachinePlayback::get_blend_from_node() const { +StringName AnimationNodeStateMachinePlayback::get_fading_from_node() const { return fading_from; } @@ -204,6 +228,22 @@ float AnimationNodeStateMachinePlayback::get_current_length() const { return len_current; } +float AnimationNodeStateMachinePlayback::get_fade_from_play_pos() const { + return pos_fade_from; +} + +float AnimationNodeStateMachinePlayback::get_fade_from_length() const { + return len_fade_from; +} + +float AnimationNodeStateMachinePlayback::get_fading_time() const { + return fading_time; +} + +float AnimationNodeStateMachinePlayback::get_fading_pos() const { + return fading_pos; +} + bool AnimationNodeStateMachinePlayback::_travel(AnimationNodeStateMachine *p_state_machine, const StringName &p_travel) { ERR_FAIL_COND_V(!playing, false); ERR_FAIL_COND_V(!p_state_machine->states.has(p_travel), false); @@ -212,7 +252,7 @@ bool AnimationNodeStateMachinePlayback::_travel(AnimationNodeStateMachine *p_sta path.clear(); //a new one will be needed if (current == p_travel) { - return true; //nothing to do + return !p_state_machine->is_allow_transition_to_self(); } Vector2 current_pos = p_state_machine->states[current].position; @@ -323,6 +363,15 @@ bool AnimationNodeStateMachinePlayback::_travel(AnimationNodeStateMachine *p_sta } double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek, bool p_is_external_seeking) { + double rem = _process(p_state_machine, p_time, p_seek, p_is_external_seeking); + start_request = StringName(); + next_request = false; + stop_request = false; + reset_request_on_teleport = false; + return rem; +} + +double AnimationNodeStateMachinePlayback::_process(AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek, bool p_is_external_seeking) { if (p_time == -1) { Ref<AnimationNodeStateMachine> anodesm = p_state_machine->states[current].node; if (anodesm.is_valid()) { @@ -335,14 +384,13 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s //if not playing and it can restart, then restart if (!playing && start_request == StringName()) { if (!stop_request && p_state_machine->start_node) { - start(p_state_machine->start_node); + _start(p_state_machine->start_node); } else { return 0; } } if (playing && stop_request) { - stop_request = false; playing = false; return 0; } @@ -350,42 +398,47 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s bool play_start = false; if (start_request != StringName()) { - if (start_request_travel) { - if (!playing) { - if (!stop_request && p_state_machine->start_node) { - // can restart, just postpone traveling - path.clear(); - current = p_state_machine->start_node; - playing = true; - play_start = true; - } else { - // stopped, invalid state - String node_name = start_request; - start_request = StringName(); //clear start request - ERR_FAIL_V_MSG(0, "Can't travel to '" + node_name + "' if state machine is not playing. Maybe you need to enable Autoplay on Load for one of the nodes in your state machine or call .start() first?"); - } - } else { - if (!_travel(p_state_machine, start_request)) { - // can't travel, then teleport - path.clear(); - current = start_request; - play_start = true; - } - start_request = StringName(); //clear start request - } + // teleport to start + if (p_state_machine->states.has(start_request)) { + path.clear(); + current = start_request; + playing = true; + play_start = true; } else { - // teleport to start - if (p_state_machine->states.has(start_request)) { + StringName node = start_request; + ERR_FAIL_V_MSG(0, "No such node: '" + node + "'"); + } + } else if (travel_request != StringName()) { + if (!playing) { + if (!stop_request && p_state_machine->start_node) { + // can restart, just postpone traveling path.clear(); - current = start_request; + current = p_state_machine->start_node; playing = true; play_start = true; - start_request = StringName(); //clear start request } else { - StringName node = start_request; - start_request = StringName(); //clear start request - ERR_FAIL_V_MSG(0, "No such node: '" + node + "'"); + // stopped, invalid state + String node_name = travel_request; + travel_request = StringName(); + ERR_FAIL_V_MSG(0, "Can't travel to '" + node_name + "' if state machine is not playing. Maybe you need to enable Autoplay on Load for one of the nodes in your state machine or call .start() first?"); + } + } else { + if (!_travel(p_state_machine, travel_request)) { + // can't travel, then teleport + if (p_state_machine->states.has(travel_request)) { + path.clear(); + if (current != travel_request || reset_request_on_teleport) { + current = travel_request; + play_start = true; + reset_request = reset_request_on_teleport; + } + } else { + StringName node = travel_request; + travel_request = StringName(); + ERR_FAIL_V_MSG(0, "No such node: '" + node + "'"); + } } + travel_request = StringName(); } } @@ -396,8 +449,11 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s current = p_state_machine->start_node; } - len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, p_is_external_seeking, 1.0, AnimationNode::FILTER_IGNORE, true); - pos_current = 0; + if (reset_request) { + len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, p_is_external_seeking, 1.0, AnimationNode::FILTER_IGNORE, true); + pos_current = 0; + reset_request = false; + } } if (!p_state_machine->states.has(current)) { @@ -421,11 +477,22 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s if (current_curve.is_valid()) { fade_blend = current_curve->sample(fade_blend); } - double rem = p_state_machine->blend_node(current, p_state_machine->states[current].node, p_time, p_seek, p_is_external_seeking, Math::is_zero_approx(fade_blend) ? CMP_EPSILON : fade_blend, AnimationNode::FILTER_IGNORE, true); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. + + double rem = do_start ? len_current : p_state_machine->blend_node(current, p_state_machine->states[current].node, p_time, p_seek, p_is_external_seeking, Math::is_zero_approx(fade_blend) ? CMP_EPSILON : fade_blend, AnimationNode::FILTER_IGNORE, true); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. if (fading_from != StringName()) { double fade_blend_inv = 1.0 - fade_blend; - p_state_machine->blend_node(fading_from, p_state_machine->states[fading_from].node, p_time, p_seek, p_is_external_seeking, Math::is_zero_approx(fade_blend_inv) ? CMP_EPSILON : fade_blend_inv, AnimationNode::FILTER_IGNORE, true); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. + float fading_from_rem = 0.0; + fading_from_rem = p_state_machine->blend_node(fading_from, p_state_machine->states[fading_from].node, p_time, p_seek, p_is_external_seeking, Math::is_zero_approx(fade_blend_inv) ? CMP_EPSILON : fade_blend_inv, AnimationNode::FILTER_IGNORE, true); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. + //guess playback position + if (fading_from_rem > len_fade_from) { // weird but ok + len_fade_from = fading_from_rem; + } + + { //advance and loop check + float next_pos = len_fade_from - fading_from_rem; + pos_fade_from = next_pos; //looped + } if (fade_blend >= 1.0) { fading_from = StringName(); } @@ -457,6 +524,7 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s next_xfade = p_state_machine->transitions[i].transition->get_xfade_time(); current_curve = p_state_machine->transitions[i].transition->get_xfade_curve(); switch_mode = p_state_machine->transitions[i].transition->get_switch_mode(); + reset_request = p_state_machine->transitions[i].transition->is_reset(); next = path[0]; } } @@ -513,6 +581,7 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s current_curve = p_state_machine->transitions[auto_advance_to].transition->get_xfade_curve(); next_xfade = p_state_machine->transitions[auto_advance_to].transition->get_xfade_time(); switch_mode = p_state_machine->transitions[auto_advance_to].transition->get_switch_mode(); + reset_request = p_state_machine->transitions[auto_advance_to].transition->is_reset(); } } @@ -567,7 +636,7 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s goto_next = fading_from == StringName(); } - if (goto_next) { //end_loop should be used because fade time may be too small or zero and animation may have looped + if (next_request || goto_next) { //end_loop should be used because fade time may be too small or zero and animation may have looped if (next_xfade) { //time to fade, baby fading_from = current; @@ -590,8 +659,12 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s } current = next; + pos_fade_from = pos_current; + len_fade_from = len_current; - len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, p_is_external_seeking, CMP_EPSILON, AnimationNode::FILTER_IGNORE, true); // Process next node's first key in here. + if (reset_request) { + len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, p_is_external_seeking, CMP_EPSILON, AnimationNode::FILTER_IGNORE, true); // Process next node's first key in here. + } if (switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_SYNC) { pos_current = MIN(pos_current, len_current); p_state_machine->blend_node(current, p_state_machine->states[current].node, pos_current, true, p_is_external_seeking, 0, AnimationNode::FILTER_IGNORE, true); @@ -652,13 +725,15 @@ bool AnimationNodeStateMachinePlayback::_check_advance_condition(const Ref<Anima } void AnimationNodeStateMachinePlayback::_bind_methods() { - ClassDB::bind_method(D_METHOD("travel", "to_node"), &AnimationNodeStateMachinePlayback::travel); - ClassDB::bind_method(D_METHOD("start", "node"), &AnimationNodeStateMachinePlayback::start); + ClassDB::bind_method(D_METHOD("travel", "to_node", "reset_on_teleport"), &AnimationNodeStateMachinePlayback::travel, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("start", "node", "reset"), &AnimationNodeStateMachinePlayback::start, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("next"), &AnimationNodeStateMachinePlayback::next); ClassDB::bind_method(D_METHOD("stop"), &AnimationNodeStateMachinePlayback::stop); ClassDB::bind_method(D_METHOD("is_playing"), &AnimationNodeStateMachinePlayback::is_playing); ClassDB::bind_method(D_METHOD("get_current_node"), &AnimationNodeStateMachinePlayback::get_current_node); ClassDB::bind_method(D_METHOD("get_current_play_position"), &AnimationNodeStateMachinePlayback::get_current_play_pos); 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); } @@ -669,7 +744,7 @@ AnimationNodeStateMachinePlayback::AnimationNodeStateMachinePlayback() { /////////////////////////////////////////////////////// void AnimationNodeStateMachine::get_parameter_list(List<PropertyInfo> *r_list) const { - r_list->push_back(PropertyInfo(Variant::OBJECT, playback, PROPERTY_HINT_RESOURCE_TYPE, "AnimationNodeStateMachinePlayback", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE)); + r_list->push_back(PropertyInfo(Variant::OBJECT, playback, PROPERTY_HINT_RESOURCE_TYPE, "AnimationNodeStateMachinePlayback", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ALWAYS_DUPLICATE)); List<StringName> advance_conditions; for (int i = 0; i < transitions.size(); i++) { StringName ac = transitions[i].transition->get_advance_condition_name(); @@ -716,6 +791,8 @@ void AnimationNodeStateMachine::add_node(const StringName &p_name, Ref<Animation emit_signal(SNAME("tree_changed")); p_node->connect("tree_changed", callable_mp(this, &AnimationNodeStateMachine::_tree_changed), CONNECT_REFERENCE_COUNTED); + p_node->connect("animation_node_renamed", callable_mp(this, &AnimationNodeStateMachine::_animation_node_renamed), CONNECT_REFERENCE_COUNTED); + p_node->connect("animation_node_removed", callable_mp(this, &AnimationNodeStateMachine::_animation_node_removed), CONNECT_REFERENCE_COUNTED); } void AnimationNodeStateMachine::replace_node(const StringName &p_name, Ref<AnimationNode> p_node) { @@ -727,6 +804,8 @@ void AnimationNodeStateMachine::replace_node(const StringName &p_name, Ref<Anima Ref<AnimationNode> node = states[p_name].node; if (node.is_valid()) { node->disconnect("tree_changed", callable_mp(this, &AnimationNodeStateMachine::_tree_changed)); + node->disconnect("animation_node_renamed", callable_mp(this, &AnimationNodeStateMachine::_animation_node_renamed)); + node->disconnect("animation_node_removed", callable_mp(this, &AnimationNodeStateMachine::_animation_node_removed)); } } @@ -736,6 +815,16 @@ void AnimationNodeStateMachine::replace_node(const StringName &p_name, Ref<Anima emit_signal(SNAME("tree_changed")); p_node->connect("tree_changed", callable_mp(this, &AnimationNodeStateMachine::_tree_changed), CONNECT_REFERENCE_COUNTED); + p_node->connect("animation_node_renamed", callable_mp(this, &AnimationNodeStateMachine::_animation_node_renamed), CONNECT_REFERENCE_COUNTED); + p_node->connect("animation_node_removed", callable_mp(this, &AnimationNodeStateMachine::_animation_node_removed), CONNECT_REFERENCE_COUNTED); +} + +void AnimationNodeStateMachine::set_allow_transition_to_self(bool p_enable) { + allow_transition_to_self = p_enable; +} + +bool AnimationNodeStateMachine::is_allow_transition_to_self() const { + return allow_transition_to_self; } bool AnimationNodeStateMachine::can_edit_node(const StringName &p_name) const { @@ -801,10 +890,13 @@ void AnimationNodeStateMachine::remove_node(const StringName &p_name) { Ref<AnimationNode> node = states[p_name].node; ERR_FAIL_COND(node.is_null()); node->disconnect("tree_changed", callable_mp(this, &AnimationNodeStateMachine::_tree_changed)); + node->disconnect("animation_node_renamed", callable_mp(this, &AnimationNodeStateMachine::_animation_node_renamed)); + node->disconnect("animation_node_removed", callable_mp(this, &AnimationNodeStateMachine::_animation_node_removed)); } states.erase(p_name); + emit_signal(SNAME("animation_node_removed"), get_instance_id(), p_name); emit_changed(); emit_signal(SNAME("tree_changed")); } @@ -824,6 +916,7 @@ void AnimationNodeStateMachine::rename_node(const StringName &p_name, const Stri _rename_transitions(p_name, p_new_name); + emit_signal(SNAME("animation_node_renamed"), get_instance_id(), p_name, p_new_name); emit_changed(); emit_signal(SNAME("tree_changed")); } @@ -1282,7 +1375,15 @@ Vector2 AnimationNodeStateMachine::get_node_position(const StringName &p_name) c void AnimationNodeStateMachine::_tree_changed() { emit_changed(); - emit_signal(SNAME("tree_changed")); + AnimationRootNode::_tree_changed(); +} + +void AnimationNodeStateMachine::_animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name) { + AnimationRootNode::_animation_node_renamed(p_oid, p_old_name, p_new_name); +} + +void AnimationNodeStateMachine::_animation_node_removed(const ObjectID &p_oid, const StringName &p_node) { + AnimationRootNode::_animation_node_removed(p_oid, p_node); } void AnimationNodeStateMachine::_bind_methods() { @@ -1308,6 +1409,11 @@ void AnimationNodeStateMachine::_bind_methods() { ClassDB::bind_method(D_METHOD("set_graph_offset", "offset"), &AnimationNodeStateMachine::set_graph_offset); ClassDB::bind_method(D_METHOD("get_graph_offset"), &AnimationNodeStateMachine::get_graph_offset); + + ClassDB::bind_method(D_METHOD("set_allow_transition_to_self", "enable"), &AnimationNodeStateMachine::set_allow_transition_to_self); + ClassDB::bind_method(D_METHOD("is_allow_transition_to_self"), &AnimationNodeStateMachine::is_allow_transition_to_self); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_transition_to_self"), "set_allow_transition_to_self", "is_allow_transition_to_self"); } AnimationNodeStateMachine::AnimationNodeStateMachine() { diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h index 116589eb2f..5867b6c65a 100644 --- a/scene/animation/animation_node_state_machine.h +++ b/scene/animation/animation_node_state_machine.h @@ -57,6 +57,7 @@ private: StringName advance_condition_name; float xfade_time = 0.0; Ref<Curve> xfade_curve; + bool reset = true; int priority = 1; String advance_expression; @@ -84,6 +85,9 @@ public: void set_xfade_time(float p_xfade); float get_xfade_time() const; + void set_reset(bool p_reset); + bool is_reset() const; + void set_xfade_curve(const Ref<Curve> &p_curve); Ref<Curve> get_xfade_curve() const; @@ -114,6 +118,9 @@ class AnimationNodeStateMachinePlayback : public Resource { StringName next; }; + double len_fade_from = 0.0; + double pos_fade_from = 0.0; + double len_current = 0.0; double pos_current = 0.0; bool end_loop = false; @@ -131,10 +138,15 @@ class AnimationNodeStateMachinePlayback : public Resource { bool playing = false; StringName start_request; - bool start_request_travel = false; + StringName travel_request; + bool reset_request = false; + bool reset_request_on_teleport = false; + bool next_request = false; bool stop_request = false; bool _travel(AnimationNodeStateMachine *p_state_machine, const StringName &p_travel); + void _start(const StringName &p_state); + double _process(AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek, bool p_is_external_seeking); double process(AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek, bool p_is_external_seeking); @@ -144,16 +156,23 @@ protected: static void _bind_methods(); public: - void travel(const StringName &p_state); - void start(const StringName &p_state); + void travel(const StringName &p_state, bool p_reset_on_teleport = true); + void start(const StringName &p_state, bool p_reset = true); + void next(); void stop(); bool is_playing() const; StringName get_current_node() const; - StringName get_blend_from_node() const; + StringName get_fading_from_node() const; Vector<StringName> get_travel_path() const; float get_current_play_pos() const; float get_current_length() const; + float get_fade_from_play_pos() const; + float get_fade_from_length() const; + + float get_fading_time() const; + float get_fading_pos() const; + AnimationNodeStateMachinePlayback(); }; @@ -169,6 +188,7 @@ private: }; HashMap<StringName, State> states; + bool allow_transition_to_self = false; struct Transition { StringName from; @@ -187,7 +207,6 @@ private: Vector2 graph_offset; - void _tree_changed(); void _remove_transition(const Ref<AnimationNodeStateMachineTransition> p_transition); void _rename_transitions(const StringName &p_name, const StringName &p_new_name); bool _can_connect(const StringName &p_name, Vector<AnimationNodeStateMachine *> p_parents = Vector<AnimationNodeStateMachine *>()); @@ -201,6 +220,10 @@ protected: void _get_property_list(List<PropertyInfo> *p_list) const; bool _check_advance_condition(const Ref<AnimationNodeStateMachine> p_state_machine, const Ref<AnimationNodeStateMachineTransition> p_transition) const; + virtual void _tree_changed() override; + virtual void _animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name) override; + virtual void _animation_node_removed(const ObjectID &p_oid, const StringName &p_node) override; + virtual void reset_state() override; public: @@ -235,6 +258,9 @@ public: void remove_transition_by_index(const int p_transition); void remove_transition(const StringName &p_from, const StringName &p_to); + void set_allow_transition_to_self(bool p_enable); + bool is_allow_transition_to_self() const; + bool can_edit_node(const StringName &p_name) const; AnimationNodeStateMachine *get_prev_state_machine() const; diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index 7f42c8fac3..fc3a3d306f 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -143,8 +143,8 @@ bool AnimationPlayer::_get(const StringName &p_name, Variant &r_ret) const { } else if (name.begins_with("libraries")) { Dictionary d; - for (uint32_t i = 0; i < animation_libraries.size(); i++) { - d[animation_libraries[i].name] = animation_libraries[i].library; + for (const AnimationLibraryData &lib : animation_libraries) { + d[lib.name] = lib.library; } r_ret = d; @@ -199,7 +199,7 @@ void AnimationPlayer::_validate_property(PropertyInfo &p_property) const { void AnimationPlayer::_get_property_list(List<PropertyInfo> *p_list) const { List<PropertyInfo> anim_names; - anim_names.push_back(PropertyInfo(Variant::DICTIONARY, "libraries")); + anim_names.push_back(PropertyInfo(Variant::DICTIONARY, PNAME("libraries"))); for (const KeyValue<StringName, AnimationData> &E : animation_set) { if (E.value.next != StringName()) { @@ -431,6 +431,17 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim, Node *p_root_ov } } + if (a->track_get_type(i) == Animation::TYPE_AUDIO) { + if (!node_cache->audio_anim.has(a->track_get_path(i).get_concatenated_names())) { + TrackNodeCache::AudioAnim aa; + aa.object = (Object *)child; + aa.audio_stream.instantiate(); + aa.audio_stream->set_polyphony(audio_max_polyphony); + + node_cache->audio_anim[a->track_get_path(i).get_concatenated_names()] = aa; + } + } + node_cache->last_setup_pass = setup_pass; } } @@ -451,6 +462,15 @@ static void _call_object(Object *p_object, const StringName &p_method, const Vec } } +Variant AnimationPlayer::post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx) { + Variant res; + if (GDVIRTUAL_CALL(_post_process_key_value, p_anim, p_track, p_value, const_cast<Object *>(p_object), p_object_idx, res)) { + return res; + } + + return _post_process_key_value(p_anim, p_track, p_value, p_object, p_object_idx); +} + Variant AnimationPlayer::_post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx) { switch (p_anim->track_get_type(p_track)) { #ifndef _3D_DISABLED @@ -473,7 +493,9 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double ERR_FAIL_COND(p_anim->node_cache.size() != p_anim->animation->get_track_count()); Animation *a = p_anim->animation.operator->(); +#ifdef TOOLS_ENABLED bool can_call = is_inside_tree() && !Engine::get_singleton()->is_editor_hint(); +#endif // TOOLS_ENABLED bool backward = signbit(p_delta); for (int i = 0; i < a->get_track_count(); i++) { @@ -512,7 +534,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double if (err != OK) { continue; } - loc = _post_process_key_value(a, i, loc, nc->node_3d, nc->bone_idx); + loc = post_process_key_value(a, i, loc, nc->node_3d, nc->bone_idx); if (nc->accum_pass != accum_pass) { ERR_CONTINUE(cache_update_size >= NODE_CACHE_UPDATE_MAX); @@ -540,7 +562,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double if (err != OK) { continue; } - rot = _post_process_key_value(a, i, rot, nc->node_3d, nc->bone_idx); + rot = post_process_key_value(a, i, rot, nc->node_3d, nc->bone_idx); if (nc->accum_pass != accum_pass) { ERR_CONTINUE(cache_update_size >= NODE_CACHE_UPDATE_MAX); @@ -568,7 +590,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double if (err != OK) { continue; } - scale = _post_process_key_value(a, i, scale, nc->node_3d, nc->bone_idx); + scale = post_process_key_value(a, i, scale, nc->node_3d, nc->bone_idx); if (nc->accum_pass != accum_pass) { ERR_CONTINUE(cache_update_size >= NODE_CACHE_UPDATE_MAX); @@ -596,7 +618,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double if (err != OK) { continue; } - blend = _post_process_key_value(a, i, blend, nc->node_blend_shape, nc->blend_shape_idx); + blend = post_process_key_value(a, i, blend, nc->node_blend_shape, nc->blend_shape_idx); if (nc->accum_pass != accum_pass) { ERR_CONTINUE(cache_update_size >= NODE_CACHE_UPDATE_MAX); @@ -649,7 +671,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double if (p_time < first_key_time) { double c = Math::ease(p_time / first_key_time, transition); Variant first_value = a->track_get_key_value(i, first_key); - first_value = _post_process_key_value(a, i, first_value, nc->node); + first_value = post_process_key_value(a, i, first_value, nc->node); Variant interp_value = Animation::interpolate_variant(pa->capture, first_value, c); if (pa->accum_pass != accum_pass) { ERR_CONTINUE(cache_update_prop_size >= NODE_CACHE_UPDATE_MAX); @@ -670,7 +692,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double if (value == Variant()) { continue; } - value = _post_process_key_value(a, i, value, nc->node); + value = post_process_key_value(a, i, value, nc->node); if (pa->accum_pass != accum_pass) { ERR_CONTINUE(cache_update_prop_size >= NODE_CACHE_UPDATE_MAX); @@ -701,7 +723,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double for (int &F : indices) { Variant value = a->track_get_key_value(i, F); - value = _post_process_key_value(a, i, value, nc->node); + value = post_process_key_value(a, i, value, nc->node); switch (pa->special) { case SP_NONE: { bool valid; @@ -745,11 +767,13 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double } break; case Animation::TYPE_METHOD: { - if (!nc->node) { +#ifdef TOOLS_ENABLED + if (!can_call) { continue; } - if (!p_is_current) { - break; +#endif // TOOLS_ENABLED + if (!p_is_current || !nc->node || is_stopping) { + continue; } List<int> indices; @@ -772,16 +796,12 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double for (int &E : indices) { StringName method = a->method_track_get_name(i, E); Vector<Variant> params = a->method_track_get_params(i, E); - #ifdef DEBUG_ENABLED if (!nc->node->has_method(method)) { ERR_PRINT("Invalid method call '" + method + "'. '" + a->get_name() + "' at node '" + get_path() + "'."); } #endif - - if (can_call) { - _call_object(nc->node, method, params, method_call_mode == ANIMATION_METHOD_CALL_DEFERRED); - } + _call_object(nc->node, method, params, method_call_mode == ANIMATION_METHOD_CALL_DEFERRED); } } break; @@ -796,7 +816,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double TrackNodeCache::BezierAnim *ba = &E->value; real_t bezier = a->bezier_track_interpolate(i, p_time); - bezier = _post_process_key_value(a, i, bezier, nc->node); + bezier = post_process_key_value(a, i, bezier, nc->node); if (ba->accum_pass != accum_pass) { ERR_CONTINUE(cache_update_bezier_size >= NODE_CACHE_UPDATE_MAX); cache_update_bezier[cache_update_bezier_size++] = ba; @@ -808,113 +828,100 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double } break; case Animation::TYPE_AUDIO: { - if (!nc->node) { + if (!nc->node || is_stopping) { continue; } +#ifdef TOOLS_ENABLED + if (p_seeked && !can_call) { + continue; // To avoid spamming the preview in editor. + } +#endif // TOOLS_ENABLED + HashMap<StringName, TrackNodeCache::AudioAnim>::Iterator E = nc->audio_anim.find(a->track_get_path(i).get_concatenated_names()); + ERR_CONTINUE(!E); //should it continue, or create a new one? - if (p_seeked) { - //find whatever should be playing - int idx = a->track_find_key(i, p_time); - if (idx < 0) { - continue; - } - - Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx); - if (!stream.is_valid()) { - nc->node->call(SNAME("stop")); - nc->audio_playing = false; - playing_caches.erase(nc); - } else { - float start_ofs = a->audio_track_get_key_start_offset(i, idx); - start_ofs += p_time - a->track_get_key_time(i, idx); - float end_ofs = a->audio_track_get_key_end_offset(i, idx); - float len = stream->get_length(); - - if (start_ofs > len - end_ofs) { - nc->node->call(SNAME("stop")); - nc->audio_playing = false; - playing_caches.erase(nc); - continue; - } - - nc->node->call(SNAME("set_stream"), stream); - nc->node->call(SNAME("play"), start_ofs); - - nc->audio_playing = true; - playing_caches.insert(nc); - if (len && end_ofs > 0) { //force an end at a time - nc->audio_len = len - start_ofs - end_ofs; - } else { - nc->audio_len = 0; - } + TrackNodeCache::AudioAnim *aa = &E->value; + Node *asp = Object::cast_to<Node>(aa->object); + if (!asp) { + continue; + } + aa->length = a->get_length(); + aa->time = p_time; + aa->loop = a->get_loop_mode() != Animation::LOOP_NONE; + aa->backward = backward; + if (aa->accum_pass != accum_pass) { + ERR_CONTINUE(cache_update_audio_size >= NODE_CACHE_UPDATE_MAX); + cache_update_audio[cache_update_audio_size++] = aa; + aa->accum_pass = accum_pass; + } - nc->audio_start = p_time; + HashMap<int, TrackNodeCache::PlayingAudioStreamInfo> &map = aa->playing_streams; + // Find stream. + int idx = -1; + if (p_seeked || p_started) { + idx = a->track_find_key(i, p_time); + // Discard previous stream when seeking. + if (map.has(idx)) { + aa->audio_stream_playback->stop_stream(map[idx].index); + map.erase(idx); } - } else { - //find stuff to play List<int> to_play; - if (p_started) { - int first_key = a->track_find_key(i, p_prev_time, Animation::FIND_MODE_EXACT); - if (first_key >= 0) { - to_play.push_back(first_key); - } - } + a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play, p_looped_flag); if (to_play.size()) { - int idx = to_play.back()->get(); - - Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx); - if (!stream.is_valid()) { - nc->node->call(SNAME("stop")); - nc->audio_playing = false; - playing_caches.erase(nc); - } else { - float start_ofs = a->audio_track_get_key_start_offset(i, idx); - float end_ofs = a->audio_track_get_key_end_offset(i, idx); - float len = stream->get_length(); - - nc->node->call(SNAME("set_stream"), stream); - nc->node->call(SNAME("play"), start_ofs); - - nc->audio_playing = true; - playing_caches.insert(nc); - if (len && end_ofs > 0) { //force an end at a time - nc->audio_len = len - start_ofs - end_ofs; - } else { - nc->audio_len = 0; - } - - nc->audio_start = p_time; - } - } else if (nc->audio_playing) { - bool loop = a->get_loop_mode() != Animation::LOOP_NONE; + idx = to_play.back()->get(); + } + } + if (idx < 0) { + continue; + } - bool stop = false; + // Play stream. + Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx); + if (stream.is_valid()) { + double start_ofs = a->audio_track_get_key_start_offset(i, idx); + double end_ofs = a->audio_track_get_key_end_offset(i, idx); + double len = stream->get_length(); - if (!loop) { - if ((p_time < nc->audio_start && !backward) || (p_time > nc->audio_start && backward)) { - stop = true; - } - } else if (nc->audio_len > 0) { - float len = nc->audio_start > p_time ? (a->get_length() - nc->audio_start) + p_time : p_time - nc->audio_start; + if (p_seeked || p_started) { + start_ofs += p_time - a->track_get_key_time(i, idx); + } - if (len > nc->audio_len) { - stop = true; - } + if (aa->object->call(SNAME("get_stream")) != aa->audio_stream) { + aa->object->call(SNAME("set_stream"), aa->audio_stream); + aa->audio_stream_playback.unref(); + if (!playing_audio_stream_players.has(asp)) { + playing_audio_stream_players.push_back(asp); } + } + if (!aa->object->call(SNAME("is_playing"))) { + aa->object->call(SNAME("play")); + } + if (!aa->object->call(SNAME("has_stream_playback"))) { + aa->audio_stream_playback.unref(); + continue; + } + if (aa->audio_stream_playback.is_null()) { + aa->audio_stream_playback = aa->object->call(SNAME("get_stream_playback")); + } - if (stop) { - //time to stop - nc->node->call(SNAME("stop")); - nc->audio_playing = false; - playing_caches.erase(nc); - } + TrackNodeCache::PlayingAudioStreamInfo pasi; + pasi.index = aa->audio_stream_playback->play_stream(stream, start_ofs); + pasi.start = p_time; + if (len && end_ofs > 0) { // Force an end at a time. + pasi.len = len - start_ofs - end_ofs; + } else { + pasi.len = 0; } + map[idx] = pasi; } } break; case Animation::TYPE_ANIMATION: { + if (is_stopping) { + continue; + } + AnimationPlayer *player = Object::cast_to<AnimationPlayer>(nc->node); if (!player) { continue; @@ -1206,6 +1213,53 @@ void AnimationPlayer::_animation_update_transforms() { ERR_CONTINUE(ba->accum_pass != accum_pass); ba->object->set_indexed(ba->bezier_property, ba->bezier_accum); } + + for (int i = 0; i < cache_update_audio_size; i++) { + TrackNodeCache::AudioAnim *aa = cache_update_audio[i]; + + ERR_CONTINUE(aa->accum_pass != accum_pass); + + // Audio ending process. + LocalVector<int> erase_list; + for (const KeyValue<int, TrackNodeCache::PlayingAudioStreamInfo> &K : aa->playing_streams) { + TrackNodeCache::PlayingAudioStreamInfo pasi = K.value; + + bool stop = false; + if (!aa->audio_stream_playback->is_stream_playing(pasi.index)) { + stop = true; + } + if (!aa->loop) { + if (!aa->backward) { + if (aa->time < pasi.start) { + stop = true; + } + } else { + if (aa->time > pasi.start) { + stop = true; + } + } + } + if (pasi.len > 0) { + double len = 0.0; + if (!aa->backward) { + len = pasi.start > aa->time ? (aa->length - pasi.start) + aa->time : aa->time - pasi.start; + } else { + len = pasi.start < aa->time ? (aa->length - aa->time) + pasi.start : pasi.start - aa->time; + } + if (len > pasi.len) { + stop = true; + } + } + if (stop) { + // Time to stop. + aa->audio_stream_playback->stop_stream(pasi.index); + erase_list.push_back(K.key); + } + } + for (uint32_t erase_idx = 0; erase_idx < erase_list.size(); erase_idx++) { + aa->playing_streams.erase(erase_list[erase_idx]); + } + } } void AnimationPlayer::_animation_process(double p_delta) { @@ -1221,6 +1275,7 @@ void AnimationPlayer::_animation_process(double p_delta) { cache_update_size = 0; cache_update_prop_size = 0; cache_update_bezier_size = 0; + cache_update_audio_size = 0; AnimationData *prev_from = playback.current.from; _animation_process2(p_delta, started); @@ -1230,6 +1285,8 @@ void AnimationPlayer::_animation_process(double p_delta) { _animation_update_transforms(); if (end_reached) { + _clear_audio_streams(); + _stop_playing_caches(false); if (queued.size()) { String old = playback.assigned; play(queued.front()->get()); @@ -1265,13 +1322,13 @@ void AnimationPlayer::_animation_set_cache_update() { bool clear_cache_needed = false; // Update changed and add otherwise - for (uint32_t i = 0; i < animation_libraries.size(); i++) { - for (const KeyValue<StringName, Ref<Animation>> &K : animation_libraries[i].library->animations) { - StringName key = animation_libraries[i].name == StringName() ? K.key : StringName(String(animation_libraries[i].name) + "/" + String(K.key)); + for (const AnimationLibraryData &lib : animation_libraries) { + for (const KeyValue<StringName, Ref<Animation>> &K : lib.library->animations) { + StringName key = lib.name == StringName() ? K.key : StringName(String(lib.name) + "/" + String(K.key)); if (!animation_set.has(key)) { AnimationData ad; ad.animation = K.value; - ad.animation_library = animation_libraries[i].name; + ad.animation_library = lib.name; ad.name = key; ad.last_update = animation_set_update_pass; animation_set.insert(ad.name, ad); @@ -1279,11 +1336,11 @@ void AnimationPlayer::_animation_set_cache_update() { AnimationData &ad = animation_set[key]; if (ad.last_update != animation_set_update_pass) { // Was not updated, update. If the animation is duplicated, the second one will be ignored. - if (ad.animation != K.value || ad.animation_library != animation_libraries[i].name) { + if (ad.animation != K.value || ad.animation_library != lib.name) { // Animation changed, update and clear caches. clear_cache_needed = true; ad.animation = K.value; - ad.animation_library = animation_libraries[i].name; + ad.animation_library = lib.name; } ad.last_update = animation_set_update_pass; @@ -1401,11 +1458,11 @@ Error AnimationPlayer::add_animation_library(const StringName &p_name, const Ref int insert_pos = 0; - for (uint32_t i = 0; i < animation_libraries.size(); i++) { - ERR_FAIL_COND_V_MSG(animation_libraries[i].name == p_name, ERR_ALREADY_EXISTS, "Can't add animation library twice with name: " + String(p_name)); - ERR_FAIL_COND_V_MSG(animation_libraries[i].library == p_animation_library, ERR_ALREADY_EXISTS, "Can't add animation library twice (adding as '" + p_name.operator String() + "', exists as '" + animation_libraries[i].name.operator String() + "'."); + for (const AnimationLibraryData &lib : animation_libraries) { + ERR_FAIL_COND_V_MSG(lib.name == p_name, ERR_ALREADY_EXISTS, "Can't add animation library twice with name: " + String(p_name)); + ERR_FAIL_COND_V_MSG(lib.library == p_animation_library, ERR_ALREADY_EXISTS, "Can't add animation library twice (adding as '" + p_name.operator String() + "', exists as '" + lib.name.operator String() + "'."); - if (animation_libraries[i].name.operator String() >= p_name.operator String()) { + if (lib.name.operator String() >= p_name.operator String()) { break; } @@ -1464,21 +1521,21 @@ void AnimationPlayer::rename_animation_library(const StringName &p_name, const S #endif bool found = false; - for (uint32_t i = 0; i < animation_libraries.size(); i++) { - ERR_FAIL_COND_MSG(animation_libraries[i].name == p_new_name, "Can't rename animation library to another existing name: " + String(p_new_name)); - if (animation_libraries[i].name == p_name) { + for (AnimationLibraryData &lib : animation_libraries) { + ERR_FAIL_COND_MSG(lib.name == p_new_name, "Can't rename animation library to another existing name: " + String(p_new_name)); + if (lib.name == p_name) { found = true; - animation_libraries[i].name = p_new_name; + lib.name = p_new_name; // rename connections - animation_libraries[i].library->disconnect(SNAME("animation_added"), callable_mp(this, &AnimationPlayer::_animation_added)); - animation_libraries[i].library->disconnect(SNAME("animation_removed"), callable_mp(this, &AnimationPlayer::_animation_removed)); - animation_libraries[i].library->disconnect(SNAME("animation_renamed"), callable_mp(this, &AnimationPlayer::_animation_renamed)); + lib.library->disconnect(SNAME("animation_added"), callable_mp(this, &AnimationPlayer::_animation_added)); + lib.library->disconnect(SNAME("animation_removed"), callable_mp(this, &AnimationPlayer::_animation_removed)); + lib.library->disconnect(SNAME("animation_renamed"), callable_mp(this, &AnimationPlayer::_animation_renamed)); - animation_libraries[i].library->connect(SNAME("animation_added"), callable_mp(this, &AnimationPlayer::_animation_added).bind(p_new_name)); - animation_libraries[i].library->connect(SNAME("animation_removed"), callable_mp(this, &AnimationPlayer::_animation_removed).bind(p_new_name)); - animation_libraries[i].library->connect(SNAME("animation_renamed"), callable_mp(this, &AnimationPlayer::_animation_renamed).bind(p_new_name)); + lib.library->connect(SNAME("animation_added"), callable_mp(this, &AnimationPlayer::_animation_added).bind(p_new_name)); + lib.library->connect(SNAME("animation_removed"), callable_mp(this, &AnimationPlayer::_animation_removed).bind(p_new_name)); + lib.library->connect(SNAME("animation_renamed"), callable_mp(this, &AnimationPlayer::_animation_renamed).bind(p_new_name)); - for (const KeyValue<StringName, Ref<Animation>> &K : animation_libraries[i].library->animations) { + for (const KeyValue<StringName, Ref<Animation>> &K : lib.library->animations) { StringName old_name = p_name == StringName() ? K.key : StringName(String(p_name) + "/" + String(K.key)); StringName new_name = p_new_name == StringName() ? K.key : StringName(String(p_new_name) + "/" + String(K.key)); _rename_animation(old_name, new_name); @@ -1498,8 +1555,8 @@ void AnimationPlayer::rename_animation_library(const StringName &p_name, const S } bool AnimationPlayer::has_animation_library(const StringName &p_name) const { - for (uint32_t i = 0; i < animation_libraries.size(); i++) { - if (animation_libraries[i].name == p_name) { + for (const AnimationLibraryData &lib : animation_libraries) { + if (lib.name == p_name) { return true; } } @@ -1508,9 +1565,9 @@ bool AnimationPlayer::has_animation_library(const StringName &p_name) const { } Ref<AnimationLibrary> AnimationPlayer::get_animation_library(const StringName &p_name) const { - for (uint32_t i = 0; i < animation_libraries.size(); i++) { - if (animation_libraries[i].name == p_name) { - return animation_libraries[i].library; + for (const AnimationLibraryData &lib : animation_libraries) { + if (lib.name == p_name) { + return lib.library; } } ERR_FAIL_V(Ref<AnimationLibrary>()); @@ -1518,15 +1575,15 @@ Ref<AnimationLibrary> AnimationPlayer::get_animation_library(const StringName &p TypedArray<StringName> AnimationPlayer::_get_animation_library_list() const { TypedArray<StringName> ret; - for (uint32_t i = 0; i < animation_libraries.size(); i++) { - ret.push_back(animation_libraries[i].name); + for (const AnimationLibraryData &lib : animation_libraries) { + ret.push_back(lib.name); } return ret; } void AnimationPlayer::get_animation_library_list(List<StringName> *p_libraries) const { - for (uint32_t i = 0; i < animation_libraries.size(); i++) { - p_libraries->push_back(animation_libraries[i].name); + for (const AnimationLibraryData &lib : animation_libraries) { + p_libraries->push_back(lib.name); } } @@ -1658,7 +1715,8 @@ void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, floa } if (get_current_animation() != p_name) { - _stop_playing_caches(); + _clear_audio_streams(); + _stop_playing_caches(false); } c.current.from = &animation_set[name]; @@ -1705,8 +1763,11 @@ bool AnimationPlayer::is_playing() const { void AnimationPlayer::set_current_animation(const String &p_anim) { if (p_anim == "[stop]" || p_anim.is_empty()) { stop(); - } else if (!is_playing() || playback.assigned != p_anim) { + } else if (!is_playing()) { play(p_anim); + } else if (playback.assigned != p_anim) { + float speed = get_playing_speed(); + play(p_anim, -1.0, speed, signbit(speed)); } else { // Same animation, do not replay from start } @@ -1718,7 +1779,8 @@ String AnimationPlayer::get_current_animation() const { void AnimationPlayer::set_assigned_animation(const String &p_anim) { if (is_playing()) { - play(p_anim); + float speed = get_playing_speed(); + play(p_anim, -1.0, speed, signbit(speed)); } else { ERR_FAIL_COND_MSG(!animation_set.has(p_anim), vformat("Animation not found: %s.", p_anim)); playback.current.pos = 0; @@ -1731,18 +1793,12 @@ String AnimationPlayer::get_assigned_animation() const { return playback.assigned; } -void AnimationPlayer::stop(bool p_reset) { - _stop_playing_caches(); - Playback &c = playback; - c.blend.clear(); - if (p_reset) { - c.current.from = nullptr; - c.current.speed_scale = 1; - c.current.pos = 0; - } - _set_process(false); - queued.clear(); - playing = false; +void AnimationPlayer::pause() { + _stop_internal(false, false); +} + +void AnimationPlayer::stop(bool p_keep_state) { + _stop_internal(true, p_keep_state); } void AnimationPlayer::set_speed_scale(float p_speed) { @@ -1761,15 +1817,18 @@ float AnimationPlayer::get_playing_speed() const { } void AnimationPlayer::seek(double p_time, bool p_update) { + playback.current.pos = p_time; + if (!playback.current.from) { if (playback.assigned) { ERR_FAIL_COND_MSG(!animation_set.has(playback.assigned), vformat("Animation not found: %s.", playback.assigned)); playback.current.from = &animation_set[playback.assigned]; } - ERR_FAIL_COND(!playback.current.from); + if (!playback.current.from) { + return; // There is no animation. + } } - playback.current.pos = p_time; playback.seeked = true; if (p_update) { _animation_process(0); @@ -1777,20 +1836,22 @@ void AnimationPlayer::seek(double p_time, bool p_update) { } void AnimationPlayer::seek_delta(double p_time, double p_delta) { + playback.current.pos = p_time - p_delta; + if (!playback.current.from) { if (playback.assigned) { ERR_FAIL_COND_MSG(!animation_set.has(playback.assigned), vformat("Animation not found: %s.", playback.assigned)); playback.current.from = &animation_set[playback.assigned]; } - ERR_FAIL_COND(!playback.current.from); + if (!playback.current.from) { + return; // There is no animation. + } } - playback.current.pos = p_time - p_delta; if (speed_scale != 0.0) { p_delta /= speed_scale; } _animation_process(p_delta); - //playback.current.pos=p_time; } bool AnimationPlayer::is_valid() const { @@ -1814,7 +1875,7 @@ void AnimationPlayer::_animation_changed(const StringName &p_name) { } } -void AnimationPlayer::_stop_playing_caches() { +void AnimationPlayer::_stop_playing_caches(bool p_reset) { for (TrackNodeCache *E : playing_caches) { if (E->node && E->audio_playing) { E->node->call(SNAME("stop")); @@ -1824,7 +1885,12 @@ void AnimationPlayer::_stop_playing_caches() { if (!player) { continue; } - player->stop(); + + if (p_reset) { + player->stop(); + } else { + player->pause(); + } } } @@ -1836,7 +1902,8 @@ void AnimationPlayer::_node_removed(Node *p_node) { } void AnimationPlayer::clear_caches() { - _stop_playing_caches(); + _clear_audio_streams(); + _stop_playing_caches(true); node_cache_map.clear(); @@ -1847,10 +1914,19 @@ void AnimationPlayer::clear_caches() { cache_update_size = 0; cache_update_prop_size = 0; cache_update_bezier_size = 0; + cache_update_audio_size = 0; emit_signal(SNAME("caches_cleared")); } +void AnimationPlayer::_clear_audio_streams() { + for (int i = 0; i < playing_audio_stream_players.size(); i++) { + playing_audio_stream_players[i]->call(SNAME("stop")); + playing_audio_stream_players[i]->call(SNAME("set_stream"), Ref<AudioStream>()); + } + playing_audio_stream_players.clear(); +} + void AnimationPlayer::set_active(bool p_active) { if (active == p_active) { return; @@ -1930,6 +2006,15 @@ AnimationPlayer::AnimationMethodCallMode AnimationPlayer::get_method_call_mode() return method_call_mode; } +void AnimationPlayer::set_audio_max_polyphony(int p_audio_max_polyphony) { + ERR_FAIL_COND(p_audio_max_polyphony < 0 || p_audio_max_polyphony > 128); + audio_max_polyphony = p_audio_max_polyphony; +} + +int AnimationPlayer::get_audio_max_polyphony() const { + return audio_max_polyphony; +} + void AnimationPlayer::set_movie_quit_on_finish_enabled(bool p_enabled) { movie_quit_on_finish = p_enabled; } @@ -1957,6 +2042,27 @@ void AnimationPlayer::_set_process(bool p_process, bool p_force) { processing = p_process; } +void AnimationPlayer::_stop_internal(bool p_reset, bool p_keep_state) { + _clear_audio_streams(); + _stop_playing_caches(p_reset); + Playback &c = playback; + c.blend.clear(); + if (p_reset) { + if (p_keep_state) { + c.current.pos = 0; + } else { + is_stopping = true; + seek(0, true); + is_stopping = false; + } + c.current.from = nullptr; + c.current.speed_scale = 1; + } + _set_process(false); + queued.clear(); + playing = false; +} + void AnimationPlayer::animation_set_next(const StringName &p_animation, const StringName &p_next) { ERR_FAIL_COND_MSG(!animation_set.has(p_animation), vformat("Animation not found: %s.", p_animation)); animation_set[p_animation].next = p_next; @@ -2081,7 +2187,7 @@ Ref<AnimatedValuesBackup> AnimationPlayer::apply_reset(bool p_user_initiated) { Ref<AnimatedValuesBackup> new_values = aux_player->backup_animated_values(); old_values->restore(); - Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Animation Apply Reset")); ur->add_do_method(new_values.ptr(), "restore"); ur->add_undo_method(old_values.ptr(), "restore"); @@ -2119,7 +2225,8 @@ void AnimationPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("play", "name", "custom_blend", "custom_speed", "from_end"), &AnimationPlayer::play, DEFVAL(""), DEFVAL(-1), DEFVAL(1.0), DEFVAL(false)); ClassDB::bind_method(D_METHOD("play_backwards", "name", "custom_blend"), &AnimationPlayer::play_backwards, DEFVAL(""), DEFVAL(-1)); - ClassDB::bind_method(D_METHOD("stop", "reset"), &AnimationPlayer::stop, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("pause"), &AnimationPlayer::pause); + ClassDB::bind_method(D_METHOD("stop", "keep_state"), &AnimationPlayer::stop, DEFVAL(false)); ClassDB::bind_method(D_METHOD("is_playing"), &AnimationPlayer::is_playing); ClassDB::bind_method(D_METHOD("set_current_animation", "anim"), &AnimationPlayer::set_current_animation); @@ -2157,6 +2264,9 @@ void AnimationPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_method_call_mode", "mode"), &AnimationPlayer::set_method_call_mode); ClassDB::bind_method(D_METHOD("get_method_call_mode"), &AnimationPlayer::get_method_call_mode); + ClassDB::bind_method(D_METHOD("set_audio_max_polyphony", "max_polyphony"), &AnimationPlayer::set_audio_max_polyphony); + ClassDB::bind_method(D_METHOD("get_audio_max_polyphony"), &AnimationPlayer::get_audio_max_polyphony); + ClassDB::bind_method(D_METHOD("set_movie_quit_on_finish_enabled", "enabled"), &AnimationPlayer::set_movie_quit_on_finish_enabled); ClassDB::bind_method(D_METHOD("is_movie_quit_on_finish_enabled"), &AnimationPlayer::is_movie_quit_on_finish_enabled); @@ -2166,6 +2276,8 @@ void AnimationPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("seek", "seconds", "update"), &AnimationPlayer::seek, DEFVAL(false)); ClassDB::bind_method(D_METHOD("advance", "delta"), &AnimationPlayer::advance); + GDVIRTUAL_BIND(_post_process_key_value, "animation", "track", "value", "object", "object_idx"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_node"), "set_root", "get_root"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "current_animation", PROPERTY_HINT_ENUM, "", PROPERTY_USAGE_EDITOR), "set_current_animation", "get_current_animation"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "assigned_animation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_assigned_animation", "get_assigned_animation"); @@ -2178,8 +2290,9 @@ void AnimationPlayer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_process_mode", PROPERTY_HINT_ENUM, "Physics,Idle,Manual"), "set_process_callback", "get_process_callback"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "playback_default_blend_time", PROPERTY_HINT_RANGE, "0,4096,0.01,suffix:s"), "set_default_blend_time", "get_default_blend_time"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playback_active", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_active", "is_active"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "playback_speed", PROPERTY_HINT_RANGE, "-64,64,0.01"), "set_speed_scale", "get_speed_scale"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "-64,64,0.01"), "set_speed_scale", "get_speed_scale"); ADD_PROPERTY(PropertyInfo(Variant::INT, "method_call_mode", PROPERTY_HINT_ENUM, "Deferred,Immediate"), "set_method_call_mode", "get_method_call_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "audio_max_polyphony", PROPERTY_HINT_RANGE, "1,127,1"), "set_audio_max_polyphony", "get_audio_max_polyphony"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "movie_quit_on_finish"), "set_movie_quit_on_finish_enabled", "is_movie_quit_on_finish_enabled"); diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h index f431253876..b0975fbead 100644 --- a/scene/animation/animation_player.h +++ b/scene/animation/animation_player.h @@ -37,6 +37,7 @@ #include "scene/3d/skeleton_3d.h" #include "scene/resources/animation.h" #include "scene/resources/animation_library.h" +#include "scene/resources/audio_stream_polyphonic.h" #ifdef TOOLS_ENABLED class AnimatedValuesBackup : public RefCounted { @@ -147,6 +148,26 @@ private: HashMap<StringName, BezierAnim> bezier_anim; + struct PlayingAudioStreamInfo { + AudioStreamPlaybackPolyphonic::ID index = -1; + double start = 0.0; + double len = 0.0; + }; + + struct AudioAnim { + Ref<AudioStreamPolyphonic> audio_stream; + Ref<AudioStreamPlaybackPolyphonic> audio_stream_playback; + HashMap<int, PlayingAudioStreamInfo> playing_streams; + Object *object = nullptr; + uint64_t accum_pass = 0; + double length = 0.0; + double time = 0.0; + bool loop = false; + bool backward = false; + }; + + HashMap<StringName, AudioAnim> audio_anim; + uint32_t last_setup_pass = 0; TrackNodeCache() {} }; @@ -187,11 +208,15 @@ private: int cache_update_prop_size = 0; TrackNodeCache::BezierAnim *cache_update_bezier[NODE_CACHE_UPDATE_MAX]; int cache_update_bezier_size = 0; + TrackNodeCache::AudioAnim *cache_update_audio[NODE_CACHE_UPDATE_MAX]; + int cache_update_audio_size = 0; HashSet<TrackNodeCache *> playing_caches; + Vector<Node *> playing_audio_stream_players; uint64_t accum_pass = 1; float speed_scale = 1.0; double default_blend_time = 0.0; + bool is_stopping = false; struct AnimationData { String name; @@ -262,6 +287,7 @@ private: bool reset_on_save = true; AnimationProcessCallback process_callback = ANIMATION_PROCESS_IDLE; AnimationMethodCallMode method_call_mode = ANIMATION_METHOD_CALL_DEFERRED; + int audio_max_polyphony = 32; bool movie_quit_on_finish = false; bool processing = false; bool active = true; @@ -277,7 +303,8 @@ private: void _animation_process(double p_delta); void _node_removed(Node *p_node); - void _stop_playing_caches(); + void _clear_audio_streams(); + void _stop_playing_caches(bool p_reset); // bind helpers Vector<String> _get_animation_list() const { @@ -294,6 +321,7 @@ private: void _animation_changed(const StringName &p_name); void _set_process(bool p_process, bool p_force = false); + void _stop_internal(bool p_reset, bool p_keep_state); bool playing = false; @@ -315,6 +343,8 @@ protected: static void _bind_methods(); + GDVIRTUAL5RC(Variant, _post_process_key_value, Ref<Animation>, int, Variant, Object *, int); + Variant post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx = -1); virtual Variant _post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx = -1); public: @@ -346,7 +376,8 @@ public: void queue(const StringName &p_name); Vector<String> get_queue(); void clear_queue(); - void stop(bool p_reset = true); + void pause(); + void stop(bool p_keep_state = false); bool is_playing() const; String get_current_animation() const; void set_current_animation(const String &p_anim); @@ -372,6 +403,9 @@ public: void set_method_call_mode(AnimationMethodCallMode p_mode); AnimationMethodCallMode get_method_call_mode() const; + void set_audio_max_polyphony(int p_audio_max_polyphony); + int get_audio_max_polyphony() const; + void set_movie_quit_on_finish_enabled(bool p_enabled); bool is_movie_quit_on_finish_enabled() const; diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index ab341797c7..9f9916c1c6 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -54,13 +54,19 @@ Variant AnimationNode::get_parameter_default_value(const StringName &p_parameter return ret; } +bool AnimationNode::is_parameter_read_only(const StringName &p_parameter) const { + bool ret = false; + GDVIRTUAL_CALL(_is_parameter_read_only, p_parameter, ret); + return ret; +} + void AnimationNode::set_parameter(const StringName &p_name, const Variant &p_value) { ERR_FAIL_COND(!state); ERR_FAIL_COND(!state->tree->property_parent_map.has(base_path)); ERR_FAIL_COND(!state->tree->property_parent_map[base_path].has(p_name)); StringName path = state->tree->property_parent_map[base_path][p_name]; - state->tree->property_map[path] = p_value; + state->tree->property_map[path].first = p_value; } Variant AnimationNode::get_parameter(const StringName &p_name) const { @@ -69,7 +75,7 @@ Variant AnimationNode::get_parameter(const StringName &p_name) const { ERR_FAIL_COND_V(!state->tree->property_parent_map[base_path].has(p_name), Variant()); StringName path = state->tree->property_parent_map[base_path][p_name]; - return state->tree->property_map[path]; + return state->tree->property_map[path].first; } void AnimationNode::get_child_nodes(List<ChildNode> *r_child_nodes) { @@ -297,36 +303,21 @@ double AnimationNode::_blend_node(const StringName &p_subpath, const Vector<Stri return p_node->_pre_process(new_path, new_parent, state, p_time, p_seek, p_is_external_seeking, p_connections); } -int AnimationNode::get_input_count() const { - return inputs.size(); -} - -String AnimationNode::get_input_name(int p_input) { - ERR_FAIL_INDEX_V(p_input, inputs.size(), String()); - return inputs[p_input].name; -} - String AnimationNode::get_caption() const { String ret = "Node"; GDVIRTUAL_CALL(_get_caption, ret); return ret; } -void AnimationNode::add_input(const String &p_name) { +bool AnimationNode::add_input(const String &p_name) { //root nodes can't add inputs - ERR_FAIL_COND(Object::cast_to<AnimationRootNode>(this) != nullptr); + ERR_FAIL_COND_V(Object::cast_to<AnimationRootNode>(this) != nullptr, false); Input input; - ERR_FAIL_COND(p_name.contains(".") || p_name.contains("/")); + ERR_FAIL_COND_V(p_name.contains(".") || p_name.contains("/"), false); input.name = p_name; inputs.push_back(input); emit_changed(); -} - -void AnimationNode::set_input_name(int p_input, const String &p_name) { - ERR_FAIL_INDEX(p_input, inputs.size()); - ERR_FAIL_COND(p_name.contains(".") || p_name.contains("/")); - inputs.write[p_input].name = p_name; - emit_changed(); + return true; } void AnimationNode::remove_input(int p_index) { @@ -335,6 +326,34 @@ void AnimationNode::remove_input(int p_index) { emit_changed(); } +bool AnimationNode::set_input_name(int p_input, const String &p_name) { + ERR_FAIL_INDEX_V(p_input, inputs.size(), false); + ERR_FAIL_COND_V(p_name.contains(".") || p_name.contains("/"), false); + inputs.write[p_input].name = p_name; + emit_changed(); + return true; +} + +String AnimationNode::get_input_name(int p_input) const { + ERR_FAIL_INDEX_V(p_input, inputs.size(), String()); + return inputs[p_input].name; +} + +int AnimationNode::get_input_count() const { + return inputs.size(); +} + +int AnimationNode::find_input(const String &p_name) const { + int idx = -1; + for (int i = 0; i < inputs.size(); i++) { + if (inputs[i].name == p_name) { + idx = i; + break; + } + } + return idx; +} + double AnimationNode::process(double p_time, bool p_seek, bool p_is_external_seeking) { double ret = 0; GDVIRTUAL_CALL(_process, p_time, p_seek, p_is_external_seeking, ret); @@ -398,11 +417,12 @@ Ref<AnimationNode> AnimationNode::get_child_by_name(const StringName &p_name) { } void AnimationNode::_bind_methods() { - ClassDB::bind_method(D_METHOD("get_input_count"), &AnimationNode::get_input_count); - ClassDB::bind_method(D_METHOD("get_input_name", "input"), &AnimationNode::get_input_name); - ClassDB::bind_method(D_METHOD("add_input", "name"), &AnimationNode::add_input); ClassDB::bind_method(D_METHOD("remove_input", "index"), &AnimationNode::remove_input); + ClassDB::bind_method(D_METHOD("set_input_name", "input", "name"), &AnimationNode::set_input_name); + ClassDB::bind_method(D_METHOD("get_input_name", "input"), &AnimationNode::get_input_name); + ClassDB::bind_method(D_METHOD("get_input_count"), &AnimationNode::get_input_count); + ClassDB::bind_method(D_METHOD("find_input", "name"), &AnimationNode::find_input); ClassDB::bind_method(D_METHOD("set_filter_path", "path", "enable"), &AnimationNode::set_filter_path); ClassDB::bind_method(D_METHOD("is_path_filtered", "path"), &AnimationNode::is_path_filtered); @@ -427,11 +447,14 @@ void AnimationNode::_bind_methods() { GDVIRTUAL_BIND(_get_parameter_list); GDVIRTUAL_BIND(_get_child_by_name, "name"); GDVIRTUAL_BIND(_get_parameter_default_value, "parameter"); + GDVIRTUAL_BIND(_is_parameter_read_only, "parameter"); GDVIRTUAL_BIND(_process, "time", "seek", "is_external_seeking"); GDVIRTUAL_BIND(_get_caption); GDVIRTUAL_BIND(_has_filter); ADD_SIGNAL(MethodInfo("tree_changed")); + ADD_SIGNAL(MethodInfo("animation_node_renamed", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::STRING, "old_name"), PropertyInfo(Variant::STRING, "new_name"))); + ADD_SIGNAL(MethodInfo("animation_node_removed", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::STRING, "name"))); BIND_ENUM_CONSTANT(FILTER_IGNORE); BIND_ENUM_CONSTANT(FILTER_PASS); @@ -444,15 +467,33 @@ AnimationNode::AnimationNode() { //////////////////// +void AnimationRootNode::_tree_changed() { + emit_signal(SNAME("tree_changed")); +} + +void AnimationRootNode::_animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name) { + emit_signal(SNAME("animation_node_renamed"), p_oid, p_old_name, p_new_name); +} + +void AnimationRootNode::_animation_node_removed(const ObjectID &p_oid, const StringName &p_node) { + emit_signal(SNAME("animation_node_removed"), p_oid, p_node); +} + +//////////////////// + void AnimationTree::set_tree_root(const Ref<AnimationNode> &p_root) { if (root.is_valid()) { root->disconnect("tree_changed", callable_mp(this, &AnimationTree::_tree_changed)); + root->disconnect("animation_node_renamed", callable_mp(this, &AnimationTree::_animation_node_renamed)); + root->disconnect("animation_node_removed", callable_mp(this, &AnimationTree::_animation_node_removed)); } root = p_root; if (root.is_valid()) { root->connect("tree_changed", callable_mp(this, &AnimationTree::_tree_changed)); + root->connect("animation_node_renamed", callable_mp(this, &AnimationTree::_animation_node_renamed)); + root->connect("animation_node_removed", callable_mp(this, &AnimationTree::_animation_node_removed)); } properties_dirty = true; @@ -479,13 +520,7 @@ void AnimationTree::set_active(bool p_active) { } if (!active && is_inside_tree()) { - for (const TrackCache *E : playing_caches) { - if (ObjectDB::get_instance(E->object_id)) { - E->object->call(SNAME("stop")); - } - } - - playing_caches.clear(); + _clear_caches(); } } @@ -524,6 +559,7 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { if (!player->has_node(player->get_root())) { ERR_PRINT("AnimationTree: AnimationPlayer root is invalid."); set_active(false); + _clear_caches(); return false; } Node *parent = player->get_node(player->get_root()); @@ -531,6 +567,10 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { List<StringName> sname; player->get_animation_list(&sname); + root_motion_cache.loc = Vector3(0, 0, 0); + root_motion_cache.rot = Quaternion(0, 0, 0, 1); + root_motion_cache.scale = Vector3(1, 1, 1); + Ref<Animation> reset_anim; bool has_reset_anim = player->has_animation(SceneStringNames::get_singleton()->RESET); if (has_reset_anim) { @@ -747,7 +787,7 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { if (has_reset_anim) { int rt = reset_anim->find_track(path, track_type); if (rt >= 0 && reset_anim->track_get_key_count(rt) > 0) { - track_bezier->init_value = reset_anim->track_get_key_value(rt, 0); + track_bezier->init_value = (reset_anim->track_get_key_value(rt, 0).operator Array())[0]; } } } break; @@ -756,6 +796,8 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { track_audio->object = child; track_audio->object_id = track_audio->object->get_instance_id(); + track_audio->audio_stream.instantiate(); + track_audio->audio_stream->set_polyphony(audio_max_polyphony); track = track_audio; @@ -853,14 +895,32 @@ void AnimationTree::_animation_player_changed() { } void AnimationTree::_clear_caches() { + _clear_audio_streams(); + _clear_playing_caches(); for (KeyValue<NodePath, TrackCache *> &K : track_cache) { memdelete(K.value); } - playing_caches.clear(); track_cache.clear(); cache_valid = false; } +void AnimationTree::_clear_audio_streams() { + for (int i = 0; i < playing_audio_stream_players.size(); i++) { + playing_audio_stream_players[i]->call(SNAME("stop")); + playing_audio_stream_players[i]->call(SNAME("set_stream"), Ref<AudioStream>()); + } + playing_audio_stream_players.clear(); +} + +void AnimationTree::_clear_playing_caches() { + for (const TrackCache *E : playing_caches) { + if (ObjectDB::get_instance(E->object_id)) { + E->object->call(SNAME("stop")); + } + } + playing_caches.clear(); +} + static void _call_object(Object *p_object, const StringName &p_method, const Vector<Variant> &p_params, bool p_deferred) { // Separate function to use alloca() more efficiently const Variant **argptrs = (const Variant **)alloca(sizeof(const Variant **) * p_params.size()); @@ -979,14 +1039,13 @@ void AnimationTree::_process_graph(double p_delta) { case Animation::TYPE_POSITION_3D: { TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track); if (track->root_motion) { - t->loc = Vector3(0, 0, 0); - t->rot = Quaternion(0, 0, 0, 1); - t->scale = Vector3(1, 1, 1); - } else { - t->loc = t->init_loc; - t->rot = t->init_rot; - t->scale = t->init_scale; + root_motion_cache.loc = Vector3(0, 0, 0); + root_motion_cache.rot = Quaternion(0, 0, 0, 1); + root_motion_cache.scale = Vector3(1, 1, 1); } + t->loc = t->init_loc; + t->rot = t->init_rot; + t->scale = t->init_scale; } break; case Animation::TYPE_BLEND_SHAPE: { TrackCacheBlendShape *t = static_cast<TrackCacheBlendShape *>(track); @@ -1000,6 +1059,13 @@ void AnimationTree::_process_graph(double p_delta) { TrackCacheBezier *t = static_cast<TrackCacheBezier *>(track); t->value = t->init_value; } break; + case Animation::TYPE_AUDIO: { + TrackCacheAudio *t = static_cast<TrackCacheAudio *>(track); + for (KeyValue<ObjectID, PlayingAudioTrackInfo> &L : t->playing_streams) { + PlayingAudioTrackInfo &track_info = L.value; + track_info.volume = 0.0; + } + } break; default: { } break; } @@ -1008,8 +1074,9 @@ void AnimationTree::_process_graph(double p_delta) { // Apply value/transform/blend/bezier blends to track caches and execute method/audio/animation tracks. { +#ifdef TOOLS_ENABLED bool can_call = is_inside_tree() && !Engine::get_singleton()->is_editor_hint(); - +#endif // TOOLS_ENABLED for (const AnimationNode::AnimationState &as : state.animation_states) { Ref<Animation> a = as.animation; double time = as.time; @@ -1018,8 +1085,8 @@ void AnimationTree::_process_graph(double p_delta) { bool seeked = as.seeked; Animation::LoopedFlag looped_flag = as.looped_flag; bool is_external_seeking = as.is_external_seeking; + bool backward = signbit(delta); // This flag is used by the root motion calculates or detecting the end of audio stream. #ifndef _3D_DISABLED - bool backward = signbit(delta); // This flag is required only for the root motion since it calculates the difference between the previous and current frames. bool calc_root = !seeked || is_external_seeking; #endif // _3D_DISABLED @@ -1038,9 +1105,6 @@ void AnimationTree::_process_graph(double p_delta) { int blend_idx = state.track_map[path]; ERR_CONTINUE(blend_idx < 0 || blend_idx >= state.track_count); real_t blend = (*as.track_blends)[blend_idx] * weight; - if (Math::is_zero_approx(blend)) { - continue; // Nothing to blend. - } Animation::TrackType ttype = a->track_get_type(i); if (ttype != Animation::TYPE_POSITION_3D && ttype != Animation::TYPE_ROTATION_3D && ttype != Animation::TYPE_SCALE_3D && track->type != ttype) { @@ -1052,7 +1116,11 @@ void AnimationTree::_process_graph(double p_delta) { switch (ttype) { case Animation::TYPE_POSITION_3D: { #ifndef _3D_DISABLED + if (Math::is_zero_approx(blend)) { + continue; // Nothing to blend. + } TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track); + if (track->root_motion && calc_root) { double prev_time = time - delta; if (!backward) { @@ -1097,10 +1165,10 @@ void AnimationTree::_process_graph(double p_delta) { if (err != OK) { continue; } - loc[0] = _post_process_key_value(a, i, loc[0], t->object, t->bone_idx); + loc[0] = post_process_key_value(a, i, loc[0], t->object, t->bone_idx); a->position_track_interpolate(i, (double)a->get_length(), &loc[1]); - loc[1] = _post_process_key_value(a, i, loc[1], t->object, t->bone_idx); - t->loc += (loc[1] - loc[0]) * blend; + loc[1] = post_process_key_value(a, i, loc[1], t->object, t->bone_idx); + root_motion_cache.loc += (loc[1] - loc[0]) * blend; prev_time = 0; } } else { @@ -1109,10 +1177,10 @@ void AnimationTree::_process_graph(double p_delta) { if (err != OK) { continue; } - loc[0] = _post_process_key_value(a, i, loc[0], t->object, t->bone_idx); + loc[0] = post_process_key_value(a, i, loc[0], t->object, t->bone_idx); a->position_track_interpolate(i, 0, &loc[1]); - loc[1] = _post_process_key_value(a, i, loc[1], t->object, t->bone_idx); - t->loc += (loc[1] - loc[0]) * blend; + loc[1] = post_process_key_value(a, i, loc[1], t->object, t->bone_idx); + root_motion_cache.loc += (loc[1] - loc[0]) * blend; prev_time = (double)a->get_length(); } } @@ -1121,21 +1189,21 @@ void AnimationTree::_process_graph(double p_delta) { if (err != OK) { continue; } - loc[0] = _post_process_key_value(a, i, loc[0], t->object, t->bone_idx); - + loc[0] = post_process_key_value(a, i, loc[0], t->object, t->bone_idx); a->position_track_interpolate(i, time, &loc[1]); - loc[1] = _post_process_key_value(a, i, loc[1], t->object, t->bone_idx); - t->loc += (loc[1] - loc[0]) * blend; + loc[1] = post_process_key_value(a, i, loc[1], t->object, t->bone_idx); + root_motion_cache.loc += (loc[1] - loc[0]) * blend; prev_time = !backward ? 0 : (double)a->get_length(); + } - } else { + { Vector3 loc; Error err = a->position_track_interpolate(i, time, &loc); if (err != OK) { continue; } - loc = _post_process_key_value(a, i, loc, t->object, t->bone_idx); + loc = post_process_key_value(a, i, loc, t->object, t->bone_idx); t->loc += (loc - t->init_loc) * blend; } @@ -1143,7 +1211,11 @@ void AnimationTree::_process_graph(double p_delta) { } break; case Animation::TYPE_ROTATION_3D: { #ifndef _3D_DISABLED + if (Math::is_zero_approx(blend)) { + continue; // Nothing to blend. + } TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track); + if (track->root_motion && calc_root) { double prev_time = time - delta; if (!backward) { @@ -1188,10 +1260,10 @@ void AnimationTree::_process_graph(double p_delta) { if (err != OK) { continue; } - rot[0] = _post_process_key_value(a, i, rot[0], t->object, t->bone_idx); + rot[0] = post_process_key_value(a, i, rot[0], t->object, t->bone_idx); a->rotation_track_interpolate(i, (double)a->get_length(), &rot[1]); - rot[1] = _post_process_key_value(a, i, rot[1], t->object, t->bone_idx); - t->rot = (t->rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); + rot[1] = post_process_key_value(a, i, rot[1], t->object, t->bone_idx); + root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); prev_time = 0; } } else { @@ -1200,9 +1272,9 @@ void AnimationTree::_process_graph(double p_delta) { if (err != OK) { continue; } - rot[0] = _post_process_key_value(a, i, rot[0], t->object, t->bone_idx); + rot[0] = post_process_key_value(a, i, rot[0], t->object, t->bone_idx); a->rotation_track_interpolate(i, 0, &rot[1]); - t->rot = (t->rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); + root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); prev_time = (double)a->get_length(); } } @@ -1211,21 +1283,22 @@ void AnimationTree::_process_graph(double p_delta) { if (err != OK) { continue; } - rot[0] = _post_process_key_value(a, i, rot[0], t->object, t->bone_idx); + rot[0] = post_process_key_value(a, i, rot[0], t->object, t->bone_idx); a->rotation_track_interpolate(i, time, &rot[1]); - rot[1] = _post_process_key_value(a, i, rot[1], t->object, t->bone_idx); - t->rot = (t->rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); + rot[1] = post_process_key_value(a, i, rot[1], t->object, t->bone_idx); + root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); prev_time = !backward ? 0 : (double)a->get_length(); + } - } else { + { Quaternion rot; Error err = a->rotation_track_interpolate(i, time, &rot); if (err != OK) { continue; } - rot = _post_process_key_value(a, i, rot, t->object, t->bone_idx); + rot = post_process_key_value(a, i, rot, t->object, t->bone_idx); t->rot = (t->rot * Quaternion().slerp(t->init_rot.inverse() * rot, blend)).normalized(); } @@ -1233,7 +1306,11 @@ void AnimationTree::_process_graph(double p_delta) { } break; case Animation::TYPE_SCALE_3D: { #ifndef _3D_DISABLED + if (Math::is_zero_approx(blend)) { + continue; // Nothing to blend. + } TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track); + if (track->root_motion && calc_root) { double prev_time = time - delta; if (!backward) { @@ -1278,10 +1355,10 @@ void AnimationTree::_process_graph(double p_delta) { if (err != OK) { continue; } - scale[0] = _post_process_key_value(a, i, scale[0], t->object, t->bone_idx); + scale[0] = post_process_key_value(a, i, scale[0], t->object, t->bone_idx); a->scale_track_interpolate(i, (double)a->get_length(), &scale[1]); - t->scale += (scale[1] - scale[0]) * blend; - scale[1] = _post_process_key_value(a, i, scale[1], t->object, t->bone_idx); + root_motion_cache.scale += (scale[1] - scale[0]) * blend; + scale[1] = post_process_key_value(a, i, scale[1], t->object, t->bone_idx); prev_time = 0; } } else { @@ -1290,10 +1367,10 @@ void AnimationTree::_process_graph(double p_delta) { if (err != OK) { continue; } - scale[0] = _post_process_key_value(a, i, scale[0], t->object, t->bone_idx); + scale[0] = post_process_key_value(a, i, scale[0], t->object, t->bone_idx); a->scale_track_interpolate(i, 0, &scale[1]); - scale[1] = _post_process_key_value(a, i, scale[1], t->object, t->bone_idx); - t->scale += (scale[1] - scale[0]) * blend; + scale[1] = post_process_key_value(a, i, scale[1], t->object, t->bone_idx); + root_motion_cache.scale += (scale[1] - scale[0]) * blend; prev_time = (double)a->get_length(); } } @@ -1302,21 +1379,22 @@ void AnimationTree::_process_graph(double p_delta) { if (err != OK) { continue; } - scale[0] = _post_process_key_value(a, i, scale[0], t->object, t->bone_idx); + scale[0] = post_process_key_value(a, i, scale[0], t->object, t->bone_idx); a->scale_track_interpolate(i, time, &scale[1]); - scale[1] = _post_process_key_value(a, i, scale[1], t->object, t->bone_idx); - t->scale += (scale[1] - scale[0]) * blend; + scale[1] = post_process_key_value(a, i, scale[1], t->object, t->bone_idx); + root_motion_cache.scale += (scale[1] - scale[0]) * blend; prev_time = !backward ? 0 : (double)a->get_length(); + } - } else { + { Vector3 scale; Error err = a->scale_track_interpolate(i, time, &scale); if (err != OK) { continue; } - scale = _post_process_key_value(a, i, scale, t->object, t->bone_idx); + scale = post_process_key_value(a, i, scale, t->object, t->bone_idx); t->scale += (scale - t->init_scale) * blend; } @@ -1324,6 +1402,9 @@ void AnimationTree::_process_graph(double p_delta) { } break; case Animation::TYPE_BLEND_SHAPE: { #ifndef _3D_DISABLED + if (Math::is_zero_approx(blend)) { + continue; // Nothing to blend. + } TrackCacheBlendShape *t = static_cast<TrackCacheBlendShape *>(track); float value; @@ -1334,19 +1415,22 @@ void AnimationTree::_process_graph(double p_delta) { if (err != OK) { continue; } - value = _post_process_key_value(a, i, value, t->object, t->shape_index); + value = post_process_key_value(a, i, value, t->object, t->shape_index); t->value += (value - t->init_value) * blend; #endif // _3D_DISABLED } break; case Animation::TYPE_VALUE: { + if (Math::is_zero_approx(blend)) { + continue; // Nothing to blend. + } TrackCacheValue *t = static_cast<TrackCacheValue *>(track); Animation::UpdateMode update_mode = a->value_track_get_update_mode(i); if (update_mode == Animation::UPDATE_CONTINUOUS || update_mode == Animation::UPDATE_CAPTURE) { Variant value = a->value_track_interpolate(i, time); - value = _post_process_key_value(a, i, value, t->object); + value = post_process_key_value(a, i, value, t->object); if (value == Variant()) { continue; @@ -1386,14 +1470,14 @@ void AnimationTree::_process_graph(double p_delta) { continue; } Variant value = a->track_get_key_value(i, idx); - value = _post_process_key_value(a, i, value, t->object); + value = post_process_key_value(a, i, value, t->object); t->object->set_indexed(t->subpath, value); } else { List<int> indices; a->track_get_key_indices_in_range(i, time, delta, &indices, looped_flag); for (int &F : indices) { Variant value = a->track_get_key_value(i, F); - value = _post_process_key_value(a, i, value, t->object); + value = post_process_key_value(a, i, value, t->object); t->object->set_indexed(t->subpath, value); } } @@ -1401,6 +1485,14 @@ void AnimationTree::_process_graph(double p_delta) { } break; case Animation::TYPE_METHOD: { +#ifdef TOOLS_ENABLED + if (!can_call) { + continue; + } +#endif // TOOLS_ENABLED + if (Math::is_zero_approx(blend)) { + continue; // Nothing to blend. + } TrackCacheMethod *t = static_cast<TrackCacheMethod *>(track); if (seeked) { @@ -1410,137 +1502,116 @@ void AnimationTree::_process_graph(double p_delta) { } StringName method = a->method_track_get_name(i, idx); Vector<Variant> params = a->method_track_get_params(i, idx); - if (can_call) { - _call_object(t->object, method, params, false); - } + _call_object(t->object, method, params, false); } else { List<int> indices; a->track_get_key_indices_in_range(i, time, delta, &indices, looped_flag); for (int &F : indices) { StringName method = a->method_track_get_name(i, F); Vector<Variant> params = a->method_track_get_params(i, F); - if (can_call) { - _call_object(t->object, method, params, true); - } + _call_object(t->object, method, params, true); } } } break; case Animation::TYPE_BEZIER: { + if (Math::is_zero_approx(blend)) { + continue; // Nothing to blend. + } TrackCacheBezier *t = static_cast<TrackCacheBezier *>(track); real_t bezier = a->bezier_track_interpolate(i, time); - bezier = _post_process_key_value(a, i, bezier, t->object); + bezier = post_process_key_value(a, i, bezier, t->object); t->value += (bezier - t->init_value) * blend; } break; case Animation::TYPE_AUDIO: { TrackCacheAudio *t = static_cast<TrackCacheAudio *>(track); - if (seeked) { - //find whatever should be playing - int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT); - if (idx < 0) { - continue; - } - - Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx); - if (!stream.is_valid()) { - t->object->call(SNAME("stop")); - t->playing = false; - playing_caches.erase(t); - } else { - double start_ofs = a->audio_track_get_key_start_offset(i, idx); - start_ofs += time - a->track_get_key_time(i, idx); - double end_ofs = a->audio_track_get_key_end_offset(i, idx); - double len = stream->get_length(); - - if (start_ofs > len - end_ofs) { - t->object->call(SNAME("stop")); - t->playing = false; - playing_caches.erase(t); - continue; - } - - t->object->call(SNAME("set_stream"), stream); - t->object->call(SNAME("play"), start_ofs); - - t->playing = true; - playing_caches.insert(t); - if (len && end_ofs > 0) { //force an end at a time - t->len = len - start_ofs - end_ofs; - } else { - t->len = 0; - } + Node *asp = Object::cast_to<Node>(t->object); + if (!asp) { + t->playing_streams.clear(); + continue; + } - t->start = time; + ObjectID oid = a->get_instance_id(); + if (!t->playing_streams.has(oid)) { + t->playing_streams[oid] = PlayingAudioTrackInfo(); + } + // The end of audio should be observed even if the blend value is 0, build up the information and store to the cache for that. + PlayingAudioTrackInfo &track_info = t->playing_streams[oid]; + track_info.length = a->get_length(); + track_info.time = time; + track_info.volume += blend; + track_info.loop = a->get_loop_mode() != Animation::LOOP_NONE; + track_info.backward = backward; + track_info.use_blend = a->audio_track_is_use_blend(i); + + HashMap<int, PlayingAudioStreamInfo> &map = track_info.stream_info; + // Find stream. + int idx = -1; + if (seeked) { + idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT); + // Discard previous stream when seeking. + if (map.has(idx)) { + t->audio_stream_playback->stop_stream(map[idx].index); + map.erase(idx); } - } else { - //find stuff to play List<int> to_play; a->track_get_key_indices_in_range(i, time, delta, &to_play, looped_flag); if (to_play.size()) { - int idx = to_play.back()->get(); - - Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx); - if (!stream.is_valid()) { - t->object->call(SNAME("stop")); - t->playing = false; - playing_caches.erase(t); - } else { - double start_ofs = a->audio_track_get_key_start_offset(i, idx); - double end_ofs = a->audio_track_get_key_end_offset(i, idx); - double len = stream->get_length(); - - t->object->call(SNAME("set_stream"), stream); - t->object->call(SNAME("play"), start_ofs); + idx = to_play.back()->get(); + } + } + if (idx < 0) { + continue; + } - t->playing = true; - playing_caches.insert(t); - if (len && end_ofs > 0) { //force an end at a time - t->len = len - start_ofs - end_ofs; - } else { - t->len = 0; - } + // Play stream. + Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx); + if (stream.is_valid()) { + double start_ofs = a->audio_track_get_key_start_offset(i, idx); + double end_ofs = a->audio_track_get_key_end_offset(i, idx); + double len = stream->get_length(); - t->start = time; - } - } else if (t->playing) { - bool loop = a->get_loop_mode() != Animation::LOOP_NONE; - - bool stop = false; - - if (!loop) { - if (delta > 0) { - if (time < t->start) { - stop = true; - } - } else if (delta < 0) { - if (time > t->start) { - stop = true; - } - } - } else if (t->len > 0) { - double len = t->start > time ? (a->get_length() - t->start) + time : time - t->start; + if (seeked) { + start_ofs += time - a->track_get_key_time(i, idx); + } - if (len > t->len) { - stop = true; - } + if (t->object->call(SNAME("get_stream")) != t->audio_stream) { + t->object->call(SNAME("set_stream"), t->audio_stream); + t->audio_stream_playback.unref(); + if (!playing_audio_stream_players.has(asp)) { + playing_audio_stream_players.push_back(asp); } + } + if (!t->object->call(SNAME("is_playing"))) { + t->object->call(SNAME("play")); + } + if (!t->object->call(SNAME("has_stream_playback"))) { + t->audio_stream_playback.unref(); + continue; + } + if (t->audio_stream_playback.is_null()) { + t->audio_stream_playback = t->object->call(SNAME("get_stream_playback")); + } - if (stop) { - //time to stop - t->object->call(SNAME("stop")); - t->playing = false; - playing_caches.erase(t); - } + PlayingAudioStreamInfo pasi; + pasi.index = t->audio_stream_playback->play_stream(stream, start_ofs); + pasi.start = time; + if (len && end_ofs > 0) { // Force an end at a time. + pasi.len = len - start_ofs - end_ofs; + } else { + pasi.len = 0; } + map[idx] = pasi; } - real_t db = Math::linear_to_db(MAX(blend, 0.00001)); - t->object->call(SNAME("set_volume_db"), db); } break; case Animation::TYPE_ANIMATION: { + if (Math::is_zero_approx(blend)) { + continue; // Nothing to blend. + } TrackCacheAnimation *t = static_cast<TrackCacheAnimation *>(track); AnimationPlayer *player2 = Object::cast_to<AnimationPlayer>(t->object); @@ -1629,10 +1700,12 @@ void AnimationTree::_process_graph(double p_delta) { TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track); if (t->root_motion) { - root_motion_position = t->loc; - root_motion_rotation = t->rot; - root_motion_scale = t->scale - Vector3(1, 1, 1); - + root_motion_position = root_motion_cache.loc; + root_motion_rotation = root_motion_cache.rot; + root_motion_scale = root_motion_cache.scale - Vector3(1, 1, 1); + root_motion_position_accumulator = t->loc; + root_motion_rotation_accumulator = t->rot; + root_motion_scale_accumulator = t->scale; } else if (t->skeleton && t->bone_idx >= 0) { if (t->loc_used) { t->skeleton->set_bone_pose_position(t->bone_idx, t->loc); @@ -1686,6 +1759,64 @@ void AnimationTree::_process_graph(double p_delta) { t->object->set_indexed(t->subpath, t->value); } break; + case Animation::TYPE_AUDIO: { + TrackCacheAudio *t = static_cast<TrackCacheAudio *>(track); + + // Audio ending process. + LocalVector<ObjectID> erase_maps; + for (KeyValue<ObjectID, PlayingAudioTrackInfo> &L : t->playing_streams) { + PlayingAudioTrackInfo &track_info = L.value; + float db = Math::linear_to_db(track_info.use_blend ? track_info.volume : 1.0); + LocalVector<int> erase_streams; + HashMap<int, PlayingAudioStreamInfo> &map = track_info.stream_info; + for (const KeyValue<int, PlayingAudioStreamInfo> &M : map) { + PlayingAudioStreamInfo pasi = M.value; + + bool stop = false; + if (!t->audio_stream_playback->is_stream_playing(pasi.index)) { + stop = true; + } + if (!track_info.loop) { + if (!track_info.backward) { + if (track_info.time < pasi.start) { + stop = true; + } + } else if (track_info.backward) { + if (track_info.time > pasi.start) { + stop = true; + } + } + } + if (pasi.len > 0) { + double len = 0.0; + if (!track_info.backward) { + len = pasi.start > track_info.time ? (track_info.length - pasi.start) + track_info.time : track_info.time - pasi.start; + } else { + len = pasi.start < track_info.time ? (track_info.length - track_info.time) + pasi.start : pasi.start - track_info.time; + } + if (len > pasi.len) { + stop = true; + } + } + if (stop) { + // Time to stop. + t->audio_stream_playback->stop_stream(pasi.index); + erase_streams.push_back(M.key); + } else { + t->audio_stream_playback->set_stream_volume(pasi.index, db); + } + } + for (uint32_t erase_idx = 0; erase_idx < erase_streams.size(); erase_idx++) { + map.erase(erase_streams[erase_idx]); + } + if (map.size() == 0) { + erase_maps.push_back(L.key); + } + } + for (uint32_t erase_idx = 0; erase_idx < erase_maps.size(); erase_idx++) { + t->playing_streams.erase(erase_maps[erase_idx]); + } + } break; default: { } //the rest don't matter } @@ -1693,6 +1824,15 @@ void AnimationTree::_process_graph(double p_delta) { } } +Variant AnimationTree::post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx) { + Variant res; + if (GDVIRTUAL_CALL(_post_process_key_value, p_anim, p_track, p_value, const_cast<Object *>(p_object), p_object_idx, res)) { + return res; + } + + return _post_process_key_value(p_anim, p_track, p_value, p_object, p_object_idx); +} + Variant AnimationTree::_post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx) { switch (p_anim->track_get_type(p_track)) { #ifndef _3D_DISABLED @@ -1755,6 +1895,8 @@ void AnimationTree::_setup_animation_player() { return; } + cache_valid = false; + AnimationPlayer *new_player = nullptr; if (!animation_player.is_empty()) { new_player = Object::cast_to<AnimationPlayer>(get_node_or_null(animation_player)); @@ -1802,6 +1944,15 @@ NodePath AnimationTree::get_advance_expression_base_node() const { return advance_expression_base_node; } +void AnimationTree::set_audio_max_polyphony(int p_audio_max_polyphony) { + ERR_FAIL_COND(p_audio_max_polyphony < 0 || p_audio_max_polyphony > 128); + audio_max_polyphony = p_audio_max_polyphony; +} + +int AnimationTree::get_audio_max_polyphony() const { + return audio_max_polyphony; +} + bool AnimationTree::is_state_invalid() const { return !state.valid; } @@ -1856,6 +2007,18 @@ Vector3 AnimationTree::get_root_motion_scale() const { return root_motion_scale; } +Vector3 AnimationTree::get_root_motion_position_accumulator() const { + return root_motion_position_accumulator; +} + +Quaternion AnimationTree::get_root_motion_rotation_accumulator() const { + return root_motion_rotation_accumulator; +} + +Vector3 AnimationTree::get_root_motion_scale_accumulator() const { + return root_motion_scale_accumulator; +} + void AnimationTree::_tree_changed() { if (properties_dirty) { return; @@ -1865,11 +2028,46 @@ void AnimationTree::_tree_changed() { properties_dirty = true; } +void AnimationTree::_animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name) { + ERR_FAIL_COND(!property_reference_map.has(p_oid)); + String base_path = property_reference_map[p_oid]; + String old_base = base_path + p_old_name; + String new_base = base_path + p_new_name; + for (const PropertyInfo &E : properties) { + if (E.name.begins_with(old_base)) { + String new_name = E.name.replace_first(old_base, new_base); + property_map[new_name] = property_map[E.name]; + property_map.erase(E.name); + } + } + + //update tree second + properties_dirty = true; + _update_properties(); +} + +void AnimationTree::_animation_node_removed(const ObjectID &p_oid, const StringName &p_node) { + ERR_FAIL_COND(!property_reference_map.has(p_oid)); + String base_path = String(property_reference_map[p_oid]) + String(p_node); + for (const PropertyInfo &E : properties) { + if (E.name.begins_with(base_path)) { + property_map.erase(E.name); + } + } + + //update tree second + properties_dirty = true; + _update_properties(); +} + void AnimationTree::_update_properties_for_node(const String &p_base_path, Ref<AnimationNode> node) { ERR_FAIL_COND(node.is_null()); if (!property_parent_map.has(p_base_path)) { property_parent_map[p_base_path] = HashMap<StringName, StringName>(); } + if (!property_reference_map.has(node->get_instance_id())) { + property_reference_map[node->get_instance_id()] = p_base_path; + } if (node->get_input_count() && !input_activity_map.has(p_base_path)) { Vector<Activity> activity; @@ -1889,7 +2087,10 @@ void AnimationTree::_update_properties_for_node(const String &p_base_path, Ref<A StringName key = pinfo.name; if (!property_map.has(p_base_path + key)) { - property_map[p_base_path + key] = node->get_parameter_default_value(key); + Pair<Variant, bool> param; + param.first = node->get_parameter_default_value(key); + param.second = node->is_parameter_read_only(key); + property_map[p_base_path + key] = param; } property_parent_map[p_base_path][key] = p_base_path + key; @@ -1912,6 +2113,7 @@ void AnimationTree::_update_properties() { } properties.clear(); + property_reference_map.clear(); property_parent_map.clear(); input_activity_map.clear(); input_activity_map_get.clear(); @@ -1931,7 +2133,10 @@ bool AnimationTree::_set(const StringName &p_name, const Variant &p_value) { } if (property_map.has(p_name)) { - property_map[p_name] = p_value; + if (is_inside_tree() && property_map[p_name].second) { + return false; // Prevent to set property by user. + } + property_map[p_name].first = p_value; return true; } @@ -1944,7 +2149,7 @@ bool AnimationTree::_get(const StringName &p_name, Variant &r_ret) const { } if (property_map.has(p_name)) { - r_ret = property_map[p_name]; + r_ret = property_map[p_name].first; return true; } @@ -1961,20 +2166,6 @@ void AnimationTree::_get_property_list(List<PropertyInfo> *p_list) const { } } -void AnimationTree::rename_parameter(const String &p_base, const String &p_new_base) { - //rename values first - for (const PropertyInfo &E : properties) { - if (E.name.begins_with(p_base)) { - String new_name = E.name.replace_first(p_base, p_new_base); - property_map[new_name] = property_map[E.name]; - } - } - - //update tree second - properties_dirty = true; - _update_properties(); -} - real_t AnimationTree::get_connection_activity(const StringName &p_path, int p_connection) const { if (!input_activity_map_get.has(p_path)) { return 0; @@ -2011,22 +2202,30 @@ void AnimationTree::_bind_methods() { ClassDB::bind_method(D_METHOD("set_root_motion_track", "path"), &AnimationTree::set_root_motion_track); ClassDB::bind_method(D_METHOD("get_root_motion_track"), &AnimationTree::get_root_motion_track); + ClassDB::bind_method(D_METHOD("set_audio_max_polyphony", "max_polyphony"), &AnimationTree::set_audio_max_polyphony); + ClassDB::bind_method(D_METHOD("get_audio_max_polyphony"), &AnimationTree::get_audio_max_polyphony); + ClassDB::bind_method(D_METHOD("get_root_motion_position"), &AnimationTree::get_root_motion_position); ClassDB::bind_method(D_METHOD("get_root_motion_rotation"), &AnimationTree::get_root_motion_rotation); ClassDB::bind_method(D_METHOD("get_root_motion_scale"), &AnimationTree::get_root_motion_scale); + ClassDB::bind_method(D_METHOD("get_root_motion_position_accumulator"), &AnimationTree::get_root_motion_position_accumulator); + ClassDB::bind_method(D_METHOD("get_root_motion_rotation_accumulator"), &AnimationTree::get_root_motion_rotation_accumulator); + ClassDB::bind_method(D_METHOD("get_root_motion_scale_accumulator"), &AnimationTree::get_root_motion_scale_accumulator); ClassDB::bind_method(D_METHOD("_update_properties"), &AnimationTree::_update_properties); - ClassDB::bind_method(D_METHOD("rename_parameter", "old_name", "new_name"), &AnimationTree::rename_parameter); - ClassDB::bind_method(D_METHOD("advance", "delta"), &AnimationTree::advance); + GDVIRTUAL_BIND(_post_process_key_value, "animation", "track", "value", "object", "object_idx"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tree_root", PROPERTY_HINT_RESOURCE_TYPE, "AnimationRootNode"), "set_tree_root", "get_tree_root"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "anim_player", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "AnimationPlayer"), "set_animation_player", "get_animation_player"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "advance_expression_base_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node"), "set_advance_expression_base_node", "get_advance_expression_base_node"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "is_active"); ADD_PROPERTY(PropertyInfo(Variant::INT, "process_callback", PROPERTY_HINT_ENUM, "Physics,Idle,Manual"), "set_process_callback", "get_process_callback"); + ADD_GROUP("Audio", "audio_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "audio_max_polyphony", PROPERTY_HINT_RANGE, "1,127,1"), "set_audio_max_polyphony", "get_audio_max_polyphony"); ADD_GROUP("Root Motion", "root_motion_"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_motion_track"), "set_root_motion_track", "get_root_motion_track"); diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h index 2c1be6199c..c68cae56ea 100644 --- a/scene/animation/animation_tree.h +++ b/scene/animation/animation_tree.h @@ -35,6 +35,7 @@ #include "scene/3d/node_3d.h" #include "scene/3d/skeleton_3d.h" #include "scene/resources/animation.h" +#include "scene/resources/audio_stream_polyphonic.h" class AnimationNodeBlendTree; class AnimationNodeStartState; @@ -117,6 +118,7 @@ protected: GDVIRTUAL0RC(Array, _get_parameter_list) GDVIRTUAL1RC(Ref<AnimationNode>, _get_child_by_name, StringName) GDVIRTUAL1RC(Variant, _get_parameter_default_value, StringName) + GDVIRTUAL1RC(bool, _is_parameter_read_only, StringName) GDVIRTUAL3RC(double, _process, double, bool, bool) GDVIRTUAL0RC(String, _get_caption) GDVIRTUAL0RC(bool, _has_filter) @@ -124,6 +126,7 @@ protected: public: virtual void get_parameter_list(List<PropertyInfo> *r_list) const; virtual Variant get_parameter_default_value(const StringName &p_parameter) const; + virtual bool is_parameter_read_only(const StringName &p_parameter) const; void set_parameter(const StringName &p_name, const Variant &p_value); Variant get_parameter(const StringName &p_name) const; @@ -138,12 +141,12 @@ public: virtual double process(double p_time, bool p_seek, bool p_is_external_seeking); virtual String get_caption() const; + virtual bool add_input(const String &p_name); + virtual void remove_input(int p_index); + virtual bool set_input_name(int p_input, const String &p_name); + virtual String get_input_name(int p_input) const; int get_input_count() const; - String get_input_name(int p_input); - - void add_input(const String &p_name); - void set_input_name(int p_input, const String &p_name); - void remove_input(int p_index); + int find_input(const String &p_name) const; void set_filter_path(const NodePath &p_path, bool p_enable); bool is_path_filtered(const NodePath &p_path) const; @@ -164,6 +167,11 @@ VARIANT_ENUM_CAST(AnimationNode::FilterAction) class AnimationRootNode : public AnimationNode { GDCLASS(AnimationRootNode, AnimationNode); +protected: + virtual void _tree_changed(); + virtual void _animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name); + virtual void _animation_node_removed(const ObjectID &p_oid, const StringName &p_node); + public: AnimationRootNode() {} }; @@ -220,6 +228,12 @@ private: } }; + struct RootMotionCache { + Vector3 loc = Vector3(0, 0, 0); + Quaternion rot = Quaternion(0, 0, 0, 1); + Vector3 scale = Vector3(1, 1, 1); + }; + struct TrackCacheBlendShape : public TrackCache { MeshInstance3D *mesh_3d = nullptr; float init_value = 0; @@ -250,10 +264,28 @@ private: } }; - struct TrackCacheAudio : public TrackCache { - bool playing = false; + // Audio stream information for each audio stream placed on the track. + struct PlayingAudioStreamInfo { + AudioStreamPlaybackPolyphonic::ID index = -1; // ID retrieved from AudioStreamPlaybackPolyphonic. double start = 0.0; double len = 0.0; + }; + + // Audio track information for mixng and ending. + struct PlayingAudioTrackInfo { + HashMap<int, PlayingAudioStreamInfo> stream_info; + double length = 0.0; + double time = 0.0; + real_t volume = 0.0; + bool loop = false; + bool backward = false; + bool use_blend = false; + }; + + struct TrackCacheAudio : public TrackCache { + Ref<AudioStreamPolyphonic> audio_stream; + Ref<AudioStreamPlaybackPolyphonic> audio_stream_playback; + HashMap<ObjectID, PlayingAudioTrackInfo> playing_streams; // Key is Animation resource ObjectID. TrackCacheAudio() { type = Animation::TYPE_AUDIO; @@ -268,8 +300,10 @@ private: } }; + RootMotionCache root_motion_cache; HashMap<NodePath, TrackCache *> track_cache; HashSet<TrackCache *> playing_caches; + Vector<Node *> playing_audio_stream_players; Ref<AnimationNode> root; NodePath advance_expression_base_node = NodePath(String(".")); @@ -277,6 +311,7 @@ private: AnimationProcessCallback process_callback = ANIMATION_PROCESS_IDLE; bool active = false; NodePath animation_player; + int audio_max_polyphony = 32; AnimationNode::State state; bool cache_valid = false; @@ -285,6 +320,8 @@ private: void _setup_animation_player(); void _animation_player_changed(); void _clear_caches(); + void _clear_playing_caches(); + void _clear_audio_streams(); bool _update_caches(AnimationPlayer *player); void _process_graph(double p_delta); @@ -297,14 +334,20 @@ private: Vector3 root_motion_position = Vector3(0, 0, 0); Quaternion root_motion_rotation = Quaternion(0, 0, 0, 1); Vector3 root_motion_scale = Vector3(0, 0, 0); + Vector3 root_motion_position_accumulator = Vector3(0, 0, 0); + Quaternion root_motion_rotation_accumulator = Quaternion(0, 0, 0, 1); + Vector3 root_motion_scale_accumulator = Vector3(1, 1, 1); friend class AnimationNode; bool properties_dirty = true; void _tree_changed(); + void _animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name); + void _animation_node_removed(const ObjectID &p_oid, const StringName &p_node); void _update_properties(); List<PropertyInfo> properties; HashMap<StringName, HashMap<StringName, StringName>> property_parent_map; - HashMap<StringName, Variant> property_map; + HashMap<ObjectID, StringName> property_reference_map; + HashMap<StringName, Pair<Variant, bool>> property_map; // Property value and read-only flag. struct Activity { uint64_t last_pass = 0; @@ -326,6 +369,8 @@ protected: void _notification(int p_what); static void _bind_methods(); + GDVIRTUAL5RC(Variant, _post_process_key_value, Ref<Animation>, int, Variant, Object *, int); + Variant post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx = -1); virtual Variant _post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx = -1); public: @@ -344,6 +389,9 @@ public: void set_advance_expression_base_node(const NodePath &p_advance_expression_base_node); NodePath get_advance_expression_base_node() const; + void set_audio_max_polyphony(int p_audio_max_polyphony); + int get_audio_max_polyphony() const; + PackedStringArray get_configuration_warnings() const override; bool is_state_invalid() const; @@ -356,11 +404,13 @@ public: Quaternion get_root_motion_rotation() const; Vector3 get_root_motion_scale() const; + Vector3 get_root_motion_position_accumulator() const; + Quaternion get_root_motion_rotation_accumulator() const; + Vector3 get_root_motion_scale_accumulator() const; + real_t get_connection_activity(const StringName &p_path, int p_connection) const; void advance(double p_time); - void rename_parameter(const String &p_base, const String &p_new_base); - uint64_t get_last_process_pass() const; AnimationTree(); ~AnimationTree(); diff --git a/scene/animation/root_motion_view.cpp b/scene/animation/root_motion_view.cpp index e6b258df3e..fc758b9456 100644 --- a/scene/animation/root_motion_view.cpp +++ b/scene/animation/root_motion_view.cpp @@ -80,13 +80,15 @@ bool RootMotionView::get_zero_y() const { void RootMotionView::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { - immediate_material = StandardMaterial3D::get_material_for_2d(false, true, false, false, false); + immediate_material = StandardMaterial3D::get_material_for_2d(false, BaseMaterial3D::TRANSPARENCY_ALPHA, false); + first = true; } break; case NOTIFICATION_INTERNAL_PROCESS: case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { Transform3D transform; + Basis diff; if (has_node(path)) { Node *node = get_node(path); @@ -102,9 +104,9 @@ void RootMotionView::_notification(int p_what) { set_process_internal(true); set_physics_process_internal(false); } - transform.origin = tree->get_root_motion_position(); transform.basis = tree->get_root_motion_rotation(); // Scale is meaningless. + diff = tree->get_root_motion_rotation_accumulator(); } } @@ -114,8 +116,10 @@ void RootMotionView::_notification(int p_what) { first = false; - accumulated.origin += transform.origin; accumulated.basis *= transform.basis; + transform.origin = (diff.inverse() * accumulated.basis).xform(transform.origin); + accumulated.origin += transform.origin; + accumulated.origin.x = Math::fposmod(accumulated.origin.x, cell_size); if (zero_y) { accumulated.origin.y = 0; diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp index b88695427e..abc7814877 100644 --- a/scene/animation/tween.cpp +++ b/scene/animation/tween.cpp @@ -60,7 +60,7 @@ void Tweener::_bind_methods() { ADD_SIGNAL(MethodInfo("finished")); } -void Tween::start_tweeners() { +void Tween::_start_tweeners() { if (tweeners.is_empty()) { dead = true; ERR_FAIL_MSG("Tween without commands, aborting."); @@ -71,6 +71,15 @@ void Tween::start_tweeners() { } } +void Tween::_stop_internal(bool p_reset) { + running = false; + if (p_reset) { + started = false; + dead = false; + total_time = 0; + } +} + Ref<PropertyTweener> Tween::tween_property(Object *p_target, NodePath p_property, Variant p_to, double p_duration) { ERR_FAIL_NULL_V(p_target, nullptr); ERR_FAIL_COND_V_MSG(!valid, nullptr, "Tween invalid. Either finished or created outside scene tree."); @@ -135,14 +144,11 @@ void Tween::append(Ref<Tweener> p_tweener) { } void Tween::stop() { - started = false; - running = false; - dead = false; - total_time = 0; + _stop_internal(true); } void Tween::pause() { - running = false; + _stop_internal(false); } void Tween::play() { @@ -274,11 +280,20 @@ bool Tween::step(double p_delta) { } if (!started) { - ERR_FAIL_COND_V_MSG(tweeners.is_empty(), false, "Tween started, but has no Tweeners."); + if (tweeners.is_empty()) { + String tween_id; + Node *node = get_bound_node(); + if (node) { + tween_id = vformat("Tween (bound to %s)", node->is_inside_tree() ? (String)node->get_path() : (String)node->get_name()); + } else { + tween_id = to_string(); + } + ERR_FAIL_V_MSG(false, tween_id + ": started with no Tweeners."); + } current_step = 0; loops_done = 0; total_time = 0; - start_tweeners(); + _start_tweeners(); started = true; } @@ -319,7 +334,7 @@ bool Tween::step(double p_delta) { } else { emit_signal(SNAME("loop_finished"), loops_done); current_step = 0; - start_tweeners(); + _start_tweeners(); #ifdef DEBUG_ENABLED if (loops <= 0 && Math::is_equal_approx(rem_delta, initial_delta)) { if (!potential_infinite) { @@ -332,7 +347,7 @@ bool Tween::step(double p_delta) { #endif } } else { - start_tweeners(); + _start_tweeners(); } } } @@ -387,6 +402,15 @@ Variant Tween::interpolate_variant(Variant p_initial_val, Variant p_delta_val, d return ret; } +String Tween::to_string() { + String ret = Object::to_string(); + Node *node = get_bound_node(); + if (node) { + ret += vformat(" (bound to %s)", node->get_name()); + } + return ret; +} + void Tween::_bind_methods() { ClassDB::bind_method(D_METHOD("tween_property", "object", "property", "final_val", "duration"), &Tween::tween_property); ClassDB::bind_method(D_METHOD("tween_interval", "time"), &Tween::tween_interval); @@ -570,7 +594,7 @@ PropertyTweener::PropertyTweener(Object *p_target, NodePath p_property, Variant } PropertyTweener::PropertyTweener() { - ERR_FAIL_MSG("Can't create empty PropertyTweener. Use get_tree().tween_property() or tween_property() instead."); + ERR_FAIL_MSG("PropertyTweener can't be created directly. Use the tween_property() method in Tween."); } void IntervalTweener::start() { @@ -601,7 +625,7 @@ IntervalTweener::IntervalTweener(double p_time) { } IntervalTweener::IntervalTweener() { - ERR_FAIL_MSG("Can't create empty IntervalTweener. Use get_tree().tween_interval() instead."); + ERR_FAIL_MSG("IntervalTweener can't be created directly. Use the tween_interval() method in Tween."); } Ref<CallbackTweener> CallbackTweener::set_delay(double p_delay) { @@ -652,7 +676,7 @@ CallbackTweener::CallbackTweener(Callable p_callback) { } CallbackTweener::CallbackTweener() { - ERR_FAIL_MSG("Can't create empty CallbackTweener. Use get_tree().tween_callback() instead."); + ERR_FAIL_MSG("CallbackTweener can't be created directly. Use the tween_callback() method in Tween."); } Ref<MethodTweener> MethodTweener::set_delay(double p_delay) { @@ -745,5 +769,5 @@ MethodTweener::MethodTweener(Callable p_callback, Variant p_from, Variant p_to, } MethodTweener::MethodTweener() { - ERR_FAIL_MSG("Can't create empty MethodTweener. Use get_tree().tween_method() instead."); + ERR_FAIL_MSG("MethodTweener can't be created directly. Use the tween_method() method in Tween."); } diff --git a/scene/animation/tween.h b/scene/animation/tween.h index 8f65416e71..58217db535 100644 --- a/scene/animation/tween.h +++ b/scene/animation/tween.h @@ -123,12 +123,15 @@ private: typedef real_t (*interpolater)(real_t t, real_t b, real_t c, real_t d); static interpolater interpolaters[TRANS_MAX][EASE_MAX]; - void start_tweeners(); + void _start_tweeners(); + void _stop_internal(bool p_reset); protected: static void _bind_methods(); public: + virtual String to_string() override; + Ref<PropertyTweener> tween_property(Object *p_target, NodePath p_property, Variant p_to, double p_duration); Ref<IntervalTweener> tween_interval(double p_time); Ref<CallbackTweener> tween_callback(Callable p_callback); |