AS3: Stalling a for loop at each pass - actionscript-3

So I've worked out a great way to create a pixelized wipe effect, with movie clips of pixels nested in rows. I've created a loop that goes through each row, and then another loop inside that loop for the pixels in each row. I then use a random number between 1-0 for for the delay before it alpha-ups the pixel. I used Greensock for the tween and it works great.
One problem, I can't stall the for loop for the rows, and every row comes up at once. Still, the pixels are staggered, and this is a nice effect, but I want it to move from left to right. So my code looks like this: (BTW, yes, AS3 does have setTimeout and it works fine)
function stripeWipeUp(stripe:MovieClip):void {
var total = stripe.numChildren;
for (var i:int = 0; i<total; i++) { // the rows
trace(i);
setTimeout(function() {
trace("setTimeout: "+i);
var row:MovieClip = stripe.getChildAt(i) as MovieClip;
var pixTotal = row.numChildren;
for (var j:int = 0; j<pixTotal; j++) { the pixels
var pix:MovieClip = row.getChildAt(j) as MovieClip;
var num = Math.floor(Math.random()*100)/100; // the delay
pixUp(pix, num); // my greensock function
}
}, 500);
}
}
What happens, with the setTimeout in there, is that the "i" for loop hits every row (there are 27) before one setTimeout gets finished. So, it doesn't work as it should - the next loop should not execute until the setTimeout is finished. Any idea how to accomplish this so each row gets stalled by about 500 ms? Thanks.

Your problem is with this.
trace("setTimeout: "+i);
"i" is not what you think it is.
The scope of "i" is in stripeWipeUp not the anonymous function.
With that being said "i" is always the value of the last itteration of the loop that was run across all of your anonymous functions.
So as you can see when the anon functions trigger "i" will most-likely be equal to "total".
This is why we avoid using anonymous functions as scope is very hard determine.
Create an array and store a custom class in it for each "pixel" that will control what you want to do.
In this class use a Timer Object not setTimeOut. setTimeOut is a garbage function left over from AS2.

drop the setTimeout and just add a delay multiplied with i
var num = ( i * 500 ) + Math.floor(Math.random()*100)/100; // the delay

I would encapsulate i and j, a Timer, and an event handler for the Timer, into a class. In the event handler I would:
Do your effect for the pixel corresponding to j and i
Update j (and i)
If there are more pixels, schedule the next event

I was able to get it to work by taking the inner part out and making it a separate function. I would prefer a much cleaner solution like Creynders', but for some reason that didn't work. Here is the code:
// the row of pixels
function row(stripe:MovieClip, i:int, up:Boolean, del:Number):void {
setTimeout(function() {
var row:MovieClip = stripe.getChildAt(i) as MovieClip;
var pixTotal = row.numChildren;
for (var j:int = 0; j<pixTotal; j++) {
var pix:MovieClip = row.getChildAt(j) as MovieClip;
var num = Math.floor(Math.random()*100)/100;
(up) ? pixUp(pix, num) : pixDown(pix, num);
}
}, del);
}
// the rows in the "stripe"
function stripeWipe(stripe:MovieClip, up:Boolean):void {
var total = stripe.numChildren;
var del:Number = 0;
for (var i:int = 0; i<total; i++) {
row(stripe, i, up, del);
del = del+100;
}
}

Related

AS3 - How do you wait for a tween inside a for loop to complete?

I'm drawing a large shape using an array of points in a for loop and tweenlite as found at http://www.flashperfection.com/tutorials/Animated-line-drawing-using-TweenLite-in-AS3-22013.html
for(var i:uint = 0; i < pointsArray.length; i++){
TweenLite.to(dot2, .05, {x:pointsArray[i].x,
y:pointsArray[i].y,
delay:i*.05,
overwrite:false,
onUpdate:updateHandler});
}
function updateHandler():void{
lineAnim.graphics.lineTo(dot2.x, dot2.y);
lineAnim.graphics.moveTo(dot2.x, dot2.y);
}
I would like the animation to complete before continuing, but I'm unable to find a way to be notified when the full animation is complete. onComplete does not work as it triggers on the first set of coords. I also tried triggering when
i == pointsArray.length
but the loop finishes multiple seconds before the animation completes. I would like to avoid using a timer.
You could do an asynchronous loop by doing something like the following:
var curIndex:int = 0; //a var to hold the current index of the points array
tweenItem();
//a function to call every time a tween is finished
function tweenItem(){
if(curIndex >= pointsArray.length){
//all done, do something
return;
}
TweenLite.to(dot2, .05, {
x:pointsArray[curIndex].x,
y:pointsArray[curIndex].y,
onUpdate:updateHandler,
onComplete: tweenItem //call this function again once the tween completes
});
curIndex++; //incriment the index
}
Now, you could save yourself the trouble and just use TimelineLite, made by the same author, which makes sequencing multiple tweens very easy.
As I said in my comment, you need to use a callback.
I'm not too familiar with TweenLite, but can you add the onComplete callback to the last call only? Something like this:
for(var i:uint = 0; i < pointsArray.length; i++){
var extraArguments:Object = {
x:pointsArray[i].x,
y:pointsArray[i].y,
delay:i*.05,
overwrite:false,
onUpdate:updateHandler
};
// add in the onComplete callback only if it's the last loop.
if (i == pointsArray.length - 1 ) {
extraArguments.onComplete = yourOnCompleteFunction;
}
TweenLite.to(dot2, .05, extraArguments);
}

AS3 shuffling movieclips

I've added the basic targets and applying drag and drop for my puzzle pieces, now Im having trouble making the shuffling aspect. As in, after the player completes or opens up the fla, each time will start the puzzle pieces in random places of the stage. I understand using arrays for shuffling somehow but Im not sure exactly how to achieve this. I've stored the instance of my 19 puzzle pieces inside the array but now I have no idea what to do with this array. Other tutorials were abit out of my league and leaves my head scratching.
Just started doing coding for flash professional so yeah, any help with the shuffling movie clips ie the puzzles pieces would be greatly appreciated.
Heres's my code, Im not posting the whole thing since from P1 to P19 is basically copy pasting:
import flash.events.Event;
stage.addEventListener(Event.ENTER_FRAME, EntFrame)
function EntFrame(e: Event) : void
{
P1.addEventListener(MouseEvent.MOUSE_DOWN, fl_ClickToDrag);
function fl_ClickToDrag(event:MouseEvent):void
{
P1.startDrag();
}
stage.addEventListener(MouseEvent.MOUSE_UP, fl_ReleaseToDrop);
function fl_ReleaseToDrop(event:MouseEvent):void
{
P1.stopDrag();
}
if (T1.hitTestObject(P1.Tar1))
{
P1.x = 313.15;
P1.y = 242.75;
}
P19.addEventListener(MouseEvent.MOUSE_DOWN, fl_ClickToDrag_19);
function fl_ClickToDrag_19(event:MouseEvent):void
{
P19.startDrag();
}
stage.addEventListener(MouseEvent.MOUSE_UP, fl_ReleaseToDrop_19);
function fl_ReleaseToDrop_19(event:MouseEvent):void
{
P19.stopDrag();
}
if (T19.hitTestObject(P19.Tar19))
{
P19.x = 624.35;
P19.y = 455.60;
}
}
Here is what I hope is more holistic answer.
First, ditch those inline functions. Right now you make an ENTER_FRAME listener and inside that function you have inline function defined. This means every frame tick (which is tied to your frame rate, not the main timeline), those functions are going to get created again, and since you are adding them as handlers for listeners, they will stay in memory forever.
Here is a way you code this, showing ways to reduce redundancy and get rid of those memory leaks. This assumes the following:
You have 19 objects on the stage called T1 - T19, that represent the possible locations the pieces can go.
You have 19 pieces on the stage called P1 - P19, that, and the numbers correlate to the T locations as per the correct location of the piece.
//let's create a function to randomize the piece location
function seedPieces() {
//create an array consisting of the integers 1 - 19
var unusedSpaces:Vector.<int> = new Vector.<int>;
var i:int;
for (i = 1; i <= 19; i++) {
//populate that array
unusedSpaces.push(i);
}
var curLocation:DisplayObject; //helper var for the loop below
var curPiece:Sprite; //helper var for the loop below
//loop 19 times (from 1 - 19) - one iteration for each piece
for (i = 1; i <= 19; i++) {
curPiece = this["P" + i] as Sprite; //you can get the piece this way, or use an array if you've made one, like `pieces[i];`
trace(curPiece.name);
//splice removes and returns the item at the specified index (in this case a random number between 0 and arrays length less 1) - the second parameter is amount of items to remove (just 1 for this case)
curLocation = this["T" + unusedSpaces.splice(int(Math.random() * unusedSpaces.length), 1)] as DisplayObject;
trace(" ",curLocation.name);
//move the piece to the random location:
curPiece.x = curLocation.x;
curPiece.y = curLocation.y;
}
}
//NOW, as an aside, you should use a loop to add all your listeners for the sake of sanity - if you have them in an array, loop through that, or use the sloppy way like this:
for (var i:int = 1; i <= 19; i++) {
Sprite(this["P" + i]).addEventListener(MouseEvent.MOUSE_DOWN, fl_ClickToDrag);
}
//create a var to hold any piece that is currently being dragged, so you know which piece to stop drag on later
var currentDraggingItem:Sprite;
seedPieces();
function fl_ClickToDrag(event:MouseEvent):void
{
//assign this clicked item to the currentDraggingItem var
currentDraggingItem = event.currentTarget as Sprite;
//bring this one to the front
currentDraggingItem.parent.addChild(currentDraggingItem);
//you can use this one click handler for all pieces
//the piece that was actually clicked, is referenced by event.currentTarget
currentDraggingItem.startDrag();
//add the mouse up listener now that the mouse is currently DOWN
stage.addEventListener(MouseEvent.MOUSE_UP, fl_ReleaseToDrop);
//listen every frame while dragging
stage.addEventListener(Event.ENTER_FRAME, EntFrame);
}
function fl_ReleaseToDrop(event:MouseEvent):void
{
//if currentDraggingItem has a value, stop drag it
if (currentDraggingItem) {
currentDraggingItem.stopDrag();
//send to the back
currentDraggingItem.parent.addChildAt(currentDraggingItem,0);
}
//remove the mouse up and enter frame listener now that the mouse is UP
stage.removeEventListener(MouseEvent.MOUSE_UP, fl_ReleaseToDrop);
stage.removeEventListener(Event.ENTER_FRAME, EntFrame);
if(checkComplete()){
//game over, do something
}
}
function EntFrame(e: Event) : void
{
//this will snap the peice to the correct spot when the mouse is touching the correct spot
if(currentDraggingItem){
if (this[currentDraggingItem.name.replace("P","T")].hitTestPoint(mouseX,mouseY))
{
currentDraggingItem.x = this[currentDraggingItem.name.replace("P","T")].x;
currentDraggingItem.y = this[currentDraggingItem.name.replace("P","T")].y;
}
}
}
function checkComplete():Boolean {
//use a loop to go through all your pieces and check if they are in the right spot. Again, you could have them in an array, or do it the lazy way
for (var i:int = 1; i <= 19; i++) {
if (!this["T"+i].hitTestObject(this["P"+i]))
{
return false;
}
}
return true;
}
Well, in general you can shuffle with the following code:
var shuffledVector:Vector.<someClass> = new Vector.<someClass>;
while (originalVector.length > 0) {
shuffledVector.push(originalVector.splice(Math.random() * originalVector.length, 1)[0]);
}
Longer, explained version:
var shuffledVector:Vector.<someClass> = new Vector.<someClass>; //We will store our shuffled vector in here
var randomIndex:int; //Random index from the originalVector
var resultVector:Vector.<someClass>; //result from the originalVector.splice(...) function
var randomElement:someClass; //Random element from the originalVector
while (originalVector.length > 0) { //We will reduce the size of the originalVector until the originalVector is empty.
randomIndex = Math.random() * originalVector.length; //Calculate a random index within the range of the originalVector from 0 to originalVector.lenght-1 (note that the range decreases by one on every loop)
randomVector = originalVector.splice(randomIndex, 1); //Use splice to remove one element at the randomly choosen index, we will receive a vector with the removed element...
randomElement = randomVector[0]; //...so we need to access the element
shuffledVector.push(randomElement); //Add the randomly choosen element to our shuffled vector
}
I've written the code for a vector as i suggest to use a vector instead of an array, but the principle behind it is the same for an array.
In your case the originalVector is a vector filled with your P1-P19 Movieclips and someClass would be MovieClip. The originalVector is empty at the end and could be replaced with the shuffled one and of course it would make a lot more sense if you put the code in a seperate function like this:
function Shuffle(originalVector:Vector.<someClass>) : void {
var shuffledVector:Vector.<someClass> = new Vector.<someClass>;
while (originalVector.length > 0) {
shuffledVector.push(originalVector.splice(Math.random() * originalVector.length, 1)[0]);
}
originalVector = shuffledVector;
}
Offtopic, but important for further coding: Someone else already mentioned, that it is not good to add EventListeners on every frame, because it is absolutely unnecessary. You only need to add the Listeners once. Your code is very repetitive, you should use a function which accepts a MovieClip, x and y then call that function 19 times.
e.g.:
function setUpMovieClip(MC:MovieClip, x:int, y:int) : {
MC.addEventListener(MouseEvent.MOUSE_DOWN, clickToDrag);
//more code...
}
within the clickToDrag function you can access the MovieClip which was clicked via the event.target property:
function clickToDrag(e:MouseEvent) : {
e.target.startDrag();
//more code...
}
I hope you get the idea.

AS3 .addEventListener in a loop?

I am creating a line of Sprite elements. Every sprite element has different job, when it is clicked. How can I make the function inside the addEventListener to know which button was clicked?
In this case, the traced value of i when it is cliicked is always 6. Which is wrong, because 6 is only the last element of the array. What about the rest of them, the beginning?
for (var i:int = 0; i < 6; i++) {
var barPart:Sprite = new Sprite();
barPart.x = i * (30);
barPart.y = 0;
barPart.graphics.beginFill(0x000000, 0.2);
barPart.graphics.drawRect(0, 0, 10, 10);
barPart.graphics.endFill();
barPart.addEventListener(MouseEvent.CLICK, function(_event:MouseEvent):void {
trace(i);
});
}
When the application is build and the listeners are added, the loop has already executed, so the index "i" will always be six by the time the user ends up clicking the button.
To distinguish between the different items, use their "name" property (prop of DisplayObject) like shown ...
Try not to have listener function as a method closure in a loop, instead do this:
for (...)
{
... code
barPart.name = "barPart-" +i;
barPart.addEventListener(MouseEvent.CLICK, barPart_clickHandler);
}
and implement the function (event handler separately) like:
private function barPart_clickHandler(e:MouseEvent):void
{
// the event's target will let you know who dispatched the function
var name:String = Sprite(e.currentTarget).name;
name = name.replace("barpart-", "");
switch(name)
{
case '0':
// your code
break;
.
.
}
}
#Shally Virk - My mistake. I was thinking of MovieClip which is a dynamic object so it allows adding arbitrary fields. You are right, there are lots of ways to get around this problem, but your suggestion works fine.
While the cause is not clear to me, the answer is a little more simple,
The events are registered correctly, but flash takes the last computed value.
Knowing that we can work around.
The work around can either do like Shally Virk wrote, but that tends to get confusing on bigger scale. So we want something more general and simple.
Now here are the steps taking this in mind:
1. We know sprite are not dynamic, so we make class to extend spirte and make it dynamic. Since the class has basically 0 code, the only difference being the dynamic , the amount of memory added is small
2. Having the class here's the code:
for (var i:int = 0; i < 6; i++) {
var barPart:CustomSprite = new CustomSprite();
barPart.x = i * (30);
barPart.y = 0;
barPart.graphics.beginFill(0x000000, 0.2);
barPart.graphics.drawRect(0, 0, 10, 10);
barPart.graphics.endFill();
barPart.i = i;
barPart.addEventListener(MouseEvent.CLICK, function(_event:MouseEvent):void {
trace(_event.currentTarget.i);
});
}
:)

Object removed from screen before the relevant command is called (Error #1009)

I'm sorry if it's asking for too much, but I'm too confused by now. I'm making this really simple shooter game for my nephew in AS3. It all seems to be working just fine, except for one really annoying error that keeps popping up every second or third time the game is launched.
IT is Error #1009: Cannot access a property or method of a null object reference. The problem is always with parent.removeChild(this) command in the relevant class (EnemyClass, BulletClass or MissileClass). This happens in two cases: either when checkFinishConditions method in Main is called and the EnemyClass instance needs to be deleted. So if I get the #1009 error does this mean the instance has already been deleted? The second situation is when inst.hitTestObject(enemyInstance) is checked in Main class. Does this mean the EnemyClass instance has already been deleted somehow? I'm totally lost here to be honest.
private function checkCollision():void
{
//loop through missiles
for (var i:int = 0; i < aMissileArray.length; i++) {
//get the current missile
var currentMissile:missileClass = aMissileArray[i];
//loop through enemies
for (var j:int = 0; j < aEnemyArray.length; j++) {
var thisEnemy:EnemyClass = aEnemyArray[j];
if (currentMissile.hitTestObject(thisEnemy)) {
var thisExplode:ExplosionClass = new ExplosionClass(thisEnemy.x,thisEnemy.y);
addChild(thisExplode);
currentMissile.destroyThis();
aMissileArray.splice(i,1);
thisEnemy.deleteEnemy();
aEnemyArray.splice(j, 1);
aDamageArray.splice(j, 1);
scoreValueText += 1;
j--;
i--;
}
//break;
}
}
//loop through bullets
for (var l:int = 0; l < aBulletArray.length; l++) {
//get the current missile
var currentBullet:BulletClass = aBulletArray[l];
//loop through enemies
for (var k:int = 0; k < aEnemyArray.length; k++) {
var currentEnemy:EnemyClass = aEnemyArray[k];
if (currentBullet.hitTestObject(currentEnemy)) {
currentBullet.destroyThis();
aBulletArray.splice(l, 1);
aDamageArray[k] -= 1;
l--;
if (aDamageArray[k] < 1) {
//create an explosion
var thisBulletExplode:ExplosionClass = new ExplosionClass(currentEnemy.x,currentEnemy.y);
addChild(thisBulletExplode);
currentEnemy.deleteEnemy();
aEnemyArray.splice(k, 1);
aDamageArray.splice(k, 1);
scoreValueText += 1;
k--;
}
break;
}
}
}
}
There is alot of code to look over, but one potential issue I see is here :
for each(var currentDieEnemy:EnemyClass in aEnemyArray) {
aEnemyArray.splice(0, 1);
currentDieEnemy.deleteEnemy();
}
The potential issue is that you are 'assuming' that the order of this loop is sequential based on the actual index order of the Array. You might want to stick a trace in there to verify that is actually what is happening, because I believe it's possible that they can be out of order.
See this question for details -- For-Each Loop AS3: Is the direction guaranteed?
So imagine the scenario where the 3rd item in the array is first and you splice the item at the 0 index. Now, you have an item in the array that is removed from the display list, but not from the array. So what happens when you get to that item ? Pretty much what you are describing will happen since the parent property is null, since you already removed it from the display list.
The way to fix that is to do something like this :
while (aEnemyArray.length > 0)
{
var currentDieEnemy:EnemyClass = aEnemyArray[0];
currentDieEnemy.deleteEnemy();
aEnemyArray.splice(0,1);
}
I found another issue in this block of code :
for (var j:int = 0; j < aEnemyArray.length; j++) {
var thisEnemy:EnemyClass = aEnemyArray[j];
if (currentMissile.hitTestObject(thisEnemy)) {
var thisExplode:ExplosionClass = new ExplosionClass(thisEnemy.x,thisEnemy.y);
addChild(thisExplode);
currentMissile.destroyThis();
aMissileArray.splice(i,1);
thisEnemy.deleteEnemy();
aEnemyArray.splice(j, 1); // now array composition is different.
aDamageArray.splice(j, 1);
scoreValueText += 1;
}
}
When you loop through an Array and splice an item at the loop index, you have to realize what happens to the composition of the array. Here's an example
Suppose enemy3 needs to be spliced because of a collision. Given your code, this is what happens :
Before (j == 2)
(0) enemy1
(1) enemy2
(2) enemy3
(3) enemy4
(4) enemy5
AFTER
(0) enemy1
(1) enemy2
(2) enemy4
(3) enemy5
So when the loop continues, j will now increment and equal 3.
The result is that enemy4 doesn't get evaluated as the composition of the array collapses to fill the hole created by the splice and you didn't adjust your loop index variable.
So, what you can do in that situation is simply decrement the loop index variable at the end of the block like so :
aEnemyArray.splice(j, 1); // now array composition is different.
aDamageArray.splice(j, 1);
scoreValueText += 1;
j--; // decrement loop index variable.
So while this is one solution, the main point to take away here is the change in the composition of your Array after a splice.
Yep, you are splicing your objects from arrays in a weird way. First, you are traversing the array forwards and splicing in the loop. This can lead to issues of several bullets not trigger collisions when they should, or say not move. See, if you are running your loop for bullets at index i, once you call bullets.splice(i,1); the bullet that was to be iterated next becomes at position i, then you increment i and that bullet remains unprocessed.
Next, you have nested loops, and in the inner loop you are removing an object from OUTER loop. This means once you did a removal of the outer loop's object, your inner loop is now invalidated, you have to preemptively terminate inner loop.
//loop through bullets
for (var l:int = aBulletArray.length-1; l>=0; l--) {
// first, traverse both arrays backwards
//get the current missile
var currentBullet:BulletClass = aBulletArray[l];
//loop through enemies
for (var k:int = aEnemyArray.length-1; k>=0; k--) {
var currentEnemy:EnemyClass = aEnemyArray[k];
if (currentBullet.hitTestObject(currentEnemy)) {
currentBullet.destroyThis();
aBulletArray.splice(l, 1);
aDamageArray[k] -= 1;
if (aDamageArray[k] < 1) {
//create an explosion
var thisExplode:ExplosionClass = new ExplosionClass(currentEnemy.x,currentEnemy.y);
addChild(thisExplode);
currentEnemy.deleteEnemy();
aEnemyArray.splice(k, 1);
aDamageArray.splice(k, 1);
scoreValueText += 1;
}
// and since we don't have "currentBullet" anymore, do this
break;
}
}
}
Fix all the other iterations through your arrays where you do splicing, like I did in this part of code, and you should avoid 1009 errors in these loops.
Also, you should not post your entire project code, but instead post only relevant parts, say an entire function that's reported as throwing an error, and explain which line is producing the error - it's written as number in the error's stack trace.

assign mouse event listener with loop to 10 movieclips

I am trying to loop through my 10 "bomb" movieclips and assign an eventlistener that calls the same function but passes the current movieclips name. the "bomb" movieclip names are incremented.
Below is my try
var i:number;
i=0;
while (i <= 10){
var current_bomb:Movieclip = (movingbomb_+i);
current_bomb.addEventListener(MouseEvent.ROLL_OVER, function updateBomb(current_bomb));
i++
}
function updateBomb(currentBomb):void{
currentBomb.gotoAndPlay(2);
}
Close, but not quite. Also, using a for loop is a much better idea here. With those changes, the code should look something like this:
for (var i:int = 0; i < 10; i++) {
var currentBomb:MovieClip = this["movingbomb_" + i];
currentBomb.addEventListener(MouseEvent.ROLL_OVER, function (evt:MouseEvent):void { updateBomb(currentBomb); });
}
function updateBomb(currentBomb:MovieClip):void {
currentBomb.gotoAndPlay(2);
}
Here's how this works.
The for loop simplifies all your while looping code into a single statement for efficiency.
We then select the current bomb using array-bracket selection syntax. To do this, you use this[name], where name is a string. The string we use will be "movingbomb_" with i tacked on to the end.
Finally, we create a unique anonymous function for each new event listener which redirects the mouse event to your updateBomb function and passes the currentBomb object.