diff --git a/doc/classes/AnimationMixer.xml b/doc/classes/AnimationMixer.xml
index a77e9e28c694..98b0ad886d39 100644
--- a/doc/classes/AnimationMixer.xml
+++ b/doc/classes/AnimationMixer.xml
@@ -309,7 +309,7 @@
Notifies when an animation finished playing.
- [b]Note:[/b] This signal is not emitted if an animation is looping.
+ [b]Note:[/b] This signal is not emitted if an animation is looping. For looping animations, use [signal animation_looped] instead.
@@ -322,6 +322,14 @@
Notifies when an animation list is changed.
+
+
+
+
+ Notifies when a looping animation finished playing. [param backwards] is [code]true[/code] if the looping "barrier" has been crossed backwards when playing the animation, [code]false[/code] otherwise. In practice, [param backwards] is [code]true[/code] every time when the animation is played backwards, or every 2 signals emitted when ping-pong looping is used.
+ [b]Note:[/b] This signal is not emitted if an animation is not looping. For non-looping animations, use [signal animation_finished] instead.
+
+
diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp
index f3385b4cdc38..37a98e306783 100644
--- a/scene/animation/animation_blend_tree.cpp
+++ b/scene/animation/animation_blend_tree.cpp
@@ -147,6 +147,7 @@ AnimationNode::NodeTimeInfo AnimationNodeAnimation::_process(const AnimationMixe
if (!Math::is_zero_approx(cur_len)) {
if (prev_time <= cur_len && cur_time > cur_len) {
is_just_looped = true; // Don't break with negative timescale since remain will not be 0.
+ process_state->tree->call_deferred(SNAME("emit_signal"), SceneStringNames::get_singleton()->animation_looped, animation, node_backward);
}
cur_time = Math::fposmod(cur_time, cur_len);
}
@@ -155,9 +156,11 @@ AnimationNode::NodeTimeInfo AnimationNodeAnimation::_process(const AnimationMixe
if (!Math::is_zero_approx(cur_len)) {
if (prev_time >= 0 && cur_time < 0) {
backward = !backward;
+ process_state->tree->call_deferred(SNAME("emit_signal"), SceneStringNames::get_singleton()->animation_looped, animation, !node_backward);
} else if (prev_time <= cur_len && cur_time > cur_len) {
backward = !backward;
is_just_looped = true; // Don't break with negative timescale since remain will not be 0.
+ process_state->tree->call_deferred(SNAME("emit_signal"), SceneStringNames::get_singleton()->animation_looped, animation, node_backward);
}
cur_time = Math::pingpong(cur_time, cur_len);
}
diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp
index d22b58346f03..2461a34842f2 100644
--- a/scene/animation/animation_mixer.cpp
+++ b/scene/animation/animation_mixer.cpp
@@ -2276,6 +2276,7 @@ void AnimationMixer::_bind_methods() {
ADD_SIGNAL(MethodInfo(SNAME("animation_list_changed")));
ADD_SIGNAL(MethodInfo(SNAME("animation_libraries_updated")));
ADD_SIGNAL(MethodInfo(SNAME("animation_finished"), PropertyInfo(Variant::STRING_NAME, "anim_name")));
+ ADD_SIGNAL(MethodInfo(SNAME("animation_looped"), PropertyInfo(Variant::STRING_NAME, "anim_name"), PropertyInfo(Variant::BOOL, "backwards")));
ADD_SIGNAL(MethodInfo(SNAME("animation_started"), PropertyInfo(Variant::STRING_NAME, "anim_name")));
ADD_SIGNAL(MethodInfo(SNAME("caches_cleared")));
ADD_SIGNAL(MethodInfo(SNAME("mixer_applied")));
diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp
index 22138004769d..f44b6a358622 100644
--- a/scene/animation/animation_player.cpp
+++ b/scene/animation/animation_player.cpp
@@ -181,9 +181,11 @@ void AnimationPlayer::_process_playback_data(PlaybackData &cd, double p_delta, f
case Animation::LOOP_LINEAR: {
if (next_pos < 0 && cd.pos >= 0) {
looped_flag = Animation::LOOPED_FLAG_START;
+ emit_signal(SceneStringNames::get_singleton()->animation_looped, playback.assigned, !backwards);
}
if (next_pos > len && cd.pos <= len) {
looped_flag = Animation::LOOPED_FLAG_END;
+ emit_signal(SceneStringNames::get_singleton()->animation_looped, playback.assigned, backwards);
}
next_pos = Math::fposmod(next_pos, (double)len);
} break;
@@ -192,10 +194,12 @@ void AnimationPlayer::_process_playback_data(PlaybackData &cd, double p_delta, f
if (next_pos < 0 && cd.pos >= 0) {
cd.speed_scale *= -1.0;
looped_flag = Animation::LOOPED_FLAG_START;
+ emit_signal(SceneStringNames::get_singleton()->animation_looped, playback.assigned, !backwards);
}
if (next_pos > len && cd.pos <= len) {
cd.speed_scale *= -1.0;
looped_flag = Animation::LOOPED_FLAG_END;
+ emit_signal(SceneStringNames::get_singleton()->animation_looped, playback.assigned, backwards);
}
next_pos = Math::pingpong(next_pos, (double)len);
} break;
diff --git a/scene/scene_string_names.cpp b/scene/scene_string_names.cpp
index 5610b2f6421f..e72a8feba6aa 100644
--- a/scene/scene_string_names.cpp
+++ b/scene/scene_string_names.cpp
@@ -58,6 +58,7 @@ SceneStringNames::SceneStringNames() {
finished = StaticCString::create("finished");
animation_finished = StaticCString::create("animation_finished");
+ animation_looped = StaticCString::create("animation_looped");
animation_changed = StaticCString::create("animation_changed");
animation_started = StaticCString::create("animation_started");
RESET = StaticCString::create("RESET");
diff --git a/scene/scene_string_names.h b/scene/scene_string_names.h
index 60254e3006a9..a9121f4c8781 100644
--- a/scene/scene_string_names.h
+++ b/scene/scene_string_names.h
@@ -94,6 +94,7 @@ class SceneStringNames {
StringName finished;
StringName animation_finished;
+ StringName animation_looped;
StringName animation_changed;
StringName animation_started;
StringName RESET;