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();