Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Tween.tween_subtween method for nesting tweens within each other #98660

Merged
merged 1 commit into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions doc/classes/SubtweenTweener.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="SubtweenTweener" inherits="Tweener" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
Runs a [Tween] nested within another [Tween].
</brief_description>
<description>
[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.
</description>
<tutorials>
</tutorials>
<methods>
<method name="set_delay">
<return type="SubtweenTweener" />
<param index="0" name="delay" type="float" />
<description>
Sets the time in seconds after which the [SubtweenTweener] will start running the subtween. By default there's no delay.
</description>
</method>
</methods>
</class>
21 changes: 21 additions & 0 deletions doc/classes/Tween.xml
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,27 @@
[/codeblocks]
</description>
</method>
<method name="tween_subtween">
<return type="SubtweenTweener" />
<param index="0" name="subtween" type="Tween" />
<description>
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.
</description>
</method>
</methods>
<signals>
<signal name="finished">
Expand Down
73 changes: 73 additions & 0 deletions scene/animation/tween.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,25 @@ Ref<MethodTweener> Tween::tween_method(const Callable &p_callback, const Variant
return tweener;
}

Ref<SubtweenTweener> Tween::tween_subtween(const Ref<Tween> &p_subtween) {
CHECK_VALID();

// Ensure that the subtween being added is not null.
ERR_FAIL_COND_V(p_subtween.is_null(), nullptr);

Ref<SubtweenTweener> 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<Tweener> p_tweener) {
p_tweener->set_tween(this);

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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> PropertyTweener::from(const Variant &p_value) {
Ref<Tween> tween = _get_tween();
ERR_FAIL_COND_V(tween.is_null(), nullptr);
Expand Down Expand Up @@ -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> 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<Tween> &p_subtween) {
subtween = p_subtween;
}

SubtweenTweener::SubtweenTweener() {
ERR_FAIL_MSG("SubtweenTweener can't be created directly. Use the tween_subtween() method in Tween.");
}
25 changes: 25 additions & 0 deletions scene/animation/tween.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

class Tween;
class Node;
class SceneTree;

class Tweener : public RefCounted {
GDCLASS(Tweener, RefCounted);
Expand All @@ -60,6 +61,7 @@ class PropertyTweener;
class IntervalTweener;
class CallbackTweener;
class MethodTweener;
class SubtweenTweener;

class Tween : public RefCounted {
GDCLASS(Tween, RefCounted);
Expand Down Expand Up @@ -109,6 +111,7 @@ class Tween : public RefCounted {
EaseType default_ease = EaseType::EASE_IN_OUT;
ObjectID bound_node;

SceneTree *parent_tree = nullptr;
Vector<List<Ref<Tweener>>> tweeners;
double total_time = 0;
int current_step = -1;
Expand Down Expand Up @@ -145,6 +148,7 @@ class Tween : public RefCounted {
Ref<IntervalTweener> tween_interval(double p_time);
Ref<CallbackTweener> tween_callback(const Callable &p_callback);
Ref<MethodTweener> tween_method(const Callable &p_callback, const Variant p_from, Variant p_to, double p_duration);
Ref<SubtweenTweener> tween_subtween(const Ref<Tween> &p_subtween);
void append(Ref<Tweener> p_tweener);

bool custom_step(double p_delta);
Expand Down Expand Up @@ -187,6 +191,7 @@ class Tween : public RefCounted {

Tween();
Tween(bool p_valid);
Tween(SceneTree *p_parent_tree);
};

VARIANT_ENUM_CAST(Tween::TweenPauseMode);
Expand Down Expand Up @@ -305,4 +310,24 @@ class MethodTweener : public Tweener {
Ref<RefCounted> ref_copy;
};

class SubtweenTweener : public Tweener {
GDCLASS(SubtweenTweener, Tweener);

public:
Ref<Tween> subtween;
void start() override;
bool step(double &r_delta) override;

Ref<SubtweenTweener> set_delay(double p_delay);

SubtweenTweener(const Ref<Tween> &p_subtween);
SubtweenTweener();

protected:
static void _bind_methods();

private:
double delay = 0;
};

#endif // TWEEN_H
8 changes: 7 additions & 1 deletion scene/main/scene_tree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1583,11 +1583,17 @@ Ref<SceneTreeTimer> SceneTree::create_timer(double p_delay_sec, bool p_process_a

Ref<Tween> SceneTree::create_tween() {
_THREAD_SAFE_METHOD_
Ref<Tween> tween = memnew(Tween(true));
Ref<Tween> tween;
tween.instantiate(this);
tweens.push_back(tween);
return tween;
}

bool SceneTree::remove_tween(const Ref<Tween> &p_tween) {
_THREAD_SAFE_METHOD_
return tweens.erase(p_tween);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tweens is a linked List, so erasing can be costly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing that out! That may be a good reason to allow users to manually create Tween objects, and then add them to the SceneTree (or not) as necessary, rather than only allowing them to be created via create_tween.

}

TypedArray<Tween> SceneTree::get_processed_tweens() {
_THREAD_SAFE_METHOD_
TypedArray<Tween> ret;
Expand Down
1 change: 1 addition & 0 deletions scene/main/scene_tree.h
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ class SceneTree : public MainLoop {

Ref<SceneTreeTimer> create_timer(double p_delay_sec, bool p_process_always = true, bool p_process_in_physics = false, bool p_ignore_time_scale = false);
Ref<Tween> create_tween();
bool remove_tween(const Ref<Tween> &p_tween);
TypedArray<Tween> get_processed_tweens();

//used by Main::start, don't use otherwise
Expand Down
1 change: 1 addition & 0 deletions scene/register_scene_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down