in AS3, removeEventListener(Event.ENTER_FRAME) is not working

22.8k views Asked by At

I have been dealing with this problem for days already. I am at my wits' end! I can't seem to find a definitive answer anywhere on any of the forums, documentation, etc.

Everything looks fine at first run, or when I load a next level for the user to play. But if the user hits the ESC key to load a different level, the ENTER FRAME listener does not get removed and it duplicates all the triggers in it, showing the player going really fast, and all funky, because it builds on top of the previously instantiated ENTER FRAME listener.

I don't know if I have a problem of an anonymous function, or an unknown instance being referenced in my removeEvent... command... Bottom line, I give up and I need this working HELP!!!

Here's the code:

function initPlay():void
{ 
    //code here determining what display object to add to the list and assign it to the currentLevel variable (a movieclip)

    if(userIsLoadingOtherLevel){
        removeEnterFrameListener();
        addChild(currentLevel);
        }
    if(userIsGointToNextLevel)
        addChild(currentLevel);

    currentLevel.addEventListener(Event.ENTER_FRAME, onEnterFrame);
    function onEnterFrame(event:Event):void
    {
        //collision detection, parallax scrolling, etc, etc is done here.
        if(allCoinsCollected)
            loadNextLevel();
        if(ESCKeyPressed)
            ESCKeyPressHandler();
    }
    function loadNextLevel():void
    {
        removeChild(currentLevel);
        newLevelToLoad++
        removeEnterFrameListener();
        initPlay();
    }

    function ESCKeyPressHandler():void
    {
        removeChild(currentLevel);
        initPlay();
    }
    function removeEnterFrameListener();
    {
        currentLevel.removeEventListener(Event.ENTER_FRAME,onEnterFrame)
        trace("currentLevel.hasEventListener(Event.ENTER_FRAME) = "+currentLevel.hasEventListener(Event.ENTER_FRAME)); //outputs TRUE if called from loadNextLevel but FALSE if called from initPlay() !!!
    }
}

I also tried to add and remove the eventListener to stage, MovieClip(Root), or nothing at all and the result is always the same.

I know that there may be other ways to design such a process, but please note I am not really flexible at the moment on doing this because the project is very long (about 4000 lines of code) and removing the ENTER FRAME this way, crazy or not should still work!!

THANK YOU in advance for anyone willing to help.

2

There are 2 answers

3
Sunil D. On BEST ANSWER

The problem appears to be the nested functions inside the initPlay() method.

Each time you call initPlay() you are defining new functions. Some of these nested functions call initPlay() themselves.

Functions are objects (memory references). So each time you call initPlay() you are making new references to new functions. So when you try to remove an event listener, you're only able to remove one of these event handlers (the one in the current scope of execution).

I'm not sure if I'm explaining this clearly, perhaps this example will help. I'll use numbers to represent the references to each function, and a simple scenario that is similar to yours:

function example():void
{
    addEventListener(MouseEvent.CLICK, mouseClickHandler);

    function mouseClickHandler(event:Event):void
    {
        if (someCondition)
        {
            example();
        }
        else
        {
            removeEventListener(MouseEvent.CLICK, mouseClickHandler);
        }
    }
}

When we run this function the first time, a new function is defined within the scope of the example() function. Lets use the number 1 to represent the reference to this nested function. someCondition is true on the first time around, and so the example() function is called again.

On the second execution of the example() function, a new reference to the mouse event handler is created (#2). We also add the event listener again. At this point, there are two event handling functions in memory, and both will be executed when the event is dispatched.

Let's say that in the second invocation of example() that someCondition is false and now we want to remove the listener. When we call:

removeEventListener(MouseEvent.CLICK, mouseClickHandler);

It's referring to event handler #2. Event handler #1 still exists, and because it's hidden in the scope of the first invocation of example() it can't be removed here.

My simple example breaks down after this... but I hope it makes it clear why your event handlers shouldn't be nested inside a function. Admittedly, this is difficult to describe and even more so in a real world example like yours. But I'm pretty confident that this is the source of most, if not all, of the issues you describe.

0
Leo Perez On

Here's how I was able to get around this without changing the scope of the nested functions (although I agree that would be the preferred solution) by creating a boolean variable called "loadingNewGame" and changing it to true from outside the onEnterFrame (in fact, this assignment was done from initPlay() and then from onEnterframe I called removeEnterFrameListener() function. This did the trick.

here's the code in case anybody is interested:

// package, and other code here.

var loadingNewGame:Boolean = new Boolean(false);
function initPlay():void
{ 

    //code here determining what display object to add to the list and assign 
    //it to the currentLevel variable (a movieclip)

    if(userIsLoadingOtherLevel)
        {
        loadingNewGame = true;
        removeEnterFrameListener();
        addChild(currentLevel);
        }
    if(userIsGointToNextLevel)
        addChild(currentLevel);

    loadingNewGame:Boolean = false;
    currentLevel.addEventListener(Event.ENTER_FRAME, onEnterFrame);
    function onEnterFrame(event:Event):void
    {
        if(loadingNewGame)
            removeChild(currentLevel);
        //collision detection, parallax scrolling, etc, etc is done here.
        if(allCoinsCollected)
            loadNextLevel();
        if(ESCKeyPressed)
            ESCKeyPressHandler();
    }
    function loadNextLevel():void
    {
        removeChild(currentLevel);
        newLevelToLoad++
        removeEnterFrameListener();
        initPlay();
    }

    function ESCKeyPressHandler():void
    {
        initPlay();
    }
    function removeEnterFrameListener();
    {
        currentLevel.removeEventListener(Event.ENTER_FRAME,onEnterFrame)
        trace("currentLevel.hasEventListener(Event.ENTER_FRAME) = "+currentLevel.hasEventListener(Event.ENTER_FRAME)); 
        //outputs true
    }