I'm making a game as a project for school and have recently encountered a little problem in as3. I'm making a game where you are maneuvering ship avoiding asteroids, and just added the function so that when you hit an asteroid your ship is removed from the stage via stage.removeChild. Everything is fine until you actually hit something, then this error comes up(and I mean alot, like it keeps repeating itself for as long as the game is on):
ArgumentError: Error #2025: The supplied DisplayObject must be a child of the caller.
at flash.display::DisplayObjectContainer/removeChild()
at Function/com.asgamer.basics1:Engine/private:loop/com.asgamer.basics1:krash()[C:\Users\nti\Desktop\Ship game\com\asgamer\basics1\Engine.as:54]
Here's the code(I marked out line 54):
package com.asgamer.basics1
{
import flash.display.MovieClip;
import flash.display.Stage;
import flash.events.Event;
public class Engine extends MovieClip
{
private var numStars:int = 80;
private var enemyList:Array = new Array();
private var ourShip:Ship;
public function Engine() : void
{
ourShip = new Ship(stage);
stage.addChild(ourShip);
ourShip.x = stage.stageWidth / 2;
ourShip.y = stage.stageHeight / 2;
for (var i:int = 0; i < numStars; i++)
{
stage.addChildAt(new Star(stage), stage.getChildIndex(ourShip));
}
addEventListener(Event.ENTER_FRAME, loop, false, 0, true);
}
private function loop(e:Event) : void
{
if (Math.floor(Math.random() * 14) == 5)
{
var enemy:Asteroid = new Asteroid(stage);
enemy.addEventListener(Event.REMOVED_FROM_STAGE, removeEnemy, false, 0, true);
enemyList.push(enemy);
stage.addChild(enemy);
stage.addEventListener(Event.ENTER_FRAME, krash);
function krash(e:Event):void{
if (enemy.hitTestObject(ourShip)==true){
stage.removeChild(ourShip); <-------------------------- LINE 54
}
}
}
}
private function removeEnemy(e:Event)
{
enemyList.splice(enemyList.indexOf(e.currentTarget), 1);
}
}
}
Please do remember that I'm sort of a beginner to coding, which would explain other possible "faults" in the code. :)
The problem is, as was mentioned a while ago, is that you don't clean up your ourShip after actually removing it from stage. See, once you call stage.removeChild() the ship is no longer on stage, but collision check still goes because you didn't remove the enter frame listener. You should add the remove listener statement to the code branch where you remove the ship.
stage.addEventListener(Event.ENTER_FRAME, krash);
... // other code
function krash(e:Event):void{
for each (var enemy in enemyList)
if (enemy.hitTestObject(ourShip)==true){
stage.removeChild(ourShip);
stage.removeEventListener(Event.ENTER_FRAME, krash); // THIS line
}
}
EDIT: first, move the addEventListener(Event.ENTER_FRAME, krash) line to the point where you add a loop listener, because you don't want to have more than a single one of these, and second, make a complete loop for hitTestObject versus enemyList. The code above is updated.
Note: Some validity checks may need to be present in the listener code, for example, if ourShip is already removed you might just return from the event listener function, or skip the hit test checks. This will also help you once you'll start advancing to a single master enter frame listener - it's generally faster having one function as event listener than many for the same event, and also helps you consolidate every bit of code you want to be executed in response for a particular event in one place, instead of searching where did you place that statement that bugs out. This also helps to not fiddle with adding/removing listeners on a regular basis, although sometimes playing with listeners can do you better.
Problem is caused by the fact that you're trying remove ourShip from the stage several times. More precisely on each Event.ENTER_FRAME event.
Related
I was looking for this answer and had no luck. One place I looked actually had a very discouraging answer: "You cannot force mouse or keyboard events - they HAVE TO come from mouse or keyboard."
Huh?
I tried 'brute force' and came up with this solution. Maybe I'm going about it wrongly or stupidly; is there a better way?
I had a keyboard event that launched a class and wanted to put a sprite on the stage that would initiate this same action -- clicking on the sprite would launch the keyboard event (Escape key).
In the eventListener function, I traced the event e itself:
private function keys(e:KeyboardEvent):void {
trace("EscapeKey: ",e);
if (e.keyCode == 27) {
...
}
}
The output was
EscapeKey: [KeyboardEvent type="keyDown" bubbles=true cancelable=false eventPhase=2 charCode=27 keyCode=27 keyLocation=0 ctrlKey=false altKey=false shiftKey=false]
I then had the mouseClick listener create and dispatch a new keyboardEvent using the values I got from the above trace:
private function pauseClick(e:MouseEvent):void {
var a:KeyboardEvent = new KeyboardEvent("keyDown", true, false, 27, 27, 0, false, false, false);
stage.dispatchEvent(a);
}
Presto!
Hopefully, this post will come in handy to others looking for these types of mouse/keyboard event redundancies.
EDIT --- A complete class example requested in comments:
package{
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.KeyboardEvent;
public class KeyboardMouse extends Sprite {
private var pauseInfo:PauseInfo;
private var escapeKey:EscapeKey;
public function KeyboardMouse() {
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void {
removeEventListener(Event.ADDED_TO_STAGE, init);
stage.addEventListener(KeyboardEvent.KEY_DOWN, keys);
escapeKey = new EscapeKey();
stage.addChild(escapeKey);
pauseInfo = new PauseInfo();
pauseInfo.x = stage.stageWidth;
pauseInfo.y = stage.stageHeight;
pauseInfo.addEventListener(MouseEvent.CLICK,pauseClick);
addChild(pauseInfo);
}
private function keys(e:KeyboardEvent):void {
trace("KeyboardEvent ",e);
if (e.keyCode == 27) { // esc key
if (stage.contains(escapeKey)){
trace("remove escape");
escapeKey.visible = false;
}
else {
trace("show escape");
escapeKey.visible = true;
}
}
}
private function pauseClick(e:MouseEvent):void {
// The trace in 'keys' gives this:
//[KeyboardEvent type="keyDown" bubbles=true cancelable=false eventPhase=2 charCode=27 keyCode=27 keyLocation=0 ctrlKey=false altKey=false shiftKey=false]
var a:KeyboardEvent = new KeyboardEvent("keyDown", true, false, 27, 27, 0, false, false, false);
stage.dispatchEvent(a);
}
}
}
Alright, now I get this. I just don’t get it’s purpose. Also there is, I think, an annoying error in the code.
First: you’ve shown that you can essentially ‘cast’ one type of event (e.g. MouseEvent) as another (KeyboardEvent). But you still have a listener function registered for each Event type (which, at some point, you’ll have to remove for memory management purposes) and a newly dispatched Event, so you’re not exactly minimizing code. What, exactly, is the value of this trick? Why not just register a single listener function with multiple event types? Within that function you can discriminate between the Event types and do stuff accordingly, for instance with a line like this:
if(e.type == 'keyDown' || e.type == 'click')
Second: you say: if (stage.contains(escapeKey)) and then you try to make the ‘visible’ property of escapeKey dependent on that. But by making the escapeKey.visible = false you’re not changing the fact that the stage still contains escapeKey. So the escapeKey will never become visible again because the 'else' condition doesn't exist. I believe that you want to say “removeChild(escapeKey)” and then “addChild(escapeKey)” instead of setting “escapeKey.visible = false” and then “escapeKey.visible = true”. Then you’re little program does what I think you want it to.
+1 and big props to #Neal Davis for his comments!
As he suggests, having one listener for both events with an argument of (e:Event) is a cleaner, less convoluted way of achieving the same result:
pauseInfo.addEventListener(MouseEvent.CLICK,keys);
stage.addEventListener(KeyboardEvent.KEY_DOWN, keys);
private function keys(e:Event):void {
trace("EscapeKey: ",e);
if (e.keyCode == 27) {
...
}
}
Both listeners must still be removed at some point, though, even though only one Listener Function is handling both, of course!
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.
Alright, so i've got some code that says that if a hit test is preformed, a variable is increased by 1. However, when I run it, I get crazy numbers like 1,1,1,2,5,3,2,5,2,3,4,1,1
There's no pattern, it's just random. Here's the code:
public function loop(e:Event)
{
y += speed;
if (y > stage.stageHeight)
{
setupAsteroid(false);
}
//hittest for the asteroid
if (hitTestObject(target))
{
stageRef.addChild(new Explosion(stageRef, x, y));
trace("Hit");
kills ++;
trace(kills);
if(kills == 3){
trace("Success");
}
}
I've included all the code in the function used, and not all of it is necessary to the hit test as you can see. also, the variable is declared correctly as
private var kills:Number = 0;
inside the same .as file and class.
You add an Explosion to the stage but there is no sign of you removing anything from the stage, or at least stopping the check for hitTest. Also I'm guessing that your function loop(e:Event) listens for an ENTER_FRAME event and in that case if you're not removing anything from the stage that hitTest is going to be returning true every frame and creating a new Explosion every frame. Why not just add a public variable to target, like public var destroyed:boolean = false; and reconstruct your hitTest to read if(hitTestObject(target) && !target.destroyed) and in the body of your conditional just add target.destroyed = true;
More code would definitely help though.
I'm getting an error I can't seem to fix. I think I know kind of what is going on but I'm not sure enough to be able to fix it. I keep getting error
"TypeError: Error #2007: Parameter hitTestObject must be non-null.
at flash.display::DisplayObject/_hitTest()
at flash.display::DisplayObject/hitTestObject()"
Basically I fire an attack in my game. It hits an enemy and he is killed just fine. BUT, he has an animation as he dies that takes a couple seconds. It seems if I fire another attack during his animation, my attack IMMEDIATELY gives this error (before striking anything, that is). Once the animation is over, everything is fine again. Also, the game was working 100% fine before I put in this animation.
Here is my document class
package com.classes
{
import flash.display.MovieClip;
import flash.display.Stage;
import flash.events.Event;
public class DocumentClass extends MovieClip
{
// we need to keep track of our enemies.
public static var enemyList1:Array = new Array();
// moved stickobject1 to a class variable.
private var stickobject1:Stickman2;
public function DocumentClass() : void
{
//removed the var stickobject1:Stickman2 because we declared it above.
var bg1:background1 = new background1();
stage.addChild(bg1);
stickobject1 = new Stickman2(stage);
stage.addChild(stickobject1);
stickobject1.x=50;
stickobject1.y=300;
//running a loop now.... so we can keep creating enemies randomly.
addEventListener(Event.ENTER_FRAME, loop, false, 0, true);
}
//our loop function
private function loop(e:Event) : void
{
//run if condition is met.
if (Math.floor(Math.random() * 90) == 5)
{
//create our enemyObj1
var enemyObj1:Enemy1 = new Enemy1(stage, stickobject1);
//listen for enemyObj1 being removed from stage
enemyObj1.addEventListener(Event.REMOVED_FROM_STAGE, removeEnemyObj1, false, 0, true);
//add our enemyObj1 to the enemyList1
enemyList1.push(enemyObj1);
stage.addChild(enemyObj1);
}
}
private function removeEnemyObj1(e:Event)
{
enemyList1.splice(enemyList1.indexOf(e.currentTarget), 1);
}
}
}
And here is my attack1 class
package com.classes {
import flash.display.MovieClip;
import flash.display.Stage;
import com.senocular.utils.KeyObject;
import flash.ui.Keyboard;
import flash.events.Event;
public class attack1 extends MovieClip {
private var stageRef:Stage;
private var bulletSpeed:Number = 16;
public function attack1 (stageRef:Stage, x:Number, y:Number) : void
{
this.stageRef = stageRef;
this.x = x;
this.y = y;
addEventListener(Event.ENTER_FRAME, loop, false, 0, true);
}
private function loop(e:Event) : void
{
//move bullet up
x += bulletSpeed;
if (x > stageRef.stageWidth)
removeSelf();
for (var i:int = 0; i < DocumentClass.enemyList1.length; i++)
{
if (hitTestObject(DocumentClass.enemyList1[i].hit))
{
trace("hitEnemy");
removeSelf();
DocumentClass.enemyList1[i].takeHit();
}
}
}
private function removeSelf() : void
{
removeEventListener(Event.ENTER_FRAME, loop);
if (stageRef.contains(this))
stageRef.removeChild(this);
}
}
}
Don't think you should need any other of my classes to figure out what's going on, but let me know if you do! Thanks very much =)
You don't want to do a hit test against any object that may have been removed from the scene (or from the enemyList array). The extra condition added to attack1.loop's for loop should get rid of your error. A better fix is to splice out the items you remove, so they are never tested against in the loop.
The break line will make it stop trying to hit other enemies after the bullet is removed. If the line "DocumentClass.enemyList1[i].takeHit();" removes the item from the enemyList1, you need to make sure you use "i--;" at the bottom of the loop as well, if you plan on looping through the remainder of the enemies. "i--" or "break", you will probably need one of them in that loop.
Double check the order in which you are executing your removal methods. Sometimes it's better to flag the items for removal and remove them in a separate loop than to remove an item that may be needed later in the same loop.
for (var i:int = 0; i < DocumentClass.enemyList1.length; i++){
if(DocumentClass.enemyList1[i] && DocumentClass.enemyList1[i].hit){
if (hitTestObject(DocumentClass.enemyList1[i].hit)){
trace("hitEnemy");
removeSelf();
DocumentClass.enemyList1[i].takeHit();
break;
}
}
}
Not the correct solution in this question. You can always do != null in a conditional statement.
if(object!=null){
party.drink();
}
I'm making a game where if the enemy's bullet hits the user, the bullet disappears. Everything works fine except that I keep getting,
'Error #1009: Cannot access a property or method of a null object
reference'
Once the bullet hits the user (bullet disappears though). It confuses me because I've used almost exact the same code in another class where it works perfect.
package classes.enemy
{
import flash.display.MovieClip;
import flash.events.Event;
import flash.display.Stage;
import classes.Main;
public class Bullet extends MovieClip
{
var speed:Number;
public function initialize()
{
var stageReff:Stage = this.stage as Stage;
stage.addEventListener("enterFrame", enterFrame);
}
//code
function enterFrame(e:Event):void
{
this.x += speed;
if(this.hitTestObject(Main.user))
{
removeEventListener("enterFrame", enterFrame);
this.parent.removeChild(this);
// line above gives the error.
}
}
}
}
I have no clue what could be wrong.
Thanks in advance.
My guess is it has to do with the fact that you're adding an enter frame event listener to the stage, yet try to remove it from the listening object itself.
Try changing
var stageReff:Stage = this.stage as Stage;
stage.addEventListener("enterFrame", enterFrame);
to
addEventListener("enterFrame", enterFrame);
you're not removing the event listener from the stage, but the object itself :)
I would not recommend doing it like this, create one listener in your main game class, and call an update function on all objects!
The reason why it fails is because you're removing your Bullet from it's parent. So when you're referencing this.parent it returns null because there simply isn't a parent anymore. You're trying to remove the ENTER_FRAME event but because you set it on the stage the original ENTER_FRAME event keeps running. You can simply fix it like so:
if(this.x > 30)
{
stage.removeEventListener("enterFrame", enterFrame);
this.parent.removeChild(this);
}
But like #RasmusWriedtLarsen already pointed out, it's better to handle these events more globally. And also let the parent handle the removing of the Bullet.