One after an other - actionscript-3

I'm having trouble making something happen over and over without a for loop. Take a look at this:
package {
import flash.display.Sprite;
import flash.events.Event;
public class Main extends Sprite {
public function Main() {
addEventListener("done", caller);
caller();
}
public function caller(e:Event = null):void {
trace("hello!");
dispatchEvent(new Event("done"));
}
}
}
sing this will get you an "Error #2094: Event dispatch recursion overflow." really fast. It will show that the event dispatcher and caller() are getting called inside of them selves, nesting until the error happens.
What I want to do is this:
"When caller() is done, call it again"
not:
"call caller() before it finishes"
Now, before people start suggesting using a timer to guess how long it will take or use ENTER_FRAME, This caller() will not have any graphic data and won't be connected to a Sprite and the time it takes to finish may vary greatly from call to call. I'm really looking for a way to run it only after it has completely finished.
Thanks for your help.
Thank you for your responses. I used Timer and still could overflow with too many calls and too short a timer interval. So I simplified and tried to just make an Event based for loop class (A class that operates like a for loop, but with events to avoid gobbling up all the resources) The solution was to call the function, on it's completion call the timer; on the timer's completion call the function again and bounce them off of each other. Basically:
call function
wait
call function
wait etc.
Even if the timer is set to 0 and it freezes the swf until the all the functions are called, the function will complete before running again.
try it out:
package {
import flash.display.Sprite;
public class Efl extends Sprite { // the main class
public function Efl() {
// make four functions...
function init (o:Object):void { // akin to the first part of the for loop
o.value = 0;
}
function condition(o:Object):Boolean { // like the condition portion of the for loop
if (o.value <= 100) {
return (true);
} else {
return (false);
}
}
function next(o:Object):void { // the increment part of a for loop
o.value++;
}
function statements(o:Object):void { // the body of the for loop
trace(o.value);
}
// put the four functions in one new EventForLoop
var test1:EventForLoop = new EventForLoop(init, condition, next, statements, 1); // delay is 1 ms
test1.start(); // set it into motion
// do it again all in one line - not pretty but it works
var test2:EventForLoop = new EventForLoop(
function (o:Object):void { o.value = 0; },
function (o:Object):Boolean { if (o.value <= 50) return (true); else return (false); },
function (o:Object):void { o.value++ },
function (o:Object):void { trace("> " + o.value) },
20); // delay in 100ms
test2.start(); // start it up
// if you try this out, the two will run intertwined since the delays are different.
}
}
}
Here is the Class that runs the loop:
package {
import flash.events.EventDispatcher;
import flash.events.Event;
import flash.events.TimerEvent;
import flash.utils.Timer;
public class EventForLoop extends EventDispatcher {
// functions to call when simulating the for loop
private var initializer:Function; // is run once at the start of the loop
private var condition:Function; // returns boolean to tell the loop to continue or not
private var step:Function; // the function that runs after the loop is complete
private var statements:Function; // the actual body of the loop
private var timeout:Timer; // the timer to avaoid overflows
private var operator:Object = new Object(); // this is an object to hold and pass values across all the sub loop functions. it is the parameter passed to all four functions
// some event constants
static const NEXT:String = new String("EFLNext");
static const DONE:String = new String("EFLDone");
// constructor just loads vars and sets up timer
public function EventForLoop (init:Function, cond:Function, stepper:Function, stat:Function, delay:Number = 0) {
initializer = init;
condition = cond;
step = stepper;
statements = stat;
timeout = new Timer(delay, 1);
}
// the mail loop function...
private function next(e:Event = null):void {
// Try this and the lone afte the loop:
// trace ("start statements");
if (condition.call(null, operator)) { // if the condition is still true...
statements.call(null, operator); // do the body statements of the loop
step.call(null, operator); // increment
dispatchEvent(new Event(EventForLoop.NEXT)); // dispatch the event so that thw wait can start
} else { // condition returns false??
dispatchEvent(new Event(EventForLoop.DONE)); // tell the event dispatcher the loop is done
removeEventListener(EventForLoop.NEXT, wait); // stop event listeners
timeout.removeEventListener(TimerEvent.TIMER_COMPLETE, next);
}
// trace ("finish statements\n");
// this line and the one before the if() will show that the functcion ends before starting again, even if the Timer wait 0ms
}
// very simple function that waits and ten triggers the loop again
private function wait(e:Event):void {
timeout.reset();
timeout.start();
}
// metod used to set the loop running
public function start():void {
initializer.call(null, operator); // use the initioalizer to set the operator Object
addEventListener(EventForLoop.NEXT, wait); // when the loops done, wait
timeout.addEventListener(TimerEvent.TIMER_COMPLETE, next); // when done waiting, loop again
next(); //do the first loop
}
}
}

You might want to experiment with flash.utils.setTimeout(). Put it at the bottom of caller() and have it set a timeout for itself. If you give it a very small timeout interval, it will asynchronously recurse the next time Flash gets the chance.
Alternatively, an ENTER_FRAME event will do more or less the same thing (except at extremely high framerates). Flash will delay the rendering of the next frame until all the processing logic on one frame has finished. Furthermore, Flash is single-threaded, so you can be guaranteed that two copies of your function will never run simultaneously.

I've got questions similar to some of the other responders. How often do you want the call to happen? If what you want is for the call to immediately repeat as soon as it finishes, no other part of your program will ever get a chance to execute.

Is this for an assignment?
If you don't want for loops, how about a while loop?
Trying to use timers could work but it gets messy. If you absolutely must user a Timer then have some boolean flag set to true/false if your function is still running. The timer event would see if your function is finished, if so then call it again.

I would use enterFrame... Flash is frame based... when your process is finished, you check if you still have time for another call to the function, if not, just wait for the next frame to come...
addEventListener("enterFrame", loop);
function loop(e) {
var maxtime=1000/stage.frameRate;
var t1=getTimer();
while(getTimer()-t1 < maxtime) {
myProcess();
}
}

OK, I know you said
his caller() will not have any graphic data and won't be connected to a Sprite
And
I'm really looking for a way to run it only after it has completely finished.
So I'll address those and then tell you an enterframe is the best solution :)
You don't need a graphical representation, or access to the stage to use a enter frame event listener. You can simply do the following:
var s:Shape = new Shape();
s.addEventListener(Event.ENTER_FRAME, caller)
private function caller():void
{
//do stuff
}
Above we simple create a shape to listen for the enter frame events, and thats all we use it for.
As for the second part, when code is being interpreted at runtime and it comes to a function, caller in this case, it won't execute another function, or line of code outside that function, until it has finished it. So you know that it will never execute again until it has finished the previous call.
So an enterframe (or a timer) are your best / only solutions.

What you want to do is dispatch a new event when the Caller() finishes that then calls caller again.
But your need to have a max loop counter otherwise you will just get a stack overflow error.
Don't forget to use weak reference to your event listeners, as it will alway unused object to get garbage collected and help your app run smoother + faster.
package {
import flash.display.Sprite;
import flash.events.Event;
public class Main extends Sprite {
public function Main() {
addEventListener("Call_ME_AGAIN", callCaller, false, 0, true );
caller();
}
private var _counter:int = 0;
private const LOOP_TIMES:int = 100;
public function caller(e:Event = null):void {
trace("hello!");
if (counter != LOOP_TIMES)
{
dispatchEvent(new Event("Call_ME_AGAIN"));
counter++;
}
else if (counter == LOOP_TIMES)
{ //reset the counter so it can happen again when you want
counter = 0;
}
}
public function callCaller(e:Event = null):void {
e.stopImmediatePropagation();
caller(null);
}
}
}

Related

Remove a function from the stage

I have a big issue with my code
I have a function called "delayCallFuntions":
function delayCallFuntions(delay: int, func: Function) {
var timer: Timer = new Timer(delay, 1);
timer.addEventListener(TimerEvent.TIMER, func);
timer.start();
}
And I used this function like below to make connection between 2 point in my screen:
delayCallFuntions(1, function (e: Event) {timer011(wireColor);});
And function "timer011" is making the connections:
function timer011(firstColor: int): void {
wireColor = firstColor;
//GRID is a class
//Path A to B
var PathAB: Grid;
PathAB = new Grid(4, 5, 20, 17, canvas, wireColor);
this.addChild(PathAB);
}
My problem is:
I have several of these functions like "timer012", "timer013", ... that they need to execute one after another.
When I go out of this scene and come back again, these is still some of these functions are working while I need them to start from the beginning and go one by one.
for example: when i come back, "timer011" is starting while "timer016" is also completing at the same time.
hope someone can help me as this problem made me frustrated.
Currently you are creating a whole new timer everytime you add a function. That timer will stay in memory because of the event listener, and since it's encapsulated in the function, you have no easy way to reference it again to stop them.
What would be a better approach, is to create just one timer globally referenced so you can stop it if needed.
Here is a way you could accomplish this:
//create an array that will hold all the functions you are planning on calling
var delayedFuncs:Array = [];
//this var will be used to store the current function that will be called next
var currentFuncObj:Object = null; //set it to null so it clears the value when you return to this frame
//create a single, global timer reference for everything
//don't initialize it here though
//if you revisit this frame, you don't want to create a whole new timer, but keep using the previous one
var funcTimer:Timer;
//if the timer has already been created (you've been to this frame before), stop it
if (funcTimer) {
funcTimer.stop();
}else {
//if you haven't been to this frame before, create the timer and add the listener
funcTimer = new Timer(1,1);
funcTimer.addEventListener(TimerEvent.TIMER, nextFunc, false, 0, true);
}
//this function adds items to your queue. I've added the ability to also include parameters
function delayCallFunctions(delay:int, func:Function, ... funcParams):void {
//add an object to the array that stores the function, delay, and any parameters to pass to that function
delayedFuncs.push({delay: delay, func: func, params: funcParams});
//if the timer hasn't started running yet, start it since we've added something
if(!funcTimer.running) nextFunc();
}
//this function runs when the timer completes
function nextFunc(e:Event = null):void {
//if we have an existing function to call, call it
if (currentFuncObj){
//invoke the function with the parameters
currentFuncObj.func.apply(null, currentFuncObj.params);
}
//if there are still items in the array, grab the next one
if(delayedFuncs.length > 0){
//array.shift grabs the first element in the array and removes it from the array
currentFuncObj = delayedFuncs.shift();
//reset the timer
funcTimer.reset();
//set the appropriate delay
funcTimer.delay = currentFuncObj.delay;
//start the timer again
funcTimer.start();
}
}
So now, you'd use by doing:
delayCallFunctions(3000, trace, "hello", "world", "I'll be traced 3 seconds from now");
delayCallFunctions(2000, trace, "I'll be called 2 seconds after the last one");
Or, with your specific code:
delayCallFuntions(1000, timer011, wireColor);
Now at any time (say you hit a button to go to change scenes), you can just stop the global timer.
funcTimer.stop();

Stopping Script for x seconds

I am new to actions script and I want to stall the code itself for just a bit, but all the tutorials and libraries I have looked at seems to focus more on stopping the Movie clip. Also seems that you have to call a function with a timer, but I just purely want it to wait a few seconds.
What I want to achieve:
Want a function to finish before continuing to the next function.
The Code:
(Credit for code goes to Garry Law. Using his code to provide better clarity)
function PlayGame() {
PauseThenDisplayText(1,"Get Ready");
PauseThenDisplayText(1,"Get Ready");
}
function PauseThenDisplayText(sec,txt) {
trace('timer starts');
var timer1:Timer = new Timer (sec*1000,1);
timer1.start();
timer1.addEventListener(TimerEvent.TIMER,onTimer);
function onTimer(evt:TimerEvent):void{
trace('timer event triggered');
timer1.removeEventListener(TimerEvent.TIMER,onTimer);
Display_Screen.text = txt;
}
}
the reason your code won't work is because the code is all being read before your timer ends. You'll need to add listeners to the Timer event. For example:
function PlayGame() {
PauseThenDisplayText1(1);
}
function PauseThenDisplayText1(sec) {
trace('timer starts 1');
var timer1:Timer = new Timer (sec*1000,1);
timer1.start();
timer1.addEventListener(TimerEvent.TIMER,onTimer);
function onTimer(evt:TimerEvent):void{
trace('timer event triggered 1');
timer1.removeEventListener(TimerEvent.TIMER,onTimer);
Display_Screen.text = "Get Ready";
//now that first timer finishes, start next one
PauseThenDisplayText2(1);
}
}
function PauseThenDisplayText2(sec) {
trace('timer starts 2');
var timer2:Timer = new Timer (sec*1000,1);
timer2.start();
timer2.addEventListener(TimerEvent.TIMER,onTimer);
function onTimer(evt:TimerEvent):void{
trace('timer event triggered 2');
timer2.removeEventListener(TimerEvent.TIMER,onTimer);
Display_Screen.text = "Start Game";
}
}

addEventListener and memory leak

Im doing a small game for college work and I don't understand very well how the garbage collector works
with EventListeners, I feel that the "preCastTimer" EventListener never gets removed on the code below. The problem is that I have no idea how to remove it once its complete.
below is the code Im using to cast a spell when a key is pressed
Here I have the casting functions called by KeyboardEvents, fireball is a MovieClip
preCast(fireball);
function preCast(spell)
{
var tempSpell:Object = new spell;//create an object for the spell so castTime is accessible.
var preCastTimer:Timer = new Timer(tempSpell.castTime,1);
var spellFunc:Function = cast(spell);
preCastTimer.addEventListener(TimerEvent.TIMER_COMPLETE, spellFunc);
preCastTimer.start();
}
function cast(spell):Function {
return function(e:TimerEvent):void {
parent.addChild(new spell);//For some reason if spell is not created here it never gets a parent
};
}
Here is the code for the fireball MovieClip:
package {
import flash.display.MovieClip;
public class fireball extends MovieClip {
public var castTime:uint = 1000;
public function fireball() {
// constructor code
}
}
}
The code below is in the fireball timeline. I understand it's better to use class, but I still don't understand parenting when the code is in the package and not on the timeline frame
import flash.events.Event;
import flash.utils.Timer;
import flash.events.TimerEvent;
import flash.geom.Point;
if (parent)//only run if a parent exists, when created as object no parent is defined
{
x = parent.getChildByName("player").x;
y = parent.getChildByName("player").y;
var direction = new Point(parent.mouseX - x,parent.mouseY - y);
rotation = Math.atan2(parent.mouseY - y,parent.mouseX - x) * 180 / Math.PI;
direction.normalize(5);
if (direction.x == 0 && direction.y == 0)
{
parent.removeChild(this);
return;
}
var spellTimer:Timer = new Timer(500,1);
spellTimer.addEventListener(TimerEvent.TIMER_COMPLETE, spellKiller);
this.addEventListener(Event.ENTER_FRAME, motion);
spellTimer.start();
}
function spellKiller(e:TimerEvent):void
{
this.removeEventListener(Event.ENTER_FRAME, motion);
spellTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, spellKiller);
parent.removeChild(this);
}
function motion(e:Event)
{
x += direction.x * 5;
y += direction.y * 5;
}
Notice that addEventListener has the useWeakReference argument (5th argument).
public function addEventListener(
type:String,
listener:Function,
useCapture:Boolean = false,
priority:int = 0,
useWeakReference:Boolean = false
):void;
From the EventDispatcher documentation:
If you no longer need an event listener, remove it by calling
removeEventListener(), or memory problems could result. Event
listeners are not automatically removed from memory because the
garbage collector does not remove the listener as long as the
dispatching object exists (unless the useWeakReference parameter is
set to true).
The solution would be to simply convert your addEventListener calls to use weak references, where appropriate.
foo.addEventListener(type, listener, false, 0, true);
Please let me know if you're not sure how this helps you.
So your code is a bit overly complicated with some important parts missing so I cannot really comment on that parent thing. As far as I understand the timer should fire once and then you want its listener removed, correct? Well, that's pretty easy to achieve:
function cast(spell):Function {
return function(e:TimerEvent):void {
parent.addChild(new spell);
e.target.removeEventListener(TimerEvent.TIMER_COMPLETE, spellFunc);
};
}
Why do you have the feeling that this is not the correct solution? You can test that the listener is removed by simply changing the TimerEvent.TIMER_COMPLETE to TimerEvent.TIMER (and removing the repeat count passed to the constructor of your timer). It should add the spell once and no more.
Also note that the garbage collector will not pick it up right away (well, very probably not!). It may pick it up somewhere in the future or never. Actually, the timer will probably never get picked if you don't set it to null or you don't create another timer object and assign it to the same variable as your reference will remain and therefore it will never get eligible for garbage collection.

How to get the width of a MovieClip for a different frame instantly?

Is there a way to get the width of a MovieClip (that does have a name) on a different frame? I have tried to using .width and .getBounds(null).width, however, both of them will give me only the width of the current frame. I have tried to do gotoAndStop(frameiwant), but the information doesn't seem to be correct until at least the next frame
I would like to get the width of the frame instantly so I don't have to wait until the next frame for the width.
The only way I could think of doing this was to have an initial phase in your project which will:
Run through all of the frames in your timeline. Create an object which will hold information about the children in that frame. It can be called Frame.
Iterate over all the children that are added to the stage in that frame and add a definition object that describes that child. The description can be as basic or vast as you need. We can call this class an ObjectDefintion.
The downside of this process is that you need to wait for the FRAME_CONSTRUCTED event like #Larusso pointed out in his answer. This means that the frame actually has to finish rendering before you are able to get information about its children, which of course means you have to go through and render every single frame in your timeline during this phase. All you can really do to mitigate this problem is set the frameRate to something high and then set it back when you're done assessing all the frames.
I have set this up and it works well - I'll paste each class and try explain what they do.
So for your document class (or whichever MovieClip holds the frames you want to look at), I have this:
public class Main extends MovieClip
{
private var _userFrameRate:int;
private var _frames:Vector.<Frame> = new <Frame>[];
public function Main()
{
_userFrameRate = stage.frameRate;
stage.frameRate = 120;
addEventListener(Event.FRAME_CONSTRUCTED, _assess);
}
public function getFrame(index:int):Frame
{
return _frames[index - 1];
}
private function _assess(e:Event):void
{
var frame:Frame = new Frame(this);
_frames.push(frame);
if(currentFrame === totalFrames)
{
removeEventListener(Event.FRAME_CONSTRUCTED, _assess);
gotoAndStop(1);
stage.frameRate = _userFrameRate;
ready();
}
else play();
}
public function ready():void
{
// Start here.
// There is a MovieClip on frame 10 with the instance name 'test'.
// We can get the width of it like this.
trace( getFrame(10).define("test").property("width") );
}
}
This basically initializes the phase in which we will run over each frame in the MovieClip and assess its children. The ready() method is used as the entry point for your code post-assessment.
Next we have the Frame class, which serves to hold information about children related to a frame:
public class Frame
{
private var _main:Main;
private var _content:Object = {};
public function Frame(main:Main)
{
_main = main;
update();
}
public function update():void
{
_content = {};
for(var i:int = 0; i < _main.numChildren; i++)
{
var target:DisplayObject = _main.getChildAt(i);
// This will be explained below.
var definition:ObjectDefinition = new ObjectDefinition(target, "x", "y", "width", "height");
_content[target.name] = definition;
}
}
public function define(name:String):ObjectDefinition
{
return _content[name];
}
}
It's pretty straightforward - you give it a reference to Main so that it can check children that are existent within it each frame.
The ObjectDefinition class is also pretty straightforward, acting purely as a repository for data that you want to keep track of on each child of the frame:
public class ObjectDefinition
{
private var _definition:Object = {};
public function ObjectDefinition(target:DisplayObject, ...properties)
{
for each(var i:String in properties)
{
_definition[i] = target[i];
}
}
public function property(property:String):*
{
return _definition[property];
}
}
You'll notice that the constructor accepts the target DisplayObject that will be defined, as well as any amount of properties you want to keep track of as strings (see above within Frame for implementation).
Once complete, you can chain the methods Main.getFrame(), Frame.define() and ObjectDefinition.property() to get properties of children that will exist throughout the timeline. For example, if you have a MovieClip with the instance name square on frame 15 and you want to get its width and height, you can do this within .ready() like so:
var square:ObjectDefinition = getFrame(15).define("square");
trace(square.property("width"), square.property("height"));
Of course this process is not ideal - but unfortunately it is the only way I can see that what you want to achieve is possible.
You have to listen to a specific event before you can ask for the information.
clip.addEventListener(Event.FRAME_CONSTRUCTED, frameReadyHandler);
clip.gotoAndStop(frame);
function frameReadyHandler(event:Event):void
{
clip.removeEventListener(Event.FRAME_CONSTRUCTED, frameReadyHandler);
var width = clip.width;
}
The Frame constructed event is the first of several events that gets dispatched. It gets dispatches right before the frame script gets executed. You could also wait for the on enter frame event.
You could add an event listener for 1 millisecond and test if the previousWidth you had stored is different. If it is, there you go. If not, its probably listening to the same frame.
A 1 millisecond timer is not such a big deal, stop it if you don't need it, resume it if you do, else, keep it running constantly. When it changes, dispatch an event or whatever needs to happen.
If you know the maximum size of the MovieClip, you may try this:
// Create movie clip
var movie :MovieClip = new MovieClipWith3Frames();
// Move to second frame
movie.gotoAndStop(2);
// Create bitmap witch magenta background
var bd :BitmapData = new BitmapData(200, 200, false, 0xFF00FF);
// Draw second frame
bd.draw(movie);
// Found the bounds of shape
var movieBounds:Rectangle = bd.getColorBoundsRect(0xFFFFFF, 0xFF00FF, false);
trace(movieBounds); // (x=42, y=15, w=32, h=33)

Need help using a timer in Action Script 3

Alright, so I am fairly new to AS3 and I have a level in my game where you have to stay alive for 45 seconds. If I use a code like (Or if there is a better code, I'll use that one)
var myTimer:Timer = new Timer(1000, 1); // 1 second
myTimer.addEventListener(TimerEvent.TIMER, runOnce);
myTimer.start();
function runOnce(event:TimerEvent):void {
trace("runOnce() called # " + getTimer() + " ms");
}
How can I use this to make my game move to scene 6 if they stay alive for 45 seconds? I also want to display text on the animation that keeps track of how long they've been alive so they know how long they have left. How could I accomplish this?
private var startTime:int;
function startGame() {
// this is called when your game starts
startTime=getTimer();
... // rest of init code
}
function onEnterFrame(e:Event):void {
// main loop, whatever you need to do in here
currentTime=getTimer()-startTime; // here we receive the elapsed time
// pause handling is excluded from this example!!11
if (weAreDead()) {
survivalTime= currentTime;// here
...
} else if (currentTime>45000) {
//advance to scene 6 here
}
}
Set the listener for Event.ENTER_FRAME to onEnterFrame, start the game with setting the stored time, and pwn.
The simplest solution is to go ahead and use the timer, but set the value to 45000 and make sure to keep a reference of the timer or it will be garbage collected. Also, create a separate function which allows you to kill the timer from anywhere if this particular thing ever needs to just "go away" without completing.
public static const DELAY:int = 45;
private var _timer:Timer;
public function setTimer():void
{
_timer = new Timer( DELAY * 1000, 1 );
_timer.addEventListener( TimerEvent.TIMER_COMPLETE, timerCompleteHandler );
_timer.start();
}
private function timerCompleteHandler( event:TimerEvent ):void
{
disposeTimer();
goDoTheThingThatYouNeededToDo();
}
public function disposeTimer():void
{
_timer.stop();
_timer.removeEventListener( TimerEvent.TIMER_COMPLETE, timerCompleteHandler );
_timer = null;
}