From 6dd410854c0a9bab155ba020a1e5055f7384f453 Mon Sep 17 00:00:00 2001 From: "Silc Lizard (Tokage) Renew" <61938263+TokageItLab@users.noreply.github.com> Date: Mon, 8 Jan 2024 06:08:10 +0900 Subject: [PATCH] Rework AnimationNode process for retrieving the semantic time info --- doc/classes/AnimationNode.xml | 11 +- doc/classes/AnimationNodeAnimation.xml | 18 + doc/classes/AnimationNodeOneShot.xml | 5 + .../AnimationNodeStateMachineTransition.xml | 4 + doc/classes/AnimationNodeTransition.xml | 16 + .../animation_blend_tree_editor_plugin.cpp | 9 +- .../animation_state_machine_editor.cpp | 4 +- scene/animation/animation_blend_space_1d.cpp | 39 +- scene/animation/animation_blend_space_1d.h | 3 +- scene/animation/animation_blend_space_2d.cpp | 34 +- scene/animation/animation_blend_space_2d.h | 3 +- scene/animation/animation_blend_tree.cpp | 504 +++++++++++++----- scene/animation/animation_blend_tree.h | 61 ++- .../animation_node_state_machine.cpp | 138 +++-- .../animation/animation_node_state_machine.h | 19 +- scene/animation/animation_tree.cpp | 99 +++- scene/animation/animation_tree.h | 50 +- scene/resources/animation.h | 4 +- 18 files changed, 703 insertions(+), 318 deletions(-) diff --git a/doc/classes/AnimationNode.xml b/doc/classes/AnimationNode.xml index d7fb735b4d47..960bbe68adb4 100644 --- a/doc/classes/AnimationNode.xml +++ b/doc/classes/AnimationNode.xml @@ -6,6 +6,13 @@ Base resource for [AnimationTree] nodes. In general, it's not used directly, but you can create custom ones with custom blending formulas. Inherit this when creating animation nodes mainly for use in [AnimationNodeBlendTree], otherwise [AnimationRootNode] should be used instead. + You can access the time information as read-only parameter which is processed and stored in the previous frame for all nodes except [AnimationNodeOutput]. + [b]Note:[/b] If more than two inputs exist in the [AnimationNode], which time information takes precedence depends on the type of [AnimationNode]. + [codeblock] + var current_length = $AnimationTree[parameters/AnimationNodeName/current_length] + var current_position = $AnimationTree[parameters/AnimationNodeName/current_position] + var current_delta = $AnimationTree[parameters/AnimationNodeName/current_delta] + [/codeblock] $DOCS_URL/tutorials/animation/animation_tree.html @@ -56,7 +63,7 @@ When inheriting from [AnimationRootNode], implement this virtual method to return whether the [param parameter] is read-only. Parameters are custom local memory used for your animation nodes, given a resource can be reused in multiple trees. - + @@ -65,7 +72,7 @@ When inheriting from [AnimationRootNode], implement this virtual method to run some code when this animation node is processed. The [param time] parameter is a relative delta, unless [param seek] is [code]true[/code], in which case it is absolute. Here, call the [method blend_input], [method blend_node] or [method blend_animation] functions. You can also use [method get_parameter] and [method set_parameter] to modify local memory. - This function should return the time left for the current animation to finish (if unsure, pass the value from the main blend being called). + This function should return the delta. diff --git a/doc/classes/AnimationNodeAnimation.xml b/doc/classes/AnimationNodeAnimation.xml index d965d31b03d7..5683371182bd 100644 --- a/doc/classes/AnimationNodeAnimation.xml +++ b/doc/classes/AnimationNodeAnimation.xml @@ -15,9 +15,27 @@ Animation to use as an output. It is one of the animations provided by [member AnimationTree.anim_player]. + + If [member use_custom_timeline] is [code]true[/code], override the loop settings of the original [Animation] resource with the value. + Determines the playback direction of the animation. + + If [member use_custom_timeline] is [code]true[/code], offset the start position of the animation. + This is useful for adjusting which foot steps first in 3D walking animations. + + + If [code]true[/code], scales the time so that the length specified in [member timeline_length] is one cycle. + This is useful for matching the periods of walking and running animations. + If [code]false[/code], the original animation length is respected. If you set the loop to [member loop_mode], the animation will loop in [member timeline_length]. + + + If [member use_custom_timeline] is [code]true[/code], offset the start position of the animation. + + + If [code]true[/code], [AnimationNode] provides an animation based on the [Animation] resource with some parameters adjusted. + diff --git a/doc/classes/AnimationNodeOneShot.xml b/doc/classes/AnimationNodeOneShot.xml index ac7cf701331d..6ff2d6f6db41 100644 --- a/doc/classes/AnimationNodeOneShot.xml +++ b/doc/classes/AnimationNodeOneShot.xml @@ -66,17 +66,22 @@ If [member autorestart] is [code]true[/code], a random additional delay (in seconds) between 0 and this value will be added to [member autorestart_delay]. + + If [code]true[/code], breaks the loop at the end of the loop cycle for transition, even if the animation is looping. + Determines how cross-fading between animations is eased. If empty, the transition will be linear. The fade-in duration. For example, setting this to [code]1.0[/code] for a 5 second length animation will produce a cross-fade that starts at 0 second and ends at 1 second during the animation. + [b]Note:[/b] [AnimationNodeOneShot] transitions the current state after the end of the fading. When [AnimationNodeOutput] is considered as the most upstream, so the [member fadein_time] is scaled depending on the downstream delta. For example, if this value is set to [code]1.0[/code] and a [AnimationNodeTimeScale] with a value of [code]2.0[/code] is chained downstream, the actual processing time will be 0.5 second. Determines how cross-fading between animations is eased. If empty, the transition will be linear. The fade-out duration. For example, setting this to [code]1.0[/code] for a 5 second length animation will produce a cross-fade that starts at 4 second and ends at 5 second during the animation. + [b]Note:[/b] [AnimationNodeOneShot] transitions the current state after the end of the fading. When [AnimationNodeOutput] is considered as the most upstream, so the [member fadeout_time] is scaled depending on the downstream delta. For example, if this value is set to [code]1.0[/code] and an [AnimationNodeTimeScale] with a value of [code]2.0[/code] is chained downstream, the actual processing time will be 0.5 second. The blend type. diff --git a/doc/classes/AnimationNodeStateMachineTransition.xml b/doc/classes/AnimationNodeStateMachineTransition.xml index 7b7797f594cc..7bd0bd7e7e2b 100644 --- a/doc/classes/AnimationNodeStateMachineTransition.xml +++ b/doc/classes/AnimationNodeStateMachineTransition.xml @@ -28,6 +28,9 @@ Determines whether the transition should disabled, enabled when using [method AnimationNodeStateMachinePlayback.travel], or traversed automatically if the [member advance_condition] and [member advance_expression] checks are true (if assigned). + + If [code]true[/code], breaks the loop at the end of the loop cycle for transition, even if the animation is looping. + Lower priority transitions are preferred when travelling through the tree via [method AnimationNodeStateMachinePlayback.travel] or [member advance_mode] is set to [constant ADVANCE_MODE_AUTO]. @@ -42,6 +45,7 @@ The time to cross-fade between this state and the next. + [b]Note:[/b] [AnimationNodeStateMachine] transitions the current state immediately after the start of the fading. The precise remaining time can only be inferred from the main animation. When [AnimationNodeOutput] is considered as the most upstream, so the [member xfade_time] is not scaled depending on the downstream delta. See also [member AnimationNodeOneShot.fadeout_time]. diff --git a/doc/classes/AnimationNodeTransition.xml b/doc/classes/AnimationNodeTransition.xml index 3e1a0a28b51d..775a2087356b 100644 --- a/doc/classes/AnimationNodeTransition.xml +++ b/doc/classes/AnimationNodeTransition.xml @@ -42,6 +42,13 @@ https://godotengine.org/asset-library/asset/678 + + + + + Returns whether the animation breaks the loop at the end of the loop cycle for transition. + + @@ -64,6 +71,14 @@ Enables or disables auto-advance for the given [param input] index. If enabled, state changes to the next input after playing the animation once. If enabled for the last input state, it loops to the first. + + + + + + If [code]true[/code], breaks the loop at the end of the loop cycle for transition, even if the animation is looping. + + @@ -85,6 +100,7 @@ Cross-fading time (in seconds) between each animation connected to the inputs. + [b]Note:[/b] [AnimationNodeTransition] transitions the current state immediately after the start of the fading. The precise remaining time can only be inferred from the main animation. When [AnimationNodeOutput] is considered as the most upstream, so the [member xfade_time] is not scaled depending on the downstream delta. See also [member AnimationNodeOneShot.fadeout_time]. diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp index 0412141775d9..bda2cb666b0b 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.cpp +++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp @@ -256,10 +256,6 @@ void AnimationNodeBlendTreeEditor::update_graph() { options.push_back(F); } - if (tree->has_animation(anim->get_animation())) { - pb->set_max(tree->get_animation(anim->get_animation())->get_length()); - } - pb->set_show_percentage(false); pb->set_custom_minimum_size(Vector2(0, 14) * EDSCALE); animations[E] = pb; @@ -994,9 +990,10 @@ void AnimationNodeBlendTreeEditor::_notification(int p_what) { if (tree->has_animation(an->get_animation())) { Ref anim = tree->get_animation(an->get_animation()); if (anim.is_valid()) { - E.value->set_max(anim->get_length()); //StringName path = AnimationTreeEditor::get_singleton()->get_base_path() + E.input_node; - StringName time_path = AnimationTreeEditor::get_singleton()->get_base_path() + String(E.key) + "/time"; + StringName length_path = AnimationTreeEditor::get_singleton()->get_base_path() + String(E.key) + "/current_length"; + StringName time_path = AnimationTreeEditor::get_singleton()->get_base_path() + String(E.key) + "/current_position"; + E.value->set_max(tree->get(length_path)); E.value->set_value(tree->get(time_path)); } } diff --git a/editor/plugins/animation_state_machine_editor.cpp b/editor/plugins/animation_state_machine_editor.cpp index dbfb143b2270..91ed0bd17890 100644 --- a/editor/plugins/animation_state_machine_editor.cpp +++ b/editor/plugins/animation_state_machine_editor.cpp @@ -1247,14 +1247,14 @@ void AnimationNodeStateMachineEditor::_state_machine_pos_draw_all() { { float len = MAX(0.0001, current_length); float pos = CLAMP(current_play_pos, 0, len); - float c = current_length == HUGE_LENGTH ? 1 : (pos / len); + float c = pos / len; _state_machine_pos_draw_individual(playback->get_current_node(), c); } { float len = MAX(0.0001, fade_from_length); float pos = CLAMP(fade_from_current_play_pos, 0, len); - float c = fade_from_length == HUGE_LENGTH ? 1 : (pos / len); + float c = pos / len; _state_machine_pos_draw_individual(playback->get_fading_from_node(), c); } } diff --git a/scene/animation/animation_blend_space_1d.cpp b/scene/animation/animation_blend_space_1d.cpp index 4cbd9b1d7620..36343edd1144 100644 --- a/scene/animation/animation_blend_space_1d.cpp +++ b/scene/animation/animation_blend_space_1d.cpp @@ -33,12 +33,17 @@ #include "animation_blend_tree.h" void AnimationNodeBlendSpace1D::get_parameter_list(List *r_list) const { + AnimationNode::get_parameter_list(r_list); 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 { + Variant ret = AnimationNode::get_parameter_default_value(p_parameter); + if (ret != Variant()) { + return ret; + } + if (p_parameter == closest) { return -1; } else { @@ -272,9 +277,9 @@ void AnimationNodeBlendSpace1D::_add_blend_point(int p_index, const Ref max_weight) { + max_weight = pi.weight; + mind = t; + first = false; + } } else if (sync) { pi.weight = 0; blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only); @@ -365,7 +374,7 @@ double AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_ } if (new_closest != cur_closest && new_closest != -1) { - double from = 0.0; + NodeTimeInfo from; if (blend_mode == BLEND_MODE_DISCRETE_CARRY && cur_closest != -1) { //for ping-pong loop Ref na_c = static_cast>(blend_points[cur_closest].node); @@ -376,18 +385,17 @@ double AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_ //see how much animation remains pi.seeked = false; pi.weight = 0; - from = cur_length_internal - blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only); + from = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only); } - pi.time = from; + pi.time = from.position; pi.seeked = true; pi.weight = 1.0; - max_time_remaining = blend_node(blend_points[new_closest].node, blend_points[new_closest].name, pi, FILTER_IGNORE, true, p_test_only); - cur_length_internal = from + max_time_remaining; + mind = blend_node(blend_points[new_closest].node, blend_points[new_closest].name, pi, FILTER_IGNORE, true, p_test_only); cur_closest = new_closest; } else { pi.weight = 1.0; - max_time_remaining = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only); + mind = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only); } if (sync) { @@ -402,8 +410,7 @@ double AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_ } set_parameter(closest, cur_closest); - set_parameter(length_internal, cur_length_internal); - return max_time_remaining; + return mind; } String AnimationNodeBlendSpace1D::get_caption() const { diff --git a/scene/animation/animation_blend_space_1d.h b/scene/animation/animation_blend_space_1d.h index 40679d55ef16..64ae4d0505df 100644 --- a/scene/animation/animation_blend_space_1d.h +++ b/scene/animation/animation_blend_space_1d.h @@ -68,7 +68,6 @@ class AnimationNodeBlendSpace1D : public AnimationRootNode { StringName blend_position = "blend_position"; StringName closest = "closest"; - StringName length_internal = "length_internal"; BlendMode blend_mode = BLEND_MODE_INTERPOLATED; @@ -114,7 +113,7 @@ class AnimationNodeBlendSpace1D : public AnimationRootNode { void set_use_sync(bool p_sync); bool is_using_sync() const; - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; String get_caption() const override; Ref get_child_by_name(const StringName &p_name) const override; diff --git a/scene/animation/animation_blend_space_2d.cpp b/scene/animation/animation_blend_space_2d.cpp index d5c6253e9d03..263424823173 100644 --- a/scene/animation/animation_blend_space_2d.cpp +++ b/scene/animation/animation_blend_space_2d.cpp @@ -34,16 +34,19 @@ #include "core/math/geometry_2d.h" void AnimationNodeBlendSpace2D::get_parameter_list(List *r_list) const { + AnimationNode::get_parameter_list(r_list); r_list->push_back(PropertyInfo(Variant::VECTOR2, 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 AnimationNodeBlendSpace2D::get_parameter_default_value(const StringName &p_parameter) const { + Variant ret = AnimationNode::get_parameter_default_value(p_parameter); + if (ret != Variant()) { + return ret; + } + if (p_parameter == closest) { return -1; - } else if (p_parameter == length_internal) { - return 0; } else { return Vector2(); } @@ -442,19 +445,18 @@ void AnimationNodeBlendSpace2D::_blend_triangle(const Vector2 &p_pos, const Vect r_weights[2] = w; } -double AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { _update_triangles(); Vector2 blend_pos = get_parameter(blend_position); int cur_closest = get_parameter(closest); - double cur_length_internal = get_parameter(length_internal); - double mind = 0.0; //time of min distance point + NodeTimeInfo mind; //time of min distance point AnimationMixer::PlaybackInfo pi = p_playback_info; if (blend_mode == BLEND_MODE_INTERPOLATED) { if (triangles.size() == 0) { - return 0; + return NodeTimeInfo(); } Vector2 best_point; @@ -500,7 +502,7 @@ double AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_ } } - ERR_FAIL_COND_V(blend_triangle == -1, 0); //should never reach here + ERR_FAIL_COND_V(blend_triangle == -1, NodeTimeInfo()); //should never reach here int triangle_points[3]; for (int j = 0; j < 3; j++) { @@ -509,15 +511,17 @@ double AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_ first = true; + bool found = false; + double max_weight = 0.0; for (int i = 0; i < blend_points_used; i++) { - bool found = false; for (int j = 0; j < 3; j++) { if (i == triangle_points[j]) { //blend with the given weight pi.weight = blend_weights[j]; - double t = blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only); - if (first || t < mind) { + NodeTimeInfo t = blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only); + if (first || pi.weight > max_weight) { mind = t; + max_weight = pi.weight; first = false; } found = true; @@ -543,7 +547,7 @@ double AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_ } if (new_closest != cur_closest && new_closest != -1) { - double from = 0.0; + NodeTimeInfo from; if (blend_mode == BLEND_MODE_DISCRETE_CARRY && cur_closest != -1) { //for ping-pong loop Ref na_c = static_cast>(blend_points[cur_closest].node); @@ -554,14 +558,13 @@ double AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_ //see how much animation remains pi.seeked = false; pi.weight = 0; - from = cur_length_internal - blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only); + from = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only); } - pi.time = from; + pi.time = from.position; pi.seeked = true; pi.weight = 1.0; mind = blend_node(blend_points[new_closest].node, blend_points[new_closest].name, pi, FILTER_IGNORE, true, p_test_only); - cur_length_internal = from + mind; cur_closest = new_closest; } else { pi.weight = 1.0; @@ -580,7 +583,6 @@ double AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_ } set_parameter(closest, cur_closest); - set_parameter(length_internal, cur_length_internal); return mind; } diff --git a/scene/animation/animation_blend_space_2d.h b/scene/animation/animation_blend_space_2d.h index 33a821d80cda..c26ff2bce0fe 100644 --- a/scene/animation/animation_blend_space_2d.h +++ b/scene/animation/animation_blend_space_2d.h @@ -65,7 +65,6 @@ class AnimationNodeBlendSpace2D : public AnimationRootNode { StringName blend_position = "blend_position"; StringName closest = "closest"; - StringName length_internal = "length_internal"; Vector2 max_space = Vector2(1, 1); Vector2 min_space = Vector2(-1, -1); Vector2 snap = Vector2(0.1, 0.1); @@ -129,7 +128,7 @@ class AnimationNodeBlendSpace2D : public AnimationRootNode { void set_y_label(const String &p_label); String get_y_label() const; - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; virtual String get_caption() const override; Vector2 get_closest_point(const Vector2 &p_point); diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp index 4a01b2ad6503..71f9c45eea51 100644 --- a/scene/animation/animation_blend_tree.cpp +++ b/scene/animation/animation_blend_tree.cpp @@ -44,7 +44,26 @@ StringName AnimationNodeAnimation::get_animation() const { Vector (*AnimationNodeAnimation::get_editable_animation_list)() = nullptr; void AnimationNodeAnimation::get_parameter_list(List *r_list) const { - r_list->push_back(PropertyInfo(Variant::FLOAT, time, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); + AnimationNode::get_parameter_list(r_list); +} + +AnimationNode::NodeTimeInfo AnimationNodeAnimation::get_node_time_info() const { + NodeTimeInfo nti; + if (!process_state->tree->has_animation(animation)) { + return nti; + } + + if (use_custom_timeline) { + nti.length = timeline_length; + nti.loop_mode = loop_mode; + } else { + Ref anim = process_state->tree->get_animation(animation); + nti.length = (double)anim->get_length(); + nti.loop_mode = anim->get_loop_mode(); + } + nti.position = get_parameter(current_position); + + return nti; } void AnimationNodeAnimation::_validate_property(PropertyInfo &p_property) const { @@ -62,11 +81,34 @@ void AnimationNodeAnimation::_validate_property(PropertyInfo &p_property) const p_property.hint_string = anims; } } + + if (!use_custom_timeline) { + if (p_property.name == "timeline_length" || p_property.name == "start_offset" || p_property.name == "loop_mode" || p_property.name == "stretch_time_scale") { + p_property.usage = PROPERTY_USAGE_NONE; + } + } } -double AnimationNodeAnimation::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { - double cur_time = get_parameter(time); +AnimationNode::NodeTimeInfo AnimationNodeAnimation::process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { + process_state->is_testing = p_test_only; + AnimationMixer::PlaybackInfo pi = p_playback_info; + if (p_playback_info.seeked) { + pi.delta = get_node_time_info().position - p_playback_info.time; + } else { + pi.time = get_node_time_info().position + (backward ? -p_playback_info.delta : p_playback_info.delta); + } + + NodeTimeInfo nti = _process(pi, p_test_only); + + if (!p_test_only) { + set_node_time_info(nti); + } + + return nti; +} + +AnimationNode::NodeTimeInfo AnimationNodeAnimation::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { if (!process_state->tree->has_animation(animation)) { AnimationNodeBlendTree *tree = Object::cast_to(node_state.parent); if (tree) { @@ -77,87 +119,129 @@ double AnimationNodeAnimation::_process(const AnimationMixer::PlaybackInfo p_pla make_invalid(vformat(RTR("Animation not found: '%s'"), animation)); } - return 0; + return NodeTimeInfo(); } Ref anim = process_state->tree->get_animation(animation); double anim_size = (double)anim->get_length(); - double step = 0.0; - double prev_time = cur_time; + + NodeTimeInfo cur_nti = get_node_time_info(); + double cur_len = cur_nti.length; + double cur_time = p_playback_info.time; + double cur_delta = p_playback_info.delta; + + Animation::LoopMode cur_loop_mode = cur_nti.loop_mode; + double prev_time = cur_nti.position; + Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE; bool node_backward = play_mode == PLAY_MODE_BACKWARD; - double p_time = p_playback_info.time; bool p_seek = p_playback_info.seeked; bool p_is_external_seeking = p_playback_info.is_external_seeking; - if (p_playback_info.seeked) { - step = p_time - cur_time; - cur_time = p_time; + bool is_just_looped = false; + + // 1. Progress for AnimationNode. + if (cur_loop_mode != Animation::LOOP_NONE) { + if (cur_loop_mode == Animation::LOOP_LINEAR) { + if (!Math::is_zero_approx(cur_len)) { + if (prev_time <= cur_len && cur_time > cur_len) { + is_just_looped = true; // Don't break with negative timescale since remain will not be 0. + } + cur_time = Math::fposmod(cur_time, cur_len); + } + backward = false; + } else { + if (!Math::is_zero_approx(cur_len)) { + if (prev_time >= 0 && cur_time < 0) { + backward = !backward; + } else if (prev_time <= cur_len && cur_time > cur_len) { + backward = !backward; + is_just_looped = true; // Don't break with negative timescale since remain will not be 0. + } + cur_time = Math::pingpong(cur_time, cur_len); + } + } } else { - p_time *= backward ? -1.0 : 1.0; - cur_time = cur_time + p_time; - step = p_time; + if (cur_time < 0) { + cur_delta += cur_time; + cur_time = 0; + } else if (cur_time > cur_len) { + cur_delta += cur_time - cur_len; + cur_time = cur_len; + } + backward = false; + // If ended, don't progress AnimationNode. So set delta to 0. + if (!Math::is_zero_approx(cur_delta)) { + if (play_mode == PLAY_MODE_FORWARD) { + if (prev_time >= cur_len) { + cur_delta = 0; + } + } else { + if (prev_time <= 0) { + cur_delta = 0; + } + } + } } - bool is_looping = false; - if (anim->get_loop_mode() == Animation::LOOP_PINGPONG) { + // 2. For return, store "AnimationNode" time info here, not "Animation" time info as below. + NodeTimeInfo nti; + nti.length = cur_len; + nti.position = cur_time; + nti.delta = cur_delta; + nti.loop_mode = cur_loop_mode; + nti.is_just_looped = is_just_looped; + + // 3. Progress for Animation. + double prev_playback_time = prev_time - start_offset; + double cur_playback_time = cur_time - start_offset; + if (stretch_time_scale) { + double mlt = anim_size / cur_len; + cur_playback_time *= mlt; + cur_delta *= mlt; + } + if (cur_loop_mode == Animation::LOOP_LINEAR) { if (!Math::is_zero_approx(anim_size)) { - if (prev_time >= 0 && cur_time < 0) { - backward = !backward; + prev_playback_time = Math::fposmod(prev_playback_time, anim_size); + cur_playback_time = Math::fposmod(cur_playback_time, anim_size); + if (prev_playback_time >= 0 && cur_playback_time < 0) { looped_flag = node_backward ? Animation::LOOPED_FLAG_END : Animation::LOOPED_FLAG_START; } - if (prev_time <= anim_size && cur_time > anim_size) { - backward = !backward; + if (prev_playback_time <= anim_size && cur_playback_time > anim_size) { looped_flag = node_backward ? Animation::LOOPED_FLAG_START : Animation::LOOPED_FLAG_END; } - cur_time = Math::pingpong(cur_time, anim_size); } - is_looping = true; - } else if (anim->get_loop_mode() == Animation::LOOP_LINEAR) { + } else if (cur_loop_mode == Animation::LOOP_PINGPONG) { if (!Math::is_zero_approx(anim_size)) { - if (prev_time >= 0 && cur_time < 0) { + if (Math::fposmod(cur_playback_time, anim_size * 2.0) >= anim_size) { + cur_delta = -cur_delta; // Needed for retrieveing discrete keys correctly. + } + prev_playback_time = Math::pingpong(prev_playback_time, anim_size); + cur_playback_time = Math::pingpong(cur_playback_time, anim_size); + if (prev_playback_time >= 0 && cur_playback_time < 0) { looped_flag = node_backward ? Animation::LOOPED_FLAG_END : Animation::LOOPED_FLAG_START; } - if (prev_time <= anim_size && cur_time > anim_size) { + if (prev_playback_time <= anim_size && cur_playback_time > anim_size) { looped_flag = node_backward ? Animation::LOOPED_FLAG_START : Animation::LOOPED_FLAG_END; } - cur_time = Math::fposmod(cur_time, anim_size); } - backward = false; - is_looping = true; } else { - if (cur_time < 0) { - step += cur_time; - cur_time = 0; - } else if (cur_time > anim_size) { - step += anim_size - cur_time; - cur_time = anim_size; - } - backward = false; - - // If ended, don't progress animation. So set delta to 0. - if (p_time > 0) { - if (play_mode == PLAY_MODE_FORWARD) { - if (prev_time >= anim_size) { - step = 0; - } - } else { - if (prev_time <= 0) { - step = 0; - } - } + if (cur_playback_time < 0) { + cur_playback_time = 0; + } else if (cur_playback_time > anim_size) { + cur_playback_time = anim_size; } // Emit start & finish signal. Internally, the detections are the same for backward. // We should use call_deferred since the track keys are still being processed. if (process_state->tree && !p_test_only) { // AnimationTree uses seek to 0 "internally" to process the first key of the animation, which is used as the start detection. - if (p_seek && !p_is_external_seeking && cur_time == 0) { + if (p_seek && !p_is_external_seeking && cur_playback_time == 0) { process_state->tree->call_deferred(SNAME("emit_signal"), "animation_started", animation); } // Finished. - if (prev_time < anim_size && cur_time >= anim_size) { + if (prev_time - start_offset < anim_size && cur_playback_time >= anim_size) { process_state->tree->call_deferred(SNAME("emit_signal"), "animation_finished", animation); } } @@ -166,19 +250,18 @@ double AnimationNodeAnimation::_process(const AnimationMixer::PlaybackInfo p_pla if (!p_test_only) { AnimationMixer::PlaybackInfo pi = p_playback_info; if (play_mode == PLAY_MODE_FORWARD) { - pi.time = cur_time; - pi.delta = step; + pi.time = cur_playback_time; + pi.delta = cur_delta; } else { - pi.time = anim_size - cur_time; - pi.delta = -step; + pi.time = anim_size - cur_playback_time; + pi.delta = -cur_delta; } pi.weight = 1.0; pi.looped_flag = looped_flag; blend_animation(animation, pi); } - set_parameter(time, cur_time); - return is_looping ? HUGE_LENGTH : anim_size - cur_time; + return nti; } String AnimationNodeAnimation::get_caption() const { @@ -201,6 +284,48 @@ bool AnimationNodeAnimation::is_backward() const { return backward; } +void AnimationNodeAnimation::set_use_custom_timeline(bool p_use_custom_timeline) { + use_custom_timeline = p_use_custom_timeline; + notify_property_list_changed(); +} + +bool AnimationNodeAnimation::is_using_custom_timeline() const { + return use_custom_timeline; +} + +void AnimationNodeAnimation::set_timeline_length(double p_length) { + timeline_length = p_length; +} + +double AnimationNodeAnimation::get_timeline_length() const { + return timeline_length; +} + +void AnimationNodeAnimation::set_stretch_time_scale(bool p_strech_time_scale) { + stretch_time_scale = p_strech_time_scale; + notify_property_list_changed(); +} + +bool AnimationNodeAnimation::is_stretching_time_scale() const { + return stretch_time_scale; +} + +void AnimationNodeAnimation::set_start_offset(double p_offset) { + start_offset = p_offset; +} + +double AnimationNodeAnimation::get_start_offset() const { + return start_offset; +} + +void AnimationNodeAnimation::set_loop_mode(Animation::LoopMode p_loop_mode) { + loop_mode = p_loop_mode; +} + +Animation::LoopMode AnimationNodeAnimation::get_loop_mode() const { + return loop_mode; +} + void AnimationNodeAnimation::_bind_methods() { ClassDB::bind_method(D_METHOD("set_animation", "name"), &AnimationNodeAnimation::set_animation); ClassDB::bind_method(D_METHOD("get_animation"), &AnimationNodeAnimation::get_animation); @@ -208,8 +333,28 @@ void AnimationNodeAnimation::_bind_methods() { ClassDB::bind_method(D_METHOD("set_play_mode", "mode"), &AnimationNodeAnimation::set_play_mode); ClassDB::bind_method(D_METHOD("get_play_mode"), &AnimationNodeAnimation::get_play_mode); + ClassDB::bind_method(D_METHOD("set_use_custom_timeline", "use_custom_timeline"), &AnimationNodeAnimation::set_use_custom_timeline); + ClassDB::bind_method(D_METHOD("is_using_custom_timeline"), &AnimationNodeAnimation::is_using_custom_timeline); + + ClassDB::bind_method(D_METHOD("set_timeline_length", "timeline_length"), &AnimationNodeAnimation::set_timeline_length); + ClassDB::bind_method(D_METHOD("get_timeline_length"), &AnimationNodeAnimation::get_timeline_length); + + ClassDB::bind_method(D_METHOD("set_stretch_time_scale", "stretch_time_scale"), &AnimationNodeAnimation::set_stretch_time_scale); + ClassDB::bind_method(D_METHOD("is_stretching_time_scale"), &AnimationNodeAnimation::is_stretching_time_scale); + + ClassDB::bind_method(D_METHOD("set_start_offset", "start_offset"), &AnimationNodeAnimation::set_start_offset); + ClassDB::bind_method(D_METHOD("get_start_offset"), &AnimationNodeAnimation::get_start_offset); + + ClassDB::bind_method(D_METHOD("set_loop_mode", "loop_mode"), &AnimationNodeAnimation::set_loop_mode); + ClassDB::bind_method(D_METHOD("get_loop_mode"), &AnimationNodeAnimation::get_loop_mode); + ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "animation"), "set_animation", "get_animation"); ADD_PROPERTY(PropertyInfo(Variant::INT, "play_mode", PROPERTY_HINT_ENUM, "Forward,Backward"), "set_play_mode", "get_play_mode"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_custom_timeline"), "set_use_custom_timeline", "is_using_custom_timeline"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "timeline_length", PROPERTY_HINT_RANGE, "0.001,60,0.001,or_greater,or_less,hide_slider,suffix:s"), "set_timeline_length", "get_timeline_length"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stretch_time_scale"), "set_stretch_time_scale", "is_stretching_time_scale"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "start_offset", PROPERTY_HINT_RANGE, "-60,60,0.001,or_greater,or_less,hide_slider,suffix:s"), "set_start_offset", "get_start_offset"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_mode", PROPERTY_HINT_ENUM, "None,Linear,Ping-Pong"), "set_loop_mode", "get_loop_mode"); BIND_ENUM_CONSTANT(PLAY_MODE_FORWARD); BIND_ENUM_CONSTANT(PLAY_MODE_BACKWARD); @@ -240,16 +385,21 @@ AnimationNodeSync::AnimationNodeSync() { //////////////////////////////////////////////////////// void AnimationNodeOneShot::get_parameter_list(List *r_list) const { + AnimationNode::get_parameter_list(r_list); 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::BOOL, internal_active, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_READ_ONLY)); r_list->push_back(PropertyInfo(Variant::INT, request, PROPERTY_HINT_ENUM, ",Fire,Abort,Fade Out")); - 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, fade_in_remaining, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); r_list->push_back(PropertyInfo(Variant::FLOAT, fade_out_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 { + Variant ret = AnimationNode::get_parameter_default_value(p_parameter); + if (ret != Variant()) { + return ret; + } + if (p_parameter == request) { return ONE_SHOT_REQUEST_NONE; } else if (p_parameter == active || p_parameter == internal_active) { @@ -262,6 +412,10 @@ Variant AnimationNodeOneShot::get_parameter_default_value(const StringName &p_pa } bool AnimationNodeOneShot::is_parameter_read_only(const StringName &p_parameter) const { + if (AnimationNode::is_parameter_read_only(p_parameter)) { + return true; + } + if (p_parameter == active || p_parameter == internal_active) { return true; } @@ -332,6 +486,14 @@ AnimationNodeOneShot::MixMode AnimationNodeOneShot::get_mix_mode() const { return mix; } +void AnimationNodeOneShot::set_break_loop_at_end(bool p_enable) { + break_loop_at_end = p_enable; +} + +bool AnimationNodeOneShot::is_loop_broken_at_end() const { + return break_loop_at_end; +} + String AnimationNodeOneShot::get_caption() const { return "OneShot"; } @@ -340,14 +502,14 @@ bool AnimationNodeOneShot::has_filter() const { return true; } -double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { OneShotRequest cur_request = static_cast((int)get_parameter(request)); bool cur_active = get_parameter(active); bool cur_internal_active = get_parameter(internal_active); - double cur_time = get_parameter(time); - double cur_remaining = get_parameter(remaining); - double cur_fade_out_remaining = get_parameter(fade_out_remaining); + NodeTimeInfo cur_nti = get_node_time_info(); double cur_time_to_restart = get_parameter(time_to_restart); + double cur_fade_in_remaining = get_parameter(fade_in_remaining); + double cur_fade_out_remaining = get_parameter(fade_out_remaining); set_parameter(request, ONE_SHOT_REQUEST_NONE); @@ -356,6 +518,8 @@ double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playb bool is_fading_out = cur_active == true && cur_internal_active == false; double p_time = p_playback_info.time; + double p_delta = p_playback_info.delta; + double abs_delta = Math::abs(p_delta); bool p_seek = p_playback_info.seeked; bool p_is_external_seeking = p_playback_info.is_external_seeking; @@ -374,6 +538,7 @@ double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playb // Request fading. is_fading_out = true; cur_fade_out_remaining = fade_out; + cur_fade_in_remaining = 0; } else { // Shot is ended, do nothing. is_shooting = false; @@ -382,7 +547,7 @@ double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playb set_parameter(time_to_restart, -1); } else if (!do_start && !cur_active) { if (cur_time_to_restart >= 0.0 && !p_seek) { - cur_time_to_restart -= p_time; + cur_time_to_restart -= abs_delta; if (cur_time_to_restart < 0) { do_start = true; // Restart. } @@ -413,8 +578,11 @@ double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playb } if (do_start) { - cur_time = 0; os_seek = true; + if (!cur_internal_active) { + cur_fade_in_remaining = fade_in; // If already active, don't fade-in again. + } + cur_internal_active = true; set_parameter(request, ONE_SHOT_REQUEST_NONE); set_parameter(internal_active, true); set_parameter(active, true); @@ -422,20 +590,17 @@ double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playb real_t blend = 1.0; bool use_blend = sync; - if (cur_time < fade_in) { + + if (cur_fade_in_remaining > 0) { if (fade_in > 0) { use_blend = true; - blend = cur_time / fade_in; + blend = (fade_in - cur_fade_in_remaining) / fade_in; if (fade_in_curve.is_valid()) { blend = fade_in_curve->sample(blend); } } else { blend = 0; // Should not happen. } - } else if (!do_start && !is_fading_out && cur_remaining <= fade_out) { - is_fading_out = true; - cur_fade_out_remaining = cur_remaining; - set_parameter(internal_active, false); } if (is_fading_out) { @@ -451,34 +616,36 @@ double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playb } AnimationMixer::PlaybackInfo pi = p_playback_info; - double main_rem = 0.0; + NodeTimeInfo main_nti; if (mix == MIX_MODE_ADD) { pi.weight = 1.0; - main_rem = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only); + main_nti = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only); } else { pi.seeked &= use_blend; pi.weight = 1.0 - blend; - main_rem = blend_input(0, pi, FILTER_BLEND, sync, p_test_only); // Unlike below, processing this edge is a corner case. + main_nti = blend_input(0, pi, FILTER_BLEND, sync, p_test_only); // Unlike below, processing this edge is a corner case. } + pi = p_playback_info; - if (os_seek) { - pi.time = cur_time; + if (do_start) { + pi.time = 0; + } else if (os_seek) { + pi.time = cur_nti.position; } pi.seeked = os_seek; pi.weight = Math::is_zero_approx(blend) ? CMP_EPSILON : blend; - double os_rem = blend_input(1, pi, FILTER_PASS, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. - if (do_start) { - cur_remaining = os_rem; + NodeTimeInfo os_nti = blend_input(1, pi, FILTER_PASS, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. + + if (cur_fade_in_remaining <= 0 && !do_start && !is_fading_out && os_nti.get_remain(break_loop_at_end) <= fade_out) { + is_fading_out = true; + cur_fade_out_remaining = os_nti.get_remain(break_loop_at_end); + cur_fade_in_remaining = 0; + set_parameter(internal_active, false); } - if (p_seek) { - cur_time = p_time; - } else { - cur_time += p_time; - cur_remaining = os_rem; - cur_fade_out_remaining -= p_time; - if (cur_remaining <= 0 || (is_fading_out && cur_fade_out_remaining <= 0)) { + if (!p_seek) { + if (os_nti.get_remain(break_loop_at_end) <= 0 || (is_fading_out && cur_fade_out_remaining <= 0)) { set_parameter(internal_active, false); set_parameter(active, false); if (auto_restart) { @@ -486,13 +653,17 @@ double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playb set_parameter(time_to_restart, restart_sec); } } + double d = Math::abs(os_nti.delta); + if (!do_start) { + cur_fade_in_remaining = MAX(0, cur_fade_in_remaining - d); // Don't consider seeked delta by restart. + } + cur_fade_out_remaining = MAX(0, cur_fade_out_remaining - d); } - set_parameter(time, cur_time); - set_parameter(remaining, cur_remaining); + set_parameter(fade_in_remaining, cur_fade_in_remaining); set_parameter(fade_out_remaining, cur_fade_out_remaining); - return MAX(main_rem, cur_remaining); + return cur_internal_active ? os_nti : main_nti; } void AnimationNodeOneShot::_bind_methods() { @@ -508,6 +679,9 @@ void AnimationNodeOneShot::_bind_methods() { ClassDB::bind_method(D_METHOD("set_fadeout_curve", "curve"), &AnimationNodeOneShot::set_fade_out_curve); ClassDB::bind_method(D_METHOD("get_fadeout_curve"), &AnimationNodeOneShot::get_fade_out_curve); + ClassDB::bind_method(D_METHOD("set_break_loop_at_end", "enable"), &AnimationNodeOneShot::set_break_loop_at_end); + ClassDB::bind_method(D_METHOD("is_loop_broken_at_end"), &AnimationNodeOneShot::is_loop_broken_at_end); + ClassDB::bind_method(D_METHOD("set_autorestart", "active"), &AnimationNodeOneShot::set_auto_restart_enabled); ClassDB::bind_method(D_METHOD("has_autorestart"), &AnimationNodeOneShot::is_auto_restart_enabled); @@ -526,6 +700,7 @@ void AnimationNodeOneShot::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "fadein_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_fadein_curve", "get_fadein_curve"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fadeout_time", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater,suffix:s"), "set_fadeout_time", "get_fadeout_time"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "fadeout_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_fadeout_curve", "get_fadeout_curve"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "break_loop_at_end"), "set_break_loop_at_end", "is_loop_broken_at_end"); ADD_GROUP("Auto Restart", "autorestart_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autorestart"), "set_autorestart", "has_autorestart"); @@ -550,10 +725,16 @@ AnimationNodeOneShot::AnimationNodeOneShot() { //////////////////////////////////////////////// void AnimationNodeAdd2::get_parameter_list(List *r_list) const { + AnimationNode::get_parameter_list(r_list); r_list->push_back(PropertyInfo(Variant::FLOAT, add_amount, PROPERTY_HINT_RANGE, "0,1,0.01,or_less,or_greater")); } Variant AnimationNodeAdd2::get_parameter_default_value(const StringName &p_parameter) const { + Variant ret = AnimationNode::get_parameter_default_value(p_parameter); + if (ret != Variant()) { + return ret; + } + return 0; } @@ -565,16 +746,16 @@ bool AnimationNodeAdd2::has_filter() const { return true; } -double AnimationNodeAdd2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNodeAdd2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { double amount = get_parameter(add_amount); AnimationMixer::PlaybackInfo pi = p_playback_info; pi.weight = 1.0; - double rem0 = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only); + NodeTimeInfo nti = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only); pi.weight = amount; blend_input(1, pi, FILTER_PASS, sync, p_test_only); - return rem0; + return nti; } void AnimationNodeAdd2::_bind_methods() { @@ -588,10 +769,16 @@ AnimationNodeAdd2::AnimationNodeAdd2() { //////////////////////////////////////////////// void AnimationNodeAdd3::get_parameter_list(List *r_list) const { + AnimationNode::get_parameter_list(r_list); r_list->push_back(PropertyInfo(Variant::FLOAT, add_amount, PROPERTY_HINT_RANGE, "-1,1,0.01,or_less,or_greater")); } Variant AnimationNodeAdd3::get_parameter_default_value(const StringName &p_parameter) const { + Variant ret = AnimationNode::get_parameter_default_value(p_parameter); + if (ret != Variant()) { + return ret; + } + return 0; } @@ -603,18 +790,18 @@ bool AnimationNodeAdd3::has_filter() const { return true; } -double AnimationNodeAdd3::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNodeAdd3::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { double amount = get_parameter(add_amount); AnimationMixer::PlaybackInfo pi = p_playback_info; pi.weight = MAX(0, -amount); blend_input(0, pi, FILTER_PASS, sync, p_test_only); pi.weight = 1.0; - double rem0 = blend_input(1, pi, FILTER_IGNORE, sync, p_test_only); + NodeTimeInfo nti = blend_input(1, pi, FILTER_IGNORE, sync, p_test_only); pi.weight = MAX(0, amount); blend_input(2, pi, FILTER_PASS, sync, p_test_only); - return rem0; + return nti; } void AnimationNodeAdd3::_bind_methods() { @@ -629,10 +816,16 @@ AnimationNodeAdd3::AnimationNodeAdd3() { ///////////////////////////////////////////// void AnimationNodeBlend2::get_parameter_list(List *r_list) const { + AnimationNode::get_parameter_list(r_list); r_list->push_back(PropertyInfo(Variant::FLOAT, blend_amount, PROPERTY_HINT_RANGE, "0,1,0.01,or_less,or_greater")); } Variant AnimationNodeBlend2::get_parameter_default_value(const StringName &p_parameter) const { + Variant ret = AnimationNode::get_parameter_default_value(p_parameter); + if (ret != Variant()) { + return ret; + } + return 0; // For blend amount. } @@ -640,16 +833,16 @@ String AnimationNodeBlend2::get_caption() const { return "Blend2"; } -double AnimationNodeBlend2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNodeBlend2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { double amount = get_parameter(blend_amount); AnimationMixer::PlaybackInfo pi = p_playback_info; pi.weight = 1.0 - amount; - double rem0 = blend_input(0, pi, FILTER_BLEND, sync, p_test_only); + NodeTimeInfo nti0 = blend_input(0, pi, FILTER_BLEND, sync, p_test_only); pi.weight = amount; - double rem1 = blend_input(1, pi, FILTER_PASS, sync, p_test_only); + NodeTimeInfo nti1 = blend_input(1, pi, FILTER_PASS, sync, p_test_only); - return amount > 0.5 ? rem1 : rem0; // Hacky but good enough. + return amount > 0.5 ? nti1 : nti0; // Hacky but good enough. } bool AnimationNodeBlend2::has_filter() const { @@ -667,10 +860,16 @@ AnimationNodeBlend2::AnimationNodeBlend2() { ////////////////////////////////////// void AnimationNodeBlend3::get_parameter_list(List *r_list) const { + AnimationNode::get_parameter_list(r_list); r_list->push_back(PropertyInfo(Variant::FLOAT, blend_amount, PROPERTY_HINT_RANGE, "-1,1,0.01,or_less,or_greater")); } Variant AnimationNodeBlend3::get_parameter_default_value(const StringName &p_parameter) const { + Variant ret = AnimationNode::get_parameter_default_value(p_parameter); + if (ret != Variant()) { + return ret; + } + return 0; // For blend amount. } @@ -678,18 +877,18 @@ String AnimationNodeBlend3::get_caption() const { return "Blend3"; } -double AnimationNodeBlend3::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNodeBlend3::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { double amount = get_parameter(blend_amount); AnimationMixer::PlaybackInfo pi = p_playback_info; pi.weight = MAX(0, -amount); - double rem0 = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only); + NodeTimeInfo nti0 = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only); pi.weight = 1.0 - ABS(amount); - double rem1 = blend_input(1, pi, FILTER_IGNORE, sync, p_test_only); + NodeTimeInfo nti1 = blend_input(1, pi, FILTER_IGNORE, sync, p_test_only); pi.weight = MAX(0, amount); - double rem2 = blend_input(2, pi, FILTER_IGNORE, sync, p_test_only); + NodeTimeInfo nti2 = blend_input(2, pi, FILTER_IGNORE, sync, p_test_only); - return amount > 0.5 ? rem2 : (amount < -0.5 ? rem0 : rem1); // Hacky but good enough. + return amount > 0.5 ? nti2 : (amount < -0.5 ? nti0 : nti1); // Hacky but good enough. } void AnimationNodeBlend3::_bind_methods() { @@ -704,10 +903,16 @@ AnimationNodeBlend3::AnimationNodeBlend3() { //////////////////////////////////////////////// void AnimationNodeSub2::get_parameter_list(List *r_list) const { + AnimationNode::get_parameter_list(r_list); r_list->push_back(PropertyInfo(Variant::FLOAT, sub_amount, PROPERTY_HINT_RANGE, "0,1,0.01,or_less,or_greater")); } Variant AnimationNodeSub2::get_parameter_default_value(const StringName &p_parameter) const { + Variant ret = AnimationNode::get_parameter_default_value(p_parameter); + if (ret != Variant()) { + return ret; + } + return 0; } @@ -719,7 +924,7 @@ bool AnimationNodeSub2::has_filter() const { return true; } -double AnimationNodeSub2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNodeSub2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { double amount = get_parameter(sub_amount); AnimationMixer::PlaybackInfo pi = p_playback_info; @@ -742,10 +947,16 @@ AnimationNodeSub2::AnimationNodeSub2() { ///////////////////////////////// void AnimationNodeTimeScale::get_parameter_list(List *r_list) const { + AnimationNode::get_parameter_list(r_list); r_list->push_back(PropertyInfo(Variant::FLOAT, scale, PROPERTY_HINT_RANGE, "-32,32,0.01,or_less,or_greater")); } Variant AnimationNodeTimeScale::get_parameter_default_value(const StringName &p_parameter) const { + Variant ret = AnimationNode::get_parameter_default_value(p_parameter); + if (ret != Variant()) { + return ret; + } + return 1.0; // Initial timescale. } @@ -753,13 +964,13 @@ String AnimationNodeTimeScale::get_caption() const { return "TimeScale"; } -double AnimationNodeTimeScale::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNodeTimeScale::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { double cur_scale = get_parameter(scale); AnimationMixer::PlaybackInfo pi = p_playback_info; pi.weight = 1.0; if (!pi.seeked) { - pi.time *= cur_scale; + pi.delta *= cur_scale; } return blend_input(0, pi, FILTER_IGNORE, true, p_test_only); @@ -775,10 +986,16 @@ AnimationNodeTimeScale::AnimationNodeTimeScale() { //////////////////////////////////// void AnimationNodeTimeSeek::get_parameter_list(List *r_list) const { + AnimationNode::get_parameter_list(r_list); 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 { + Variant ret = AnimationNode::get_parameter_default_value(p_parameter); + if (ret != Variant()) { + return ret; + } + return -1.0; // Initial seek request. } @@ -786,7 +1003,7 @@ String AnimationNodeTimeSeek::get_caption() const { return "TimeSeek"; } -double AnimationNodeTimeSeek::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNodeTimeSeek::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { double cur_seek_pos = get_parameter(seek_pos_request); AnimationMixer::PlaybackInfo pi = p_playback_info; @@ -833,6 +1050,8 @@ bool AnimationNodeTransition::_set(const StringName &p_path, const Variant &p_va set_input_name(which, p_value); } else if (what == "auto_advance") { set_input_as_auto_advance(which, p_value); + } else if (what == "break_loop_at_end") { + set_input_break_loop_at_end(which, p_value); } else if (what == "reset") { set_input_reset(which, p_value); } else { @@ -858,6 +1077,8 @@ bool AnimationNodeTransition::_get(const StringName &p_path, Variant &r_ret) con r_ret = get_input_name(which); } else if (what == "auto_advance") { r_ret = is_input_set_as_auto_advance(which); + } else if (what == "break_loop_at_end") { + r_ret = is_input_loop_broken_at_end(which); } else if (what == "reset") { r_ret = is_input_reset(which); } else { @@ -868,6 +1089,7 @@ bool AnimationNodeTransition::_get(const StringName &p_path, Variant &r_ret) con } void AnimationNodeTransition::get_parameter_list(List *r_list) const { + AnimationNode::get_parameter_list(r_list); String anims; for (int i = 0; i < get_input_count(); i++) { if (i > 0) { @@ -880,12 +1102,16 @@ void AnimationNodeTransition::get_parameter_list(List *r_list) con 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)); } Variant AnimationNodeTransition::get_parameter_default_value(const StringName &p_parameter) const { - if (p_parameter == time || p_parameter == prev_xfading) { + Variant ret = AnimationNode::get_parameter_default_value(p_parameter); + if (ret != Variant()) { + return ret; + } + + if (p_parameter == prev_xfading) { return 0.0; } else if (p_parameter == prev_index || p_parameter == current_index) { return -1; @@ -895,6 +1121,10 @@ Variant AnimationNodeTransition::get_parameter_default_value(const StringName &p } bool AnimationNodeTransition::is_parameter_read_only(const StringName &p_parameter) const { + if (AnimationNode::is_parameter_read_only(p_parameter)) { + return true; + } + if (p_parameter == current_state || p_parameter == current_index) { return true; } @@ -947,6 +1177,16 @@ bool AnimationNodeTransition::is_input_set_as_auto_advance(int p_input) const { return input_data[p_input].auto_advance; } +void AnimationNodeTransition::set_input_break_loop_at_end(int p_input, bool p_enable) { + ERR_FAIL_INDEX(p_input, get_input_count()); + input_data.write[p_input].break_loop_at_end = p_enable; +} + +bool AnimationNodeTransition::is_input_loop_broken_at_end(int p_input) const { + ERR_FAIL_INDEX_V(p_input, get_input_count(), false); + return input_data[p_input].break_loop_at_end; +} + 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; @@ -981,12 +1221,12 @@ bool AnimationNodeTransition::is_allow_transition_to_self() const { return allow_transition_to_self; } -double AnimationNodeTransition::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNodeTransition::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { 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); + NodeTimeInfo cur_nti = get_node_time_info(); double cur_prev_xfading = get_parameter(prev_xfading); bool switched = false; @@ -1052,7 +1292,6 @@ double AnimationNodeTransition::_process(const AnimationMixer::PlaybackInfo p_pl // Special case for restart. if (restart) { - set_parameter(time, 0); pi.time = 0; pi.seeked = true; pi.weight = 1.0; @@ -1061,16 +1300,12 @@ double AnimationNodeTransition::_process(const AnimationMixer::PlaybackInfo p_pl if (switched) { cur_prev_xfading = xfade_time; - cur_time = 0; } if (cur_current_index < 0 || cur_current_index >= get_input_count() || cur_prev_index >= get_input_count()) { - return 0; + return NodeTimeInfo(); } - double rem = 0.0; - double abs_time = Math::abs(p_time); - if (sync) { pi.weight = 0; for (int i = 0; i < get_input_count(); i++) { @@ -1081,20 +1316,11 @@ double AnimationNodeTransition::_process(const AnimationMixer::PlaybackInfo p_pl } if (cur_prev_index < 0) { // Process current animation, check for transition. - pi.weight = 1.0; - rem = blend_input(cur_current_index, pi, FILTER_IGNORE, true, p_test_only); - - if (p_seek) { - cur_time = abs_time; - } else { - cur_time += abs_time; - } - - if (input_data[cur_current_index].auto_advance && rem <= xfade_time) { + cur_nti = blend_input(cur_current_index, pi, FILTER_IGNORE, true, p_test_only); + if (input_data[cur_current_index].auto_advance && cur_nti.get_remain(input_data[cur_current_index].break_loop_at_end) <= xfade_time) { set_parameter(transition_request, get_input_name((cur_current_index + 1) % get_input_count())); } - } else { // Cross-fading from prev to current. real_t blend = 0.0; @@ -1117,33 +1343,30 @@ double AnimationNodeTransition::_process(const AnimationMixer::PlaybackInfo p_pl pi.time = 0; pi.seeked = true; } - rem = blend_input(cur_current_index, pi, FILTER_IGNORE, true, p_test_only); + cur_nti = blend_input(cur_current_index, pi, FILTER_IGNORE, true, p_test_only); pi = p_playback_info; pi.seeked &= use_blend; pi.weight = blend; blend_input(cur_prev_index, pi, FILTER_IGNORE, true, p_test_only); - if (p_seek) { - cur_time = abs_time; - } else { - cur_time += abs_time; - cur_prev_xfading -= abs_time; - if (cur_prev_xfading < 0) { + if (!p_seek) { + if (cur_prev_xfading <= 0) { set_parameter(prev_index, -1); } + cur_prev_xfading -= Math::abs(p_playback_info.delta); } } - set_parameter(time, cur_time); set_parameter(prev_xfading, cur_prev_xfading); - return rem; + return cur_nti; } void AnimationNodeTransition::_get_property_list(List *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) + "/break_loop_at_end", 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)); } } @@ -1154,6 +1377,9 @@ void AnimationNodeTransition::_bind_methods() { 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_break_loop_at_end", "input", "enable"), &AnimationNodeTransition::set_input_break_loop_at_end); + ClassDB::bind_method(D_METHOD("is_input_loop_broken_at_end", "input"), &AnimationNodeTransition::is_input_loop_broken_at_end); + 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); @@ -1181,7 +1407,7 @@ String AnimationNodeOutput::get_caption() const { return "Output"; } -double AnimationNodeOutput::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNodeOutput::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { AnimationMixer::PlaybackInfo pi = p_playback_info; pi.weight = 1.0; return blend_input(0, pi, FILTER_IGNORE, true, p_test_only); @@ -1400,10 +1626,10 @@ String AnimationNodeBlendTree::get_caption() const { return "BlendTree"; } -double AnimationNodeBlendTree::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNodeBlendTree::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { Ref output = nodes[SceneStringNames::get_singleton()->output].node; node_state.connections = nodes[SceneStringNames::get_singleton()->output].connections; - ERR_FAIL_COND_V(output.is_null(), 0); + ERR_FAIL_COND_V(output.is_null(), NodeTimeInfo()); AnimationMixer::PlaybackInfo pi = p_playback_info; pi.weight = 1.0; diff --git a/scene/animation/animation_blend_tree.h b/scene/animation/animation_blend_tree.h index cf0884b892f2..c7ef7ed62489 100644 --- a/scene/animation/animation_blend_tree.h +++ b/scene/animation/animation_blend_tree.h @@ -37,7 +37,12 @@ class AnimationNodeAnimation : public AnimationRootNode { GDCLASS(AnimationNodeAnimation, AnimationRootNode); StringName animation; - StringName time = "time"; + + bool use_custom_timeline = false; + double timeline_length = 1.0; + Animation::LoopMode loop_mode = Animation::LOOP_NONE; + bool stretch_time_scale = true; + double start_offset = 0.0; uint64_t last_version = 0; bool skip = false; @@ -50,10 +55,13 @@ class AnimationNodeAnimation : public AnimationRootNode { void get_parameter_list(List *r_list) const override; + virtual NodeTimeInfo get_node_time_info() const override; // Wrapper of get_parameter(). + static Vector (*get_editable_animation_list)(); virtual String get_caption() const override; - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; void set_animation(const StringName &p_name); StringName get_animation() const; @@ -64,6 +72,21 @@ class AnimationNodeAnimation : public AnimationRootNode { void set_backward(bool p_backward); bool is_backward() const; + void set_use_custom_timeline(bool p_use_custom_timeline); + bool is_using_custom_timeline() const; + + void set_timeline_length(double p_length); + double get_timeline_length() const; + + void set_stretch_time_scale(bool p_strech_time_scale); + bool is_stretching_time_scale() const; + + void set_start_offset(double p_offset); + double get_start_offset() const; + + void set_loop_mode(Animation::LoopMode p_loop_mode); + Animation::LoopMode get_loop_mode() const; + AnimationNodeAnimation(); protected: @@ -118,12 +141,12 @@ class AnimationNodeOneShot : public AnimationNodeSync { double auto_restart_delay = 1.0; double auto_restart_random_delay = 0.0; MixMode mix = MIX_MODE_BLEND; + bool break_loop_at_end = false; StringName request = PNAME("request"); StringName active = PNAME("active"); StringName internal_active = PNAME("internal_active"); - StringName time = "time"; - StringName remaining = "remaining"; + StringName fade_in_remaining = "fade_in_remaining"; StringName fade_out_remaining = "fade_out_remaining"; StringName time_to_restart = "time_to_restart"; @@ -160,8 +183,11 @@ class AnimationNodeOneShot : public AnimationNodeSync { void set_mix_mode(MixMode p_mix); MixMode get_mix_mode() const; + void set_break_loop_at_end(bool p_enable); + bool is_loop_broken_at_end() const; + virtual bool has_filter() const override; - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; AnimationNodeOneShot(); }; @@ -184,7 +210,7 @@ class AnimationNodeAdd2 : public AnimationNodeSync { virtual String get_caption() const override; virtual bool has_filter() const override; - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; AnimationNodeAdd2(); }; @@ -204,7 +230,7 @@ class AnimationNodeAdd3 : public AnimationNodeSync { virtual String get_caption() const override; virtual bool has_filter() const override; - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; AnimationNodeAdd3(); }; @@ -222,7 +248,7 @@ class AnimationNodeBlend2 : public AnimationNodeSync { virtual Variant get_parameter_default_value(const StringName &p_parameter) const override; virtual String get_caption() const override; - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; virtual bool has_filter() const override; AnimationNodeBlend2(); @@ -242,7 +268,7 @@ class AnimationNodeBlend3 : public AnimationNodeSync { virtual String get_caption() const override; - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; AnimationNodeBlend3(); }; @@ -261,7 +287,7 @@ class AnimationNodeSub2 : public AnimationNodeSync { virtual String get_caption() const override; virtual bool has_filter() const override; - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; AnimationNodeSub2(); }; @@ -280,7 +306,7 @@ class AnimationNodeTimeScale : public AnimationNode { virtual String get_caption() const override; - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; AnimationNodeTimeScale(); }; @@ -299,7 +325,7 @@ class AnimationNodeTimeSeek : public AnimationNode { virtual String get_caption() const override; - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; AnimationNodeTimeSeek(); }; @@ -309,11 +335,11 @@ class AnimationNodeTransition : public AnimationNodeSync { struct InputData { bool auto_advance = false; + bool break_loop_at_end = false; bool reset = true; }; Vector input_data; - StringName time = "time"; StringName prev_xfading = "prev_xfading"; StringName prev_index = "prev_index"; StringName current_index = PNAME("current_index"); @@ -351,6 +377,9 @@ class AnimationNodeTransition : public AnimationNodeSync { 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_break_loop_at_end(int p_input, bool p_enable); + bool is_input_loop_broken_at_end(int p_input) const; + void set_input_reset(int p_input, bool p_enable); bool is_input_reset(int p_input) const; @@ -363,7 +392,7 @@ class AnimationNodeTransition : public AnimationNodeSync { void set_allow_transition_to_self(bool p_enable); bool is_allow_transition_to_self() const; - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; AnimationNodeTransition(); }; @@ -373,7 +402,7 @@ class AnimationNodeOutput : public AnimationNode { public: virtual String get_caption() const override; - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; AnimationNodeOutput(); }; @@ -445,7 +474,7 @@ class AnimationNodeBlendTree : public AnimationRootNode { void get_node_connections(List *r_connections) const; virtual String get_caption() const override; - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; void get_node_list(List *r_list); diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index ec4464148439..048469455517 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -101,12 +101,22 @@ float AnimationNodeStateMachineTransition::get_xfade_time() const { void AnimationNodeStateMachineTransition::set_xfade_curve(const Ref &p_curve) { xfade_curve = p_curve; + emit_changed(); } Ref AnimationNodeStateMachineTransition::get_xfade_curve() const { return xfade_curve; } +void AnimationNodeStateMachineTransition::set_break_loop_at_end(bool p_enable) { + break_loop_at_end = p_enable; + emit_changed(); +} + +bool AnimationNodeStateMachineTransition::is_loop_broken_at_end() const { + return break_loop_at_end; +} + void AnimationNodeStateMachineTransition::set_reset(bool p_reset) { reset = p_reset; emit_changed(); @@ -141,6 +151,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_break_loop_at_end", "enable"), &AnimationNodeStateMachineTransition::set_break_loop_at_end); + ClassDB::bind_method(D_METHOD("is_loop_broken_at_end"), &AnimationNodeStateMachineTransition::is_loop_broken_at_end); + ClassDB::bind_method(D_METHOD("set_reset", "reset"), &AnimationNodeStateMachineTransition::set_reset); ClassDB::bind_method(D_METHOD("is_reset"), &AnimationNodeStateMachineTransition::is_reset); @@ -153,6 +166,7 @@ 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, "break_loop_at_end"), "set_break_loop_at_end", "is_loop_broken_at_end"); 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"); @@ -310,19 +324,19 @@ TypedArray AnimationNodeStateMachinePlayback::_get_travel_path() con } float AnimationNodeStateMachinePlayback::get_current_play_pos() const { - return pos_current; + return current_nti.position; } float AnimationNodeStateMachinePlayback::get_current_length() const { - return len_current; + return current_nti.length; } float AnimationNodeStateMachinePlayback::get_fade_from_play_pos() const { - return pos_fade_from; + return fadeing_from_nti.position; } float AnimationNodeStateMachinePlayback::get_fade_from_length() const { - return len_fade_from; + return fadeing_from_nti.length; } float AnimationNodeStateMachinePlayback::get_fading_time() const { @@ -665,21 +679,22 @@ bool AnimationNodeStateMachinePlayback::_make_travel_path(AnimationTree *p_tree, } } -double AnimationNodeStateMachinePlayback::process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { - double rem = _process(p_base_path, p_state_machine, p_playback_info, p_test_only); +AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { + AnimationNode::NodeTimeInfo nti = _process(p_base_path, p_state_machine, p_playback_info, p_test_only); start_request = StringName(); next_request = false; stop_request = false; reset_request_on_teleport = false; - return rem; + return nti; } -double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { _set_base_path(p_base_path); AnimationTree *tree = p_state_machine->process_state->tree; double p_time = p_playback_info.time; + double p_delta = p_playback_info.delta; bool p_seek = p_playback_info.seeked; bool p_is_external_seeking = p_playback_info.is_external_seeking; @@ -690,8 +705,8 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An if (p_state_machine->get_state_machine_type() != AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) { path.clear(); _clear_path_children(tree, p_state_machine, p_test_only); - _start(p_state_machine); } + _start(p_state_machine); reset_request = true; } else { // Reset current state. @@ -705,11 +720,11 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An travel_request = StringName(); path.clear(); playing = false; - return 0; + return AnimationNode::NodeTimeInfo(); } if (!playing && start_request != StringName() && travel_request != StringName()) { - return 0; + return AnimationNode::NodeTimeInfo(); } // Process start/travel request. @@ -732,7 +747,7 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An _start(p_state_machine); } else { StringName node = start_request; - ERR_FAIL_V_MSG(0, "No such node: '" + node + "'"); + ERR_FAIL_V_MSG(AnimationNode::NodeTimeInfo(), "No such node: '" + node + "'"); } } @@ -766,7 +781,7 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An teleport_request = true; } } else { - ERR_FAIL_V_MSG(0, "No such node: '" + temp_travel_request + "'"); + ERR_FAIL_V_MSG(AnimationNode::NodeTimeInfo(), "No such node: '" + temp_travel_request + "'"); } } } @@ -777,16 +792,14 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An teleport_request = false; // Clear fadeing on teleport. fading_from = StringName(); + fadeing_from_nti = AnimationNode::NodeTimeInfo(); fading_pos = 0; // Init current length. - pos_current = 0; // Overwritten suddenly in main process. - pi.time = 0; pi.seeked = true; pi.is_external_seeking = false; pi.weight = 0; - - len_current = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true); + current_nti = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true); // Don't process first node if not necessary, insteads process next node. _transition_to_next_recursive(tree, p_state_machine, p_test_only); } @@ -795,7 +808,7 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An if (!p_state_machine->states.has(current)) { playing = false; // Current does not exist. _set_current(p_state_machine, StringName()); - return 0; + return AnimationNode::NodeTimeInfo(); } // Special case for grouped state machine Start/End to make priority with parent blend (means don't treat Start and End states as RESET animations). @@ -813,7 +826,7 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An fading_from = StringName(); } else { if (!p_seek) { - fading_pos += p_time; + fading_pos += Math::abs(p_delta); } fade_blend = MIN(1.0, fading_pos / fading_time); } @@ -829,18 +842,14 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An } // Main process. - double rem = 0.0; pi = p_playback_info; pi.weight = fade_blend; if (reset_request) { reset_request = false; pi.time = 0; pi.seeked = true; - len_current = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); - rem = len_current; - } else { - rem = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. } + current_nti = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. // Cross-fade process. if (fading_from != StringName()) { @@ -852,7 +861,6 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An fade_blend_inv = 1.0; } - float fading_from_rem = 0.0; pi = p_playback_info; pi.weight = fade_blend_inv; if (_reset_request_for_fading_from) { @@ -860,57 +868,41 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An pi.time = 0; pi.seeked = true; } - fading_from_rem = p_state_machine->blend_node(p_state_machine->states[fading_from].node, fading_from, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); // 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; - } - pos_fade_from = len_fade_from - fading_from_rem; + fadeing_from_nti = p_state_machine->blend_node(p_state_machine->states[fading_from].node, fading_from, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. if (fading_pos >= fading_time) { - fading_from = StringName(); // Finish fading. + // Finish fading. + fading_from = StringName(); + fadeing_from_nti = AnimationNode::NodeTimeInfo(); } } - // Guess playback position. - if (rem > len_current) { // Weird but ok. - len_current = rem; - } - pos_current = len_current - rem; - // Find next and see when to transition. _transition_to_next_recursive(tree, p_state_machine, p_test_only); // Predict remaining time. - double remain = rem; // If we can't predict the end of state machine, the time remaining must be INFINITY. - if (p_state_machine->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_NESTED) { // There is no next transition. if (!p_state_machine->has_transition_from(current)) { if (fading_from != StringName()) { - remain = MAX(rem, fading_time - fading_pos); - } else { - remain = rem; + return current_nti.get_remain() > fadeing_from_nti.get_remain() ? current_nti : fadeing_from_nti; } - return remain; + return current_nti; } } if (current == p_state_machine->end_node) { - if (fading_from != StringName()) { - remain = MAX(0, fading_time - fading_pos); - } else { - remain = 0; + if (fading_from != StringName() && fadeing_from_nti.get_remain() > 0) { + return fadeing_from_nti; } - return remain; + return AnimationNode::NodeTimeInfo(); } if (!is_end()) { - return HUGE_LENGTH; + current_nti.is_infinity = true; } - return remain; + return current_nti; } bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_test_only) { @@ -952,6 +944,7 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); } fading_from = StringName(); + fadeing_from_nti = AnimationNode::NodeTimeInfo(); fading_time = 0; fading_pos = 0; } @@ -968,11 +961,10 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT _reset_request_for_fading_from = reset_request; // To avoid processing doubly, it must be reset in the fading process within _process(). reset_request = next.is_reset; - pos_fade_from = pos_current; - len_fade_from = len_current; + fadeing_from_nti = current_nti; if (next.switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_SYNC) { - pi.time = MIN(pos_current, len_current); + pi.time = current_nti.position; pi.seeked = true; pi.is_external_seeking = false; pi.weight = 0; @@ -980,24 +972,11 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT } // Just get length to find next recursive. - double rem = 0.0; pi.time = 0; pi.is_external_seeking = false; pi.weight = 0; - if (next.is_reset) { - pi.seeked = true; - len_current = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true); // Just retrieve remain length, don't process. - rem = len_current; - } else { - pi.seeked = false; - rem = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true); // Just retrieve remain length, don't process. - } - - // Guess playback position. - if (rem > len_current) { // Weird but ok. - len_current = rem; - } - pos_current = len_current - rem; + pi.seeked = next.is_reset; + current_nti = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true); // Just retrieve remain length, don't process. // Fading must be processed. if (fading_time) { @@ -1028,6 +1007,7 @@ bool AnimationNodeStateMachinePlayback::_can_transition_to_next(AnimationTree *p playback->_next_main(); // Then, fadeing should be end. fading_from = StringName(); + fadeing_from_nti = AnimationNode::NodeTimeInfo(); fading_pos = 0; } else { return true; @@ -1039,7 +1019,7 @@ bool AnimationNodeStateMachinePlayback::_can_transition_to_next(AnimationTree *p } if (current != p_state_machine->start_node && p_next.switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_AT_END) { - return pos_current >= len_current - p_next.xfade; + return current_nti.get_remain(p_next.break_loop_at_end) <= p_next.xfade; } return true; } @@ -1084,6 +1064,7 @@ AnimationNodeStateMachinePlayback::NextInfo AnimationNodeStateMachinePlayback::_ next.curve = ref_transition->get_xfade_curve(); next.switch_mode = ref_transition->get_switch_mode(); next.is_reset = ref_transition->is_reset(); + next.break_loop_at_end = ref_transition->is_loop_broken_at_end(); } } } else { @@ -1113,6 +1094,7 @@ AnimationNodeStateMachinePlayback::NextInfo AnimationNodeStateMachinePlayback::_ next.curve = ref_transition->get_xfade_curve(); next.switch_mode = ref_transition->get_switch_mode(); next.is_reset = ref_transition->is_reset(); + next.break_loop_at_end = ref_transition->is_loop_broken_at_end(); } } @@ -1233,6 +1215,7 @@ AnimationNodeStateMachinePlayback::AnimationNodeStateMachinePlayback() { /////////////////////////////////////////////////////// void AnimationNodeStateMachine::get_parameter_list(List *r_list) const { + AnimationNode::get_parameter_list(r_list); r_list->push_back(PropertyInfo(Variant::OBJECT, playback, PROPERTY_HINT_RESOURCE_TYPE, "AnimationNodeStateMachinePlayback", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ALWAYS_DUPLICATE)); // Don't store this object in .tres, it always needs to be made as unique object. List advance_conditions; for (int i = 0; i < transitions.size(); i++) { @@ -1249,6 +1232,11 @@ void AnimationNodeStateMachine::get_parameter_list(List *r_list) c } Variant AnimationNodeStateMachine::get_parameter_default_value(const StringName &p_parameter) const { + Variant ret = AnimationNode::get_parameter_default_value(p_parameter); + if (ret != Variant()) { + return ret; + } + if (p_parameter == playback) { Ref p; p.instantiate(); @@ -1259,6 +1247,10 @@ Variant AnimationNodeStateMachine::get_parameter_default_value(const StringName } bool AnimationNodeStateMachine::is_parameter_read_only(const StringName &p_parameter) const { + if (AnimationNode::is_parameter_read_only(p_parameter)) { + return true; + } + if (p_parameter == playback) { return true; } @@ -1622,9 +1614,9 @@ Vector2 AnimationNodeStateMachine::get_graph_offset() const { return graph_offset; } -double AnimationNodeStateMachine::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNodeStateMachine::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { Ref playback_new = get_parameter(playback); - ERR_FAIL_COND_V(playback_new.is_null(), 0.0); + ERR_FAIL_COND_V(playback_new.is_null(), AnimationNode::NodeTimeInfo()); playback_new->_set_grouped(state_machine_type == STATE_MACHINE_TYPE_GROUPED); if (p_test_only) { playback_new = playback_new->duplicate(); // Don't process original when testing. diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h index fa0eca587788..8078ffb26d4c 100644 --- a/scene/animation/animation_node_state_machine.h +++ b/scene/animation/animation_node_state_machine.h @@ -57,6 +57,7 @@ class AnimationNodeStateMachineTransition : public Resource { StringName advance_condition_name; float xfade_time = 0.0; Ref xfade_curve; + bool break_loop_at_end = false; bool reset = true; int priority = 1; String advance_expression; @@ -85,6 +86,9 @@ class AnimationNodeStateMachineTransition : public Resource { void set_xfade_time(float p_xfade); float get_xfade_time() const; + void set_break_loop_at_end(bool p_enable); + bool is_loop_broken_at_end() const; + void set_reset(bool p_reset); bool is_reset() const; @@ -210,7 +214,7 @@ class AnimationNodeStateMachine : public AnimationRootNode { void set_graph_offset(const Vector2 &p_offset); Vector2 get_graph_offset() const; - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; virtual String get_caption() const override; virtual Ref get_child_by_name(const StringName &p_name) const override; @@ -246,6 +250,7 @@ class AnimationNodeStateMachinePlayback : public Resource { Ref curve; AnimationNodeStateMachineTransition::SwitchMode switch_mode; bool is_reset; + bool break_loop_at_end; }; struct ChildStateMachineInfo { @@ -257,18 +262,14 @@ class AnimationNodeStateMachinePlayback : public Resource { Ref default_transition; String base_path; - double len_fade_from = 0.0; - double pos_fade_from = 0.0; - - double len_current = 0.0; - double pos_current = 0.0; - + AnimationNode::NodeTimeInfo current_nti; StringName current; Ref current_curve; Ref group_start_transition; Ref group_end_transition; + AnimationNode::NodeTimeInfo fadeing_from_nti; StringName fading_from; float fading_time = 0.0; float fading_pos = 0.0; @@ -301,8 +302,8 @@ class AnimationNodeStateMachinePlayback : public Resource { bool _travel_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const String &p_path, bool p_is_allow_transition_to_self, bool p_is_parent_same_state, bool p_test_only); void _start_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const String &p_path, bool p_test_only); - double process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only); - double _process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only); + AnimationNode::NodeTimeInfo process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only); + AnimationNode::NodeTimeInfo _process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only); bool _check_advance_condition(const Ref p_state_machine, const Ref p_transition) const; bool _transition_to_next_recursive(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_test_only); diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index 2c2d8387f313..5f4bdcc4f40a 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -46,6 +46,10 @@ void AnimationNode::get_parameter_list(List *r_list) const { r_list->push_back(PropertyInfo::from_dict(d)); } } + + r_list->push_back(PropertyInfo(Variant::FLOAT, current_length, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_READ_ONLY)); + r_list->push_back(PropertyInfo(Variant::FLOAT, current_position, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_READ_ONLY)); + r_list->push_back(PropertyInfo(Variant::FLOAT, current_delta, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_READ_ONLY)); } Variant AnimationNode::get_parameter_default_value(const StringName &p_parameter) const { @@ -56,8 +60,15 @@ Variant AnimationNode::get_parameter_default_value(const StringName &p_parameter 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; + if (GDVIRTUAL_CALL(_is_parameter_read_only, p_parameter, ret) && ret) { + return true; + } + + if (p_parameter == current_length || p_parameter == current_position || p_parameter == current_delta) { + return true; + } + + return false; } void AnimationNode::set_parameter(const StringName &p_name, const Variant &p_value) { @@ -81,6 +92,20 @@ Variant AnimationNode::get_parameter(const StringName &p_name) const { return process_state->tree->property_map[path].first; } +void AnimationNode::set_node_time_info(const NodeTimeInfo &p_node_time_info) { + set_parameter(current_length, p_node_time_info.length); + set_parameter(current_position, p_node_time_info.position); + set_parameter(current_delta, p_node_time_info.delta); +} + +AnimationNode::NodeTimeInfo AnimationNode::get_node_time_info() const { + NodeTimeInfo nti; + nti.length = get_parameter(current_length); + nti.position = get_parameter(current_position); + nti.delta = get_parameter(current_delta); + return nti; +} + void AnimationNode::get_child_nodes(List *r_child_nodes) { Dictionary cn; if (GDVIRTUAL_CALL(_get_child_nodes, cn)) { @@ -101,11 +126,11 @@ void AnimationNode::blend_animation(const StringName &p_animation, AnimationMixe process_state->tree->make_animation_instance(p_animation, p_playback_info); } -double AnimationNode::_pre_process(ProcessState *p_process_state, AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNode::_pre_process(ProcessState *p_process_state, AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { process_state = p_process_state; - double t = process(p_playback_info, p_test_only); + NodeTimeInfo nti = process(p_playback_info, p_test_only); process_state = nullptr; - return t; + return nti; } void AnimationNode::make_invalid(const String &p_reason) { @@ -122,11 +147,11 @@ AnimationTree *AnimationNode::get_animation_tree() const { return process_state->tree; } -double AnimationNode::blend_input(int p_input, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only) { - ERR_FAIL_INDEX_V(p_input, inputs.size(), 0); +AnimationNode::NodeTimeInfo AnimationNode::blend_input(int p_input, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only) { + ERR_FAIL_INDEX_V(p_input, inputs.size(), NodeTimeInfo()); AnimationNodeBlendTree *blend_tree = Object::cast_to(node_state.parent); - ERR_FAIL_NULL_V(blend_tree, 0); + ERR_FAIL_NULL_V(blend_tree, NodeTimeInfo()); // Update connections. StringName current_name = blend_tree->get_node_name(Ref(this)); @@ -136,32 +161,31 @@ double AnimationNode::blend_input(int p_input, AnimationMixer::PlaybackInfo p_pl StringName node_name = node_state.connections[p_input]; if (!blend_tree->has_node(node_name)) { make_invalid(vformat(RTR("Nothing connected to input '%s' of node '%s'."), get_input_name(p_input), current_name)); - return 0; + return NodeTimeInfo(); } Ref node = blend_tree->get_node(node_name); - ERR_FAIL_COND_V(node.is_null(), 0); + ERR_FAIL_COND_V(node.is_null(), NodeTimeInfo()); real_t activity = 0.0; Vector *activity_ptr = process_state->tree->input_activity_map.getptr(node_state.base_path); - double ret = _blend_node(node, node_name, nullptr, p_playback_info, p_filter, p_sync, p_test_only, &activity); + NodeTimeInfo nti = _blend_node(node, node_name, nullptr, p_playback_info, p_filter, p_sync, p_test_only, &activity); if (activity_ptr && p_input < activity_ptr->size()) { activity_ptr->write[p_input].last_pass = process_state->last_pass; activity_ptr->write[p_input].activity = activity; } - return ret; + return nti; } -double AnimationNode::blend_node(Ref p_node, const StringName &p_subpath, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only) { - ERR_FAIL_COND_V(p_node.is_null(), 0); - +AnimationNode::NodeTimeInfo AnimationNode::blend_node(Ref p_node, const StringName &p_subpath, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only) { + ERR_FAIL_COND_V(p_node.is_null(), NodeTimeInfo()); p_node->node_state.connections.clear(); return _blend_node(p_node, p_subpath, this, p_playback_info, p_filter, p_sync, p_test_only, nullptr); } -double AnimationNode::_blend_node(Ref p_node, const StringName &p_subpath, AnimationNode *p_new_parent, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only, real_t *r_activity) { - ERR_FAIL_NULL_V(process_state, 0); +AnimationNode::NodeTimeInfo AnimationNode::_blend_node(Ref p_node, const StringName &p_subpath, AnimationNode *p_new_parent, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only, real_t *r_activity) { + ERR_FAIL_NULL_V(process_state, NodeTimeInfo()); int blend_count = node_state.track_weights.size(); @@ -261,7 +285,7 @@ double AnimationNode::_blend_node(Ref p_node, const StringName &p new_parent = p_new_parent; new_path = String(node_state.base_path) + String(p_subpath) + "/"; } else { - ERR_FAIL_NULL_V(node_state.parent, 0); + ERR_FAIL_NULL_V(node_state.parent, NodeTimeInfo()); new_parent = node_state.parent; new_path = String(new_parent->node_state.base_path) + String(p_subpath) + "/"; } @@ -271,7 +295,7 @@ double AnimationNode::_blend_node(Ref p_node, const StringName &p p_node->node_state.base_path = new_path; p_node->node_state.parent = new_parent; if (!p_playback_info.seeked && !p_sync && !any_valid) { - p_playback_info.time = 0.0; + p_playback_info.delta = 0.0; return p_node->_pre_process(process_state, p_playback_info, p_test_only); } return p_node->_pre_process(process_state, p_playback_info, p_test_only); @@ -328,15 +352,31 @@ int AnimationNode::find_input(const String &p_name) const { return idx; } -double AnimationNode::process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNode::process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { process_state->is_testing = p_test_only; - return _process(p_playback_info, p_test_only); + + AnimationMixer::PlaybackInfo pi = p_playback_info; + if (p_playback_info.seeked) { + pi.delta = get_node_time_info().position - p_playback_info.time; + } else { + pi.time = get_node_time_info().position + p_playback_info.delta; + } + + NodeTimeInfo nti = _process(pi, p_test_only); + + if (!p_test_only) { + set_node_time_info(nti); + } + + return nti; } -double AnimationNode::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { - double ret = 0; - GDVIRTUAL_CALL(_process, p_playback_info.time, p_playback_info.seeked, p_playback_info.is_external_seeking, p_test_only, ret); - return ret; +AnimationNode::NodeTimeInfo AnimationNode::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { + double r_ret = 0.0; + GDVIRTUAL_CALL(_process, p_playback_info.time, p_playback_info.seeked, p_playback_info.is_external_seeking, p_test_only, r_ret); + NodeTimeInfo nti; + nti.delta = r_ret; + return nti; } void AnimationNode::set_filter_path(const NodePath &p_path, bool p_enable) { @@ -432,7 +472,8 @@ double AnimationNode::blend_node_ex(const StringName &p_sub_path, Ref_pre_process(&process_state, pi, false); started = false; } else { pi.seeked = false; - pi.time = p_delta; + pi.delta = p_delta; root_animation_node->_pre_process(&process_state, pi, false); } } diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h index 87928e4d2075..c6c8cfc37d37 100644 --- a/scene/animation/animation_tree.h +++ b/scene/animation/animation_tree.h @@ -63,6 +63,34 @@ class AnimationNode : public Resource { HashMap filter; bool filter_enabled = false; + // To propagate information from upstream for use in estimation of playback progress. + // These values must be taken from the result of blend_node() or blend_input() and must be essentially read-only. + // For example, if you want to change the position, you need to change the pi.time value of PlaybackInfo passed to blend_input(pi) and get the result. + struct NodeTimeInfo { + // Retain the previous frame values. These are stored into the AnimationTree's Map and exposing them as read-only values. + double length = 0.0; + double position = 0.0; + double delta = 0.0; + + // Needs internally to estimate remain time, the previous frame values are not retained. + Animation::LoopMode loop_mode = Animation::LOOP_NONE; + bool is_just_looped = false; // For breaking loop, it is true when just looped. + bool is_infinity = false; // For unpredictable state machine's end. + + bool is_looping() { + return loop_mode != Animation::LOOP_NONE; + } + double get_remain(bool p_break_loop = false) { + if ((is_looping() && !p_break_loop) || is_infinity) { + return HUGE_LENGTH; + } + if (p_break_loop && is_just_looped) { + return 0; + } + return length - position; + } + }; + // Temporary state for blending process which needs to be stored in each AnimationNodes. struct NodeState { StringName base_path; @@ -84,16 +112,23 @@ class AnimationNode : public Resource { Array _get_filters() const; void _set_filters(const Array &p_filters); friend class AnimationNodeBlendTree; - double _blend_node(Ref p_node, const StringName &p_subpath, AnimationNode *p_new_parent, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false, real_t *r_activity = nullptr); - double _pre_process(ProcessState *p_process_state, AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false); + + // The time information is passed from upstream to downstream by AnimationMixer::PlaybackInfo::p_playback_info until AnimationNodeAnimation processes it. + // Conversely, AnimationNodeAnimation returns the processed result as NodeTimeInfo from downstream to upstream. + NodeTimeInfo _blend_node(Ref p_node, const StringName &p_subpath, AnimationNode *p_new_parent, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false, real_t *r_activity = nullptr); + NodeTimeInfo _pre_process(ProcessState *p_process_state, AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false); protected: - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false); - double process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false); + StringName current_length = "current_length"; + StringName current_position = "current_position"; + StringName current_delta = "current_delta"; + + virtual NodeTimeInfo process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false); // To organize time information. Virtualizing for especially AnimationNodeAnimation needs to take "backward" into account. + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false); // Main process. void blend_animation(const StringName &p_animation, AnimationMixer::PlaybackInfo p_playback_info); - double blend_node(Ref p_node, const StringName &p_subpath, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false); - double blend_input(int p_input, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false); + NodeTimeInfo blend_node(Ref p_node, const StringName &p_subpath, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false); + NodeTimeInfo blend_input(int p_input, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false); // Bind-able methods to expose for compatibility, moreover AnimationMixer::PlaybackInfo is not exposed. void blend_animation_ex(const StringName &p_animation, double p_time, double p_delta, bool p_seeked, bool p_is_external_seeking, real_t p_blend, Animation::LoopedFlag p_looped_flag = Animation::LOOPED_FLAG_NONE); @@ -124,6 +159,9 @@ class AnimationNode : public Resource { void set_parameter(const StringName &p_name, const Variant &p_value); Variant get_parameter(const StringName &p_name) const; + void set_node_time_info(const NodeTimeInfo &p_node_time_info); // Wrapper of set_parameter(). + virtual NodeTimeInfo get_node_time_info() const; // Wrapper of get_parameter(). + struct ChildNode { StringName name; Ref node; diff --git a/scene/resources/animation.h b/scene/resources/animation.h index d3df8d03e5f7..c72327e46449 100644 --- a/scene/resources/animation.h +++ b/scene/resources/animation.h @@ -75,6 +75,7 @@ class Animation : public Resource { LOOP_PINGPONG, }; + // LoopedFlag is used in Animataion to "process the keys at both ends correct". enum LoopedFlag { LOOPED_FLAG_NONE, LOOPED_FLAG_END, @@ -187,6 +188,7 @@ class Animation : public Resource { }; /* BEZIER TRACK */ + struct BezierKey { Vector2 in_handle; // Relative (x always <0) Vector2 out_handle; // Relative (x always >0) @@ -223,7 +225,7 @@ class Animation : public Resource { } }; - /* AUDIO TRACK */ + /* ANIMATION TRACK */ struct AnimationTrack : public Track { Vector> values;