Making Timers available for GC? - actionscript-3

In a function that creates a new timer every time it is called, is this the correct way to dispose of it?
private var _timers:Vector.<Timer> = new Vector.<Timer>;
private var _timer:Timer
private function timer():void
{
_timer = new Timer(10000, 1);
_timers.push(_timer);
_timer.addEventListener(TimerEvent.TIMER_COMPLETE, onTimer, false,0,true);
_timer.start();
}
private function onTimer(e:TimerEvent):void
{
e.target.removeEventListener(TimerEvent.TIMER_COMPLETE, onTimer);
_timers[0] = null;
_timers.shift();
}

Maybe you can add a
_timers[0].stop();

Just in case your times change for whatever reason, it's best to deal with the object directly instead of assuming the Timer object is at the 0 index.
Just a small adjustment required for onTimer():
private function onTimer(e:TimerEvent):void
{
var timer:Timer = e.currentTarget as Timer;
timer.removeEventListener(TimerEvent.TIMER_COMPLETE, onTimer);
_timers.splice(_timers.indexOf(timer), 1);
}
You don't need to set the index to null and it's never good practice to assume your item will be at a specified index, always make sure. In this case you make sure by using the indexOf() method available in the Array type object (_timers).
Hope it helps.

I've made function "delay" that handles delay calls. I made it for my game in which I have used delay calls alot and I had to find a way to handle delays efficiently. Still AS3 guru's out there may still find more efficient ways, please let know if any.
public static var timer_stack:Vector.<Timer> = new Vector.<Timer>();
public static function delay(delaytime:Number, func_name:Function, repeat:Number = 1)
{
var timer:Timer = new Timer(delaytime, repeat);
timer_stack.push(timer);
timer_stack[timer_stack.length-1].addEventListener(TimerEvent.TIMER, func_name ,false,0,true);
timer_stack[timer_stack.length-1].addEventListener(TimerEvent.TIMER_COMPLETE,
function(e:TimerEvent){ delay_complete(e, func_name); });
timer_stack[timer_stack.length-1].start();
}
public static function delay_complete(e, func_name:Functio):void
{
e.target.stop();
e.target.removeEventListener(TimerEvent.TIMER, func_name);
timer_stack[timer_stack.length-1].removeEventListener(TimerEvent.TIMER_COMPLETE,
function(){ func_name_complete(e, func_name);} );
for(var i=0; i < timer_stack.length; i++)
{
if(timer_stack[i].running == true)
trace("timer # "+i+" is running");
if(timer_stack[i].running == false)
{
timer_stack[i] = null;
timer_stack.splice(i,1);
trace("remove timer # "+i);
}
}
}

Related

as3 return value on loader complete

I am trying to get my function to return a value from a php script once it has loaded. I am having issues with the 'return' aspect. Trying to the the value of 'TheLink" from a function with a 'return' in it. It's usually null. What am I doing wrong here?
var theLink = loadAudio();
public function loadAudio():String
{
var req:URLRequest = new URLRequest("myScript.php");
var loader:URLLoader = new URLLoader(req);
loader.dataFormat = URLLoaderDataFormat.VARIABLES;
loader.addEventListener(Event.COMPLETE, Finished);
function Finished(e:Event)
{
var theValue = JSON.parse(e.target.data.audioLink);
return theValue;
}
}
A lot of things are wrong in your code but more important is that you cannot escape the asynchronous nature of AS3.
First thing first, even if AS3 wasn't asynchronous your code would still not work. The "loadAudio" never returns anything so theLink can never get any data. The 'Finished' method is the one return something but there's no variable to catch that data either.
Now the real stuff, AS3 is asynchronous that means that things will happen at a later time and this is why you need to pass an event listener in order to 'catch' when things happen. In your case the Event.COMPLETE will trigger at a later time (asynchronous) so there's no way for a variable to catch the result before the result is actually available so:
var theLink:Object = loadAudio();//this is not possible
The correct way is:
private var loader:URLLoader;//avoid GC
private var theLink:Object;//this will store result when available
//I assume you are using a class
public function MyClass()
{
loadAudio();//let's start loading stuff
}
private function loadAudio():void
{
var req:URLRequest = new URLRequest("myScript.php");
loader = new URLLoader();
loader.dataFormat = URLLoaderDataFormat.VARIABLES;
loader.addEventListener(Event.COMPLETE, handleLoaded);
loader.load(req);
}
private function handleLoaded(e:Event):void
{
theLink = JSON.parse(e.target.data.audioLink);
//done loading so store results.
}

How to get a percentage from new Date();

I have a digital clock for now, and I need the corn to know when 10 seconds as passed, this way it can go to the next frame. Im having difficulty finding out how to gather the 10 secs and make it out of 100% for example
its 9:30:21 and i pushed the button
at 9:30:31 it should be done
but I want to create a percentage bar based on that 10sec.. heres my code
farmSlot1.addEventListener(MouseEvent.CLICK, farmClick1);
var startTime: Date = new Date();
var startSec: Number = startTime.seconds;
function farmClick1(e: MouseEvent): void {
addChild(menu);
menu.x = 400;
menu.y = 90;
menu.buyCornBtn.addEventListener(MouseEvent.CLICK, buyCorn1);
}
function buyCorn1(e: MouseEvent): void {
var startTime: Date = new Date();
startSec = startTime.seconds;
menu.buyCornBtn.addEventListener(Event.ENTER_FRAME, cornloading1);
farmSlot1.progressB.visible = true;
menu.buyCornBtn.removeEventListener(MouseEvent.CLICK, buyCorn1);
removeChild(menu);
}
function cornloading1(event: Event): void {
var now: Date = new Date();
var hr: Number = now.hours;
var min: Number = now.minutes;
var sec: Number = now.seconds;
var finished: Number = startSec + 5
var percent = Math.round((finished-sec) * 100)
if(sec < finished){
farmSlot1.loader_txt.text = percent
Object(root).farmSlot1.progressB.bar.scaleX = percent;
trace("hit");
}else if (sec == finished && farmSlot1.currentLabel != "corn") {
removeEventListener(Event.ENTER_FRAME, cornloading1);
trace("It did it");
farmSlot1.loader_txt.text = ""
farmSlot1.gotoAndStop("corn");
}
}
At first, as I see you are writing the code inside Flash frames. Incapsulating your code within classes is better approach which allows to avoid tons of problems in the future. Here is a link described some basic approaches for using classes:
http://www.kirupa.com/developer/as3/classes_as3_pg1.htm
In answer to your question:
You can simply use flash.utils.Timer class instead of using Date and ENTER_FRAME event: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/utils/Timer.html
Some piece of code:
//Import timer class
import flash.utils.Timer;
...
//In the class's body
//Create a timer instance. Timer will be executed 10 times every 100 milliseconds
private var timer:Timer = new Timer(100, 10);
...
//Somewhere in class's method (usually in the constructor)
timer.addEventListener(TimerEvent.TIMER, timerHandler);
timer.addEventListener(TimerEvent.TIMER_COMPLETE, timerCompleteHandler);
//Add click handler
button.addEventListener(MouseEvent.CLICK, buttonClickHandler);
...
private function buttonClickHandler(event:MouseEvent):void
{
//Do some stuff and start timer.
timer.start();
}
private function timerHandler(event:TimerEvent):void
{
//Update progress indicator
}
private function timerCompleteHandler(event:TimerEvent):void
{
//1 second is over so do some stuff.
}

How do I unload an external ".swf" file to load another?

I am a student who's working for months on a game and now I've got stuck on a problem.
I am new to actionscript 3 but i learn fast.
I can load my menu screen ("startScreen.swf") into my game (firstgame) automatically and everything works fine when I click play or storyScreen or instructionsScreen. But the problem is that I want to return after pressing ESC button. I have tried many code but nothing works exactly how I want.
Example: I click on story and the swf (storyScreen.swf) loads and I can read the story and when I am finished, I want to press ESC to reload my swf (startScreen.swf) and all the functions in it. Like play and instructions.
You can find my code and empty space in the function (esc).
I know it is maybe easy to solve but I really don't know how. :(
public class FirstGame extends MovieClip
{
public var Player:MovieClip
private var leftKeyIsDown:Boolean;
private var RightKeyIsDown:Boolean;
private var aMissileArray:Array;
private var aEnemyArray:Array;
public var scoreTxt:TextField;
public var ammoTxt:TextField;
public var MenuEnd:EndGameScreen;
public var menuAgain:EndGameScreen;
private var MenuStart:mcStartGameScreen;
private var MenuStory:mcStartGameScreen;
private var MenuInstructions:mcStartGameScreen;
private var nScore:Number;
private var nAmmo:Number;
private var tEnemyTimer:Timer;
public function FirstGame()
{
//Create a loader object
var startLoader:Loader = new Loader();
//add event listener to listen for the complete event
startLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, startLoaded);
//load our loader object
startLoader.load(new URLRequest("startScreen.swf"));
}
public function startLoaded(e:Event):void
{
MenuEnd.hideScreen();
Player.visible = false;
scoreTxt.visible = false;
ammoTxt.visible = false;
//get a reference to the loaded movieclip
MenuStart = e.target.content as mcStartGameScreen;
//listen for start game event
MenuStart.addEventListener("START_GAME", playGameAgain);
//add it to the stage
addChild(MenuStart);
//get a reference to the loaded movieclip
MenuStory = e.target.content as mcStartGameScreen;
//listen for start game event
MenuStory.addEventListener("SHOW_STORY", storyGameScreen);
//add it to the stage
addChild(MenuStory);
//get a reference to the loaded movieclip
MenuInstructions = e.target.content as mcStartGameScreen;
//listen for start game event
MenuInstructions.addEventListener("SHOW_INSTRUCTIONS", instructionsGameScreen);
//add it to the stage
addChild(MenuInstructions);
}
private function instructionsGameScreen(e:Event):void
{
var instructionsLoader:Loader = new Loader();
var url:URLRequest = new URLRequest("instructionsScreen.swf");
instructionsLoader.load(url);
addChild(instructionsLoader);
stage.addEventListener(KeyboardEvent.KEY_UP, esc);
}
private function storyGameScreen(e:Event):void
{
var storyLoader:Loader = new Loader();
var url:URLRequest = new URLRequest("storyScreen.swf");
storyLoader.load(url);
addChild(storyLoader);
stage.addEventListener(KeyboardEvent.KEY_UP, esc);
}
private function esc(e:KeyboardEvent):void
{
if (e.keyCode == 27)
{
//fscommand("quit");
/////////test//////////////////(new URLRequest(stage.loaderInfo.url), "FirstGame.swf");
}
}
private function playGameAgain(e:Event):void
{
//initialize variables
aMissileArray = new Array();
aEnemyArray = new Array();
nScore = 0;
nAmmo = 20;
Player.x = 262,95
Player.y = 323,30
Player.visible = true;
scoreTxt.visible = true;
ammoTxt.visible = true;
MenuStart.hideScreen();
MenuEnd.addEventListener("PLAY_AGAIN", playGameAgain);
MenuEnd.hideScreen();
updateScoreText();
updateAmmoText();
//trace("First Game Loaded");
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDown);
stage.addEventListener(KeyboardEvent.KEY_UP, keyUp);
stage.addEventListener(Event.ENTER_FRAME, gameLoop)
//creat an timer object
tEnemyTimer = new Timer(1000)
//listen for the timer ticks/intervals
tEnemyTimer.addEventListener(TimerEvent.TIMER, addEnemy)
//start our timer
tEnemyTimer.start();
}
I believe you should be able to start a new request on the same loader object. Either way, you're dealing with object creation + cleanup. The crux of the solution I'm offering is that you reuse your loaders.
Your current code is somewhat repetitious, so I've modified it slightly to demonstrate how you could simplify the code. Some thoughts while reviewing your code:
You're adding the same object to the stage multiple times (ie.,
MenuStart, MenuStory, MenuInstructions); these are all pointers to
the same root swf you loaded (a.k.a., startLoader).
You've registered events the stage at multiple locations. Best
practice is to place these in your constructor as they are
persistent.
Because you want to reuse your loaders at a later point, keeping a
table with them makes it easier to reference.
Any time you find yourself programming the same code in similar ways,
it's a good indication that you can simplify with a single function
(simply change the arguments).
Give this a try:
public var Player:MovieClip
private var leftKeyIsDown:Boolean;
private var RightKeyIsDown:Boolean;
private var aMissileArray:Array;
private var aEnemyArray:Array;
public var scoreTxt:TextField;
public var ammoTxt:TextField;
public var MenuEnd:EndGameScreen;
public var menuAgain:EndGameScreen;
private var MenuStart:mcStartGameScreen;
private var MenuStory:mcStartGameScreen;
private var MenuInstructions:mcStartGameScreen;
private var nScore:Number;
private var nAmmo:Number;
private var tEnemyTimer:Timer;
private var screens:Object = {
"SHOW_INSTRUCTIONS":{
"loader":new Loader(),
"url":new URLRequest("instructionsScreen.swf")
},
"SHOW_STORY":{
"loader":new Loader(),
"url":new URLRequest("storyScreen.swf")
},
"START_GAME":{
"loader":new Loader(),
"url":new URLRequest("startScreen.swf")
}
}
public function FirstGame() {
var startLoader:Loader = loadScreen({"type":"START_GAME"});
//Register our event listeners
startLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, startLoaded);
stage.addEventListener(KeyboardEvent.KEY_UP, esc);
stage.addEventListener(KeyboardEvent.KEY_UP, keyUp);
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDown);
stage.addEventListener(Event.ENTER_FRAME, gameLoop)
}
private function loadScreen(e:Object):Loader {
screens[e.type].loader.load(screens[e.type].url);
addChild(screens[e.type].loader);
return screens[e.type].loader;
}
public function startLoaded(e:Event):void {
//initialize variables
aMissileArray = new Array();
aEnemyArray = new Array();
nScore = 0;
nAmmo = 20;
Player.x = 262, 95
Player.y = 323, 30
Player.visible = true;
scoreTxt.visible = true;
ammoTxt.visible = true;
MenuEnd.addEventListener("PLAY_AGAIN", playGameAgain);
MenuEnd.hideScreen();
updateScoreText();
updateAmmoText();
//creat an timer object
tEnemyTimer = new Timer(1000)
tEnemyTimer.addEventListener(TimerEvent.TIMER, addEnemy)
tEnemyTimer.start();
var swfRoot = screens.START_GAME.loader["content"];
swfRoot.addEventListener("START_GAME", loadScreen);
swfRoot.addEventListener("SHOW_STORY", loadScreen);
swfRoot.addEventListener("SHOW_INSTRUCTIONS", loadScreen);
}
private function esc(e:KeyboardEvent):void {
if (e.keyCode == 27) {
//fscommand("quit");
// Loop through our loaders, and reset them all.
for each (var entry:Object in screens) {
entry.loader.unloadAndStop();
removeChild(entry.loader);
}
// Reload the start screen
var startLoader:Loader = loadScreen({"type":"START_GAME"});
startLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, startLoaded);
}
}
The last thing I want to be guilty of is telling you how to run your project, however, I think you may be taking the wrong approach, here.
Instead of setting up the story and the game as separate swf files, what you would probably need to do is set up the story as a MovieClip, and the game as a MovieClip. (Yes, MovieClips can contain other symbols, including other MovieClips.) Then, you'll want to hide/show or add/remove these MovieClips from your stage using code.
If you need to know how to do this, let me give you the age-old admonishment: RTD (Read the Documentation). These are basic tasks that are covered both in the online Adobe Flash documentation, and across numerous tutorials online.
I hope that helps!

actionscript 3 Timer class and callback problems

I have a timer class in actionscript that looks like this:
public class AGTimer {
public var myTimer:Timer ;
public var done:Boolean = false;
public var timer_disable:Boolean = false;
public var started:Boolean = false;
public function AGTimer(num:Number = 0) {
this.timer_disable = false;
if (num != 0 ) timerStart(num);
// constructor code
}
public function timerStart(num:Number):void {
myTimer = new Timer(num * 1000, 1);
myTimer.addEventListener(TimerEvent.TIMER, runOnce);
myTimer.start();
started = true;
done = false;
}
public function runOnce(e:TimerEvent):void {
done = true;
}
public function timerDone():Boolean {
if (done && ! timer_disable) return true;
else if (!started ) return true;
else return false;
}
}
I have several of these in an array. I reference them with their index number. sometimes I start a timer and let it go, then before I have a chance to check it, I delete it by calling 'new AGTimer()' and creating a new one. Could it happen that the callback from the actionscript Timer object is still called somehow after the timer itself is deleted? What would I do about it if it did happen? I am trying to find a bug and am considering this sort of problem. What kind of error would I see?
Yes, a timer will continue to run callbacks even if it is not referenced somewhere. You have to remove event listener for the timer for it in order to remove it.
Basically something like this:
private function destroyTimer():void {
if(myTimer) {
myTimer.removeEventListener(runOnce);
myTimer.stop();
myTimer = null;
}
}

Problem with progressEvent Listener

for (i=0; i < _xmlContents.img_array.length; i++)
{
_loader = new Loader();
_loader.name = "image"+i;
_loader.load(new URLRequest(_xmlContents.img_array[i]));
_loader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, onLoadingAction);
//Event.COMPLETE listnere
//error Handler
}
private function onLoadingAction(e:ProgressEvent):void
{
_preLoader = new Preloader();
//addChild(_preLoader);
trace(_loader.name);
}
I want to add preloader for every image in the xml. Now I am getting for last image only.
(consider if xml length is 5, it will trace image4 only)
How can I add that?
It is because you have one _loader object. In every loop step you overwrite this loader with new one so previous image stops loading. You should use new loaders for every image:
for (i=0; i < _xmlContents.img_array.length; i++)
{
// create new loader instance, not use a global one
var _loader:Loader = new Loader();
_loader.name = "image"+i;
_loader.load(new URLRequest(_xmlContents.img_array[i]));
_loader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, onLoadingAction);
//Event.COMPLETE listnere
//error Handler
_preLoader = new Preloader();
//addChild(_preLoader);
}
private function onLoadingAction(e:ProgressEvent):void
{
// trace(e.bytesLoaded, e.bytesTotal);
}
First of all your event listeners attached to the loader instances before the last one (e.g. loaders 0 to 3) are there. They will still be there for a long long time. Remove them!
ActionScript is a very nice language - use it's power :)
for (i=0; i < img_array.length; i++)
{
_loader = new Loader();
_loader.name = "image"+i;
_loader.load(new URLRequest(img_array[i]));
_loader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS,
function onLoadingAction(e:ProgressEvent):void
{
trace((e.target as LoaderInfo).loader.name);
//Do whatever you want to do here like removing event listener
if ((e.target as LoaderInfo).bytesLoaded == (e.target as LoaderInfo).bytesTotal)
{
(e.target as LoaderInfo).removeEventListener(ProgressEvent.PROGRESS, onLoadingAction);
trace("Event listener for " + (e.target as LoaderInfo).loader.name + " removed ");
}
}, false, i * 1000 /* you can use priority if want to maintain some order in event handling*/);
}
ActionScript provides you with the ability to name inline functions and to have a reference to them. Use this approach when you don't need to keep a reference to some object.
Good luck and have fun!
for (i=0; i < _xmlContents.img_array.length; i++)
{
_loader = new Loader();
_loader.name = "image"+i;
_loader.load(new URLRequest(_xmlContents.img_array[i]));
_loader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS,
onLoadingAction);
//Event.COMPLETE listnere
//error Handler
_preLoader = new Preloader();
_bgBox.addChild(_preLoader);
}
This way is solves my problem.. But I don't think so, it's a good way.
I dont think the array is necessary and I think you'll be on a better track adding 4 instances of the preloader by calling it from Event.INIT as opposed to repeatedly adding it by using ProgressEvent.PROGRESS.
You your _loader object is defined inside the function and not referenced anywhere else, so it is garbage collected after the function ends. Create an array of loaders and push the loaders to them each time.
private var _loadersArray:Array=[]; //outside the function
for (i=0; i < _xmlContents.img_array.length; i++)
{
// create new loader instance, not use a global one
var _loader:Loader = new Loader();
_loader.name = "image"+i;
_loader.load(new URLRequest(_xmlContents.img_array[i]));
_loader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, onLoadingAction);
_loadersArray.push(_loader);
//Event.COMPLETE listnere
//error Handler
}
private function onLoadingAction(e:ProgressEvent):void
{
_preLoader = new Preloader();
//addChild(_preLoader);
// get current loader instance
var _loader:Loader = e.target.loader;
trace(_loader.name);
}