Monday, June 21, 2010

Notorious Tween and the Garbage Collector Issues

I love tween and use it a lot while making websites and applications. But whenever I use more than 3 tweens simultaneously it doesn't work properly. It works sometimes and sometimes it just freezes in the middle and any Event Listener that has been called is not triggered. Moreover it works on some computer and in some it doesn't.

Before I tell you how to fix it let me explain why it happens. Flash player runs GC (Garbage Collector) once it executes the script. It does this to free up the memory and enhance the performance. Now they way flash player runs the GC depends on the system resource. So if the users computer has less memory GC is triggered earlier and if the user runs the program on the same computer when more memory is available it triggers the GC little late.

Most of the time tween gets stuck if you have code like below where the tween time is different.

var a:Tween = new Tween(a_mc, "y", Back.easeIn, -75, 0, 1, true);
var bTween = new Tween(b_mc, "y", Back.easeIn, -75, 0, 2, true);
var c:Tween = new Tween(c_mc, "y", Back.easeIn, -75, 0, 3, true);
c.addEventListener(TweenEvent.MOTION_FINISH, loadThis);

function loadThis(e:TweenEvent):void
{
trace ("It's Loaded");
}

here the Flash Engine runs all the scripts at same time and when the first tween is finished the GC disables all the tweens. A better way to code is to change the starting "y" axis.

var a = new Tween(a_mc, "y", Back.easeIn, -95, 0, 1, true);
var b = new Tween(b_mc, "y", Back.easeIn, -85, 0, 1, true);
var c = new Tween(c_mc, "y", Back.easeIn, -75, 0, 1, true);

function loadThis(e:TweenEvent):void
{
trace ("It's Loaded");
}


this way, the tween finishes at the same time, the movie clips reaches the destination in different time like the previous code and have less change to effected by GC. Now, this is not the foolproof. It still gets stuck once in a while. This is the issue with Flash Player and there is not way out, for at least now. The issue gets even worse when functions are triggered based on the Event Listener.

To make sure that the functions are running even when the tween gets stuck we can create timer function. In this function we can run a conditional statement to check if the tween has completed and the event has triggered. Here is the code for that

var activated:Boolean;
var myTimer:Timer = new Timer(1010,1);

var a = new Tween(a_mc, "y", Back.easeIn, -95, 0, 1, true);
var b = new Tween(b_mc, "y", Back.easeIn, -85, 0, 1, true);
var c = new Tween(c_mc, "y", Back.easeIn, -75, 0, 1, true);

function loadThis(e:TweenEvent):void
{
activated = true;
trace ("It's Loaded");
}

myTimer.start();
myTimer.addEventListener(TimerEvent.TIMER, checkTween);

function checkTween(e:TimerEvent):void
{
if(activated == false)
{
trace("It's Loaded");
}
if(a_mc.y <=-1 || b_mc.y <=-1 || c_mc.y <=-1)
{
a_mc.y = b_mc.y = c_mc.y = 0;
}
}

Thursday, June 17, 2010

Switch Case = Advance If else

If you haven't used Switch conditional statement then you are punishing yourself. By using switch statement you can reduce the code by 60% and is lot easier to read and understand.

Here is a little code for linking multiple websites to multiple button by calling a function. Now isn't that a awesome, one function handeling all MouseEvents


a_btn.addEventListener(MouseEvent.CLICK, openURL);
b_btn.addEventListener(MouseEvent.CLICK, openURL);
c_btn.addEventListener(MouseEvent.CLICK, openURL);

function openURL(e:MouseEvent):void
{
var url:String = new String(); // Creating a String Variable to pass URL
switch(e.target.parent.name) // Calling the instance name
{
// here it's checking if the name of the instance is "a_btn"
case "a_btn": // if the condition is true
url = "http://www.A.com/"; // do this
break; // and exit the loop
case "b_btn": // if the condition is true
url = "http://www.B.com"; //do this
break; // and exit the loop and so on.......
case "c_btn":
url = "http://www.C.com";
break;
default:
trace("None of the statement is true.")
}

// Once the conditional statement finishes
var myRequest:URLRequest = new URLRequest(url); // created a url request
navigateToURL(myRequest); // open the url request
}


One thing you have to be careful about he is the colon(:). The case statement ends with colon not with semicolon as other statements does.
And that is it. All the Mouse Click Event calling same function. The function check which Mouse Click Occurred base on that you call other functions of task.

Cups and Coin

Wow, it took a while for me to upload another tutorial. Sorry folks, I was working on Actionscript 3.0, XML, PHP and MySql site, NepaliRecipes.com. It is still on beta version but one can submit and search for Nepali Recipes. Now about this Game, Cups and Coin.

I programmed this game for my class project but it had few issues. First it had 459 lines of code and couple of external classes. I reduce the code to less than 200 line and no external classes. Beside that, there was some kind of bug that kept on changing the initial position of the cups after each animation. Finally, I figured it out. It was the version of Flash Player. If you use other older version then 10.1 it freezes once in a while. The 10.1 version of Flash Player and Flash CS5 handles the Tween Event with very less errors.

So, how does the game work? There are 3 cups and a coin. The coin is put inside one of the cup and moved randomly. A player has to figure out which cup has the coin. Pretty straight forward. After every correct answer level goes up, which increased the number of times the cups are moved and the moves gets faster, i.e. decreases the time. If the player gets 3 wrong picks, the game is over.
There are three life and unlimited level, I was able to play up to level ten.

The main logic I used in this game is the coin is always on the same cup, Cup A. The position of Cup A keeps on changing every time, if the player picks Cup A, he finds to coin. Since all the cups as identical, player has to visually keep track of the cup A.

Now, try the Game:


And now here is the source file and the following is the code.


import fl.transitions.Tween;
import fl.transitions.TweenEvent;
import fl.transitions.easing.*;
import flash.events.MouseEvent;

var reset_mc:mcReset = new mcReset();

var cupAx:Number = new Number(cupA_mc.x);
var cupAy:Number = new Number(cupA_mc.y);
var cupBx:Number = new Number(cupB_mc.x);
var cupBy:Number = new Number(cupB_mc.y);
var cupCx:Number = new Number(cupC_mc.x);
var cupCy:Number = new Number(cupC_mc.y);

var randomFirst:Number;
var randomSecond:Number;

var cupsArray:Array = new Array(cupA_mc, cupB_mc, cupC_mc);

var level:Number = new Number(0);
var counter:Number = new Number(0);
var time:Number = new Number(.6);
var life:Number = new Number(3);

play_mc.addEventListener(MouseEvent.CLICK, startGame);
play_mc.buttonMode = true;

level_txt.text = "0";
life_txt.text = life.toString();

function startGame(e:MouseEvent):void
{
// Disabling the play button
play_mc.alpha = 0;
play_mc.buttonMode = false;
play_mc.removeEventListener(MouseEvent.CLICK, startGame);
//Fading out the coin before the cup start moving
var myAlphaTween:Tween = new Tween(coin_mc, "alpha", Regular.easeOut, 1, 0, 1, true);
myAlphaTween.addEventListener(TweenEvent.MOTION_FINISH, selectRandomCup);
counter = 0;
level_txt.text = level.toString();
}



function selectRandomCup(e:TweenEvent):void
{
randomFirst = Math.ceil(Math.random()* 3); //generating the first random number
randomSecond = Math.ceil(Math.random()* 3); // generating the second random number
while(randomFirst == randomSecond) //run this loop untill the random second no is different than random first no
{
randomSecond = Math.ceil(Math.random()* 3);
}
if(randomFirst != randomSecond) // once the random numbers are different start swaping
{
//Getting cups from cupsArray using its index number
// Since the random no is between 1-3 and array length is 2, -1 is used so that there is no error.
startSpin(cupsArray[randomFirst-1], cupsArray[randomSecond-1]);
}
}


function startSpin(firstCup:MovieClip, secondCup:MovieClip):void
{
disableCupClicks();// disabling the cups to be clicked
setChildIndex(coin_mc,0); //Sending the coin to back of the cup(add remove child doesn't work,bug)

//Moving the first coin
var cupATweenX:Tween = new Tween(firstCup, "x", Regular.easeOut, firstCup.x, secondCup.x, time, true);
var cupATweenY:Tween = new Tween(firstCup, "y", Regular.easeOut, firstCup.y, secondCup.y, time, true);
//Moving the second coin
var cupBTweenX:Tween = new Tween(secondCup, "x", Regular.easeOut, secondCup.x, firstCup.x, time, true);
var cupBTweenY:Tween = new Tween(secondCup, "y", Regular.easeOut, secondCup.y, firstCup.y, time, true);
//increasing the counter once the move is completed
counter++;
//once the animation is doing call swap again
// Use the MOTION_STOP. For some reason MOTION_FINISH keeps on freezing randomly
cupBTweenY.addEventListener(TweenEvent.MOTION_STOP, swapAgain);
}

function swapAgain(e:TweenEvent):void
{
if(level >= counter) //Checking if the move needs to occur again
{
selectRandomCup(e);
}
else
{
enableCupClicks();
}
}

function enableCupClicks():void
{
cupA_mc.buttonMode = true;
cupB_mc.buttonMode = true;
cupC_mc.buttonMode = true;
cupA_mc.addEventListener(MouseEvent.CLICK, rightAnswer);
cupB_mc.addEventListener(MouseEvent.CLICK, wrongAnswer);
cupC_mc.addEventListener(MouseEvent.CLICK, wrongAnswer);
}


function disableCupClicks():void
{
cupA_mc.buttonMode = false;
cupB_mc.buttonMode = false;
cupC_mc.buttonMode = false;
cupA_mc.removeEventListener(MouseEvent.CLICK, rightAnswer);
cupB_mc.removeEventListener(MouseEvent.CLICK, wrongAnswer);
cupC_mc.removeEventListener(MouseEvent.CLICK, wrongAnswer);
}

function rightAnswer(e:MouseEvent):void
{
setChildIndex(coin_mc,5); //Bringing the coing to front
coin_mc.x = cupA_mc.x + cupA_mc.width/2 - coin_mc.width/2; // matching its position with cup A
coin_mc.y = cupA_mc.y+ cupA_mc.height/2 - coin_mc.height/2;
var myAlphaTween:Tween = new Tween(coin_mc, "alpha", Regular.easeOut, 0, 1, .5, true); // Animating it to be visible
checkTime(); //reducing the time if needed
level++; //increasing the level
startGame(e); //starting the game again
}

function wrongAnswer(e:MouseEvent):void
{
if(life >= 1)
{
life --; // Reducing the life count
life_txt.text = life.toString(); //Dispaying the text
setChildIndex(coin_mc,5); // Making the coin visible
coin_mc.x = cupA_mc.x + cupA_mc.width/2 - coin_mc.width/2; // Matching its posistion with cup A
coin_mc.y = cupA_mc.y+ cupA_mc.height/2 - coin_mc.height/2;
var myAlphaTween:Tween = new Tween(coin_mc, "alpha", Regular.easeOut, 0, 1, .5, true);
startGame(e);
}
else // if the life is 0
{
disableCupClicks();
addChild(reset_mc); // show Reset gamebutton
reset_mc.x = -1;
reset_mc.y =stage.stageHeight - reset_mc.height;
reset_mc.buttonMode = true;
reset_mc.addEventListener(MouseEvent.CLICK, reload);
}
}

function reload(e:MouseEvent):void
{
// Reseting everything
removeChild(reset_mc);
play_mc.alpha = 1;
play_mc.buttonMode = true;
play_mc.addEventListener(MouseEvent.CLICK, startGame);
level = 0;
level_txt.text = level.toString();
counter = 0;
time = .6;
life = 3;
life_txt.text = life.toString();
setChildIndex(coin_mc,5);
coin_mc.x = cupA_mc.x + cupA_mc.width/2 - coin_mc.width/2;
coin_mc.y = cupA_mc.y+ cupA_mc.height/2 - coin_mc.height/2;
}
function checkTime():void
{
// if the time is less than 0.2 sec it is very hard to see the animation
if(time >= .2)
{
time -= .07;
}
}