ActionScript3: removeEventListener with function parameter - actionscript-3

how can i remove EventListener from MovieClip in actionscript? Below is my sample code.
import flash.events.MouseEvent;
import flash.geom.ColorTransform;
var color: ColorTransform = new ColorTransform();
color.color = 0x00CC66;
colorChange_mc.buttonMode = true;
colorChange_mc.addEventListener(MouseEvent.MOUSE_OVER, changeColor(color));
colorChange_mc.addEventListener(MouseEvent.MOUSE_OUT, changeColorToNormal);
function changeColor(color: ColorTransform): Function {
return function (e: MouseEvent): void {
colorChange_mc.transform.colorTransform = color;
}
}
function changeColorToNormal(e: MouseEvent): void {
var color: ColorTransform = new ColorTransform();
color.color = 0x000033;
colorChange_mc.transform.colorTransform = color;
}
changer_mc.buttonMode = true;
changer_mc.addEventListener(MouseEvent.MOUSE_DOWN, removeEvent);
function removeEvent(e: MouseEvent): void {
colorChange_mc.removeEventListener(MouseEvent.MOUSE_OVER, changeColor(color));
}
I created two MovieClips on the stage , colorChange_mc has two EventListeners one for mouseDown and one for mouseOut. When change_mc button is pressed I want to remove one of the EventListener. I was able to remove EventListener without passing parameter to changeColor function. But in my real class this parameter plays crucial role.

Use a class-var instead - one you have already in your code. To keep straight I created a second ColorTransform as a class-var - normalColor
import flash.events.MouseEvent;
import flash.geom.ColorTransform;
var color: ColorTransform = new ColorTransform();
color.color = 0x00CC66;
var normalColor: ColorTransform = new ColorTransform();
normalColor.color = 0x000033;
colorChange_mc.buttonMode = true;
colorChange_mc.addEventListener(MouseEvent.MOUSE_OVER, changeColor);
colorChange_mc.addEventListener(MouseEvent.MOUSE_OUT, changeColorToNormal);
function changeColor(e:MouseEvent): void{
colorChange_mc.transform.colorTransform = color;
}
function changeColorToNormal(e:MouseEvent): void {
colorChange_mc.transform.colorTransform = normalColor;
}
changer_mc.buttonMode = true;
changer_mc.addEventListener(MouseEvent.MOUSE_DOWN, removeEvent);
function removeEvent(e:MouseEvent): void {
colorChange_mc.removeEventListener(MouseEvent.MOUSE_OVER, changeColor);
}
Greetings.

The problem is that in order to remove the listener, you have to identify the function that you originally passed to it. But you have no reference to the function, because it is anonymous and lives in the global scope.
Think object oriented. Put all the functionality of changing the color into a class. All you actually want to do is
specify a color value
be able to disable the whole thing again.
Add two methods to the class of colorChange_mc that let you do this:
public function set color(value:uint):void
and
public function set disable(value:Boolean):void
Alternatively, you can override the enabled setter function to tie your logic to it, if this is something you desire.
This way you can refactor most of the code that currently bloats your main time line into the class.

According to my first answer and your comment with new facts here is a solution for more buttons/colors using a Dictionary...
import flash.events.MouseEvent;
import flash.geom.ColorTransform;
import flash.utils.Dictionary;
import flash.display.MovieClip;
var normalColor: ColorTransform = new ColorTransform();
normalColor.color = 0x000033;
//button1-30 are the instancenames of your buttons
var initObj:Array=[{btn:button1, color:0xFFFFFF},
{btn:button2, color:0xFF0000},
{btn:button3, color:0x00FF00},
...
{btn:button30, color:0x000000},
];
var dict:Dictionary = new Dictionary();
initButtons();
function initButtons():void
{
for each (var item:Object in initObj)
{
var btn = item.btn;
btn.addEventListener(MouseEvent.MOUSE_OVER, changeColor);
btn.addEventListener(MouseEvent.MOUSE_OUT, changeColorToNormal);
dict[btn] = {color:item.color};
}
}
function changeColor(e:MouseEvent):void
{
var btn:MovieClip = e.target as MovieClip;
var color:uint = dict[btn].color;
var cT:ColorTransform=new ColorTransform();
cT.color = color;
colorChange_mc.transform.colorTransform = cT;
}
function changeColorToNormal(e:MouseEvent):void
{
colorChange_mc.transform.colorTransform = normalColor;
}
Greetings.

Thank you for your replies. As I said before the code that i posted above was just an example. Sorry if I asked unclear question. I solved my problem by disabling some specific buttons when colorChange button is pressed.
_userCardsButtons_mc.mouseEnabled = false;
_userCardsButtons_mc.mouseChildren = false;
_userCardsButtons_mc.buttonMode = false;
this code disabled event listeners that I added before.

Related

Creating inline function after loading an image causes memory leak

I was analyzing an unexpected memory leak in our game project and found some strange results. I am profiling using Adobe Scout and eliminated all other factors like starling, texture or our loading library. I reduced the code to simply load a png and immediately allocate an empty inline function on its complete event.
Loading a png allocates image on default and if you do nothing after loading gc clears that image. But creating an inline function seems to prevent that image to be garbage collected somehow. My test code is;
public class Main extends Sprite
{
private var _callbacks:Array = new Array();
public function Main()
{
load("map.png", onPngLoaded);
}
private function onPngLoaded(bitmap:Bitmap):void
{
_callbacks.push(function():void { });
}
public function load(url:String, onLoaded:Function):void
{
var loader:Loader = new Loader;
var completeHandler:Function = function(e:Event):void {
loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, completeHandler);
onLoaded(loader.content);
}
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, completeHandler);
loader.load(new URLRequest(url));
}
}
If you remove the code which creates an inline function;
private function onPngLoaded(bitmap:Bitmap):void
{
// removed the code here!
}
gc works and clears the image from memory.
Since having no logical explanation for this, I suspect of a flash / as3 bug. I will be glad to hear any comments who tests my code and gets the same results.
Note: To test, replace the main class of an empty as3 project with my code and import packages. You can load any png. I am using flashdevelop, flex-sdk 4.6.0 and flash player 14.
When you create an inline function, all local variables get stored with it in the global scope. So in this case, that would include the bitmap parameter.
For more information, see this:
http://help.adobe.com/en_US/ActionScript/3.0_ProgrammingAS3/WS5b3ccc516d4fbf351e63e3d118a9b90204-7f54.html
Here is the relevant part:
Any time a function begins execution, a number of objects and properties are created. First, a special object called an activation object is created that stores the parameters and any local variables or functions declared in the function body....Second, a scope chain is created that contains an ordered list of objects that Flash Player or Adobe AIR checks for identifier declarations. Every function that executes has a scope chain that is stored in an internal property. For a nested function, the scope chain starts with its own activation object, followed by its parent function’s activation object. The chain continues in this manner until it reaches the global object.
This is another reason why inline/anonymous functions are best avoided in most situations.
So using asc2, Flash/Air 19 : Yes I get the same results that you are seeing, but due to the anonymous function holding global references I expected that (like my original comment stated).
I rewrote it in my style based upon Adobe's GC technical articles and bulletins and no leaks are seen as all the global references are removed.
A cut/paste AIR example:
package {
import flash.events.MouseEvent;
import flash.text.TextField;
import flash.display.Sprite;
import flash.display.Bitmap;
import flash.display.Loader;
import flash.events.Event;
import flash.net.URLRequest;
import flash.system.System;
import flash.utils.Timer;
import flash.events.TimerEvent;
public class Main extends Sprite {
var timer:Timer;
var button:CustomSimpleButton;
var currentMemory:TextField;
var highMemory:TextField;
var hi:Number;
var _callbacks:Array = new Array();
public function Main() {
button = new CustomSimpleButton();
button.addEventListener(MouseEvent.CLICK, onClickButton);
addChild(button);
currentMemory = new TextField();
hi = System.privateMemory;
currentMemory.text = "c: " + hi.toString();
currentMemory.x = 100;
addChild(currentMemory);
highMemory = new TextField();
highMemory.text = "h: " + hi.toString();
highMemory.x = 200;
addChild(highMemory);
timer = new Timer(100, 1);
timer.addEventListener(TimerEvent.TIMER_COMPLETE, timerHandler);
timer.start();
}
function timerHandler(e:TimerEvent):void{
System.pauseForGCIfCollectionImminent(.25);
currentMemory.text = "c: " + System.privateMemory.toString();
hi = System.privateMemory > hi ? System.privateMemory : hi;
highMemory.text = "h: " + hi.toString();
timer.start();
}
function onClickButton(event:MouseEvent):void {
for (var i:uint = 0; i<100; i++) {
//load("foobar.png", onPngLoaded);
load2("foobar.png");
}
}
private function onPngLoaded2(bitmap:Bitmap):void {
var foobarBitMap:Bitmap = bitmap; // assuming you are doing something
foobarBitMap.smoothing = false; // with the bitmap...
callBacks(); // not sure what you are actually doing with this
}
private function callBacks():void {
_callbacks.push(function ():void {
});
}
public function completeHandler2(e:Event):void {
var target:Loader = e.currentTarget.loader as Loader;
// create a new bitmap based what is in the loader so the loader has not refs after method exits
var localBitmap:Bitmap = new Bitmap((target.content as Bitmap).bitmapData);
onPngLoaded2(localBitmap);
}
public function load2(url:String):void {
var loader2:Loader = new Loader;
loader2.contentLoaderInfo.addEventListener(Event.COMPLETE, completeHandler2, false, 0, true);
loader2.load(new URLRequest(url));
}
}
}
import flash.display.Shape;
import flash.display.SimpleButton;
class CustomSimpleButton extends SimpleButton {
private var upColor:uint = 0xFFCC00;
private var overColor:uint = 0xCCFF00;
private var downColor:uint = 0x00CCFF;
private var size:uint = 80;
public function CustomSimpleButton() {
downState = new ButtonDisplayState(downColor, size);
overState = new ButtonDisplayState(overColor, size);
upState = new ButtonDisplayState(upColor, size);
hitTestState = new ButtonDisplayState(upColor, size * 2);
hitTestState.x = -(size / 4);
hitTestState.y = hitTestState.x;
useHandCursor = true;
}
}
class ButtonDisplayState extends Shape {
private var bgColor:uint;
private var size:uint;
public function ButtonDisplayState(bgColor:uint, size:uint) {
this.bgColor = bgColor;
this.size = size;
draw();
}
private function draw():void {
graphics.beginFill(bgColor);
graphics.drawRect(0, 0, size, size);
graphics.endFill();
}
}

actionscript 3 add and remove movieclip button

Trying to make buttons on actionscript: create and remove movieclip object, but always some errors...
I need one button creates movieclip, another must to delete it by mouse click. Here is code:
import flash.events.MouseEvent;
import flash.display.MovieClip;
import flash.text.TextField;
main.addEventListener(MouseEvent.CLICK, addMain);
function addMain(e: MouseEvent) {
var movieClip: bm = new bm();
movieClip.x = 640;
movieClip.y = 570;
this.addChild(movieClip);
main.removeEventListener(MouseEvent.CLICK, addMain);
deletemain.removeEventListener(MouseEvent.CLICK, addMain);
}
deletemain.addEventListener(MouseEvent.CLICK, deleteMain);
function deleteMain(e: MouseEvent) {
var movieClip: bm = bm(event.target);
this.removeChild(movieClip);
removeChild(movieClip);
movieClip = null;
deletemain.removeEventListener(MouseEvent.CLICK, deleteMain);
}
OK, to understand why your code is not working, let's take a look on it :
main.addEventListener(MouseEvent.CLICK, addMain);
function addMain(e: MouseEvent)
{
// here you are creating a local var "movieClip"
// inside your "addMain" function, so it's only accessible here
var movieClip:bm = new bm();
movieClip.x = 640;
movieClip.y = 570;
this.addChild(movieClip);
main.removeEventListener(MouseEvent.CLICK, addMain);
// I don't really know why you do this !
deletemain.removeEventListener(MouseEvent.CLICK, addMain);
}
deletemain.addEventListener(MouseEvent.CLICK, deleteMain);
function deleteMain(e: MouseEvent)
{
// here, you are creating a local var "movieClip"
// by casting (converting) your "deletemain" button (e.target) to an "bm" object
// event.target should be e.target
var movieClip: bm = bm(event.target);
// here your are removing the "deletemain" button, once
this.removeChild(movieClip);
// and twice
removeChild(movieClip);
movieClip = null;
deletemain.removeEventListener(MouseEvent.CLICK, deleteMain);
}
So to do what you want : creating a MovieClip and removing it using two buttons, you have to declare your "movieClip" as a global var to be accessible for your two listener functions :
// declare a global "bm" object
var movieClip:bm;
btn_add.addEventListener(MouseEvent.CLICK, btn_add_onPress);
function btn_add_onPress(e: MouseEvent): void
{
// create the "movieClip" object
movieClip = new bm();
addChild(movieClip);
// remove the click event listener for the btn_add
btn_add.removeEventListener(MouseEvent.CLICK, btn_add_onPress);
}
btn_delete.addEventListener(MouseEvent.CLICK, btn_delete_onPress);
function btn_delete_onPress(e:MouseEvent) : void
{
// remove the "movieClip" object
removeChild(movieClip);
movieClip = null;
// remove the click event listener for the btn_delete
btn_delete.removeEventListener(MouseEvent.CLICK, btn_delete_onPress);
}
Hope that can help.

Actionscript 3 Call to a possibly undefined method

Here is the problem, the object is moved together with the clicked object. I want it to be moveable following the mouse pointer, but let the clicked object stays. so when an object is clicked, there will be 2 objects in the stage(the static and moving one).
I think I've figured it out by adding a new object to be moved. in function onClickHero I've tried movingHero = new heroes but it says "call to a possibly undefined method heroes". My question is there any other way how to make another clone of the clicked object since I made it in array? And why does movingHero = new heroes doesn't work?
I'm still amateur at classes. Sorry if it's messed up. Thanks for helping.
package {
import flash.display.MovieClip
import flash.events.MouseEvent
import flash.events.Event
import flash.display.Sprite
public class Hero {
private var heroesArray:Array;
private var heroContainer:Sprite = new Sprite;
private var hero1:MovieClip = new Hero1();
private var hero2:MovieClip = new Hero2();
private var moveHero:Boolean = false;
private var movingHero:MovieClip;
private var _money:Money = new Money();
private var _main:Main;
public function Hero(main:Main)
{ _main = main;
heroesArray = [hero1,hero2];
heroesArray.forEach(addHero);
}
public function addHero(heroes:MovieClip,index:int,array:Array):void
{
heroes.addEventListener(Event.ENTER_FRAME, playerMoving);
heroes.addEventListener(MouseEvent.CLICK, chooseHero);
}
public function playerMoving(e:Event):void
{
if (moveHero == true)
{
movingHero.x = _main.mouseX;
movingHero.y = _main.mouseY;
}
}
public function chooseHero(e:MouseEvent):void
{
var heroClicked:MovieClip = e.currentTarget as MovieClip;
var cost:int = _main._money.money ;
if(cost >= 10 && moveHero == false)
{
_main._money.money -= 10;
_main._money.addText(_main);
onClickHero(heroClicked);
moveHero = true;
}
}
public function onClickHero(heroes:MovieClip):void
{
movingHero = heroes;
heroContainer.addChild(movingHero);
}
public function displayHero(stage:Object):void
{
stage.addChild(heroContainer);
for (var i:int = 0; i<2;i++)
{
stage.addChild(heroesArray[i]);
heroesArray[i].x = 37;
heroesArray[i].y = 80+i*70;
heroesArray[i].width=60;
heroesArray[i].height=55;
heroesArray[i].buttonMode = true;
}
}
}
}
EDIT: I've tried to make movingHero = new Hero1(); but since I don't know which hero will be clicked so I can't just use Hero1 from library. and If I use movingHero = heroClicked I only get the value of hero1 which is a var from Hero1 movieclip. So, is there any way to call the movie clip from library the same as which hero was clicked in stage?
You seemingly want to clone an object while not knowing its type. If that object also containg game logic, it's not the best idea to say spawn new heroes of either type, this might make a mess of your code. But if not, you can get the exact class of the object given, and make an object of that class via the following code:
public function onClickHero(heroes:MovieClip):void
{
if (!heroes) {
trace('heroes is null!');
return;
}
var heroClass:Class = getDefinitionByName(getQualifiedClassName(heroes)) as Class;
movingHero = new heroClass(); // instantiate that class
heroContainer.addChild(movingHero);
// movingHero.startDrag(); if needed
}
Don't forget to clean up the movingHero once it's no longer needed.

Error #2025: The supplied DisplayObject must be a child of the caller - Not sure how to fix

So I get an error saying that the supplied DisplayObject must be a child of the caller. What happens is my game works first time around in that clicking the 'Play' button calls the startGame function and removes the menu so that the game is shown, but then at the end of the game when the playAgainBtn is clicked, instead of simply playing the game again / restarting the game, I get this error:
ArgumentError: Error #2025: The supplied DisplayObject must be a child of the caller.
at flash.display::DisplayObjectContainer/removeChild()
It specifically points to this line:
menuLayer.removeChild(mainMenu);
Here is the code:
package {
import flash.display.MovieClip;
import flash.display.LoaderInfo;
import flash.display.Sprite;
import flash.display.DisplayObject;
import flash.display.Stage;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.TimerEvent;
import flash.utils.Timer;
import flash.ui.Mouse;
import flash.media.Sound;
import flash.media.SoundChannel;
import flash.text.TextFormat;
import flash.text.TextField;
import flash.text.AntiAliasType;
import flash.display.Loader;
import flash.net.URLRequest;
import flash.net.URLLoader;
import flash.system.LoaderContext;
import flash.display.Sprite;
import flash.net.Socket;
import caurina.transitions.Tweener;
import flash.text.Font;
public class Main extends MovieClip {
public static var backgroundLayer:Sprite = new Sprite;
public static var gameLayer:Sprite = new Sprite;
public static var interfaceLayer:Sprite = new Sprite;
public static var endGameLayer:Sprite = new Sprite;
public static var menuLayer:Sprite = new Sprite;
public static var gameOverLayer:Sprite = new Sprite;
public static var howToLayer:Sprite = new Sprite;
public static var scoresLayer:Sprite = new Sprite;
public static var aboutLayer:Sprite = new Sprite;
public var mainMenu:menuMain = new menuMain;
public var gameEnd:endGame = new endGame;
public var howtoPlay:howToPlay = new howToPlay;
public var gameAbout:aboutGame = new aboutGame;
public var intro:IntroSound = new IntroSound();
public var soundControl:SoundChannel = new SoundChannel();
public var gameTime:int;
public var levelDuration:int;
public var crosshair:crosshair_mc;
static var score:Number;
var enemyShipTimer:Timer;
var enemyShipTimerMed:Timer;
var enemyShipTimerSmall:Timer;
static var scoreHeader:TextField = new TextField();
static var scoreText:TextField = new TextField();
static var timeHeader:TextField = new TextField();
static var timeText:TextField = new TextField();
static var scoreFormat = new TextFormat("Arial Rounded MT Bold", 20, 0xFFFFFF);
public var gameOverscoreFormat = new TextFormat("Arial Rounded MT Bold", 32, 0xFFFFFF);
public function Main()
{
addChild(gameLayer);
addChild(backgroundLayer);
addChild(interfaceLayer);
addChild(menuLayer);
menuLayer.addChild(mainMenu);
interfaceLayer.addChild(howtoPlay);
interfaceLayer.addChild(gameEnd);
interfaceLayer.addChild(gameAbout);
soundControl = intro.play(0, 100);
addMenuListeners();
}
public function menuReturn(e:Event)
{
addChild(gameLayer);
addChild(backgroundLayer);
addChild(interfaceLayer);
addChild(menuLayer);
menuLayer.addChild(mainMenu);
interfaceLayer.addChild(howtoPlay);
interfaceLayer.addChild(gameEnd);
interfaceLayer.addChild(gameAbout);
}
public function showAbout(e:Event)
{
menuLayer.removeChild(mainMenu);
interfaceLayer.addChild(gameAbout);
}
public function startGame(e:Event)
{
removeMenuListeners();
soundControl.stop();
interfaceLayer.removeChild(howtoPlay);
interfaceLayer.removeChild(gameAbout);
interfaceLayer.removeChild(gameEnd);
menuLayer.removeChild(mainMenu);
levelDuration = 30;
gameTime = levelDuration;
var gameTimer:Timer = new Timer(1000,levelDuration);
gameTimer.addEventListener(TimerEvent.TIMER, updateTime);
gameTimer.addEventListener(TimerEvent.TIMER_COMPLETE, timeExpired)
gameTimer.start();
scoreHeader = new TextField();
scoreHeader.text = String("Score: ");
interfaceLayer.addChild(scoreHeader);
scoreHeader.x = 5;
scoreHeader.selectable = false;
scoreHeader.embedFonts = true;
scoreHeader.antiAliasType = AntiAliasType.ADVANCED;
scoreText = new TextField();
scoreText.text = String("0");
interfaceLayer.addChild(scoreText);
scoreText.x = 75;
scoreText.y = 0;
scoreText.selectable = false;
scoreText.embedFonts = true;
scoreText.antiAliasType = AntiAliasType.ADVANCED;
timeHeader = new TextField();
timeHeader.text = String("Time: ");
interfaceLayer.addChild(timeHeader);
timeHeader.x = 500;
timeHeader.y = 0;
timeHeader.selectable = false;
timeHeader.embedFonts = true;
timeHeader.antiAliasType = AntiAliasType.ADVANCED;
timeText = new TextField();
timeText.text = gameTime.toString();
interfaceLayer.addChild(timeText);
timeText.x = 558;
timeText.y = 0;
timeText.selectable = false;
timeText.embedFonts = true;
timeText.antiAliasType = AntiAliasType.ADVANCED;
scoreHeader.setTextFormat(scoreFormat);
scoreText.setTextFormat(scoreFormat);
timeHeader.setTextFormat(scoreFormat);
timeText.setTextFormat(scoreFormat);
var timeScorebg:Sprite = new Sprite();
backgroundLayer.addChild(timeScorebg);
timeScorebg.graphics.beginFill(0x333333);
timeScorebg.graphics.drawRect(0,0,600,30);
timeScorebg.graphics.endFill();
timeScorebg.y = 0;
enemyShipTimer = new Timer(2000);
enemyShipTimer.addEventListener("timer", sendEnemy);
enemyShipTimer.start();
enemyShipTimerMed = new Timer(2500);
enemyShipTimerMed.addEventListener("timer", sendEnemyMed);
enemyShipTimerMed.start();
enemyShipTimerSmall = new Timer(2750);
enemyShipTimerSmall.addEventListener("timer", sendEnemySmall);
enemyShipTimerSmall.start();
crosshair = new crosshair_mc();
gameLayer.addChild(crosshair);
crosshair.mouseEnabled = crosshair.mouseChildren = false;
Mouse.hide();
gameLayer.addEventListener(Event.ENTER_FRAME, moveCursor);
resetScore();
}
function addMenuListeners():void
{
mainMenu.playBtn.addEventListener(MouseEvent.CLICK, startGame);
mainMenu.howToPlayBtn.addEventListener(MouseEvent.CLICK, showInstructions);
mainMenu.aboutBtn.addEventListener(MouseEvent.CLICK, showAbout);
howtoPlay.backBtn.addEventListener(MouseEvent.CLICK, menuReturn);
gameEnd.playagainBtn.addEventListener(MouseEvent.CLICK, startGame);
gameAbout.backBtn.addEventListener(MouseEvent.CLICK, menuReturn);
}
function removeMenuListeners():void
{
mainMenu.playBtn.removeEventListener(MouseEvent.CLICK, startGame);
mainMenu.howToPlayBtn.removeEventListener(MouseEvent.CLICK, showInstructions);
mainMenu.aboutBtn.removeEventListener(MouseEvent.CLICK, showAbout);
howtoPlay.backBtn.removeEventListener(MouseEvent.CLICK, menuReturn);
gameEnd.playagainBtn.removeEventListener(MouseEvent.CLICK, startGame);
gameAbout.backBtn.removeEventListener(MouseEvent.CLICK, menuReturn);
}
public function showInstructions(e:Event)
{
menuLayer.removeChild(mainMenu);
interfaceLayer.addChild(howtoPlay);
}
function sendEnemy(e:Event)
{
var enemy = new EnemyShip();
gameLayer.addChild(enemy);
gameLayer.addChild(crosshair);
}
function sendEnemyMed(e:Event)
{
var enemymed = new EnemyShipMed();
gameLayer.addChild(enemymed);
gameLayer.addChild(crosshair);
}
function sendEnemySmall(e:Event)
{
var enemysmall = new EnemyShipSmall();
gameLayer.addChild(enemysmall);
gameLayer.addChild(crosshair);
}
static function updateScore(points)
{
score += points;
scoreText.text = String(score);
scoreHeader.setTextFormat(scoreFormat);
scoreText.setTextFormat(scoreFormat);
}
static function resetScore()
{
score = 0;
scoreText.text = String(score);
scoreText.setTextFormat(scoreFormat);
}
function updateTime(e:TimerEvent):void
{
gameTime--;
timeText.defaultTextFormat = scoreFormat;
timeText.text = String(gameTime);
}
function timeExpired(e:TimerEvent):void
{
var gameTimer:Timer = e.target as Timer;
gameTimer.removeEventListener(TimerEvent.TIMER, updateTime)
gameTimer.removeEventListener(TimerEvent.TIMER, timeExpired)
interfaceLayer.addChild(gameEnd);
var thisFont:Font = new myFont();
var myFormat:TextFormat = new TextFormat();
myFormat.font = thisFont.fontName;
scoreText = new TextField();
scoreText.defaultTextFormat = myFormat;
scoreText.text = String(score);
interfaceLayer.addChild(scoreText);
scoreText.x = 278;
scoreText.y = 180;
scoreText.selectable = false;
scoreText.embedFonts = true;
scoreText.antiAliasType = AntiAliasType.ADVANCED;
scoreText.setTextFormat(gameOverscoreFormat);
Mouse.show();
removeChild(gameLayer);
addMenuListeners();
}
function moveCursor(event:Event)
{
crosshair.x=mouseX;
crosshair.y=mouseY;
}
}
}
I'm not quite sure how to fix this, so any advice or solution will be welcome. I can't get it to work the way I intended without getting errors.
Thanks.
I believe the problem is calling menuLayer.removeChild(mainMenu); on the second play-through is throwing the error due to the fact that you'd already removed it once already. The quickest solution would be to do a check to ensure menuLayer contains mainMenu before you try and remove it:
if(menuLayer contains mainMenu)
menuLayer.removeChild(mainMenu);
(Note that I don't have access to the IDE right now, but I think this should work)
A more robust solution would be to call a different method when the play button is clicked from the main menu that removes mainMenu from menuLayer, then calls startGame (where as playAgain calls startGame directly).
EDIT
Ok I see what you mean. Perhaps something like this instead:
mainMenu.playBtn.addEventListener(MouseEvent.CLICK, playGame);
gameEnd.playagainBtn.addEventListener(MouseEvent.CLICK, playGameAgain);
...
public function playGame(e:Event)
{
menuLayer.removeChild(mainMenu);
startGame();
}
...
public function playGameAgain(e:Event)
{
startGame();
}
...
public function startGame()
I have no idea why your code is not working, but there is no need to fret, try:
MovieClip(menuLayer.parent).removeChild(menuLayer);
You remove mainMenu in two different locations. My guess is it is being removed once and then again moments later.
if ( mainMenu.parent == menuLayer ) {
menuLayer.removeChild( mainMenu );
}
This will verify that mainMenu is actually a child of menuLayer before removing it. You cannot remove a child from a parent that isn't actually its parent. Imagine the state taking a child away and taking custody of them from a kidnapper. It's not the prettiest comparison, but it gives the right idea.
I cannot verify this without seeing how the game over is handled, but I think potentially the problem is that you are not removing your event listeners each time the game is played. Therefore when you go back to the main menu and add them again, you now have TWO listeners for playAgainBtn.
So when you end a game and click on the playAgainBtn, startGame gets called TWICE. So the first time it removes things just fine, and the second time - there's nothing to remove. This issue will potentially exist with all of your event listeners given your current design.
If this is the case you simply need to remove your event listeners when the menu is removed.
I suggest that whenever you make the menu active you add the listeners, and then remove them whenever you hide it. Maybe have two methods, addMenuListeners and removeMenuListeners
You could create these two functions and use them where appropriate :
function addMenuListeners():void
{
mainMenu.playBtn.addEventListener(MouseEvent.CLICK, startGame);
mainMenu.howToPlayBtn.addEventListener(MouseEvent.CLICK, showInstructions);
mainMenu.aboutBtn.addEventListener(MouseEvent.CLICK, showAbout);
howtoPlay.backBtn.addEventListener(MouseEvent.CLICK, menuReturn);
gameEnd.playagainBtn.addEventListener(MouseEvent.CLICK, startGame);
gameAbout.backBtn.addEventListener(MouseEvent.CLICK, menuReturn);
}
function removeMenuListeners():void
{
mainMenu.playBtn.removeEventListener(MouseEvent.CLICK, startGame);
mainMenu.howToPlayBtn.removeEventListener(MouseEvent.CLICK, showInstructions);
mainMenu.aboutBtn.removeEventListener(MouseEvent.CLICK, showAbout);
howtoPlay.backBtn.removeEventListener(MouseEvent.CLICK, menuReturn);
gameEnd.playagainBtn.removeEventListener(MouseEvent.CLICK, startGame);
gameAbout.backBtn.removeEventListener(MouseEvent.CLICK, menuReturn);
}
If you follow the rule of always removing the event listeners when not in use, you can avoid this issue.
if( mainMenu.parent ){ mainmenu.parent.removeChild( mainMenu );} Or perhaps its already removed / not added at all?
Change this line
menuLayer.removeChild(mainMenu);
to this one..
if (mainMenu.parent != null && mainMenu.parent == menuLayer)
{
menuLayer.removeChild(mainMenu);
}
Hope it will solve.

AS3 Mouse Over Change Image

Currently I'm trying to change image loaded in a sprite with mouseover event and change it back with mouseout. But it's not working correctly, am i missing something?
public class Tab extends Sprite
{
var imageLoader:Loader = new Loader();
var TabSprite:Sprite = new Sprite();
var SkinImages:Array = [Skin.TAB_ACTIVE,Skin.TAB_DISABLED,Skin.TAB_HOVER,Skin.TAB_VIEW];
public function Tab()
{
for each (var Image:String in SkinImages){
imageLoader.load(new URLRequest(Image));
}
TabSprite.buttonMode = true;
addChild(TabSprite);
TabSprite.addChild(imageLoader);
TabSprite.addEventListener(MouseEvent.MOUSE_OVER, onTabHover);
}
private function onTabHover(e:MouseEvent){
trace("HOVER");
TabSprite.removeEventListener(MouseEvent.MOUSE_OVER, onTabHover);
imageLoader.load(new URLRequest(Skin.TAB_HOVER));
imageLoader.contentLoaderInfo.addEventListener(Event.COMPLETE,function(e:Event):void{
TabSprite.addEventListener(MouseEvent.MOUSE_OUT, onTabOut);
});
}
private function onTabOut(e:MouseEvent){
trace("OUT");
TabSprite.removeEventListener(MouseEvent.MOUSE_OUT, onTabOut);
imageLoader.load(new URLRequest(Skin.TAB_VIEW));
imageLoader.contentLoaderInfo.addEventListener(Event.COMPLETE,function(e:Event):void{
TabSprite.addEventListener(MouseEvent.MOUSE_OVER, onTabHover);
});
}
}
You shouldn't nest listeners that way. Just add two in the constructor:
TabSprite.addEventListener(MouseEvent.ROLL_OVER, onTabHover);
TabSprite.addEventListener(MouseEvent.ROLL_OUT, onTabOut);
Note changing MOUSE_OVER to ROLL_OVER it's better in most cases. You shouldn't also load images at every mouse event. Preload them, and then use. Also using anonymous functions in listeners is bad practice as you are not able to remove that listener:
imageLoader.contentLoaderInfo.addEventListener(Event.COMPLETE,function(e:Event):void{
TabSprite.addEventListener(MouseEvent.MOUSE_OUT, onTabOut);
});
And in fact you are not removing it - this is bad.
for each (var Image:String in SkinImages){
imageLoader.load(new URLRequest(Image));
}
I doubt it works, I think you cannot load many images at once by using one loader.
Try this:
public class Tab extends Sprite
{
var imageOverLoader:Loader = new Loader();
var imageOutLoader:Loader = new Loader();
var TabSprite:Sprite = new Sprite();
var SkinImages:Array = Skin.TAB_ACTIVE,Skin.TAB_DISABLED,Skin.TAB_HOVER,Skin.TAB_VIEW];
public function Tab()
{
TabSprite.buttonMode = true;
this.addChild(TabSprite); // you also need to add as a Child "Tab" object in the Main Class
imageOutLoader.load(new URLRequest(Skin.TAB_VIEW));
imageOverLoader.load(new URLRequest(Skin.TAB_HOVER));
TabSprite.addChild(imageOutLoader);
TabSprite.addEventListener(MouseEvent.ROLL_OVER, onTabHover);
TabSprite.addEventListener(MouseEvent.ROLL_OUT, onTabOut);
}
private function onTabHover(e:MouseEvent){
TabSprite.removeChild(imageOutLoader);
TabSprite.addChild(imageOverLoader);
trace("HOVER");
}
private function onTabOut(e:MouseEvent){
TabSprite.removeChild(imageOverLoader);
TabSprite.addChild(imageOutLoader);
trace("OUT");
}
}
Try this.