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 | 506 +++++++++++++-----
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, 704 insertions(+), 319 deletions(-)
diff --git a/doc/classes/AnimationNode.xml b/doc/classes/AnimationNode.xml
index d7fb735b4d4..960bbe68adb 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 d965d31b03d..5683371182b 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 ac7cf701331..6ff2d6f6db4 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 7b7797f594c..7bd0bd7e7e2 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 3e1a0a28b51..775a2087356 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 0412141775d..bda2cb666b0 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 dbfb143b227..91ed0bd1789 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 4cbd9b1d762..36343edd114 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 40679d55ef1..64ae4d0505d 100644
--- a/scene/animation/animation_blend_space_1d.h
+++ b/scene/animation/animation_blend_space_1d.h
@@ -68,7 +68,6 @@ protected:
StringName blend_position = "blend_position";
StringName closest = "closest";
- StringName length_internal = "length_internal";
BlendMode blend_mode = BLEND_MODE_INTERPOLATED;
@@ -114,7 +113,7 @@ public:
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 d5c6253e9d0..26342482317 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 33a821d80cd..c26ff2bce0f 100644
--- a/scene/animation/animation_blend_space_2d.h
+++ b/scene/animation/animation_blend_space_2d.h
@@ -65,7 +65,6 @@ protected:
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 @@ public:
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 4a01b2ad650..71f9c45eea5 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;
- } else {
- p_time *= backward ? -1.0 : 1.0;
- cur_time = cur_time + p_time;
- step = p_time;
- }
+ bool is_just_looped = false;
- bool is_looping = false;
- if (anim->get_loop_mode() == Animation::LOOP_PINGPONG) {
- if (!Math::is_zero_approx(anim_size)) {
- if (prev_time >= 0 && cur_time < 0) {
- backward = !backward;
- looped_flag = node_backward ? Animation::LOOPED_FLAG_END : Animation::LOOPED_FLAG_START;
+ // 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);
}
- if (prev_time <= anim_size && cur_time > anim_size) {
- backward = !backward;
- looped_flag = node_backward ? Animation::LOOPED_FLAG_START : Animation::LOOPED_FLAG_END;
+ 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);
}
- cur_time = Math::pingpong(cur_time, anim_size);
}
- is_looping = true;
- } else if (anim->get_loop_mode() == Animation::LOOP_LINEAR) {
- if (!Math::is_zero_approx(anim_size)) {
- if (prev_time >= 0 && cur_time < 0) {
- looped_flag = node_backward ? Animation::LOOPED_FLAG_END : Animation::LOOPED_FLAG_START;
- }
- if (prev_time <= anim_size && cur_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_delta += cur_time;
cur_time = 0;
- } else if (cur_time > anim_size) {
- step += anim_size - cur_time;
- cur_time = anim_size;
+ } else if (cur_time > cur_len) {
+ cur_delta += cur_time - cur_len;
+ cur_time = cur_len;
}
backward = false;
-
- // If ended, don't progress animation. So set delta to 0.
- if (p_time > 0) {
+ // 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 >= anim_size) {
- step = 0;
+ if (prev_time >= cur_len) {
+ cur_delta = 0;
}
} else {
if (prev_time <= 0) {
- step = 0;
+ cur_delta = 0;
}
}
}
+ }
+
+ // 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)) {
+ 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_playback_time <= anim_size && cur_playback_time > anim_size) {
+ looped_flag = node_backward ? Animation::LOOPED_FLAG_START : Animation::LOOPED_FLAG_END;
+ }
+ }
+ } else if (cur_loop_mode == Animation::LOOP_PINGPONG) {
+ if (!Math::is_zero_approx(anim_size)) {
+ 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_playback_time <= anim_size && cur_playback_time > anim_size) {
+ looped_flag = node_backward ? Animation::LOOPED_FLAG_START : Animation::LOOPED_FLAG_END;
+ }
+ }
+ } else {
+ 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 cf0884b892f..c7ef7ed6248 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 @@ public:
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 @@ public:
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 @@ private:
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 @@ public:
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 @@ public:
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 @@ public:
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 @@ public:
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 @@ 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;
AnimationNodeBlend3();
};
@@ -261,7 +287,7 @@ public:
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 @@ 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;
AnimationNodeTimeScale();
};
@@ -299,7 +325,7 @@ 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;
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 @@ public:
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 @@ public:
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 @@ public:
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 ec446414843..04846945551 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 fa0eca58778..8078ffb26d4 100644
--- a/scene/animation/animation_node_state_machine.h
+++ b/scene/animation/animation_node_state_machine.h
@@ -57,6 +57,7 @@ private:
StringName advance_condition_name;
float xfade_time = 0.0;
Ref xfade_curve;
+ bool break_loop_at_end = false;
bool reset = true;
int priority = 1;
String advance_expression;
@@ -85,6 +86,9 @@ public:
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 @@ public:
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 2c2d8387f31..5f4bdcc4f40 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 87928e4d207..c6c8cfc37d3 100644
--- a/scene/animation/animation_tree.h
+++ b/scene/animation/animation_tree.h
@@ -63,6 +63,34 @@ public:
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 @@ public:
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 @@ public:
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 d3df8d03e5f..c72327e4644 100644
--- a/scene/resources/animation.h
+++ b/scene/resources/animation.h
@@ -75,6 +75,7 @@ public:
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 @@ private:
};
/* BEZIER TRACK */
+
struct BezierKey {
Vector2 in_handle; // Relative (x always <0)
Vector2 out_handle; // Relative (x always >0)
@@ -223,7 +225,7 @@ private:
}
};
- /* AUDIO TRACK */
+ /* ANIMATION TRACK */
struct AnimationTrack : public Track {
Vector> values;
]