Jump to content
Search Community

Endless horizontal scroll at constant speed

eco_bach test
Moderator Tag

Recommended Posts

I need to do an endless horizontal scroll of elements within a parent MovieClip.

 

No matter what ever method I try, an element of 'drift' occurs and eventually the elements start to overlap.

 

I've tried using relative recursive tweening for each element according but this method seems prone to quite a bit of error after repeated starts and stops.

 

//CODE START

function doScroll():void {

TweenLite.to(this, .25, {x:"20", ease:Linear.easeNone,onUpdate:checkPos,onComplete:doScroll});

}

//CODE END

I've reverted to doing absolute tweens to a predefined position using a contant speed. This seems to be more accurate but still some 'drift' occurs.

 

//CODE START

//_dest is predefined

var speed:Number = 500;

var dist:Number = this.x - _dest;

var distAbs:Number = dist < 0 ? -dist : dist;

//kludge to get constant velocity by recalculating time every frame

_time = distAbs / speed;

TweenLite.to(this, _time, {x:_dest, ease:Linear.easeNone,onComplete:reset});

//CODE END

Thought this should be very simple. Can anyone point me to any possible tutorials or make any suggestions?

 

Any help appreciated.

Link to comment
Share on other sites

Please post an FLA the demonstrates the "drift". I'm guessing you must have some time-dependent code that doesn't account for the fact that it's impossible in Flash to refresh the frames frequently enough to have PERFECT durations. For example, your first code starts a tween that lasts 0.25 seconds, but let's say the Flash Player is doing a lot of rendering and the frame rate drops momentarily to about 10fps (every 0.1 seconds). That means that when it calculates the tween on its last render, 0.3 seconds has elapsed rather than 0.25 seconds. TweenLite/Max always calculate their ratios correctly according to the time, but that doesn't mean they can force the frame rate to cooperate with ENDING the tween at the perfect millesecond every time. So in your case, you used an onComplete to call the method again which starts another 0.25 second tween. If it's still refreshing the frames at 10fps, you'll lose another 0.05 seconds on that iteration. And that can build up over time. Technically TweenLite is doing its job perfectly but the Flash Player is limited as far as how frequently it can refresh and calculate things.

 

TweenMax and TimelineMax are specifically built to solve this sort of "drift" problem with the way they implement their repeat logic internally. Most other tweening engines do a lazy type of repeat that simply starts another tween after the first one finishes, but TweenMax/TimelineMax calculate things precisely based on the very beginning tween in order to be more accurate. Long story - I won't bore you with the technique.

 

Anyway, you could do something like this:

var t:TweenMax = TweenMax.to(this, 0.25, {x:"20", ease:Linear.easeNone, onComplete:resetTween});
function resetTween():void {
t.invalidate(); //forces it to re-init next render (so that the relative x:"20" figures out the new start/end)
}
var tl:TimelineMax = new TimelineMax({repeat:-1}); //repeats indefinitely
tl.append(t);

Link to comment
Share on other sites

Thanks jack

tried TweenMax, same issue

 

I've uploaded an fla with stripped down class files. The relevant code is in the class ItemBase.

In particular the doScroll() method.

 

The only thing I've done is add an onUpdate handler to get a continuous scroll.

 

In the fla there are 4 MovieClip Items

 

Clicking on the left arrow repeatedly and you can see that the green rectangles representing the outlines of the MovieClips no longer stay aligned.

 

There is a bug currently and the right arrow doesn't work but you should get the idea once you've compiled and viewed in Flash.

 

Note I've commented out my previous code using TweenLite and absolute, constant speed tweens.

 

Any help appreciated!

Link to comment
Share on other sites

Ah, I see the issue - it's a logic problem in your code. In your onUpdate, you're running conditional logic such that if the x position is beyond 980, it kills the tween and moves x back to -980 and starts things over. You're doing that for each individual item, each of which begins at a different position. That initial position affects when it crosses that threshold, thus when they reposition, the offsets are different.

 

For example, let's say item1 starts at an x position of 0 and item2 starts at 490 and both start moving at 400 pixels per second and your frame rate is 60, thus they'll move 6.66666 pixels per frame. Item1 will take 147 frames to hit 980. However, item2 will take 74 frames (actually 73.5, but there's no such thing as a half-frame) to cross the 980 threshold, but when it does so it will be at an x position of 493.333333. At that point it jumps back to -980 due to your conditional logic, but notice that it traveled an EXTRA 3.333333 pixels. You intended Item1 and item2 to travel at the exact same velocities and they do during the tween, but your onUpdate logic is misaligning them on the reposition such that in the end, some are traveling more than others which affects their overall velocity.

 

There are many, many ways you could build this scrolling behavior so that it works correctly. One option is to simply move x back by an absolute amount when it crosses the threshold instead of moving it to exactly -980. For example:

BAD:

x = -980;

GOOD:

x -= 980 * 2;

 

If I were building this, I'd probably engineer things a lot differently so that a single tween was driving the entire thing and you use a simple getter/setter property that would control everything. I don't want to complicate things here, though, and I don't have much time to walk you through that whole concept so I'll spare you :) I think the code above would likely get you the result you're looking for.

 

Hopefully that explanation was clear.

Link to comment
Share on other sites

Thanks Jack!

 

Almost there. I now tween PERFECTLY when scrolling right and NO error creeps in. yeah!

 

But scrolling left still needs work. The main issue is that the scrolling when using a negative relative seems to introduce a little bit of 'stutter' to the tween. The tween is not as buttery smooth as when tweening to the right.

And as a result of this stutter I assume, some error again creeps in when scrolling left. Any idea why a relative tween using a negative value would not be as smooth?

In the code below when scrolling left _dist ="-100" and when right "100";

 

My pseudo code>

 

private function onUpdateHandler():void {
		if (MOVERIGHT) {
			if (x >= 980) {
				TweenLite.killTweensOf(this);
				//x = -980//OLD BAD
				x -= 980 * 2;///NEW GOOD
				doScroll();
			}
		}
		if (MOVELEFT) {
			if (x <= -490) {
				TweenLite.killTweensOf(this);
				x += 980 * 2;//NEW GOOD??
				doScroll();
			}
		}
	}

	private function doScroll():void {
		var t:TweenMax = TweenMax.to(this, 0.25, {x:_dist, ease:Linear.easeNone,onUpdate:onUpdateHandler, onComplete:resetTween});
		var tl:TimelineMax = new TimelineMax({repeat:-1}); //repeats indefinitely
		tl.append(t);
		function resetTween():void {
			t.invalidate();
		}
	}

Link to comment
Share on other sites

This has to do with the fact that Flash rounds x/y coordinates to the nearest 0.05. So when you do your manual reposition (wrap), small rounding errors creep in. For example, let's say TweenMax sets the exact x value to 980.799. Flash will actually round that to 980.75. Then when you reposition it as this.x -= 980 and then tween it, the value would have just lost almost 0.05 pixels on that round. Do that many times and it can add up to a half-pixel or whole pixel (or more). Again, this is NOT an issue with the tweening engine. You'll see that the engine itself sets the values correctly, but Flash rounds them internally when applied to DisplayObjects.

 

I've attached a revised set of files (actually, I just edited the ItemBase.as file (where you do the scrolling) that greatly simplifies things and improves the accuracy. It still isn't the way I'd actually recommend engineering the infinite scrolling, but I didn't want to completely re-engineer more of your project. Ultimately I'd recommend having a single class or chunk of code that controls ALL the items that you're aligning/scrolling. It would lay things out from a single reference point so that everything lines up perfectly every time. You could tween a getter/setter that applies the logic. You can see a smaller-scale example in the code I attached (the scrollX getter/setter).

 

I hope that clears things up.

 

Jack

Link to comment
Share on other sites

I know it may seem advanced, but it's actually quite a bit simpler than the other approach. Basically what's happening is I set up a scrollX getter/setter that's a value between 0 and 1 indicating the progress of the entire scroll. So for example, if it's supposed to scroll from 0 to 1000 pixels, 0.5 would be at 500, 0.75 would be at 750, etc. The setter does the actual positioning for you based on whatever value gets fed in there. If you set scrollX to 0.5, it'd put x at 500 (although in your example, there's an offset of 490 backwards and the span is something like 1470 if I remember correctly). If a value above 1 gets fed into the setter, it wraps (that's the conditional logic at the beginning of the setter). So 1.25 would become 0.25 and -0.75 would become 0.25, etc. That way, TweenLite can just tween to any relative value without any concern for wrapping since that's done in the setter. For example, if scrollX is 0.5 and we do a relative tween of "1", that would tween it from 0.5 to 1.5. No problem - once it goes past 1, the setter automatically wraps it, so 1.1 becomes 0.1. Even if we feed in crazy values like 1245.224588, it'd plot that exactly where it needs to be.

 

Make more sense now?

Link to comment
Share on other sites

Thanks, slowly making sense. What has me a bit befuddled is the actual implementation of the getter setters. When are we setting scrollX and when are we getting scrollX?

 

The only reference I see is in the TweenMax instance scrollX:change. So we're tweening the scrollX property (the setter) but we need to pass an argument of type Number.

 

Once I get past this hurdle I think it will all make sense..

Link to comment
Share on other sites

Right, TweenLite passes that number into the setter. When you tween a value, TweenLite basically does this sort of thing on every frame:

target.property = newValueCalculatedByTweenLite;

 

In your case, that's kinda like:

myItem.scrollX = value;

 

As far as getting the value, TweenLite does that internally too when it renders the first time - it says "okay he wants to tween scrollX to ___, so let me check what the value is now so I can figure out the change...start = target.property;"

 

You can do this all manually too if you want to see the mechanics. Add trace() statements inside your getter and setter and then do:

 

var start:Number = myItem.scrollX; //triggers getter
myItem.scrollX = start + 0.5; //triggers setter.

Link to comment
Share on other sites

  • 4 months later...

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.
×
×
  • Create New...