I just came across a curious scenario where I want to use removeEventListener() within a function that doesn't have a name. By this I mean, I've created the function within addEventListener(), instead of making reference to one:
addEventListener(
Event.ENTER_FRAME,
function(e:Event):void
{
if(getTimer() > 8000)
{
// removeEventListener(Event.ENTER_FRAME, <<this function>>);
// Other stuff
}
}
);
Is it possible to make a reference to the current function (ie the function I'm working within)? Or do I just need to structure the above the standard way?
Please not that I am fully aware that you can use many of the standardized methods available to achieve the above, it was purely an example snippet.
There are two options, you can either give it a name (and there are three ways to do that) or you can use arguments.callee.
In the case of the former, the three ways to name a function in AS3:
class Foo
{
// class (static or member) level
public function bar():void
{
// use a variable (technically, this function is anonymous, but we can
// still use the variable to reference the function itself.
var inVariable:Function = function():void
{
// declare it in a local scope
function local():void
{
}
}
}
}
To use a named function:
function callback(e:Event):void {
trace("tick");
removeEventListener(Event.ENTER_FRAME, callback);
}
addEventListener(Event.ENTER_FRAME, callback);
To use arguments.callee:
addEventListener(
Event.ENTER_FRAME,
function(e:Event):void
{
if(getTimer() > 8000)
{
// I get superstitious and I use a local variable.
var callee:Function = arguments.callee
removeEventListener(event.type, callee);
// Other stuff
}
}
);
You just need to give it a name, eg:
addEventListener(Event.ENTER_FRAME, function callback(e:Event):void {
trace("tick");
removeEventListener(Event.ENTER_FRAME, callback);
});
In this example "tick" will only be traced one time.
Using anonymous functions in actionscript is a bad choice, since it is really slow. Also they hardly can be garbage collected. It is also good to mention that this will only work if when the listener has been called (yes, in case of an enter_frame it will), so outside the anonymous function other functions are unable to remove the listener. Beside that, is is also a actionscript-convention to use separate functions, which makes your code more readable and it will take only a few extra chars (just to name it).
addEventListener(Event.ENTER_FRAME, onEnterFrame);
function onEnterFrame(e:Event):void
{
if(getTimer() > 8000)
{
removeEventListener(Event.ENTER_FRAME, onEnterFrame);
}
}
If you want a more easy way to remove the event listener; you could detect the type and the function callee of the listener from the target object. However I think this also makes the code a bit less readable.
e.target.removeEventListener(e.type, arguments.callee);
sources
http://jacksondunstan.com/articles/413
http://gskinner.com/blog/archives/2006/07/as3_weakly_refe.html
Related
how to stop drag(event) the object with hittestobject.. thanks.
object.addEventListener(TouchEvent.TOUCH_BEGIN, drag);
object.addEventListener(TouchEvent.TOUCH_END,drop);
addEventListener(Event.ENTER_FRAME, loop);
function drag(e:TouchEvent):void {
e.target.startTouchDrag(e.touchPointID);
}
function drop(e:TouchEvent):void {
e.target.stopTouchDrag(e.touchPointID);
}
function loop(e:Event):void {
if (object.hitTestObject(collision)) {
//code to stop drag event?
}
}
or is there other way to stop drag event aside from function drop?
sorry for my bad english.
//edited
In the function drop() e.target is the object that currenty processes the event. In the function loop() you also have some objects. It is not clear which of them is dragging but you should call either object.stopTouchDrag() or collision.stopTouchDrag().
UPDATE
There is an argument for both startTouchDrag and stopTouchDrag functions - touchPointID, it is used to determine what touch point (of many) is processed. When stopping the drag, you need to use the same touchPointID which was used for starting it. When calling the stopTouchDrag from a non-event context, you can't know what touch point it should use. So you need to remember it somehow. If your target object is a MovieClip you can just add a dynamic property to it and save the touchPointID there:
function drag(e:TouchEvent):void {
(e.target as MovieClip).touchPointID = e.touchPointID;
e.target.startTouchDrag(e.touchPointID);
}
function loop(e:Event):void {
if (object.hitTestObject(collision)) {
object.stopTouchDrag(object.touchPointID);
}
}
I have action
MC1.addEventListener (MouseEvent.MOUSE_OVER, MC1_over);
can a use other MC instead of Mouse?
In other words, when MC2 will be over MC1, my action will start. How do that?
Thanks for help
You'll have to check for intersection. It's called HitTesting and there are several ways to approach this. But first - it won't be an Event anymore, you'll have to check for an intersection in every frame. So first of all, we need to create a new Event.ENTER_FRAME listener.
addEventListener(Event.ENTER_FRAME, onEnterFrame);
function onEnterFrame(e:Event):void
{
//Your code will go here
}
Second, we check our objects for an intersection of their boudary rectangles. It's ok if you have sqare or rectangular movieclips, if your MCs are more complex (two circles for example) you'll have to use other ways of getting this intersection.
addEventListener(Event.ENTER_FRAME, onEnterFrame);
function onEnterFrame(e:Event):void
{
if(MC1.getRect(this).intersects(MC2.getRect(this)))
{
//Two movieclips are intersecting
}
}
Third, as long as this condition will be true as long as your MCs are intersecting, we need to define a flag that will tell us if we've already done something we wanted to do.
var alreadyHandled:Boolean = false;
addEventListener(Event.ENTER_FRAME, onEnterFrame);
function onEnterFrame(e:Event):void
{
if(MC1.getRect(this).intersects(MC2.getRect(this)))
{
if(!alreadyHandled)
{
doSomething();
alreadyHandled = true;
}
}
else
{
//When our movieclips are apart again, we reset our helping variable
alreadyHandled = false;
}
}
function doSomething():void
{
//We do what we want to do if our MCs are intersecting
}
If you want to do something continiously, when your movieclips are intersecting, just ignore that helping flag thing.
And by the way, I suggest you to start names your variables with a lowercase letter. In AS3 only Classes and Interfaces have names that start with a capital letter.
Thank you.
Everything works great when I do this on new as3 file.
But i need to use this in class document
When i use
addEventListener(Event.ENTER_FRAME, onEnterFrame);
function onEnterFrame(e:Event):void
{
if(MC1.getRect(this).intersects(MC2.getRect(this)))
{
trace("intersects")
}
}
Erron#1034: cannot convert type global#23b3a0d1 on
flash.display.DisplayObject.
Perhaps You known where is problem?
I want to make in loop set of buttons, and add to them some events, but anonymous functions is the same. I write example code:
for(var i:int=0;i<5;i++)
{
var button:SimpleButton = new SimpleButton(...);
...
button.addEventListener(MouseEvent.CLICK, function(event:MouseEvent):void
{
trace(i);
});
}
...
And I want to trace 0,1,2,3.. from click buttons instead of 4,4,4,4 ..
Do you know how can I make this ?
The problem you are running into is that ActionScript does not support closures.
In other words, the variable i does not get copied into it's own context per function. All functions refer to the same instance of i.
More information here:
http://flex.sys-con.com/node/309329
In order to do this, you need a function that generates a function:
public function makeFunction(i:int):Function {
return function(event:MouseEvent):void { trace(i); }
}
Now, you create new instances of the function with their own context:
button.addEventListener(MouseEvent.CLICK, makeFunction(i));
I came to AS3 from JS world, and I should confess that anonymous functions are my weakness. I tend to use them everywhere. Now, coming to AS3 I've heard and read in lots of places, that AS and Flash are enormously bad at handling garbage collection, that one should empty, dispose and remove all event handlers and objects manually to avoid weird and unexplainable memory leaks and crashes. Not sure what part of this is true, but I would like to follow best practices right from the beginning.
So my question would be - how bad is idea of using anonymous functions as event handlers? Consider for example a code like this:
addEventListener(Event.ENTER_FRAME, function() : void {
controls.elapsed = stream.time;
});
contorls.elapsed is the setter, which apart from setting current play time for video player, updates the whole UI, and stream is NetStream object, which streams the actual video.
There are lot's of other places where anonymous function may make code cleaner and more intuitive. Check the following code for simple fade-in effect for the control bar:
public function showControls() : void
{
var self:Controls = this;
if (!visible) {
visible = true;
fadeTimer = new Timer(30, 10);
fadeTimer.addEventListener(TimerEvent.TIMER, function() : void {
self.alpha += 0.1;
});
fadeTimer.addEventListener(TimerEvent.TIMER_COMPLETE, function() : void {
self.alpha = 1;
});
fadeTimer.start();
}
}
I totally like how it looks and fits into the code, but I'm concerned about leaks. While Event.ENTER_FRAME handler probably would never become harmful in this form, what about timer listeners. Should I remove those listeners manually, or they will be removed automatically, as soon as I set fadeTimer = null ? Is it possible to remove listeners with anonymous functions properly at all?
Just noticed this post -- there are a couple things that might be of use to you. One is arguments.callee (which is a reference to the current function you're in). This is useful for removing references in anonymous functions. Also, it could be noted that you could use weak references in your addEventListener code -- however, this won't work for variables that are anonymous, as they'd get GC'd pretty much immediately. For simplicity sake I rewrote your code like this: (should work -- haven't tested)
private function showControls() : void {
if (visible) {
return;
}
var self:DisplayObject = this;
var fadeTimer= new Timer(30,10);
var handler = function(e:Event) {
switch (e.type) {
// timer complete
case TimerEvent.TIMER_COMPLETE:
// remove references to this anonymous function -- for garbage collection
fadeTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, arguments.callee);
fadeTimer.removeEventListener(TimerEvent.TIMER, arguments.callee);
// break out
return self.alpha = 1;
// timer
case TimerEvent.TIMER:
return self.alpha += 0.1;
}
}
fadeTimer.addEventListener(TimerEvent.TIMER, handler);
fadeTimer.addEventListener(TimerEvent.TIMER_COMPLETE, handler);
fadeTimer.start();
}
I would do it something like this. And, be sure to use dispose() when you want to make sure to clear the timer if interrupting.
private function showControls() : void
{
if(_isVisible)
return;
// start you control here
_fadeTimer = new Timer(30, 10);
_fadeTimer.removeEventListener(TimerEvent.TIMER, updateFade);
_fadeTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, updateFadeComplete);
_fadeTimer.start();
}
private function updateFade(event : TimerEvent) : void
{
// update fade here
}
private function updateFadeComplete(event : TimerEvent) : void
{
dispose();
}
private function dispose() : void
{
if(_fadeTimer)
{
_fadeTimer.stop();
_fadeTimer.removeEventListener(TimerEvent.TIMER, updateFade);
_fadeTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, updateFadeComplete);
_fadeTimer = null;
}
}
There's nothing wrong with using function methods where it works. As far as memory leaks go, you need to track the object to the stage to see if it can be removed.
Adding an ENTER_FRAME event handler to the control ensures that the control has a reference to the anonymous function. As the code is part of the control (or so it appears), this is fine as the anonymous function will be removed when the control is.
Adding an event handler to the timer ensures that the timer has a reference to the anonymous function. If the timer is running, it will keep the anonymous function reference alive and, by association, the enture control. Once the timer has stopped, however, both it and the function should be collected.
If all else fails, use the profiler and see! ;)
I am making a boardgame in flash Action Script 3. Each position on the board is a buttons like this: button_1_1, button_1_2 etc. Whenever a character is selected you want to move it so the script has to add event listeners for positions around the selected unit
// This function adds or deletes an event listener
function listentoButton (isTrue:int, position_x:int, position_y:int):void {
var myFunction:Function = new Function;
myFunction = function ():void {userClickedPosition(position_x, position_y)};
if (isTrue == 1) {
this["button_position_"+(position_x)+"_"+(position_y)].addEventListener(MouseEvent.CLICK, myFunction);
} else {
this["button_position_"+(position_x)+"_"+(position_y)].removeEventListener(MouseEvent.CLICK, myFunction);
}
}
In the rest of the code I have:
function userClickedPosition(position_x:int, position_y:int)
it selects or deselect a unit
function selectUnit(position_x:int, position_y:int):
it uses the listentoButton(1) function to add 8 listeners (the positions around the clicked unit)
function deselectUnit(position_x:int, position_y:int):
it uses the listentoButton(0) function to delete 8 listeners (the positions around the clicked unit)
My question: adding eventlisteners is no problem but removing them dont seem to work? What did I do wrong?
When you go to remove the event, you are using a new instance of myFunction, not the same one you added it with. You either need to declare the function like you would any other function, and use the event args to examine the button's position like. I Think you want the stageX and stageY properties:
http://www.adobe.com/livedocs/flex/3/langref/flash/events/MouseEvent.html
// This function adds or deletes an event listener
function listentoButton (isTrue:int, position_x:int, position_y:int):void {
if (isTrue == 1) {
this["button_position_"+(position_x)+"_"+(position_y)].addEventListener(MouseEvent.CLICK, myFunction);
} else {
this["button_position_"+(position_x)+"_"+(position_y)].removeEventListener(MouseEvent.CLICK, myFunction);
}
}
function myFunction(eventArg:MouseEvent):void {
//use MouseEvent
};
Or you can create a little MyFunctionParameters class to hold the coordinate information and create a new instance of that class, add it to a collection indexed by the x and y coordinates, and later when you go to remove the event, you would lookup the MySpaceParameters instance in the collection, based on x and y coordinates, then use that to remove function.
class MyFunctionParameters
{
public x:int;
public y:int;
function myFunction(eventArg:MouseEvent):void {
userClickedPosition(x,y);
};
}