소스 검색

Rework AnimationNode process for retrieving the semantic time info

Silc Lizard (Tokage) Renew 1 년 전
부모
커밋
6dd410854c

+ 9 - 2
doc/classes/AnimationNode.xml

@@ -6,6 +6,13 @@
 	<description>
 	<description>
 		Base resource for [AnimationTree] nodes. In general, it's not used directly, but you can create custom ones with custom blending formulas.
 		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.
 		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]
 	</description>
 	</description>
 	<tutorials>
 	<tutorials>
 		<link title="Using AnimationTree">$DOCS_URL/tutorials/animation/animation_tree.html</link>
 		<link title="Using AnimationTree">$DOCS_URL/tutorials/animation/animation_tree.html</link>
@@ -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.
 				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.
 			</description>
 			</description>
 		</method>
 		</method>
-		<method name="_process" qualifiers="virtual const">
+		<method name="_process" qualifiers="virtual const" deprecated="Currently this is mostly useless as there is a lack of many APIs to extend AnimationNode by GDScript. It is planned that a more flexible API using structures will be provided in the future.">
 			<return type="float" />
 			<return type="float" />
 			<param index="0" name="time" type="float" />
 			<param index="0" name="time" type="float" />
 			<param index="1" name="seek" type="bool" />
 			<param index="1" name="seek" type="bool" />
@@ -65,7 +72,7 @@
 			<description>
 			<description>
 				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.
 				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.
 				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.
 			</description>
 			</description>
 		</method>
 		</method>
 		<method name="add_input">
 		<method name="add_input">

+ 18 - 0
doc/classes/AnimationNodeAnimation.xml

@@ -15,9 +15,27 @@
 		<member name="animation" type="StringName" setter="set_animation" getter="get_animation" default="&amp;&quot;&quot;">
 		<member name="animation" type="StringName" setter="set_animation" getter="get_animation" default="&amp;&quot;&quot;">
 			Animation to use as an output. It is one of the animations provided by [member AnimationTree.anim_player].
 			Animation to use as an output. It is one of the animations provided by [member AnimationTree.anim_player].
 		</member>
 		</member>
+		<member name="loop_mode" type="int" setter="set_loop_mode" getter="get_loop_mode" enum="Animation.LoopMode">
+			If [member use_custom_timeline] is [code]true[/code], override the loop settings of the original [Animation] resource with the value.
+		</member>
 		<member name="play_mode" type="int" setter="set_play_mode" getter="get_play_mode" enum="AnimationNodeAnimation.PlayMode" default="0">
 		<member name="play_mode" type="int" setter="set_play_mode" getter="get_play_mode" enum="AnimationNodeAnimation.PlayMode" default="0">
 			Determines the playback direction of the animation.
 			Determines the playback direction of the animation.
 		</member>
 		</member>
+		<member name="start_offset" type="float" setter="set_start_offset" getter="get_start_offset">
+			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.
+		</member>
+		<member name="stretch_time_scale" type="bool" setter="set_stretch_time_scale" getter="is_stretching_time_scale">
+			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].
+		</member>
+		<member name="timeline_length" type="float" setter="set_timeline_length" getter="get_timeline_length">
+			If [member use_custom_timeline] is [code]true[/code], offset the start position of the animation.
+		</member>
+		<member name="use_custom_timeline" type="bool" setter="set_use_custom_timeline" getter="is_using_custom_timeline" default="false">
+			If [code]true[/code], [AnimationNode] provides an animation based on the [Animation] resource with some parameters adjusted.
+		</member>
 	</members>
 	</members>
 	<constants>
 	<constants>
 		<constant name="PLAY_MODE_FORWARD" value="0" enum="PlayMode">
 		<constant name="PLAY_MODE_FORWARD" value="0" enum="PlayMode">

+ 5 - 0
doc/classes/AnimationNodeOneShot.xml

@@ -66,17 +66,22 @@
 		<member name="autorestart_random_delay" type="float" setter="set_autorestart_random_delay" getter="get_autorestart_random_delay" default="0.0">
 		<member name="autorestart_random_delay" type="float" setter="set_autorestart_random_delay" getter="get_autorestart_random_delay" default="0.0">
 			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 [member autorestart] is [code]true[/code], a random additional delay (in seconds) between 0 and this value will be added to [member autorestart_delay].
 		</member>
 		</member>
+		<member name="break_loop_at_end" type="bool" setter="set_break_loop_at_end" getter="is_loop_broken_at_end" default="false">
+			If [code]true[/code], breaks the loop at the end of the loop cycle for transition, even if the animation is looping.
+		</member>
 		<member name="fadein_curve" type="Curve" setter="set_fadein_curve" getter="get_fadein_curve">
 		<member name="fadein_curve" type="Curve" setter="set_fadein_curve" getter="get_fadein_curve">
 			Determines how cross-fading between animations is eased. If empty, the transition will be linear.
 			Determines how cross-fading between animations is eased. If empty, the transition will be linear.
 		</member>
 		</member>
 		<member name="fadein_time" type="float" setter="set_fadein_time" getter="get_fadein_time" default="0.0">
 		<member name="fadein_time" type="float" setter="set_fadein_time" getter="get_fadein_time" default="0.0">
 			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.
 			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.
 		</member>
 		</member>
 		<member name="fadeout_curve" type="Curve" setter="set_fadeout_curve" getter="get_fadeout_curve">
 		<member name="fadeout_curve" type="Curve" setter="set_fadeout_curve" getter="get_fadeout_curve">
 			Determines how cross-fading between animations is eased. If empty, the transition will be linear.
 			Determines how cross-fading between animations is eased. If empty, the transition will be linear.
 		</member>
 		</member>
 		<member name="fadeout_time" type="float" setter="set_fadeout_time" getter="get_fadeout_time" default="0.0">
 		<member name="fadeout_time" type="float" setter="set_fadeout_time" getter="get_fadeout_time" default="0.0">
 			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.
 			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.
 		</member>
 		</member>
 		<member name="mix_mode" type="int" setter="set_mix_mode" getter="get_mix_mode" enum="AnimationNodeOneShot.MixMode" default="0">
 		<member name="mix_mode" type="int" setter="set_mix_mode" getter="get_mix_mode" enum="AnimationNodeOneShot.MixMode" default="0">
 			The blend type.
 			The blend type.

+ 4 - 0
doc/classes/AnimationNodeStateMachineTransition.xml

@@ -28,6 +28,9 @@
 		<member name="advance_mode" type="int" setter="set_advance_mode" getter="get_advance_mode" enum="AnimationNodeStateMachineTransition.AdvanceMode" default="1">
 		<member name="advance_mode" type="int" setter="set_advance_mode" getter="get_advance_mode" enum="AnimationNodeStateMachineTransition.AdvanceMode" default="1">
 			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).
 			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).
 		</member>
 		</member>
+		<member name="break_loop_at_end" type="bool" setter="set_break_loop_at_end" getter="is_loop_broken_at_end" default="false">
+			If [code]true[/code], breaks the loop at the end of the loop cycle for transition, even if the animation is looping.
+		</member>
 		<member name="priority" type="int" setter="set_priority" getter="get_priority" default="1">
 		<member name="priority" type="int" setter="set_priority" getter="get_priority" default="1">
 			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].
 			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].
 		</member>
 		</member>
@@ -42,6 +45,7 @@
 		</member>
 		</member>
 		<member name="xfade_time" type="float" setter="set_xfade_time" getter="get_xfade_time" default="0.0">
 		<member name="xfade_time" type="float" setter="set_xfade_time" getter="get_xfade_time" default="0.0">
 			The time to cross-fade between this state and the next.
 			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].
 		</member>
 		</member>
 	</members>
 	</members>
 	<signals>
 	<signals>

+ 16 - 0
doc/classes/AnimationNodeTransition.xml

@@ -42,6 +42,13 @@
 		<link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link>
 		<link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link>
 	</tutorials>
 	</tutorials>
 	<methods>
 	<methods>
+		<method name="is_input_loop_broken_at_end" qualifiers="const">
+			<return type="bool" />
+			<param index="0" name="input" type="int" />
+			<description>
+				Returns whether the animation breaks the loop at the end of the loop cycle for transition.
+			</description>
+		</method>
 		<method name="is_input_reset" qualifiers="const">
 		<method name="is_input_reset" qualifiers="const">
 			<return type="bool" />
 			<return type="bool" />
 			<param index="0" name="input" type="int" />
 			<param index="0" name="input" type="int" />
@@ -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.
 				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.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="set_input_break_loop_at_end">
+			<return type="void" />
+			<param index="0" name="input" type="int" />
+			<param index="1" name="enable" type="bool" />
+			<description>
+				If [code]true[/code], breaks the loop at the end of the loop cycle for transition, even if the animation is looping.
+			</description>
+		</method>
 		<method name="set_input_reset">
 		<method name="set_input_reset">
 			<return type="void" />
 			<return type="void" />
 			<param index="0" name="input" type="int" />
 			<param index="0" name="input" type="int" />
@@ -85,6 +100,7 @@
 		</member>
 		</member>
 		<member name="xfade_time" type="float" setter="set_xfade_time" getter="get_xfade_time" default="0.0">
 		<member name="xfade_time" type="float" setter="set_xfade_time" getter="get_xfade_time" default="0.0">
 			Cross-fading time (in seconds) between each animation connected to the inputs.
 			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].
 		</member>
 		</member>
 	</members>
 	</members>
 </class>
 </class>

+ 3 - 6
editor/plugins/animation_blend_tree_editor_plugin.cpp

@@ -256,10 +256,6 @@ void AnimationNodeBlendTreeEditor::update_graph() {
 				options.push_back(F);
 				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_show_percentage(false);
 			pb->set_custom_minimum_size(Vector2(0, 14) * EDSCALE);
 			pb->set_custom_minimum_size(Vector2(0, 14) * EDSCALE);
 			animations[E] = pb;
 			animations[E] = pb;
@@ -994,9 +990,10 @@ void AnimationNodeBlendTreeEditor::_notification(int p_what) {
 					if (tree->has_animation(an->get_animation())) {
 					if (tree->has_animation(an->get_animation())) {
 						Ref<Animation> anim = tree->get_animation(an->get_animation());
 						Ref<Animation> anim = tree->get_animation(an->get_animation());
 						if (anim.is_valid()) {
 						if (anim.is_valid()) {
-							E.value->set_max(anim->get_length());
 							//StringName path = AnimationTreeEditor::get_singleton()->get_base_path() + E.input_node;
 							//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));
 							E.value->set_value(tree->get(time_path));
 						}
 						}
 					}
 					}

+ 2 - 2
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 len = MAX(0.0001, current_length);
 		float pos = CLAMP(current_play_pos, 0, len);
 		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);
 		_state_machine_pos_draw_individual(playback->get_current_node(), c);
 	}
 	}
 
 
 	{
 	{
 		float len = MAX(0.0001, fade_from_length);
 		float len = MAX(0.0001, fade_from_length);
 		float pos = CLAMP(fade_from_current_play_pos, 0, len);
 		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);
 		_state_machine_pos_draw_individual(playback->get_fading_from_node(), c);
 	}
 	}
 }
 }

+ 23 - 16
scene/animation/animation_blend_space_1d.cpp

@@ -33,12 +33,17 @@
 #include "animation_blend_tree.h"
 #include "animation_blend_tree.h"
 
 
 void AnimationNodeBlendSpace1D::get_parameter_list(List<PropertyInfo> *r_list) const {
 void AnimationNodeBlendSpace1D::get_parameter_list(List<PropertyInfo> *r_list) const {
+	AnimationNode::get_parameter_list(r_list);
 	r_list->push_back(PropertyInfo(Variant::FLOAT, blend_position));
 	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::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 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) {
 	if (p_parameter == closest) {
 		return -1;
 		return -1;
 	} else {
 	} else {
@@ -272,9 +277,9 @@ void AnimationNodeBlendSpace1D::_add_blend_point(int p_index, const Ref<Animatio
 	}
 	}
 }
 }
 
 
-double AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
+AnimationNode::NodeTimeInfo AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
 	if (blend_points_used == 0) {
 	if (blend_points_used == 0) {
-		return 0.0;
+		return NodeTimeInfo();
 	}
 	}
 
 
 	AnimationMixer::PlaybackInfo pi = p_playback_info;
 	AnimationMixer::PlaybackInfo pi = p_playback_info;
@@ -287,8 +292,7 @@ double AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_
 
 
 	double blend_pos = get_parameter(blend_position);
 	double blend_pos = get_parameter(blend_position);
 	int cur_closest = get_parameter(closest);
 	int cur_closest = get_parameter(closest);
-	double cur_length_internal = get_parameter(length_internal);
-	double max_time_remaining = 0.0;
+	NodeTimeInfo mind;
 
 
 	if (blend_mode == BLEND_MODE_INTERPOLATED) {
 	if (blend_mode == BLEND_MODE_INTERPOLATED) {
 		int point_lower = -1;
 		int point_lower = -1;
@@ -341,12 +345,17 @@ double AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_
 		}
 		}
 
 
 		// actually blend the animations now
 		// actually blend the animations now
-
+		bool first = true;
+		double max_weight = 0.0;
 		for (int i = 0; i < blend_points_used; i++) {
 		for (int i = 0; i < blend_points_used; i++) {
 			if (i == point_lower || i == point_higher) {
 			if (i == point_lower || i == point_higher) {
 				pi.weight = weights[i];
 				pi.weight = weights[i];
-				double remaining = blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only);
-				max_time_remaining = MAX(max_time_remaining, remaining);
+				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) {
+					max_weight = pi.weight;
+					mind = t;
+					first = false;
+				}
 			} else if (sync) {
 			} else if (sync) {
 				pi.weight = 0;
 				pi.weight = 0;
 				blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only);
 				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) {
 		if (new_closest != cur_closest && new_closest != -1) {
-			double from = 0.0;
+			NodeTimeInfo from;
 			if (blend_mode == BLEND_MODE_DISCRETE_CARRY && cur_closest != -1) {
 			if (blend_mode == BLEND_MODE_DISCRETE_CARRY && cur_closest != -1) {
 				//for ping-pong loop
 				//for ping-pong loop
 				Ref<AnimationNodeAnimation> na_c = static_cast<Ref<AnimationNodeAnimation>>(blend_points[cur_closest].node);
 				Ref<AnimationNodeAnimation> na_c = static_cast<Ref<AnimationNodeAnimation>>(blend_points[cur_closest].node);
@@ -376,18 +385,17 @@ double AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_
 				//see how much animation remains
 				//see how much animation remains
 				pi.seeked = false;
 				pi.seeked = false;
 				pi.weight = 0;
 				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.seeked = true;
 			pi.weight = 1.0;
 			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;
 			cur_closest = new_closest;
 		} else {
 		} else {
 			pi.weight = 1.0;
 			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) {
 		if (sync) {
@@ -402,8 +410,7 @@ double AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_
 	}
 	}
 
 
 	set_parameter(closest, cur_closest);
 	set_parameter(closest, cur_closest);
-	set_parameter(length_internal, cur_length_internal);
-	return max_time_remaining;
+	return mind;
 }
 }
 
 
 String AnimationNodeBlendSpace1D::get_caption() const {
 String AnimationNodeBlendSpace1D::get_caption() const {

+ 1 - 2
scene/animation/animation_blend_space_1d.h

@@ -68,7 +68,6 @@ protected:
 
 
 	StringName blend_position = "blend_position";
 	StringName blend_position = "blend_position";
 	StringName closest = "closest";
 	StringName closest = "closest";
-	StringName length_internal = "length_internal";
 
 
 	BlendMode blend_mode = BLEND_MODE_INTERPOLATED;
 	BlendMode blend_mode = BLEND_MODE_INTERPOLATED;
 
 
@@ -114,7 +113,7 @@ public:
 	void set_use_sync(bool p_sync);
 	void set_use_sync(bool p_sync);
 	bool is_using_sync() const;
 	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;
 	String get_caption() const override;
 
 
 	Ref<AnimationNode> get_child_by_name(const StringName &p_name) const override;
 	Ref<AnimationNode> get_child_by_name(const StringName &p_name) const override;

+ 18 - 16
scene/animation/animation_blend_space_2d.cpp

@@ -34,16 +34,19 @@
 #include "core/math/geometry_2d.h"
 #include "core/math/geometry_2d.h"
 
 
 void AnimationNodeBlendSpace2D::get_parameter_list(List<PropertyInfo> *r_list) const {
 void AnimationNodeBlendSpace2D::get_parameter_list(List<PropertyInfo> *r_list) const {
+	AnimationNode::get_parameter_list(r_list);
 	r_list->push_back(PropertyInfo(Variant::VECTOR2, blend_position));
 	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::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 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) {
 	if (p_parameter == closest) {
 		return -1;
 		return -1;
-	} else if (p_parameter == length_internal) {
-		return 0;
 	} else {
 	} else {
 		return Vector2();
 		return Vector2();
 	}
 	}
@@ -442,19 +445,18 @@ void AnimationNodeBlendSpace2D::_blend_triangle(const Vector2 &p_pos, const Vect
 	r_weights[2] = w;
 	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();
 	_update_triangles();
 
 
 	Vector2 blend_pos = get_parameter(blend_position);
 	Vector2 blend_pos = get_parameter(blend_position);
 	int cur_closest = get_parameter(closest);
 	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;
 	AnimationMixer::PlaybackInfo pi = p_playback_info;
 
 
 	if (blend_mode == BLEND_MODE_INTERPOLATED) {
 	if (blend_mode == BLEND_MODE_INTERPOLATED) {
 		if (triangles.size() == 0) {
 		if (triangles.size() == 0) {
-			return 0;
+			return NodeTimeInfo();
 		}
 		}
 
 
 		Vector2 best_point;
 		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];
 		int triangle_points[3];
 		for (int j = 0; j < 3; j++) {
 		for (int j = 0; j < 3; j++) {
@@ -509,15 +511,17 @@ double AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_
 
 
 		first = true;
 		first = true;
 
 
+		bool found = false;
+		double max_weight = 0.0;
 		for (int i = 0; i < blend_points_used; i++) {
 		for (int i = 0; i < blend_points_used; i++) {
-			bool found = false;
 			for (int j = 0; j < 3; j++) {
 			for (int j = 0; j < 3; j++) {
 				if (i == triangle_points[j]) {
 				if (i == triangle_points[j]) {
 					//blend with the given weight
 					//blend with the given weight
 					pi.weight = blend_weights[j];
 					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;
 						mind = t;
+						max_weight = pi.weight;
 						first = false;
 						first = false;
 					}
 					}
 					found = true;
 					found = true;
@@ -543,7 +547,7 @@ double AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_
 		}
 		}
 
 
 		if (new_closest != cur_closest && new_closest != -1) {
 		if (new_closest != cur_closest && new_closest != -1) {
-			double from = 0.0;
+			NodeTimeInfo from;
 			if (blend_mode == BLEND_MODE_DISCRETE_CARRY && cur_closest != -1) {
 			if (blend_mode == BLEND_MODE_DISCRETE_CARRY && cur_closest != -1) {
 				//for ping-pong loop
 				//for ping-pong loop
 				Ref<AnimationNodeAnimation> na_c = static_cast<Ref<AnimationNodeAnimation>>(blend_points[cur_closest].node);
 				Ref<AnimationNodeAnimation> na_c = static_cast<Ref<AnimationNodeAnimation>>(blend_points[cur_closest].node);
@@ -554,14 +558,13 @@ double AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_
 				//see how much animation remains
 				//see how much animation remains
 				pi.seeked = false;
 				pi.seeked = false;
 				pi.weight = 0;
 				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.seeked = true;
 			pi.weight = 1.0;
 			pi.weight = 1.0;
 			mind = blend_node(blend_points[new_closest].node, blend_points[new_closest].name, pi, FILTER_IGNORE, true, p_test_only);
 			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;
 			cur_closest = new_closest;
 		} else {
 		} else {
 			pi.weight = 1.0;
 			pi.weight = 1.0;
@@ -580,7 +583,6 @@ double AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_
 	}
 	}
 
 
 	set_parameter(closest, cur_closest);
 	set_parameter(closest, cur_closest);
-	set_parameter(length_internal, cur_length_internal);
 	return mind;
 	return mind;
 }
 }
 
 

+ 1 - 2
scene/animation/animation_blend_space_2d.h

@@ -65,7 +65,6 @@ protected:
 
 
 	StringName blend_position = "blend_position";
 	StringName blend_position = "blend_position";
 	StringName closest = "closest";
 	StringName closest = "closest";
-	StringName length_internal = "length_internal";
 	Vector2 max_space = Vector2(1, 1);
 	Vector2 max_space = Vector2(1, 1);
 	Vector2 min_space = Vector2(-1, -1);
 	Vector2 min_space = Vector2(-1, -1);
 	Vector2 snap = Vector2(0.1, 0.1);
 	Vector2 snap = Vector2(0.1, 0.1);
@@ -129,7 +128,7 @@ public:
 	void set_y_label(const String &p_label);
 	void set_y_label(const String &p_label);
 	String get_y_label() const;
 	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;
 	virtual String get_caption() const override;
 
 
 	Vector2 get_closest_point(const Vector2 &p_point);
 	Vector2 get_closest_point(const Vector2 &p_point);

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 363 - 137
scene/animation/animation_blend_tree.cpp


+ 45 - 16
scene/animation/animation_blend_tree.h

@@ -37,7 +37,12 @@ class AnimationNodeAnimation : public AnimationRootNode {
 	GDCLASS(AnimationNodeAnimation, AnimationRootNode);
 	GDCLASS(AnimationNodeAnimation, AnimationRootNode);
 
 
 	StringName animation;
 	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;
 	uint64_t last_version = 0;
 	bool skip = false;
 	bool skip = false;
@@ -50,10 +55,13 @@ public:
 
 
 	void get_parameter_list(List<PropertyInfo> *r_list) const override;
 	void get_parameter_list(List<PropertyInfo> *r_list) const override;
 
 
+	virtual NodeTimeInfo get_node_time_info() const override; // Wrapper of get_parameter().
+
 	static Vector<String> (*get_editable_animation_list)();
 	static Vector<String> (*get_editable_animation_list)();
 
 
 	virtual String get_caption() 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 NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
 
 
 	void set_animation(const StringName &p_name);
 	void set_animation(const StringName &p_name);
 	StringName get_animation() const;
 	StringName get_animation() const;
@@ -64,6 +72,21 @@ public:
 	void set_backward(bool p_backward);
 	void set_backward(bool p_backward);
 	bool is_backward() const;
 	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();
 	AnimationNodeAnimation();
 
 
 protected:
 protected:
@@ -118,12 +141,12 @@ private:
 	double auto_restart_delay = 1.0;
 	double auto_restart_delay = 1.0;
 	double auto_restart_random_delay = 0.0;
 	double auto_restart_random_delay = 0.0;
 	MixMode mix = MIX_MODE_BLEND;
 	MixMode mix = MIX_MODE_BLEND;
+	bool break_loop_at_end = false;
 
 
 	StringName request = PNAME("request");
 	StringName request = PNAME("request");
 	StringName active = PNAME("active");
 	StringName active = PNAME("active");
 	StringName internal_active = PNAME("internal_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 fade_out_remaining = "fade_out_remaining";
 	StringName time_to_restart = "time_to_restart";
 	StringName time_to_restart = "time_to_restart";
 
 
@@ -160,8 +183,11 @@ public:
 	void set_mix_mode(MixMode p_mix);
 	void set_mix_mode(MixMode p_mix);
 	MixMode get_mix_mode() const;
 	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 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();
 	AnimationNodeOneShot();
 };
 };
@@ -184,7 +210,7 @@ public:
 	virtual String get_caption() const override;
 	virtual String get_caption() const override;
 
 
 	virtual bool has_filter() 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();
 	AnimationNodeAdd2();
 };
 };
@@ -204,7 +230,7 @@ public:
 	virtual String get_caption() const override;
 	virtual String get_caption() const override;
 
 
 	virtual bool has_filter() 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();
 	AnimationNodeAdd3();
 };
 };
@@ -222,7 +248,7 @@ public:
 	virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
 	virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
 
 
 	virtual String get_caption() 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;
 	virtual bool has_filter() const override;
 	AnimationNodeBlend2();
 	AnimationNodeBlend2();
@@ -242,7 +268,7 @@ public:
 
 
 	virtual String get_caption() 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;
 	AnimationNodeBlend3();
 	AnimationNodeBlend3();
 };
 };
 
 
@@ -261,7 +287,7 @@ public:
 	virtual String get_caption() const override;
 	virtual String get_caption() const override;
 
 
 	virtual bool has_filter() 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();
 	AnimationNodeSub2();
 };
 };
@@ -280,7 +306,7 @@ public:
 
 
 	virtual String get_caption() 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;
 
 
 	AnimationNodeTimeScale();
 	AnimationNodeTimeScale();
 };
 };
@@ -299,7 +325,7 @@ public:
 
 
 	virtual String get_caption() 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;
 
 
 	AnimationNodeTimeSeek();
 	AnimationNodeTimeSeek();
 };
 };
@@ -309,11 +335,11 @@ class AnimationNodeTransition : public AnimationNodeSync {
 
 
 	struct InputData {
 	struct InputData {
 		bool auto_advance = false;
 		bool auto_advance = false;
+		bool break_loop_at_end = false;
 		bool reset = true;
 		bool reset = true;
 	};
 	};
 	Vector<InputData> input_data;
 	Vector<InputData> input_data;
 
 
-	StringName time = "time";
 	StringName prev_xfading = "prev_xfading";
 	StringName prev_xfading = "prev_xfading";
 	StringName prev_index = "prev_index";
 	StringName prev_index = "prev_index";
 	StringName current_index = PNAME("current_index");
 	StringName current_index = PNAME("current_index");
@@ -351,6 +377,9 @@ public:
 	void set_input_as_auto_advance(int p_input, bool p_enable);
 	void set_input_as_auto_advance(int p_input, bool p_enable);
 	bool is_input_set_as_auto_advance(int p_input) const;
 	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);
 	void set_input_reset(int p_input, bool p_enable);
 	bool is_input_reset(int p_input) const;
 	bool is_input_reset(int p_input) const;
 
 
@@ -363,7 +392,7 @@ public:
 	void set_allow_transition_to_self(bool p_enable);
 	void set_allow_transition_to_self(bool p_enable);
 	bool is_allow_transition_to_self() const;
 	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();
 	AnimationNodeTransition();
 };
 };
@@ -373,7 +402,7 @@ class AnimationNodeOutput : public AnimationNode {
 
 
 public:
 public:
 	virtual String get_caption() 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;
 	AnimationNodeOutput();
 	AnimationNodeOutput();
 };
 };
 
 
@@ -445,7 +474,7 @@ public:
 	void get_node_connections(List<NodeConnection> *r_connections) const;
 	void get_node_connections(List<NodeConnection> *r_connections) const;
 
 
 	virtual String get_caption() 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;
 
 
 	void get_node_list(List<StringName> *r_list);
 	void get_node_list(List<StringName> *r_list);
 
 

+ 65 - 73
scene/animation/animation_node_state_machine.cpp

@@ -101,12 +101,22 @@ float AnimationNodeStateMachineTransition::get_xfade_time() const {
 
 
 void AnimationNodeStateMachineTransition::set_xfade_curve(const Ref<Curve> &p_curve) {
 void AnimationNodeStateMachineTransition::set_xfade_curve(const Ref<Curve> &p_curve) {
 	xfade_curve = p_curve;
 	xfade_curve = p_curve;
+	emit_changed();
 }
 }
 
 
 Ref<Curve> AnimationNodeStateMachineTransition::get_xfade_curve() const {
 Ref<Curve> AnimationNodeStateMachineTransition::get_xfade_curve() const {
 	return xfade_curve;
 	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) {
 void AnimationNodeStateMachineTransition::set_reset(bool p_reset) {
 	reset = p_reset;
 	reset = p_reset;
 	emit_changed();
 	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("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("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("set_reset", "reset"), &AnimationNodeStateMachineTransition::set_reset);
 	ClassDB::bind_method(D_METHOD("is_reset"), &AnimationNodeStateMachineTransition::is_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::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::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::BOOL, "reset"), "set_reset", "is_reset");
 
 
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "priority", PROPERTY_HINT_RANGE, "0,32,1"), "set_priority", "get_priority");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "priority", PROPERTY_HINT_RANGE, "0,32,1"), "set_priority", "get_priority");
@@ -310,19 +324,19 @@ TypedArray<StringName> AnimationNodeStateMachinePlayback::_get_travel_path() con
 }
 }
 
 
 float AnimationNodeStateMachinePlayback::get_current_play_pos() const {
 float AnimationNodeStateMachinePlayback::get_current_play_pos() const {
-	return pos_current;
+	return current_nti.position;
 }
 }
 
 
 float AnimationNodeStateMachinePlayback::get_current_length() const {
 float AnimationNodeStateMachinePlayback::get_current_length() const {
-	return len_current;
+	return current_nti.length;
 }
 }
 
 
 float AnimationNodeStateMachinePlayback::get_fade_from_play_pos() const {
 float AnimationNodeStateMachinePlayback::get_fade_from_play_pos() const {
-	return pos_fade_from;
+	return fadeing_from_nti.position;
 }
 }
 
 
 float AnimationNodeStateMachinePlayback::get_fade_from_length() const {
 float AnimationNodeStateMachinePlayback::get_fade_from_length() const {
-	return len_fade_from;
+	return fadeing_from_nti.length;
 }
 }
 
 
 float AnimationNodeStateMachinePlayback::get_fading_time() const {
 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();
 	start_request = StringName();
 	next_request = false;
 	next_request = false;
 	stop_request = false;
 	stop_request = false;
 	reset_request_on_teleport = 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);
 	_set_base_path(p_base_path);
 
 
 	AnimationTree *tree = p_state_machine->process_state->tree;
 	AnimationTree *tree = p_state_machine->process_state->tree;
 
 
 	double p_time = p_playback_info.time;
 	double p_time = p_playback_info.time;
+	double p_delta = p_playback_info.delta;
 	bool p_seek = p_playback_info.seeked;
 	bool p_seek = p_playback_info.seeked;
 	bool p_is_external_seeking = p_playback_info.is_external_seeking;
 	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) {
 			if (p_state_machine->get_state_machine_type() != AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) {
 				path.clear();
 				path.clear();
 				_clear_path_children(tree, p_state_machine, p_test_only);
 				_clear_path_children(tree, p_state_machine, p_test_only);
-				_start(p_state_machine);
 			}
 			}
+			_start(p_state_machine);
 			reset_request = true;
 			reset_request = true;
 		} else {
 		} else {
 			// Reset current state.
 			// Reset current state.
@@ -705,11 +720,11 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
 		travel_request = StringName();
 		travel_request = StringName();
 		path.clear();
 		path.clear();
 		playing = false;
 		playing = false;
-		return 0;
+		return AnimationNode::NodeTimeInfo();
 	}
 	}
 
 
 	if (!playing && start_request != StringName() && travel_request != StringName()) {
 	if (!playing && start_request != StringName() && travel_request != StringName()) {
-		return 0;
+		return AnimationNode::NodeTimeInfo();
 	}
 	}
 
 
 	// Process start/travel request.
 	// Process start/travel request.
@@ -732,7 +747,7 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
 			_start(p_state_machine);
 			_start(p_state_machine);
 		} else {
 		} else {
 			StringName node = start_request;
 			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;
 					teleport_request = true;
 				}
 				}
 			} else {
 			} 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;
 		teleport_request = false;
 		// Clear fadeing on teleport.
 		// Clear fadeing on teleport.
 		fading_from = StringName();
 		fading_from = StringName();
+		fadeing_from_nti = AnimationNode::NodeTimeInfo();
 		fading_pos = 0;
 		fading_pos = 0;
 		// Init current length.
 		// Init current length.
-		pos_current = 0; // Overwritten suddenly in main process.
-
 		pi.time = 0;
 		pi.time = 0;
 		pi.seeked = true;
 		pi.seeked = true;
 		pi.is_external_seeking = false;
 		pi.is_external_seeking = false;
 		pi.weight = 0;
 		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.
 		// Don't process first node if not necessary, insteads process next node.
 		_transition_to_next_recursive(tree, p_state_machine, p_test_only);
 		_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)) {
 	if (!p_state_machine->states.has(current)) {
 		playing = false; // Current does not exist.
 		playing = false; // Current does not exist.
 		_set_current(p_state_machine, StringName());
 		_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).
 	// 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();
 			fading_from = StringName();
 		} else {
 		} else {
 			if (!p_seek) {
 			if (!p_seek) {
-				fading_pos += p_time;
+				fading_pos += Math::abs(p_delta);
 			}
 			}
 			fade_blend = MIN(1.0, fading_pos / fading_time);
 			fade_blend = MIN(1.0, fading_pos / fading_time);
 		}
 		}
@@ -829,18 +842,14 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
 	}
 	}
 
 
 	// Main process.
 	// Main process.
-	double rem = 0.0;
 	pi = p_playback_info;
 	pi = p_playback_info;
 	pi.weight = fade_blend;
 	pi.weight = fade_blend;
 	if (reset_request) {
 	if (reset_request) {
 		reset_request = false;
 		reset_request = false;
 		pi.time = 0;
 		pi.time = 0;
 		pi.seeked = true;
 		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.
 	// Cross-fade process.
 	if (fading_from != StringName()) {
 	if (fading_from != StringName()) {
@@ -852,7 +861,6 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
 			fade_blend_inv = 1.0;
 			fade_blend_inv = 1.0;
 		}
 		}
 
 
-		float fading_from_rem = 0.0;
 		pi = p_playback_info;
 		pi = p_playback_info;
 		pi.weight = fade_blend_inv;
 		pi.weight = fade_blend_inv;
 		if (_reset_request_for_fading_from) {
 		if (_reset_request_for_fading_from) {
@@ -860,57 +868,41 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
 			pi.time = 0;
 			pi.time = 0;
 			pi.seeked = true;
 			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) {
 		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.
 	// Find next and see when to transition.
 	_transition_to_next_recursive(tree, p_state_machine, p_test_only);
 	_transition_to_next_recursive(tree, p_state_machine, p_test_only);
 
 
 	// Predict remaining time.
 	// 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) {
 	if (p_state_machine->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_NESTED) {
 		// There is no next transition.
 		// There is no next transition.
 		if (!p_state_machine->has_transition_from(current)) {
 		if (!p_state_machine->has_transition_from(current)) {
 			if (fading_from != StringName()) {
 			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 (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()) {
 	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) {
 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);
 				p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only);
 			}
 			}
 			fading_from = StringName();
 			fading_from = StringName();
+			fadeing_from_nti = AnimationNode::NodeTimeInfo();
 			fading_time = 0;
 			fading_time = 0;
 			fading_pos = 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_for_fading_from = reset_request; // To avoid processing doubly, it must be reset in the fading process within _process().
 		reset_request = next.is_reset;
 		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) {
 		if (next.switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_SYNC) {
-			pi.time = MIN(pos_current, len_current);
+			pi.time = current_nti.position;
 			pi.seeked = true;
 			pi.seeked = true;
 			pi.is_external_seeking = false;
 			pi.is_external_seeking = false;
 			pi.weight = 0;
 			pi.weight = 0;
@@ -980,24 +972,11 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT
 		}
 		}
 
 
 		// Just get length to find next recursive.
 		// Just get length to find next recursive.
-		double rem = 0.0;
 		pi.time = 0;
 		pi.time = 0;
 		pi.is_external_seeking = false;
 		pi.is_external_seeking = false;
 		pi.weight = 0;
 		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.
 		// Fading must be processed.
 		if (fading_time) {
 		if (fading_time) {
@@ -1028,6 +1007,7 @@ bool AnimationNodeStateMachinePlayback::_can_transition_to_next(AnimationTree *p
 			playback->_next_main();
 			playback->_next_main();
 			// Then, fadeing should be end.
 			// Then, fadeing should be end.
 			fading_from = StringName();
 			fading_from = StringName();
+			fadeing_from_nti = AnimationNode::NodeTimeInfo();
 			fading_pos = 0;
 			fading_pos = 0;
 		} else {
 		} else {
 			return true;
 			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) {
 	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;
 	return true;
 }
 }
@@ -1084,6 +1064,7 @@ AnimationNodeStateMachinePlayback::NextInfo AnimationNodeStateMachinePlayback::_
 				next.curve = ref_transition->get_xfade_curve();
 				next.curve = ref_transition->get_xfade_curve();
 				next.switch_mode = ref_transition->get_switch_mode();
 				next.switch_mode = ref_transition->get_switch_mode();
 				next.is_reset = ref_transition->is_reset();
 				next.is_reset = ref_transition->is_reset();
+				next.break_loop_at_end = ref_transition->is_loop_broken_at_end();
 			}
 			}
 		}
 		}
 	} else {
 	} else {
@@ -1113,6 +1094,7 @@ AnimationNodeStateMachinePlayback::NextInfo AnimationNodeStateMachinePlayback::_
 			next.curve = ref_transition->get_xfade_curve();
 			next.curve = ref_transition->get_xfade_curve();
 			next.switch_mode = ref_transition->get_switch_mode();
 			next.switch_mode = ref_transition->get_switch_mode();
 			next.is_reset = ref_transition->is_reset();
 			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<PropertyInfo> *r_list) const {
 void AnimationNodeStateMachine::get_parameter_list(List<PropertyInfo> *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.
 	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<StringName> advance_conditions;
 	List<StringName> advance_conditions;
 	for (int i = 0; i < transitions.size(); i++) {
 	for (int i = 0; i < transitions.size(); i++) {
@@ -1249,6 +1232,11 @@ void AnimationNodeStateMachine::get_parameter_list(List<PropertyInfo> *r_list) c
 }
 }
 
 
 Variant AnimationNodeStateMachine::get_parameter_default_value(const StringName &p_parameter) const {
 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) {
 	if (p_parameter == playback) {
 		Ref<AnimationNodeStateMachinePlayback> p;
 		Ref<AnimationNodeStateMachinePlayback> p;
 		p.instantiate();
 		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 {
 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) {
 	if (p_parameter == playback) {
 		return true;
 		return true;
 	}
 	}
@@ -1622,9 +1614,9 @@ Vector2 AnimationNodeStateMachine::get_graph_offset() const {
 	return graph_offset;
 	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<AnimationNodeStateMachinePlayback> playback_new = get_parameter(playback);
 	Ref<AnimationNodeStateMachinePlayback> 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);
 	playback_new->_set_grouped(state_machine_type == STATE_MACHINE_TYPE_GROUPED);
 	if (p_test_only) {
 	if (p_test_only) {
 		playback_new = playback_new->duplicate(); // Don't process original when testing.
 		playback_new = playback_new->duplicate(); // Don't process original when testing.

+ 10 - 9
scene/animation/animation_node_state_machine.h

@@ -57,6 +57,7 @@ private:
 	StringName advance_condition_name;
 	StringName advance_condition_name;
 	float xfade_time = 0.0;
 	float xfade_time = 0.0;
 	Ref<Curve> xfade_curve;
 	Ref<Curve> xfade_curve;
+	bool break_loop_at_end = false;
 	bool reset = true;
 	bool reset = true;
 	int priority = 1;
 	int priority = 1;
 	String advance_expression;
 	String advance_expression;
@@ -85,6 +86,9 @@ public:
 	void set_xfade_time(float p_xfade);
 	void set_xfade_time(float p_xfade);
 	float get_xfade_time() const;
 	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);
 	void set_reset(bool p_reset);
 	bool is_reset() const;
 	bool is_reset() const;
 
 
@@ -210,7 +214,7 @@ public:
 	void set_graph_offset(const Vector2 &p_offset);
 	void set_graph_offset(const Vector2 &p_offset);
 	Vector2 get_graph_offset() const;
 	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 String get_caption() const override;
 
 
 	virtual Ref<AnimationNode> get_child_by_name(const StringName &p_name) const override;
 	virtual Ref<AnimationNode> get_child_by_name(const StringName &p_name) const override;
@@ -246,6 +250,7 @@ class AnimationNodeStateMachinePlayback : public Resource {
 		Ref<Curve> curve;
 		Ref<Curve> curve;
 		AnimationNodeStateMachineTransition::SwitchMode switch_mode;
 		AnimationNodeStateMachineTransition::SwitchMode switch_mode;
 		bool is_reset;
 		bool is_reset;
+		bool break_loop_at_end;
 	};
 	};
 
 
 	struct ChildStateMachineInfo {
 	struct ChildStateMachineInfo {
@@ -257,18 +262,14 @@ class AnimationNodeStateMachinePlayback : public Resource {
 	Ref<AnimationNodeStateMachineTransition> default_transition;
 	Ref<AnimationNodeStateMachineTransition> default_transition;
 	String base_path;
 	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;
 	StringName current;
 	Ref<Curve> current_curve;
 	Ref<Curve> current_curve;
 
 
 	Ref<AnimationNodeStateMachineTransition> group_start_transition;
 	Ref<AnimationNodeStateMachineTransition> group_start_transition;
 	Ref<AnimationNodeStateMachineTransition> group_end_transition;
 	Ref<AnimationNodeStateMachineTransition> group_end_transition;
 
 
+	AnimationNode::NodeTimeInfo fadeing_from_nti;
 	StringName fading_from;
 	StringName fading_from;
 	float fading_time = 0.0;
 	float fading_time = 0.0;
 	float fading_pos = 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);
 	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);
 	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<AnimationNodeStateMachine> p_state_machine, const Ref<AnimationNodeStateMachineTransition> p_transition) const;
 	bool _check_advance_condition(const Ref<AnimationNodeStateMachine> p_state_machine, const Ref<AnimationNodeStateMachineTransition> p_transition) const;
 	bool _transition_to_next_recursive(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_test_only);
 	bool _transition_to_next_recursive(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_test_only);

+ 71 - 28
scene/animation/animation_tree.cpp

@@ -46,6 +46,10 @@ void AnimationNode::get_parameter_list(List<PropertyInfo> *r_list) const {
 			r_list->push_back(PropertyInfo::from_dict(d));
 			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 {
 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 AnimationNode::is_parameter_read_only(const StringName &p_parameter) const {
 	bool ret = false;
 	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) {
 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;
 	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<ChildNode> *r_child_nodes) {
 void AnimationNode::get_child_nodes(List<ChildNode> *r_child_nodes) {
 	Dictionary cn;
 	Dictionary cn;
 	if (GDVIRTUAL_CALL(_get_child_nodes, 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);
 	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;
 	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;
 	process_state = nullptr;
-	return t;
+	return nti;
 }
 }
 
 
 void AnimationNode::make_invalid(const String &p_reason) {
 void AnimationNode::make_invalid(const String &p_reason) {
@@ -122,11 +147,11 @@ AnimationTree *AnimationNode::get_animation_tree() const {
 	return process_state->tree;
 	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<AnimationNodeBlendTree>(node_state.parent);
 	AnimationNodeBlendTree *blend_tree = Object::cast_to<AnimationNodeBlendTree>(node_state.parent);
-	ERR_FAIL_NULL_V(blend_tree, 0);
+	ERR_FAIL_NULL_V(blend_tree, NodeTimeInfo());
 
 
 	// Update connections.
 	// Update connections.
 	StringName current_name = blend_tree->get_node_name(Ref<AnimationNode>(this));
 	StringName current_name = blend_tree->get_node_name(Ref<AnimationNode>(this));
@@ -136,32 +161,31 @@ double AnimationNode::blend_input(int p_input, AnimationMixer::PlaybackInfo p_pl
 	StringName node_name = node_state.connections[p_input];
 	StringName node_name = node_state.connections[p_input];
 	if (!blend_tree->has_node(node_name)) {
 	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));
 		make_invalid(vformat(RTR("Nothing connected to input '%s' of node '%s'."), get_input_name(p_input), current_name));
-		return 0;
+		return NodeTimeInfo();
 	}
 	}
 
 
 	Ref<AnimationNode> node = blend_tree->get_node(node_name);
 	Ref<AnimationNode> 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;
 	real_t activity = 0.0;
 	Vector<AnimationTree::Activity> *activity_ptr = process_state->tree->input_activity_map.getptr(node_state.base_path);
 	Vector<AnimationTree::Activity> *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()) {
 	if (activity_ptr && p_input < activity_ptr->size()) {
 		activity_ptr->write[p_input].last_pass = process_state->last_pass;
 		activity_ptr->write[p_input].last_pass = process_state->last_pass;
 		activity_ptr->write[p_input].activity = activity;
 		activity_ptr->write[p_input].activity = activity;
 	}
 	}
-	return ret;
+	return nti;
 }
 }
 
 
-double AnimationNode::blend_node(Ref<AnimationNode> 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<AnimationNode> 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();
 	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);
 	return _blend_node(p_node, p_subpath, this, p_playback_info, p_filter, p_sync, p_test_only, nullptr);
 }
 }
 
 
-double AnimationNode::_blend_node(Ref<AnimationNode> 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<AnimationNode> 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();
 	int blend_count = node_state.track_weights.size();
 
 
@@ -261,7 +285,7 @@ double AnimationNode::_blend_node(Ref<AnimationNode> p_node, const StringName &p
 		new_parent = p_new_parent;
 		new_parent = p_new_parent;
 		new_path = String(node_state.base_path) + String(p_subpath) + "/";
 		new_path = String(node_state.base_path) + String(p_subpath) + "/";
 	} else {
 	} else {
-		ERR_FAIL_NULL_V(node_state.parent, 0);
+		ERR_FAIL_NULL_V(node_state.parent, NodeTimeInfo());
 		new_parent = node_state.parent;
 		new_parent = node_state.parent;
 		new_path = String(new_parent->node_state.base_path) + String(p_subpath) + "/";
 		new_path = String(new_parent->node_state.base_path) + String(p_subpath) + "/";
 	}
 	}
@@ -271,7 +295,7 @@ double AnimationNode::_blend_node(Ref<AnimationNode> p_node, const StringName &p
 	p_node->node_state.base_path = new_path;
 	p_node->node_state.base_path = new_path;
 	p_node->node_state.parent = new_parent;
 	p_node->node_state.parent = new_parent;
 	if (!p_playback_info.seeked && !p_sync && !any_valid) {
 	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);
 	}
 	}
 	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;
 	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;
 	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) {
 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<AnimationN
 	info.seeked = p_seek;
 	info.seeked = p_seek;
 	info.is_external_seeking = p_is_external_seeking;
 	info.is_external_seeking = p_is_external_seeking;
 	info.weight = p_blend;
 	info.weight = p_blend;
-	return blend_node(p_node, p_sub_path, info, p_filter, p_sync, p_test_only);
+	NodeTimeInfo nti = blend_node(p_node, p_sub_path, info, p_filter, p_sync, p_test_only);
+	return nti.length - nti.position;
 }
 }
 
 
 double AnimationNode::blend_input_ex(int p_input, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter, bool p_sync, bool p_test_only) {
 double AnimationNode::blend_input_ex(int p_input, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter, bool p_sync, bool p_test_only) {
@@ -441,7 +482,8 @@ double AnimationNode::blend_input_ex(int p_input, double p_time, bool p_seek, bo
 	info.seeked = p_seek;
 	info.seeked = p_seek;
 	info.is_external_seeking = p_is_external_seeking;
 	info.is_external_seeking = p_is_external_seeking;
 	info.weight = p_blend;
 	info.weight = p_blend;
-	return blend_input(p_input, info, p_filter, p_sync, p_test_only);
+	NodeTimeInfo nti = blend_input(p_input, info, p_filter, p_sync, p_test_only);
+	return nti.length - nti.position;
 }
 }
 
 
 void AnimationNode::_bind_methods() {
 void AnimationNode::_bind_methods() {
@@ -568,11 +610,12 @@ bool AnimationTree::_blend_pre_process(double p_delta, int p_track_count, const
 		if (started) {
 		if (started) {
 			// If started, seek.
 			// If started, seek.
 			pi.seeked = true;
 			pi.seeked = true;
+			pi.delta = p_delta;
 			root_animation_node->_pre_process(&process_state, pi, false);
 			root_animation_node->_pre_process(&process_state, pi, false);
 			started = false;
 			started = false;
 		} else {
 		} else {
 			pi.seeked = false;
 			pi.seeked = false;
-			pi.time = p_delta;
+			pi.delta = p_delta;
 			root_animation_node->_pre_process(&process_state, pi, false);
 			root_animation_node->_pre_process(&process_state, pi, false);
 		}
 		}
 	}
 	}

+ 44 - 6
scene/animation/animation_tree.h

@@ -63,6 +63,34 @@ public:
 	HashMap<NodePath, bool> filter;
 	HashMap<NodePath, bool> filter;
 	bool filter_enabled = false;
 	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.
 	// Temporary state for blending process which needs to be stored in each AnimationNodes.
 	struct NodeState {
 	struct NodeState {
 		StringName base_path;
 		StringName base_path;
@@ -84,16 +112,23 @@ public:
 	Array _get_filters() const;
 	Array _get_filters() const;
 	void _set_filters(const Array &p_filters);
 	void _set_filters(const Array &p_filters);
 	friend class AnimationNodeBlendTree;
 	friend class AnimationNodeBlendTree;
-	double _blend_node(Ref<AnimationNode> 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<AnimationNode> 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:
 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);
 	void blend_animation(const StringName &p_animation, AnimationMixer::PlaybackInfo p_playback_info);
-	double blend_node(Ref<AnimationNode> 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<AnimationNode> 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.
 	// 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);
 	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);
 	void set_parameter(const StringName &p_name, const Variant &p_value);
 	Variant get_parameter(const StringName &p_name) const;
 	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 {
 	struct ChildNode {
 		StringName name;
 		StringName name;
 		Ref<AnimationNode> node;
 		Ref<AnimationNode> node;

+ 3 - 1
scene/resources/animation.h

@@ -75,6 +75,7 @@ public:
 		LOOP_PINGPONG,
 		LOOP_PINGPONG,
 	};
 	};
 
 
+	// LoopedFlag is used in Animataion to "process the keys at both ends correct".
 	enum LoopedFlag {
 	enum LoopedFlag {
 		LOOPED_FLAG_NONE,
 		LOOPED_FLAG_NONE,
 		LOOPED_FLAG_END,
 		LOOPED_FLAG_END,
@@ -187,6 +188,7 @@ private:
 	};
 	};
 
 
 	/* BEZIER TRACK */
 	/* BEZIER TRACK */
+
 	struct BezierKey {
 	struct BezierKey {
 		Vector2 in_handle; // Relative (x always <0)
 		Vector2 in_handle; // Relative (x always <0)
 		Vector2 out_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 {
 	struct AnimationTrack : public Track {
 		Vector<TKey<StringName>> values;
 		Vector<TKey<StringName>> values;

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.