diff --git a/doc/classes/SceneTreeTween.xml b/doc/classes/SceneTreeTween.xml
index c90f2991be67..e456ab9ee4ee 100644
--- a/doc/classes/SceneTreeTween.xml
+++ b/doc/classes/SceneTreeTween.xml
@@ -300,6 +300,37 @@
[/codeblock]
+
+
+
+
+
+ Creates and appends a [YieldTweener]. This method can be used to wait for a signal to be emitted and create asynchronous animations/cutscenes.
+ The animation will not progress to the next step until the yielded signal is emitted or the connection becomes invalid (e.g. as a result of freeing the target object). If you know that the emission may not happen, use [method YieldTweener.set_timeout].
+ [b]Note:[/b] If you are yielding a signal from a method called in the same [SceneTreeTween], make sure the signal is emitted [i]after[/i] the yield starts. If it can't be reasonably guaranteed, you can yield and emit in the same step:
+ [codeblock]
+ var tween = create_tween()
+ tween.tween_yield(object, "signal_name")
+ tween.parallel().tween_callback(object, "method_that_emits_signal")
+ [/codeblock]
+ [b]Example:[/b] An object launches itself and explodes upon collision or after 4 seconds.
+ [codeblock]
+ var tween = create_tween()
+ tween.tween_callback(self, "launch")
+ tween.tween_yield(self, "collided").set_timeout(4.0)
+ tween.tween_callback(self, "explode")
+ [/codeblock]
+ [b]Example:[/b] A character walks to a specific point, says some lines and walks back when the player closes dialogue.
+ [codeblock]
+ var tween = create_tween()
+ tween.tween_callback(self, "walk_to", [600.0])
+ tween.tween_yield(self, "destination_reached")
+ tween.tween_callback(self, "say_dialogue", ["stuff"])
+ tween.tween_yield(self, "dialogue_closed")
+ tween.tween_callback(self, "walk_to", [0.0])
+ [/codeblock]
+
+
diff --git a/doc/classes/YieldTweener.xml b/doc/classes/YieldTweener.xml
new file mode 100644
index 000000000000..fc84cc68fc02
--- /dev/null
+++ b/doc/classes/YieldTweener.xml
@@ -0,0 +1,23 @@
+
+
+
+ Yields a specified signal.
+
+
+ [YieldTweener] is used to wait for a specified signal to be emitted, allowing asynchronous steps in [SceneTreeTween] animation. See [method SceneTreeTween.tween_yield] for more usage information.
+
+
+
+
+
+
+
+
+ Sets the maximum time a [YieldTweener] can wait for the signal. Can be used as a safeguard for signals that may never be emitted.
+ [code]timeout[/code] should be greater than [code]0[/code]. If not specified, the tweener will wait indefinitely.
+
+
+
+
+
+
diff --git a/scene/animation/scene_tree_tween.cpp b/scene/animation/scene_tree_tween.cpp
index ecaf078b2fbc..754989976f70 100644
--- a/scene/animation/scene_tree_tween.cpp
+++ b/scene/animation/scene_tree_tween.cpp
@@ -111,6 +111,16 @@ Ref SceneTreeTween::tween_method(Object *p_target, StringName p_m
return tweener;
}
+Ref SceneTreeTween::tween_yield(Object *p_target, StringName p_signal) {
+ ERR_FAIL_NULL_V(p_target, nullptr);
+ ERR_FAIL_COND_V_MSG(!valid, nullptr, "SceneTreeTween invalid. Either finished or created outside scene tree.");
+ ERR_FAIL_COND_V_MSG(started, nullptr, "Can't append to a SceneTreeTween that has started. Use stop() first.");
+
+ Ref tweener = memnew(YieldTweener(p_target, p_signal));
+ append(tweener);
+ return tweener;
+}
+
void SceneTreeTween::append(Ref p_tweener) {
p_tweener->set_tween(this);
@@ -582,6 +592,7 @@ void SceneTreeTween::_bind_methods() {
ClassDB::bind_method(D_METHOD("tween_interval", "time"), &SceneTreeTween::tween_interval);
ClassDB::bind_method(D_METHOD("tween_callback", "object", "method", "binds"), &SceneTreeTween::tween_callback, DEFVAL(Array()));
ClassDB::bind_method(D_METHOD("tween_method", "object", "method", "from", "to", "duration", "binds"), &SceneTreeTween::tween_method, DEFVAL(Array()));
+ ClassDB::bind_method(D_METHOD("tween_yield", "object", "signal"), &SceneTreeTween::tween_yield);
ClassDB::bind_method(D_METHOD("custom_step", "delta"), &SceneTreeTween::custom_step);
ClassDB::bind_method(D_METHOD("stop"), &SceneTreeTween::stop);
@@ -938,3 +949,57 @@ MethodTweener::MethodTweener(Object *p_target, StringName p_method, Variant p_fr
MethodTweener::MethodTweener() {
ERR_FAIL_MSG("Can't create empty MethodTweener. Use get_tree().tween_method() instead.");
}
+
+Ref YieldTweener::set_timeout(float p_timeout) {
+ timeout = p_timeout;
+ return this;
+}
+
+void YieldTweener::start() {
+ Object *target_instance = ObjectDB::get_instance(target);
+ if (!target_instance) {
+ return;
+ }
+
+ target_instance->connect(signal, this, "_signal_received");
+}
+
+bool YieldTweener::step(float &r_delta) {
+ if (received) {
+ return false;
+ }
+
+ if (timeout >= 0) {
+ timeout -= r_delta;
+ if (timeout <= 0) {
+ return false;
+ }
+ }
+
+ Object *target_instance = ObjectDB::get_instance(target);
+ if (!target_instance) {
+ return false;
+ }
+
+ r_delta = 0; // "Consume" all remaining time to prevent infinite loops.
+ return true;
+}
+
+YieldTweener::YieldTweener(Object *p_target, StringName p_signal) {
+ target = p_target->get_instance_id();
+ signal = p_signal;
+}
+
+YieldTweener::YieldTweener() {
+ ERR_FAIL_MSG("Can't create empty YieldTweener. Use get_tree().create_tween().tween_yield() instead.");
+}
+
+Variant YieldTweener::_signal_received(const Variant **p_args, int p_argcount, Variant::CallError &r_error) {
+ received = true;
+ return Variant();
+}
+
+void YieldTweener::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_timeout", "timeout"), &YieldTweener::set_timeout);
+ ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "_signal_received", &YieldTweener::_signal_received, MethodInfo("_signal_received"));
+}
diff --git a/scene/animation/scene_tree_tween.h b/scene/animation/scene_tree_tween.h
index d0dad0bf3c22..a38e9d5f2b5f 100644
--- a/scene/animation/scene_tree_tween.h
+++ b/scene/animation/scene_tree_tween.h
@@ -56,6 +56,7 @@ class PropertyTweener;
class IntervalTweener;
class CallbackTweener;
class MethodTweener;
+class YieldTweener;
class SceneTreeTween : public Reference {
GDCLASS(SceneTreeTween, Reference);
@@ -102,6 +103,7 @@ class SceneTreeTween : public Reference {
Ref tween_interval(float p_time);
Ref tween_callback(Object *p_target, StringName p_method, const Vector &p_binds = Vector());
Ref tween_method(Object *p_target, StringName p_method, Variant p_from, Variant p_to, float p_duration, const Vector &p_binds = Vector());
+ Ref tween_yield(Object *p_target, StringName p_signal);
void append(Ref p_tweener);
bool custom_step(float p_delta);
@@ -253,4 +255,29 @@ class MethodTweener : public Tweener {
Vector binds;
};
+class YieldTweener : public Tweener {
+ GDCLASS(YieldTweener, Tweener);
+
+public:
+ Ref set_timeout(float p_timeout);
+
+ virtual void start();
+ virtual bool step(float &r_delta);
+
+ Variant _signal_received(const Variant **p_args, int p_argcount, Variant::CallError &r_error);
+
+ YieldTweener(Object *p_target, StringName p_signal);
+ YieldTweener();
+
+protected:
+ static void _bind_methods();
+
+private:
+ bool received = false;
+ float timeout = -1;
+
+ ObjectID target;
+ StringName signal;
+};
+
#endif // SCENE_TREE_TWEEN_H
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index 3d2f67a6c258..52ed104a684b 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -404,6 +404,7 @@ void register_scene_types() {
ClassDB::register_class();
ClassDB::register_class();
ClassDB::register_class();
+ ClassDB::register_class();
ClassDB::register_class();
ClassDB::register_class();