diff --git a/doc/classes/SubtweenTweener.xml b/doc/classes/SubtweenTweener.xml
new file mode 100644
index 00000000000..c8999ac4f3d
--- /dev/null
+++ b/doc/classes/SubtweenTweener.xml
@@ -0,0 +1,21 @@
+
+
+
+ Runs a [Tween] nested within another [Tween].
+
+
+ [SubtweenTweener] is used to execute a [Tween] as one step in a sequence defined by another [Tween]. See [method Tween.tween_subtween] for more usage information.
+ [b]Note:[/b] [method Tween.tween_subtween] is the only correct way to create [SubtweenTweener]. Any [SubtweenTweener] created manually will not function correctly.
+
+
+
+
+
+
+
+
+ Sets the time in seconds after which the [SubtweenTweener] will start running the subtween. By default there's no delay.
+
+
+
+
diff --git a/doc/classes/Tween.xml b/doc/classes/Tween.xml
index 3b5b6f7844b..45fb9f92a80 100644
--- a/doc/classes/Tween.xml
+++ b/doc/classes/Tween.xml
@@ -473,6 +473,27 @@
[/codeblocks]
+
+
+
+
+ Creates and appends a [SubtweenTweener]. This method can be used to nest [param subtween] within this [Tween], allowing for the creation of more complex and composable sequences.
+ [codeblock]
+ # Subtween will rotate the object.
+ var subtween = create_tween()
+ subtween.tween_property(self, "rotation_degrees", 45.0, 1.0)
+ subtween.tween_property(self, "rotation_degrees", 0.0, 1.0)
+
+ # Parent tween will execute the subtween as one of its steps.
+ var tween = create_tween()
+ tween.tween_property(self, "position:x", 500, 3.0)
+ tween.tween_subtween(subtween)
+ tween.tween_property(self, "position:x", 300, 2.0)
+ [/codeblock]
+ [b]Note:[/b] The methods [method pause], [method stop], and [method set_loops] can cause the parent [Tween] to get stuck on the subtween step; see the documentation for those methods for more information.
+ [b]Note:[/b] The pause and process modes set by [method set_pause_mode] and [method set_process_mode] on [param subtween] will be overridden by the parent [Tween]'s settings.
+
+
diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp
index 7a2c7643558..4fa78cf5712 100644
--- a/scene/animation/tween.cpp
+++ b/scene/animation/tween.cpp
@@ -154,6 +154,25 @@ Ref Tween::tween_method(const Callable &p_callback, const Variant
return tweener;
}
+Ref Tween::tween_subtween(const Ref &p_subtween) {
+ CHECK_VALID();
+
+ // Ensure that the subtween being added is not null.
+ ERR_FAIL_COND_V(p_subtween.is_null(), nullptr);
+
+ Ref tweener;
+ tweener.instantiate(p_subtween);
+
+ // Remove the tween from its parent tree, if it has one.
+ // If the user created this tween without a parent tree attached,
+ // then this step isn't necessary.
+ if (tweener->subtween->parent_tree != nullptr) {
+ tweener->subtween->parent_tree->remove_tween(tweener->subtween);
+ }
+ append(tweener);
+ return tweener;
+}
+
void Tween::append(Ref p_tweener) {
p_tweener->set_tween(this);
@@ -447,6 +466,7 @@ void Tween::_bind_methods() {
ClassDB::bind_method(D_METHOD("tween_interval", "time"), &Tween::tween_interval);
ClassDB::bind_method(D_METHOD("tween_callback", "callback"), &Tween::tween_callback);
ClassDB::bind_method(D_METHOD("tween_method", "method", "from", "to", "duration"), &Tween::tween_method);
+ ClassDB::bind_method(D_METHOD("tween_subtween", "subtween"), &Tween::tween_subtween);
ClassDB::bind_method(D_METHOD("custom_step", "delta"), &Tween::custom_step);
ClassDB::bind_method(D_METHOD("stop"), &Tween::stop);
@@ -512,6 +532,11 @@ Tween::Tween(bool p_valid) {
valid = p_valid;
}
+Tween::Tween(SceneTree *p_parent_tree) {
+ parent_tree = p_parent_tree;
+ valid = true;
+}
+
Ref PropertyTweener::from(const Variant &p_value) {
Ref tween = _get_tween();
ERR_FAIL_COND_V(tween.is_null(), nullptr);
@@ -854,3 +879,51 @@ MethodTweener::MethodTweener(const Callable &p_callback, const Variant &p_from,
MethodTweener::MethodTweener() {
ERR_FAIL_MSG("MethodTweener can't be created directly. Use the tween_method() method in Tween.");
}
+
+void SubtweenTweener::start() {
+ elapsed_time = 0;
+ finished = false;
+
+ // Reset the subtween.
+ subtween->stop();
+ subtween->play();
+}
+
+bool SubtweenTweener::step(double &r_delta) {
+ if (finished) {
+ return false;
+ }
+
+ elapsed_time += r_delta;
+
+ if (elapsed_time < delay) {
+ r_delta = 0;
+ return true;
+ }
+
+ if (!subtween->step(r_delta)) {
+ r_delta = elapsed_time - delay - subtween->get_total_time();
+ _finish();
+ return false;
+ }
+
+ r_delta = 0;
+ return true;
+}
+
+Ref SubtweenTweener::set_delay(double p_delay) {
+ delay = p_delay;
+ return this;
+}
+
+void SubtweenTweener::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_delay", "delay"), &SubtweenTweener::set_delay);
+}
+
+SubtweenTweener::SubtweenTweener(const Ref &p_subtween) {
+ subtween = p_subtween;
+}
+
+SubtweenTweener::SubtweenTweener() {
+ ERR_FAIL_MSG("SubtweenTweener can't be created directly. Use the tween_subtween() method in Tween.");
+}
diff --git a/scene/animation/tween.h b/scene/animation/tween.h
index 4b9ffbfae19..793a5aa4b4b 100644
--- a/scene/animation/tween.h
+++ b/scene/animation/tween.h
@@ -35,6 +35,7 @@
class Tween;
class Node;
+class SceneTree;
class Tweener : public RefCounted {
GDCLASS(Tweener, RefCounted);
@@ -60,6 +61,7 @@ class PropertyTweener;
class IntervalTweener;
class CallbackTweener;
class MethodTweener;
+class SubtweenTweener;
class Tween : public RefCounted {
GDCLASS(Tween, RefCounted);
@@ -109,6 +111,7 @@ private:
EaseType default_ease = EaseType::EASE_IN_OUT;
ObjectID bound_node;
+ SceneTree *parent_tree = nullptr;
Vector>> tweeners;
double total_time = 0;
int current_step = -1;
@@ -145,6 +148,7 @@ public:
Ref tween_interval(double p_time);
Ref tween_callback(const Callable &p_callback);
Ref tween_method(const Callable &p_callback, const Variant p_from, Variant p_to, double p_duration);
+ Ref tween_subtween(const Ref &p_subtween);
void append(Ref p_tweener);
bool custom_step(double p_delta);
@@ -187,6 +191,7 @@ public:
Tween();
Tween(bool p_valid);
+ Tween(SceneTree *p_parent_tree);
};
VARIANT_ENUM_CAST(Tween::TweenPauseMode);
@@ -305,4 +310,24 @@ private:
Ref ref_copy;
};
+class SubtweenTweener : public Tweener {
+ GDCLASS(SubtweenTweener, Tweener);
+
+public:
+ Ref subtween;
+ void start() override;
+ bool step(double &r_delta) override;
+
+ Ref set_delay(double p_delay);
+
+ SubtweenTweener(const Ref &p_subtween);
+ SubtweenTweener();
+
+protected:
+ static void _bind_methods();
+
+private:
+ double delay = 0;
+};
+
#endif // TWEEN_H
diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp
index a6c438e4c72..a48885b77da 100644
--- a/scene/main/scene_tree.cpp
+++ b/scene/main/scene_tree.cpp
@@ -1583,11 +1583,17 @@ Ref SceneTree::create_timer(double p_delay_sec, bool p_process_a
Ref SceneTree::create_tween() {
_THREAD_SAFE_METHOD_
- Ref tween = memnew(Tween(true));
+ Ref tween;
+ tween.instantiate(this);
tweens.push_back(tween);
return tween;
}
+bool SceneTree::remove_tween(const Ref &p_tween) {
+ _THREAD_SAFE_METHOD_
+ return tweens.erase(p_tween);
+}
+
TypedArray SceneTree::get_processed_tweens() {
_THREAD_SAFE_METHOD_
TypedArray ret;
diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h
index f7df4c12a84..8d1d53917a4 100644
--- a/scene/main/scene_tree.h
+++ b/scene/main/scene_tree.h
@@ -411,6 +411,7 @@ public:
Ref create_timer(double p_delay_sec, bool p_process_always = true, bool p_process_in_physics = false, bool p_ignore_time_scale = false);
Ref create_tween();
+ bool remove_tween(const Ref &p_tween);
TypedArray get_processed_tweens();
//used by Main::start, don't use otherwise
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index 64bc90f8843..53d8043329c 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -504,6 +504,7 @@ void register_scene_types() {
GDREGISTER_CLASS(IntervalTweener);
GDREGISTER_CLASS(CallbackTweener);
GDREGISTER_CLASS(MethodTweener);
+ GDREGISTER_CLASS(SubtweenTweener);
GDREGISTER_ABSTRACT_CLASS(AnimationMixer);
GDREGISTER_CLASS(AnimationPlayer);