Jump to content
GreenSock

Search In
  • More options...
Find results that contain...
Find results in...
cerulean

Dynamic changes to child Timelines "on the fly"

Recommended Posts

Today is the day of many Timeline questions — thanks for your help and patience.

 

 I'm interested to the extent of which multiple nested timelines are "baked in the cake", that is how much you can change not-yet-played timelines on the fly.  Can you update the child timelines and tweens before the parent is played and while it is playing?

 

Also I was experimenting with the ability to play timelines independent of their parents.  Apparently it doesn't work. That is, if you have a timeline added to another timeline, you can't seem to play() the child timeline prematurely.  Is this true?

 

I then tried removing it from the parent timeline in a callback, but that seemed to make no difference.  Only if I created an entirely new child timeline in the callback could I play it immediately.  I assume this is all as it should be, but could someone explain why?

 

This, for instance, failed

var main:TimelineMax = new TimelineMax({paused:true});



var one:TimelineMax = new TimelineMax;
var two:TimelineMax = new TimelineMax;

one.to(aSquare,3,{onUpdate:function(){trace('in one')},x:"+=300"});
one.to(aSquare,4,{onUpdate:function(){trace('in one 2')},y:"+=300"});
two.to(aCircle,2,{onUpdate:function(){trace('in two')},y:"-=200"});
main.addCallback(dochange,1);

main.append(one,0);
main.append(two,3);
main.play();

function dochange():void {
	trace("removing two");
	trace(two.timeline); // has a parent
		main.remove(two);
		trace(two.timeline); // has no parent
		two.play(); // won't play nonetheless until already set place in parent.
}
Link to comment
Share on other sites

Believe it or not, I think you're the first person to ever ask something like this (at least as far as I can remember). Congratulations :)

 

 

The issue is that in order to play, yes, a tween/timeline must be in a timeline whose virtual playhead is moving. Think of it like a record player and a needle – if you pull the record out of the player, it ain't gonna play. Or if you stop it from spinning, it won't advance either.

 

One solution would be to add() it to the root timeline, but I kinda discourage working directly with that because…well, it makes me nervous to have people messing with that and potentially wreaking havoc. You'd probably be fine, but there's another simple solution that doesn't involve doing Animation._rootTimeline.add( yourTimeline )...

 

Whenever you want to remove() a timeline and play() it independently, you can simply drop it into another temporary TimelineLite that you create. All new tweens/timelines are plugged into the root by default, so obviously it'll play. Putting your child in there will cause everything to work (the parent timeline's playhead will be moving).

mainTimeline.remove( childTimeline );
var tl:TimelineLite = new TimelineLite();
tl.add(childTimeline, 0);

//And of course you can re-nest it later...
mainTimeline.add(childTimeline, "oldLabel");

Which automatically removes it from the other (temporary) TimelineLite.

 

Another option is to simply tween the "time" or "totalTime" of the child timeline even while it's nested in the paused mainTimeline. In fact, if the child is a TimelineMax, you can tap into its tweenTo() method or tweenFromTo(). Like:

childTimeline.tweenTo( childTimeline.duration() );

The reason that works is because it basically creates a tween that constantly moves the local virtual playhead manually, forcing updates. Tricky, huh?

 

Does that clear things up?

  • Like 1
Link to comment
Share on other sites

Thanks -- glad to be the first!

I think that clears it up a great deal —. Part of my confusion is in thinking of created timelines as simply something like separate movie clips or Classes, and not as part of a larger, interdependent structure.

I guess two main remaining questions I'd have is (1) why can I do

var tl:TimelineMax = new TimelineMax();
tl.play()

but I can't do


var tl:TimelineMax = new TimelineMax();
var tl2:TimelineMax = new TimelineMax();
tl2.add(tl);
tl2.remove(tl)
tl.play()

That is to say I understand, now, if a timeline needs to be in a parent "record player" timeline to play, but why, then, can you play a timeline by itself at all? Is it because, when it's created, it's automatically (and invisibly) added to the _rootTimeline you mentioned? I assume that's the case…?

 

And (2) if I were to setup a child timeline inside a parent timeline and then play that child timeline separately, doing something like childTimeline.tweenFromTo() before I played the parent timeline, would it still play from the start in the parent timeline, or would it's playhead now be somewhere in the middle?  

 

 

Link to comment
Share on other sites

Yep, you're exactly right - all tweens/timelines are automatically added to the root timeline initially. That's why they play() correctly. They're on the spinning record player of sorts at birth. 

 

To answer your second question, if you do a tweenFromTo() on the child, it shouldn't affect its position in the parent timeline (unless you specifically set the parent timeline's "autoSmoothChildren" property to true which would cause it to compensate its position accordingly when you adjust things like time, timeScale, reverse, etc.). So yes, it would play from the beginning when the parent's playhead arrives at its beginning.

 

The tweenFromTo() and tweenTo() methods of TimelineMax can be very powerful and useful for situations like what you've been describing. 

Link to comment
Share on other sites

Thanks.

 

It sounds nifty but it's proven problematic for me.

 

I was very careful to do something like the following for each of three objects (rock bird ball) the player is avoiding -- this is a section of a tutorial I've already played in the main timeline and if they hit something I play the relevant section again.

	// show avoiding rock
			tl.add("avoidRock","+=0.2");
			tl.add(TweenMax.set(rock,{y:_initialRockPoint.y,x:_initialRockPoint.x,z:INITIAL_OBSTACLE_Z,alpha:0}));
			tl.call(trace,['avoidRock',rock.x,rock.y,rock.z,rock.alpha,rock.visible]);
			
			tl.to(swipeUpToJump,0.25,{autoAlpha:1},"+=1");  // add 'swipe up to jump'
			tl.to(rock,4,{autoAlpha:1,z:-300});   // move rock forward
			tl.add(upSwipe,"-=3"); // simulate up swipe
			
			tl.to(rock,3,{y:stage.stageHeight},"-=3"); // override to make obstacle go off screen;
			tl.to(rock,1,{alpha:0},"-=2"); // and make it disappear
			tl.to(swipeUpToJump,0.5,{autoAlpha:0},"+=.5");  // remove 'swipe up to jump'
			tl.add("avoidRockEnd","+=.5");

and then calling like this, where each obstacle (e.g. a rock) has a variable 'crashTutorial' that will correspond to a label (e.g. "avoidRock') in the tutorial I'm trying to play

			_mainTimeline.pause();
			_tutorialTimeline.tweenFromTo($obstacle.crashTutorial,$obstacle.crashTutorial + "End", {onComplete:function() {_mainTimeline.resume()}});

HOWEVER it simply cycled (somewhat randomly) through ALL THREE LABELS until I added a seek() beforehand

		_mainTimeline.pause();
			_tutorialTimeline.seek($obstacle.crashTutorial);
			_tutorialTimeline.tweenFromTo($obstacle.crashTutorial,$obstacle.crashTutorial + "End", {onComplete:function() {_mainTimeline.resume()}});
		}

Why might that be?  I promise you I've been beating my head against it…

Link to comment
Share on other sites

And even then it still cycles into next label. It must be my code — most probably something with the "-=" offsets is pushing stuff from one label to the next. Or am I doing something wholly wrong in adding the labels? Or perhaps it's simply the trace statements are firing wrong…

 

In any case, the concept is extremely powerful! Now just getting my head around it and making it work.

Link to comment
Share on other sites

It's a little tough to troubleshoot without having a simple FLA to publish and look at, but I do see one thing that might be causing a problem for you conceptually: labels do NOT have any effect on the duration of a timeline. So, for example, if you add a label 2 seconds after the end of a timeline like this:

tl.add("newLabel", "+=2);

That won't suddenly make the timeline 2 seconds longer whereas if you add a call() or set(), those are technically just specialized tweens, so those do take up space. I thought this was the most intuitive behavior in most situations. 

 

The reason I thought this concept was important to point out to you is because you're adding tweens right after you add a label that's offset, and maybe you're expecting those tweens to line up with the labels but they won't. For example:

//let's say your tl is 5 seconds long at this point and then you do this:
tl.add("newLabel", "+=2");

trace(tl.duration()); //5 (NOT 7)

//add() always defaults to placing things at the END of the timeline (tl.duration()), so this will put the set() at the 5-second spot, NOT at 7-seconds where the "myLabel" is:
tl.add( TweenMax.set(...) ); 

Consequently, the set() will occur 2 seconds BEFORE the "newLabel". If, however, you want to place things at "newLabel", that's very easy - just pass that as the insertion point:

tl.add( TweenMax.set(...), "newLabel");

There's a set() convenience method in TimelineLite/Max, so you could simplify the code a bit too:

//OLD:
tl.add( TweenMax.set(rock, {y:100}), "newLabel");

//NEW:
tl.set(rock, {y:100}, "newLabel");

So I suspect the whole "labels don't take up any time" thing might have been tripping you up (just a guess). If you're still having trouble, please whip together a super simple FLA that demonstrates the issue and post it here (zip it first) so we can take a peek. No need to post your production files - just a simplified example please. 

 

Don't worry, we'll get this all cleared up for you, I'm sure. once you get the core concepts I think you'll really dig the tools and you won't run into these little frustrations :) 

Link to comment
Share on other sites

Thanks very much. I'll try to wrap my head around it — and if I keep banging my head will try to post an FLA, but it's a pretty complex project and for that reason (and NDA) I don't know how easy that will be

I did it like this, which seemed to be most in tune with what you were suggesting — making things relative to labels. Again, it works fine if I 'seek' to the label before tweenFromTo'ing, but otherwise, it just goes crazy, running through a couple of labels and some twice. This timeline is part of a mainTimeline and I'm playing it separately here but assume that makes no difference. Sorry to try your patience (sans FLA), but perhaps you see my obvious error here

// show avoiding buoy
            tl.add("avoidBuoy","+=0.2");
            tl.call(trace,["at avoidBuoy",tl.time(),tl.currentLabel()],"avoidBuoy+0.1");
            tl.set(buoy,{y:_initialBuoyPoint.y,x:_initialBuoyPoint.x,z:INITIAL_OBSTACLE_Z,alpha:0},"avoidBuoy");
            tl.to(swipeToAvoid,0.25,{autoAlpha:1},"avoidBuoy+=.5"); // add 'swipe to avoid'
            tl.to(buoy,4,{autoAlpha:1,z:-300},"avoidBuoy+=1"); // move buoy forward
            tl.add(rightSwipe,"avoidBuoy+=2"); // simulate right swipe
            tl.to(buoy,3,{x:0},"avoidBuoy+=2"); // override to make obstacle go off screen;
            tl.to(buoy,1,{alpha:0}); // and make it disappear
            tl.to(swipeToAvoid,0.5,{autoAlpha:0},"+=.5"); // remove 'swipe to avoid'
            tl.add("avoidBuoyEnd");
            
            // show avoiding rock
            tl.add("avoidRock","+=0.2");
            tl.call(trace,["at avoidRock",tl.time(),tl.currentLabel()],"avoidRock+0.1");
            
            tl.set(rock,{y:_initialRockPoint.y,x:_initialRockPoint.x,z:INITIAL_OBSTACLE_Z,alpha:0},"avoidRock");
            tl.to(swipeUpToJump,0.25,{autoAlpha:1},"avoidRock+=.5"); // add 'swipe up to jump'
            tl.to(rock,4,{autoAlpha:1,z:-300},"avoidRock+=1"); // move rock forward
            tl.add(upSwipe,"avoidRock+=2"); // simulate up swipe
            tl.to(rock,3,{y:stage.stageHeight},"avoidRock+=2"); // override to make obstacle go off screen;
            tl.to(rock,1,{alpha:0}); // and make it disappear
            tl.to(swipeUpToJump,0.5,{autoAlpha:0},"+=.5"); // remove 'swipe up to jump'
            tl.add("avoidRockEnd");
            
                
            // show avoiding bird
            tl.add("avoidBird","+=0.2");
            tl.call(trace,["at avoidBird",tl.time(),tl.currentLabel()],"avoidBird");
            
            tl.set(bird,{y:_initialBirdPoint.y,x:_initialBirdPoint.x,z:INITIAL_OBSTACLE_Z,alpha:0},"avoidBird+=0.1");
            tl.to(swipeToDuck,0.25,{autoAlpha:1},"avoidBird+=0.5"); // add 'swipe to duck'
            tl.to(bird,4,{autoAlpha:1,z:-300},"avoidBird+=1"); // move bird forward
            tl.add(downSwipe,"avoidBird+=2"); // simulate down swipe
            
            tl.to(bird,3,{y:0},"avoidBird+=2"); // override to make obstacle go off screen;
            tl.to(bird,1,{alpha:0},"avoidBird+=4"); // and make it disappear
            tl.to(swipeToDuck,0.5,{autoAlpha:0},"+=.5"); // remove 'swipe to duck'
            tl.add("avoidBirdEnd");
            
 
 
 
 
All the calls to trace show time == 0 and currentLabel() == null.

One question: if you have two labels added one after the other, if they don't add any time to timeline, does GSAP keep track of them separately and successively.

Thus if I have

tl.add("somethingOneEnd")
tl.add("somethingTwoStart")
tl.set(abc,{x:100},"somethingTwoStart")

and I play to "somethingOneEnd" will it know not to do the 'set'? If not, then how could one set up a series of start/end sections — do

tl.add("somethingXStart","+=.5");
tl.set(abc,{x:100},"somethingXStart");

or do a do-nothing .set in between each end and the next start?

Also, if I have multiple calls/sets pinned to one label, is there any way of knowing in what order they will fire?
Link to comment
Share on other sites

 

All the calls to trace show time == 0 and currentLabel() == null.

 

 

That is because in the following code
 
tl.call(trace,["at avoidBird",tl.time(),tl.currentLabel()],"avoidBird");

tl.time() and tl.currentLabel() are getting evaluated at the time the timeline is created not when the timeline actually plays through to the point in time that the call() actually happens. When you are adding that timeline code the timeline's current time() is 0 and I'm guessing there isn't a label at a time of 0. 

 

You would have to set those traces up differently

tl.call(getData, ["at avoidBird"], "avoidBird")

function getData(message) {
   trace(message + " " + tl.time() + " " + tl.currentLabel());
}

That would ensure that tl.time() and tl.currentlabel() are getting evaluated when the getData() function is actually called.

 

 

 

 

Thus if I have
 
tl.add("somethingOneEnd")
tl.add("somethingTwoStart")
tl.set(abc,{x:100},"somethingTwoStart")
 
and I play to "somethingOneEnd" will it know not to do the 'set'?

 

 

the set() is added at the same time as the second label which is added at the same time as the first label. Playing to the first label will absolutely trigger the set() that is placed at the second label (same time as the first label).

 

To test, paste your code into a new fla or use this:

 

var tl = new TimelineLite({paused:true});

tl.to(mc, 0.2, {x:"+=100"});
tl.add("label1")
tl.add("label2")
tl.set(mc, {alpha:0.5}, "label2");

tl.seek("label1"); //or tl.tweenTo("label1");

you will see that the set does get fired.

 

And yes, if you want time between labels, sets, and calls, use a the relative position notation

 

 

tl.add("label1")
tl.add("label2", "+=0.1")
tl.set(mc, {alpha:0.5}, "label2");

Also, if I have multiple calls/sets pinned to one label, is there any way of knowing in what order they will fire? 

 

 

Yes, in the order they were added to the timeline

 


tl.to(mc, 0.2, {x:"+=100"});
tl.add("bunchOfStuff");
tl.set(mc, {rotation:40}, "bunchOfStuff")
.call(getInfo, [mc, "rotation"], "bunchOfStuff")
.set(mc, {y:20}, "bunchOfStuff")
.call(getInfo, [mc, "y"], "bunchOfStuff")
.set(mc, {alpha:0.5}, "bunchOfStuff")
.call(getInfo, [mc, "alpha"], "bunchOfStuff")
 
function getInfo(obj, prop):void{
trace(prop +  "=" + obj[prop]);
}

 

//output

 

//rotation=40
//y=20
//alpha=0.5

 

 

I don't know enough about your main issue of the timeline jumping around to address it by just looking at your code. Hopefully this other info is of some help. 

 

  • Like 1
Link to comment
Share on other sites

Thanks.  Very helpful.

 

As per Jack's help, I believe the issue with the main code was that tweenFromTo() triggers listeners in the intervening code, which seek() got around -- that is calling seek() went to the right spot without calling the listeners in between. Jack sent me some code that skipped the listeners in the tweenFromTo() and it seems to have solved the problem.

  • Like 1
Link to comment
Share on other sites

Excellent - thanks for reporting back here.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×