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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

Meorge
Copy link
Contributor

@Meorge Meorge commented Oct 30, 2024

This is an implementation of godotengine/godot-proposals#11022, which adds the tween_subtween method to the Tween class. Using this method, Tween objects can be nested within each other and scheduled to play at specified points.

It's not fully complete yet (see the to do list below), but I wanted to get a draft posted with the core functionality so that others could give their thoughts on it. I'm especially unsure about the method used to remove a Tween from the SceneTree's tweens list once it is made into a subtween.

Example usage

extends Sprite2D

var speed := 5.0

func _ready():		
	# Holds the sequence of 5-then-6
	var subtween_5_6: Tween = create_tween()
	subtween_5_6.tween_property(self, "rotation_degrees", 80.0, 0.4 * speed) # 5
	subtween_5_6.tween_property(self, "rotation_degrees", -20.0, 0.6 * speed) # 6
	
	# Holds subsequence of 4 at the same time as 5-then-6
	var subtween_4_5_6: Tween = create_tween()
	subtween_4_5_6.tween_property(self, "position:x", 100, 1.0 * speed) # 4
	subtween_4_5_6.parallel().tween_subtween(subtween_5_6) # 5-then-6

	# Overall tween
	var tween: Tween = create_tween()
	tween.tween_property(self, "position:x", 100, 1.0 * speed) # 1
	tween.tween_property(self, "position:x", 300, 0.5 * speed) # 2
	tween.parallel().tween_property(self, "rotation_degrees", 45.0, 0.3 * speed) # 3
	tween.tween_subtween(subtween_4_5_6) # 4 and 5-then-6
Godot.Tween.Subtween.Video.1.mp4

Example project

tween-subtween.zip

Inside this project are several *_test folders; you can open them and run the scenes inside to see demos of the method in action.

To do

  • Confirm that the approach to removing Tweens from the SceneTree is okay, or improve it if necessary
  • Add functions for parity with other Tweener subclasses
    • set_trans Infeasible due to the way default transitions and tween evaluation is implemented
    • set_ease Infeasible due to the way default transitions and tween evaluation is implemented
    • set_delay
    • set_custom_interpolator May be out of scope for now, and not necessary enough (possibly best to implement on Tween itself?)
  • Confirm that the behavior of Tween features like loops, play, pause, etc doesn't break when in a parent Tween
    • Loops (note that an infinitely looping subtween will cause that step of the parent tween to never finish)
    • Play/Pause (note that a paused subtween will cause that step of the parent tween to never finish)
    • Stop
    • Kill
    • Set speed scale
  • Reset subtween when parent tween resets in loop
  • Documentation
    • Add a code example in the documentation for Tween.tween_subtween
    • Add note to Tween.play and Tween.pause that a paused subtween will result in the parent tween never moving past that step
    • Add note to Tween.set_loops that an infinitely-looping subtween will result in the parent tween never moving past that step
    • Add notes to Tween.set_pause_mode and Tween.set_process_mode that they will not have an effect when the tween is a subtween

@Meorge
Copy link
Contributor Author

Meorge commented Oct 31, 2024

I was hoping to implement set_ease, set_trans, and set_custom_interpolator methods for the SubtweenTweener, but it looks like due to the way Tweens are implemented and run under the hood, they may not be feasible. Specifically, as far as I understand the Tween::step method accepts a delta value in seconds to move forward by. If we could compute, ahead of time, the total amount of time the tween would take, then I think we could remap this value into another ease/transition type, but this might also be fragile (for example, I think the duration of a tween could be modified by changing the speed scale while it is running?)

The way that set_trans and set_ease are implemented in the Tween class right now, it appears they only set the default ease/transition for Tweeners added after those methods are called. So, in this example:

var tw := create_tween()
tw.tween_property(self, "position:x", 100, 1.0)
tw.set_trans(Tween.TRANS_SINE)  # no effect!

The object would still move with linear motion, as if the set_trans call was not there. The way the documentation describes this is a bit misleading, then, in my opinion:

Sets the default transition type for PropertyTweeners and MethodTweeners animated by this Tween.
If not specified, the default value is TRANS_LINEAR.

I might go and submit a separate PR to make this more clear. Anyways, because of this, the following doesn't work, at least in the way I would intuitively expect:

Ref<SubtweenTweener> SubtweenTweener::set_trans(Tween::TransitionType p_trans) {
	subtween->set_trans(p_trans);
	return this;
}

With this, if you start adding Tweeners after calling set_trans, then they do have the new transition type, but I don't think that's the behavior people would expect.


All this to say, while I think it would be nice to have support for methods like set_ease, set_trans, and set_custom_interpolator to control the overall behavior of a subtween, there doesn't appear to be a good way to do it; it would likely require rewriting how some of the Tween class itself works, which is beyond the scope of this PR. The set_delay method appears to be working, though, at least!

As such, for now I think I'll just go ahead with only having the set_delay method, and then in the future if the Tween class is rewritten to be more reusable/nestable/etc, we can revisit adding this functionality to SubtweenTweener.

@Meorge
Copy link
Contributor Author

Meorge commented Nov 2, 2024

I've created a small project with several testing scenes, so people can see how it behaves and what the corresponding GDScript code is like: tween-subtween.zip I'll also add this to the original post so it's easily accessible.

With this complete, I think the PR is ready to be more throughly looked at, so I'll open it for review!

  • I'm still not 100% sure if the approach for removing the Tween from the SceneTree is fine, or if there'd be a better alternative.
  • I'm also starting to wonder if a name other than tween_subtween should be used, such as tween_child or tween_nested. One of these other names would allow us to refer to "nested Tweens" or "child Tweens" as opposed to "parent Tweens" in the documentation - right now it's "subtweens" and "parent Tweens", which is a bit asymmetrical.

@Meorge Meorge marked this pull request as ready for review November 2, 2024 04:16
@Meorge Meorge requested review from a team as code owners November 2, 2024 04:16
@KoBeWi
Copy link
Member

KoBeWi commented Dec 17, 2024

I'm still not 100% sure if the approach for removing the Tween from the SceneTree is fine, or if there'd be a better alternative.

One alternative I can think of is allowing to use Tween.new(), which creates a Tween outside scene tree. Currently it results in an error. Maybe we could remove the error and do a deferred call that checks whether the Tween became "owned" (by SceneTree or another Tween).

tweens.push_back(tween);
return tween;
}

bool SceneTree::remove_tween(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.

scene/main/scene_tree.cpp Outdated Show resolved Hide resolved
@@ -207,6 +207,7 @@
<description>
Pauses the tweening. The animation can be resumed by using [method play].
[b]Note:[/b] If a Tween is paused and not bound to any node, it will exist indefinitely until manually started or invalidated. If you lose a reference to such Tween, you can retrieve it using [method SceneTree.get_processed_tweens].
[b]Note:[/b] When a subtween (set via [method tween_subtween]) is paused, it will hold up execution of the parent [Tween] on that step. Make sure to either resume the subtween with [method play] or end it with [method kill] so that the rest of the parent [Tween] can finish executing.
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we should "pollute" method description like this or just add some more notes to tween_subtween() instead.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm against this, too. It makes the whole method look more complex, even though this "mechanic" only applies to one, unique type of tweener (and the hardest to wrap your head around by far).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good! I'll make a note to remove it shortly, thanks for the feedback :)

scene/animation/tween.cpp Outdated Show resolved Hide resolved
scene/animation/tween.cpp Outdated Show resolved Hide resolved
scene/animation/tween.h Outdated Show resolved Hide resolved
scene/animation/tween.h Outdated Show resolved Hide resolved
scene/animation/tween.h Outdated Show resolved Hide resolved
class SubtweenTweener : public Tweener {
GDCLASS(SubtweenTweener, Tweener);

friend class Tween;
Copy link
Member

Choose a reason for hiding this comment

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

?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is because SubtweenTweener::subtween is a private member, but the Tween::tween_subtween method needs to be able to access it.

I see a few possibilities for how to handle this:

  • Keep friend class Tween and subtween as private
  • Remove friend class Tween, make subtween be public
  • Remove friend class Tween, keep subtween as private but add a SubtweenTweener::get_subtween method so that the subtween can be retrieved from the SubtweenTweener, but the variable itself cannot be modified from outside the class instance

Which of these do you think would be best?

@KoBeWi
Copy link
Member

KoBeWi commented Dec 17, 2024

The functionality works correctly. The implementation could be improved with the change I suggested.

The subtween looks neat and allows for some nice sequences, but tbh I'd have to think hard to come up with some real example where they are useful, let alone necessary. You made a nice test project, but these are just abstract examples, not something you'd use in a game.
The current alternative is await, and eventually the AwaitTweener. Subtweens would just be another way to do the same. Maybe it won't hurt to have it though, if some people find it more intuitive. The code isn't very complex.

Copy link
Contributor Author

@Meorge Meorge left a comment

Choose a reason for hiding this comment

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

Thank you for the feedback and suggestions! 😄 I've committed the direct suggestions, and will have other changes up shortly.

@@ -207,6 +207,7 @@
<description>
Pauses the tweening. The animation can be resumed by using [method play].
[b]Note:[/b] If a Tween is paused and not bound to any node, it will exist indefinitely until manually started or invalidated. If you lose a reference to such Tween, you can retrieve it using [method SceneTree.get_processed_tweens].
[b]Note:[/b] When a subtween (set via [method tween_subtween]) is paused, it will hold up execution of the parent [Tween] on that step. Make sure to either resume the subtween with [method play] or end it with [method kill] so that the rest of the parent [Tween] can finish executing.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good! I'll make a note to remove it shortly, thanks for the feedback :)

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

friend class Tween;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is because SubtweenTweener::subtween is a private member, but the Tween::tween_subtween method needs to be able to access it.

I see a few possibilities for how to handle this:

  • Keep friend class Tween and subtween as private
  • Remove friend class Tween, make subtween be public
  • Remove friend class Tween, keep subtween as private but add a SubtweenTweener::get_subtween method so that the subtween can be retrieved from the SubtweenTweener, but the variable itself cannot be modified from outside the class instance

Which of these do you think would be best?

scene/main/scene_tree.cpp Outdated Show resolved Hide resolved
scene/animation/tween.h Outdated Show resolved Hide resolved
tweens.push_back(tween);
return tween;
}

bool SceneTree::remove_tween(Ref<Tween> &p_tween) {
_THREAD_SAFE_METHOD_
return tweens.erase(p_tween);
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.

@Meorge
Copy link
Contributor Author

Meorge commented Dec 18, 2024

Allowing tweens to be created without create_tween will help with efficiency a lot (not requiring that every tween be erased from the SceneTree's linked list), but it'll require a few more changes:

  • Remove the error message that appears when using the default Tween constructor
  • Update the Tween (and maybe create_tween?) documentation to indicate that a Tween can be created manually, but that it won't be added to the scene tree automatically if create_tween is not used
  • Add a method to SceneTree that manually adds a given Tween to its list of tweens
  • Maybe other things I'm not thinking of right now?

tbh I'd have to think hard to come up with some real example where they are useful, let alone necessary. You made a nice test project, but these are just abstract examples, not something you'd use in a game.

I definitely get what you mean in terms of the test project - for the most part they were just intended to test functionality, making sure that specific tween settings weren't causing the feature to break.

It's been quite a while, but I remember that while working in Unity, DOTween's nestability allowed me to compose complex UI animation sequences like these. For example, the room names that slide in on the left at the very beginning of the video could each have a method that builds just their slide-in animation, then I could join/append all of those together into a single sequence, and once that sequence is complete, then play the focus animation (becoming more opaque, growing) for the active element. In GDScript pseudocode, that could look something like this:

func tween_in_rooms() -> Tween:
    var tw = create_tween()
    
    # Rooms 2 and 3 come in parallel but with slight delay, to get
    # an "overlapping animation" effect instead of being directly
    # sequential.
    tw.tween_subtween(room1.tween_in())
    tw.parallel().tween_subtween(room2.tween_in()).set_delay(0.2)
    tw.parallel().tween_subtween(room3.tween_in()).set_delay(0.4)
    
    # Once all rooms have come in, make room 1 focus.
    tw.tween_subtween(room1.tween_focus())
    
    # By returning this Tween from this method, we could make it
    # part of an even bigger overall sequence if we wanted to.
    return tw

The results screen later on in the video is much more complex, but can be broken down into "play the tween defined for object 1, then play the tween defined for object 2, then play..." etc., and each of those animations is much easier to define in a sequence. They can also then be tested independently of each other, or reused in other locations if need be.

@KoBeWi
Copy link
Member

KoBeWi commented Dec 18, 2024

Add a method to SceneTree that manually adds a given Tween to its list of tweens

That's not really needed. create_tween() would still be the default way to make Tweens, the new() would be intended only for sub-Tweens.

the room names that slide in on the left at the very beginning of the video could each have a method that builds just their slide-in animation, then I could join/append all of those together into a single sequence, and once that sequence is complete, then play the focus animation

The same can be achieved with multiple Tweens played with a delay and waiting for the last one to finish.
Here, I even generalized it to any number of rooms:

func tween_in_rooms() -> Tween:
	var last_room_tween: Tween
	var delay := 0.0

	for room in rooms:
		last_room_tween = room.tween_in(delay) # Delay is applied via tween_interval() at the beginning.
		delay += 0.2

	await last_room_tween.finished
	return rooms[0].tween_focus())

The method is async though, if you really need to return the Tween for some reason.

@Meorge
Copy link
Contributor Author

Meorge commented Dec 18, 2024

That's not really needed. create_tween() would still be the default way to make Tweens, the new() would be intended only for sub-Tweens.

My hope is that the tween objects could be produced by methods in such a way that the user could choose to make them a "top-level tween" (run directly by the scene tree, like the result of doing create_tween() now), or they could put them into a larger sequence that will get executed as a whole.

# Animate a single button tween standalone.
# The `in_tween` method creates its tween via `Tween.new` so that it
# isn't added to the tree by default, because it's intended to be usable
# as a subtween.
var button_in_tween: Tween = my_button.in_tween()
get_tree().queue_tween(button_in_tween)

# The same tween, but now it's just one step of a larger sequence.
var button_in_tween_2: Tween = my_button.in_tween()
some_larger_sequence.tween_subtween(button_in_tween_2)

It could still be accomplished without adding a queue_tween method, like so:

var button_in_tween: Tween = my_button.in_tween()
var s: Tween = create_tween()
s.tween_subtween(button_in_tween)

but it strikes me as a bit superfluous to create another tween tied to the scene tree, to only serve as a "container" for the intended tween.


The same can be achieved with multiple Tweens played with a delay and waiting for the last one to finish.

That's true, and I suppose for the results screen one could also probably string along a number of awaits on each component of the UI to finish its individual tween as well. The coroutine system, as far as I know, is a bit less controllable than the tween system once it gets going - killing a coroutine mid-sequence is a bit more involved, as is doing things like changing the speed. As you said earlier, this may be a case where technically this isn't opening up any functionality that was strictly impossible before, but (at least in my opinion) it provides a much more intuitive interface for performing these sorts of tasks.

@KoBeWi
Copy link
Member

KoBeWi commented Dec 18, 2024

My hope is that the tween objects could be produced by methods in such a way that the user could choose to make them a "top-level tween" (run directly by the scene tree, like the result of doing create_tween() now), or they could put them into a larger sequence that will get executed as a whole.

Eh, for now I'd make orphan Tweens return an error if they aren't started in the same frame and potentially improve it in the future. The main problem with creating Tweens outside Tree is that users will likely expect them to run normally, so some error/warning is a necessity.
EDIT:
Although printing error at the end of the frame like I suggested does not conflict with your solution. It only potentially limits it to when you can use the Tween (i.e. instantly, not "sometime later" if you wanted that). I'd add the method in Tween though, like Tween.add_to_tree(), so it can be used easily anywhere.

killing a coroutine mid-sequence is a bit more involved

It's actually impossible. The awaited call can be "canceled" only by getting rid of the calling object or making it wait forever. So if you can't use await, that would be when the current system falls short. The AwaitTweener doesn't have this problem, but it's not merged yet, so it's only speculative.

@Meorge
Copy link
Contributor Author

Meorge commented Dec 20, 2024

Eh, for now I'd make orphan Tweens return an error if they aren't started in the same frame and potentially improve it in the future.

I agree with this! What I meant to propose was that when you used a factory method to create a Tween, you had the option of either adding it to a SceneTree that frame, or making it part of a sequence with tween_subtween that frame, but not that you could store it somewhere for use on a later frame.

It does make me wonder, though: if a Tween is created without a SceneTree attached to it, how would it know when the end of the frame is?


I'd add the method in Tween though, like Tween.add_to_tree(), so it can be used easily anywhere.

Do you know how common it is for people to be working with multiple SceneTrees? I know I've only ever had one to deal with, so get_tree() always works fine for me. But if things can involve multiple SceneTrees, then the following scenarios could crop up:

# Queue this Tween on the main SceneTree,
# accessible via `get_tree()`.
var some_tween := Tween.new()
get_tree().queue_tween(some_tween)

# Queue a Tween to run on another SceneTree
# we have a reference to.
var some_other_tween := Tween.new()
some_other_tree.queue_tween(other_tween)

If the method is on Tween itself, then the Tween instance would have to know which SceneTree to add itself to, if the method takes no arguments:

# Queue this Tween on a SceneTree.
# How does it know what SceneTree to use?
var some_tween := Tween.new()
some_tween.add_to_tree()

If the method does take a SceneTree as an argument, the "which SceneTree" problem is solved of course, but the API is unusual in my opinion - it'd be like having my_item.append_to_array(my_array) instead of my_array.append(my_item).

# Queue this Tween on a SceneTree.
# It knows which SceneTree to use because we're
# passing it as an argument, but the order is unusual.
var some_other_tween := Tween.new()
some_other_tween.add_to_tree(get_tree())

It's actually impossible. The awaited call can be "canceled" only by getting rid of the calling object or making it wait forever. So if you can't use await, that would be when the current system falls short.

Makes sense! What I was thinking of when I said it was just "more complicated" were ways I've found to hack around the coroutine sequence, by providing early-exit points. But they have to be made on a very case-by-case basis, and maybe sometimes they are indeed just straight-up impossible to get working. 😅

@KoBeWi
Copy link
Member

KoBeWi commented Dec 20, 2024

It does make me wonder, though: if a Tween is created without a SceneTree attached to it, how would it know when the end of the frame is?

It doesn't need to be in scene tree to wait for the signal.

Do you know how common it is for people to be working with multiple SceneTrees?

There can be only one SceneTree.

@Meorge
Copy link
Contributor Author

Meorge commented Dec 21, 2024

Great! I won't be able to work on this for a few days, but when I'm back, I can experiment with having the Tween constructor get the scene tree instance and waiting for the end-of-frame signal.

This did make me think, though, if we may be able to stick with the create_tween for creating all Tweens, and perhaps add them to a data structure that's more friendly for insertions and deletions, like a HashSet(?). Then, at the end of the frame, all of the elements in that "queue pool" could be moved over into the actual execution list.

This way we wouldn't need to tell users about two different ways to create Tweens – from the user end, it would seem like the same as it always was. This would, however, make it so that the execution order of Tweens isn't guaranteed to match the creation order (as far as I understand, that is currently guaranteed). I'm not sure how much of a breaking change this would be for existing projects.

@KoBeWi
Copy link
Member

KoBeWi commented Dec 21, 2024

perhaps add them to a data structure that's more friendly for insertions and deletions, like a HashSet(?).

The Tweens need to be in a structure that is iteration-friendly. Not sure if HashSet is like that.

@Meorge
Copy link
Contributor Author

Meorge commented Dec 27, 2024

Looks like HashSet can be iterated over, and it appears to retain its order from the tests I've done so far.

HashSet<int> nums;
for (int i = 0; i < 10; i++) {
	int some_rand = rand() % 100;
	print_line("inserting", some_rand);
	nums.insert(some_rand);
}

print_line("Nums from the hash set:");
for (int num : nums) {
	print_line(num);
}
print_line("----");

I ran this twice, and got the output:

inserting 88
inserting 73
inserting 13
inserting 11
inserting 31
inserting 65
inserting 33
inserting 24
inserting 74
inserting 6
Nums from the hash set:
88
73
13
11
31
65
33
24
74
6
----
inserting 47
inserting 14
inserting 75
inserting 57
inserting 46
inserting 69
inserting 52
inserting 38
inserting 66
inserting 77
Nums from the hash set:
47
14
75
57
46
69
52
38
66
77
----

Based on how HashSet works, maybe I just didn't add enough elements, or a wide enough range, to get collisions that could cause the iteration order to not match the insertion order perfectly?

I'm currently thinking it might make sense to have a HashSet tween queue that gets emptied into the linked list at the end of each frame. That way, we can quickly remove tweens from the queue before the end of the frame, if we want to reassign a tween to be a subtween. Before I get started trying to implement that, though, it'd be good to get others' thoughts.

@KoBeWi
Copy link
Member

KoBeWi commented Dec 27, 2024

AFAIK HashSet does not guarantee order. It's an implementation detail, so it can change at any point.

@Meorge
Copy link
Contributor Author

Meorge commented Dec 29, 2024

That makes sense! How big of a compatibility break do you think it'd be to not guarantee the precise order of tween execution? This note is on the Tween documentation page:

You should avoid using more than one Tween per object's property. If two or more tweens animate one property at the same time, the last one created will take priority and assign the final value.

Given that modifying the same property from multiple tweens is discouraged behavior, IMO it wouldn't be the worst thing to update this to say "one of the Tweens created will assign the final value", and if people are running into problems with it, then the solution is for them to stop the already-discouraged behavior.

I could also envision some cases where tween_callback could have issues:

var tw1 := create_tween()
tw1.tween_interval(1.0)
tw1.tween_callback(func(): print("Tween 1"))

var tw2 := create_tween()
tw2.tween_interval(1.0)
tw2.tween_callback(func(): print("Tween 2"))

If using a HashSet instead of a linked list, these would have no guarantee of printing in the same order, which I could see causing more issues...


While I'd definitely prefer to keep create_tween() as the single way to make tweens, for simplicity's and consistency's sake, I'll look more into how Tween.new() could be used instead - providing a method to add it to the SceneTree, and having it raise some kind of alarm if it isn't by the end of the frame (or maybe just adding it to the SceneTree automatically?)

scene/animation/tween.h Outdated Show resolved Hide resolved
@KoBeWi
Copy link
Member

KoBeWi commented Dec 29, 2024

Eh, I think removing from the list is fine for now. It's costly, but it won't be noticeable until someone makes a subtween while lots of Tweens are running. It can always be improved later if it's really a problem.

For now remove subtween mentions from unrelated methods (set_pause_mode() and set_process_mode()) and the PR should be ready.

@Meorge Meorge force-pushed the tween-subtween branch 2 times, most recently from b6b33b5 to 78e5cb4 Compare December 29, 2024 18:16
@KoBeWi KoBeWi modified the milestones: 4.x, 4.4 Dec 29, 2024
scene/main/scene_tree.cpp Outdated Show resolved Hide resolved
scene/main/scene_tree.h Outdated Show resolved Hide resolved
scene/main/scene_tree.cpp Outdated Show resolved Hide resolved
No actual functionality yet

Actual subtween functionality implemented

Added documentation for Tween.tween_subtween and SubtweenTweener

Implemented some additional functions

`set_ease`, `set_trans`, and `set_delay`
Documentation only for `set_delay` so far, since I have tested it

Removed set_ease and set_trans

Upon further investigation, the way they are implemented for Tween doesn't appear to work here

Fixed indentation in documentation

Reset subtween when parent loops

Fix return type of `SubtweenTweener.set_delay`

Add notes to documentation

Apply suggestions from code review

Co-authored-by: Tomasz Chabora <[email protected]>

Apply some suggested changes

- Remove excessive documentation
- Add Tween constructor that takes in SceneTree
- Make `SubtweenTweener::subtween` public so that `Tween` doesn't have to be a friend class

Remove unneeded friend class SceneTree

Remove superfluous documentation describing subtween behavior

Apply suggestions from code review

Co-authored-by: Tomasz Chabora <[email protected]>

Apply suggestions from code review

Co-authored-by: Thaddeus Crews <[email protected]>
// 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) {
Copy link
Member

Choose a reason for hiding this comment

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

Won't this crash if I call tween.tween_subtween(null)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's a good question - I never thought to try tween.tween_subtween(null), admittedly, but I could see that situation coming up in some use cases where the user isn't sure if some other method is going to return a Tween or not.

For this specific line, I think it'd be sufficient just to check if tweener->subtween is null or not before the parent_tree check, but there may also be other parts of the code where a null subtween needs to be handled, perhaps by treating it like a no-op step in the tween sequence.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add a tween_subtween method for nesting Tweens
6 participants