Godot: Add Tween sequencing/chaining

Created on 15 Mar 2018  路  15Comments  路  Source: godotengine/godot

Godot version:
3.0.2 Stable

OS/device including version:
Windows 10

Issue description:
There currently doesn't seem to be an easy way to chain various tweens together. Having a toolset similar to GSAP would be an amazing way to setup and manage chains of tweens.

GSAP enables you to create timelines that you can add tweens to and they will fire off sequentially after you start playing the timeline.

archived feature proposal core

Most helpful comment

Yes I wouldn't change the way animations are played, they'd still go through the Tween object, I was mostly referring to the way you create an individual action to be played.

I was just wondering whether there'd be strong opposition into changing the current API in a way that might break some backwards compatibility. I'm not opposed to creating a new class, but I don't want to clutter and create confusion with a parallel system.

Anyway I'll try throw together a draft implementation this weekend and we can discuss it then, probably easier that way.

All 15 comments

Is easy to chain sequences with a single tween this way:

tween.interpolate_property(
  node,property1,start_value1,end_value1,
  time1,trans_type,ease_type)
tween.interpolate_property(
  node,property2,start_value2,end_value2,
  time2,trans_type,ease_type,
  time1) #delayed by time1 seconds
tween.start()

@eon-s Perhaps for simple tweens, but things start to get hairy if you need to add/remove tweens. Having to manage the delays isn't ideal.

GDScript's "yield to signal" can be used for that.

var tween = Tween.new()
tween.interpolate_property(node, property, start_value, end_value, time, trans_type, ease_type)
tween.start()
yield(tween, "tween_completed")
tween.interpolate_property(node, property2, start_value2, end_value2, time2, trans_type, ease_type)
tween.start()
yield(tween, "tween_completed")
# etc...

@karroffel any thoughts on how you could get a chain like that to loop? Turn on repeat seems to loop the last interpolated property.

Is anyone working on this by any chance? Painless sequencing of simple tweens is a feature I really miss from cocos2d-x and I might implement it into Godot myself if nobody else is already on it.

@eligt go ahead :+1:

I'm assuming the preference would be to change the current Tween class to add the extra function and keep backwards compatibility, but is it vital to do so at this stage of 3.x?

I'm asking because I really liked cocos2d-x way's of handling this, not the best-performing but could be adapted (I'm not even sure performance is that important with tweening which is used for simple animations).

For reference, this is an example from an actual game I wrote in cocos2d-x:

Vector<FiniteTimeAction*> actions;

actions.pushBack(CallFunc::create(callbackStart));

if (effect == "move")
    actions.pushBack(EaseCubicActionOut::create(MoveTo::create(0.5, Point::ZERO)));
else if (effect == "fade")
    actions.pushBack(EaseCubicActionOut::create(FadeIn::create(0.7)));
else if (effect == "blackout")
{
    actions.pushBack(TargetedAction::create(m_screenFader, EaseCubicActionOut::create(FadeIn::create(0.6))));
    actions.pushBack(CallFunc::create(SLIDEMATCH_CALLBACK_0(GameScene::activateScreen, this, name)));
    actions.pushBack(DelayTime::create(0.1));
    actions.pushBack(Spawn::create(
        Sequence::create(
            DelayTime::create(0.3),
            //CallFunc::create(SLIDEMATCH_CALLBACK_0(GameScene::setActive, this, true)),
        nullptr),
        TargetedAction::create(m_screenFader, EaseCubicActionIn::create(FadeOut::create(0.5))),
    nullptr)
    );
    actions.pushBack(CallFunc::create(SLIDEMATCH_CALLBACK_0(Node::setVisible, m_screenFader, false)));
}

actions.pushBack(CallFunc::create(SLIDEMATCH_CALLBACK_0(GameScene::setActive, this, true)));
actions.pushBack(DelayTime::create(0.05));
actions.pushBack(CallFunc::create(callbackEnd));
actions.pushBack(CallFunc::create(tutorialCallback));

Sequence* seq = Sequence::create(actions);
screen->runAction(seq);

I like this approach because it creates readable code, which you know what is going to do, but at the same time it's quite powerful, it's very easy to create your own actions and ease functions.

Trying to get a sense for how much wiggle room there is when it comes to upgrading the Tween system.

I personally like current Tween system to be able to create multiple tweens with different timelines (duration and delay).
how about adding a new class like the Sequence in cocos2d-x?
it won't break compatibility and play multiple tweens sequentially.

Yes I wouldn't change the way animations are played, they'd still go through the Tween object, I was mostly referring to the way you create an individual action to be played.

I was just wondering whether there'd be strong opposition into changing the current API in a way that might break some backwards compatibility. I'm not opposed to creating a new class, but I don't want to clutter and create confusion with a parallel system.

Anyway I'll try throw together a draft implementation this weekend and we can discuss it then, probably easier that way.

Just created a pull request with the changes, it was actually easier than I thought. Performance is not amazing as there's some recursion but it shouldn't be a problem unless you have thousands of tweens at the same time, we can change the implementation to make it more performing but this was the way that made the fewest changes to the existing class.

Pull request: https://github.com/godotengine/godot/pull/20934


Sample (functional) GDScript code looks like this:
https://github.com/eligt/godot/wiki/Tweening-sequences-and-spawners-sample-GDScript-code

Creates this resulting animation:
https://youtu.be/AVkn0ri0oHw

I think this is looking nice! I find that calling so many create_sequence() functions nested in each other is kind of confusing however. What about having create_sequence() return the sequence object and then having something like add_to_sequence() that let's you append to the end of the sequence?

Jumping in here as a cocos2d-x user, that adding some sort of sequencing for tweens is very important to me too! Save for the particles, every visual effect shown here used sequences of tween, similar in style to @eligt's code.

A nice side-effect of how cocos2dx does their animations and lets you override the delta time passed to each node, is that you can wrap an effect an an Ease/Tween, so if you added a MoveBy action, sequenced by a second action moving back, you could just wrap the entire sequence in a EaseBackOut, and have both nicely eased together. EaseBackOut::create(Sequence::createWithTwoActions(MoveBy::create(..), MoveBy::create(..))

If you use the duration/delay combo technique, you can get sequential tweens going, but you're not able to fire two at the same time (As you could with cocos2d::Spawn::createWithTwoActions()).

Coming from Universal Tween Engine, I too find Godot Tween class very limited. Here is an example showing how easy (and scalable) it was working with sequential and parallel timelines in Scala (startUsingOurManager is a pimp method calling just start with global tween manager; just imagine it is start()):

    Timeline.createSequence()
    .pushCallback {
      _chestBurstAnimationInProgress = true
      continueButton.setDisabled(true)
    }
    .push(
      Timeline.createParallel()
      .push(chestAnimation.createTimeline())
      .push(UniversalTweenEngineHelper.createExecutionTween({sounds.chestCreak.play()}, 1.5f, "Chest Creak"))
    )
    .pushCallback {
      _chestBurstAnimationInProgress = false
    }
    .startUsingOurManager()

More basic example:

    Timeline.createSequence()
    .push(Tween.to(subtitlesWidget, TweenTypes.TYPE_OPACITY, subtitlesFadeAnimationDuration).target(1))
    .startUsingOurManager()

I really liked that I could abstract creation of tweens/timelines to methods and then later just chain them readably:

      Timeline
      .createSequence()
      .push(graduallyShowFromHiddenTimeline())
      .push(burnFromFullTimeline())
      .push(fuse.animateSwords())
      .pushPause(gameState.afterSwordDelay)
      .pushCallback {burningFuseFinished(); ()}
      .startUsingOurManager()

I worked a lot with the first version of Cocos2D (with Objective-C) and Actions and Sequences were my favorite feature. Then in Unity, in 2010 and 2011 I used the same with iTween. Now, this is what I miss the most in Godot.

I was even considering doing it myself, but this PR https://github.com/godot-extended-libraries/godot-next/pull/50 seems to solve it.

    [node runAction: [CCSequence actions:
             [CCFadeOut actionWithDuration:kMenuTransitionDuration],
             [CCCallFuncN actionWithTarget:self selector:@selector(cleanMenuItem:)],
             nil
             ]
     ];

                [rf runAction:
                 [CCRepeatForever actionWithAction:
                  [CCSequence actions:
                   [CCMoveTo actionWithDuration:moveDuration position:go],
                   [CCMoveTo actionWithDuration:moveDuration position:back],
                   nil
                   ]
                  ]
                 ];

Closing in favor of https://github.com/godotengine/godot-proposals/issues/514, as feature proposals are now tracked in the Godot proposals repository.

Was this page helpful?
0 / 5 - 0 ratings