Removing multiple children from the stage if the item is on stage - actionscript-3

Good afternoon.
I'm making a game, and in games as you know, when you destroy an object, you must remove it from the stage.
My enemies have been added dynamically, via code and if the user goes back the enemy would still be in the display list.
I have attempted to remove enemies by trying this code.
removeChild(character); /removes player
removeChild(ground); // removes ground
childrenOnStage is a number which equals this.numChildren
for (var b:int = 0; b < childrenOnStage; b++)
{
if (getChildAt(b).name == "enemy")
{
removeChild(getChildAt(b));
}
}
When the user goes back to the main menu from the game, it runs this code.
The code loops though the all of the children on the stage, and those that have the name enemy should be removed.
But I get an error
[Fault] exception, information=RangeError: Error #2006: The supplied index is out of bounds.
My question is, how can I remove these enemies?
What if the enemy has been removed, this will cause more errors such as "null object" i.e enemy is not on stage, so why should I remove this enemy if it's not on?
Thank you.
Update due to Chernivs answer
//after adding all of the children, this must be updated last
childrenOnStage = this.numChildren;
private function fromLevtoStart(e:MouseEvent):void
{
if (e.target == backBtn1)
{
stage.removeEventListener(Event.ENTER_FRAME, level1)
stage.addEventListener(Event.ENTER_FRAME, mainGameLoop)
//container.removeChild(_character);
removeChild(character);
removeChild(ground);
for (var b:int = 0; b < childrenOnStage; b++)
{
if (getChildAt(b).name == "enemy")
{
removeChild(getChildAt(b));
//childrenOnStage --;
//update the variable below
childrenOnStage = this.numChildren;
}
}
this.gotoAndStop("Start");
}
}
This still doesn't remove all of the enemies, but only one for some reason.
it should loop through all of the children on stage that have the name enemy and remove them, but it doesn't.
After trying the asnwer below I can say that it only removes goblin1 which is named "enemy" and typ Goblin.
goblin1 = new Goblin();
goblin1.name = "enemy";
goblin2 = new Goblin();
goblin2.name = "enemy";
When I kill goblin1 it gets removed, then when I go back to the start screen goblin 2 doesn't even though that's the only goblin left with the name "enemy" it appears only goblin1 is targetted for some reason.

Instead of checking the name property, check the type of the display object. For example:
if(getChildAt(b) is Goblin) {
//remove
}

Basically, decreasing the variable should work. But if you get any problems with what you've removed, you can do this:
var enemies:Array = new Array();
for (var b:int = 0; b < childrenOnStage; b++) {
var child:DisplayObject = getChildAt(b);
if (child.name == "enemy") {
enemies.push(child);
}
}
trace(enemies);
See what you get. Loop through enemies and remove each of them - this way you won't need to decrease any variable nor think about numChildren - simply remove everything that is 'marked' as an enemy.

Related

How to hitTest same Objects in one Array?

I want to create a stacking Game. Where when you tap the screen for instance a block falls down and a new one appears where the other one originally was. Now when the User taps the screen again the same block falls down and if aligned correctly stacks on top of the first one so one and so one. Keep stacking until you miss.
I thought creating an array and pushing each new object to that array would be able to hitTest between each new one etc and have them stack on each other. I realized I don't quite understand how to go about doing this. New instances are created so I got that down. Here is my code so far:
private function engineLogic(e:Event):void
{
stackingHandler();
}
private function stackingHandler():void
{
for (var i:int = 0; i < aCatArray.length; i++)
{
var currentCat:mcCats = aCatArray[i];
//HIT TEST CATS
}
trace("NUMBER OF CATS: " + aCatArray.length);
}
private function onTap(e:MouseEvent):void
{
//Move Down
TweenLite.to(cats, 1.0, {y:(stage.stageHeight / 2) + 290, onComplete: addCats});
}
private function addCats():void
{
//Create Instance
cats = new mcCats();
//Add Objects
addChild(cats);
//Push to Array
aCatArray.push(cats);
}
I would appreciate any help from you guys. Maybe if you can push me in the right direction. Thank you in advance!
It looks like the cats variable holds the object that is currently falling?
In that case you'd do something like this:
private function stackingHandler():void
{
for (var i:int = 0; i < aCatArray.length; i++)
{
if(cats.hitTestObject(aCatArray[i])) {
// collision detected!
// kill the Tween
// set the y position of the `cats` object
// so it appears on top of the object it collided with (`aCatArray[i]`)
// (it may have moved slightly past the object before doing this check)
}
}
}
So you're looping through the array and hit testing cats against every object in the array one at a time.
It might make more sense to use a basic gravity simulation, or just linearly increasing the y value instead of using a Tween, but you didn't ask about that.
You might also want to set a flag for whether or not an object is currently falling and use that to determine whether or not to run the stackingHandler. Otherwise, you'll just be continually hit testing all the objects when nothing is moving.
This is how I was able to fix it. Creating a double for loop. Checking if they are equal to each other continue and check for hitTest:
private function stackingHandler():void
{
for (var i:int = 0; i < aCatArray.length; i++)
{
var currentCat:mcCats = aCatArray[i];
for (var j:int = 0; j < aCatArray.length; j++)
{
var newCat:mcCats = aCatArray[j];
if (currentCat == newCat) continue;
//Hit Test between Objects
if (newCat.hitTestObject(currentCat.mcHit) && newCat.bFlag == false)
{
//Stop Moving
newCat.stopMoving();
trace("HIT");
if (highScore == 0)
{
addCats();
trace("ADD CATS 1");
}else
{
TweenLite.delayedCall(0.6, addCats);
trace("ADD CATS 2");
}
//Add Points
highScore ++;
trace(highScore + " Score");
//Set Flag boolean
newCat.bFlag = true
}
}
}
}

AS3-Having a mouse click affect object in an array's timeline

I'm going crazy with this. I have a few bears on the stage and have listed them in an array. I want them to change their animation frame when clicked as long as they are not on the "down" animation frame to begin with. Below is the code. The problem occurs below the //check if they get hit line.
//put the bears in an array
var bearsArray: Array = new Array();
for (var i=0; i<numChildren;i++) {
if (getChildAt (i) is bear_mc) {
bearsArray.push(getChildAt(i));
}
}
//move them up and down
addEventListener(Event.ENTER_FRAME,upAndDown);
function upAndDown(event:Event){
if (Math.random() < 0.02){
var randomBear = Math.floor(Math.random() * 9);
bearsArray[randomBear].gotoAndPlay("popup");
}
}
//check if they get hit
for (var j:int = 0; j < bearsArray.length; j++){
bearsArray[j].addEventListener(MouseEvent.CLICK, hitBears);
}
function hitBears(e:MouseEvent){
for (var k: int=0; k<numChildren; k++){
if (bearsArray[k].currentFrame != "down"){
trace("clicked");
bearsArray[k].gotoAndPlay("surprised");
}
}
}
currentFrame returns an integer, not string. If you want to use string, you have to use currentFrameLabel or currentLabel.
EDIT: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/MovieClip.html#currentLabel
If you want to control only one bear, remember that you don't need to cycle through them (actually that would serve no purpose as you would be checking every one of them). The better (and correct) approach would be this:
function hitBears(e:MouseEvent){
var bear:MovieClip = e.currentTarget as MovieClip;
if(bear.currentLabel != "down") { //or currentFrameLabel, depends how your mc is set up
trace("clicked");
bear.gotoAndPlay("surprised");
}
}
I got it working by checking a hittest with the mouse instead of just checking the click. That works for some reason.

How to check if objects position is greater than an array of objects AS3

Hey everyone so I'm trying to figure out how to check if one movie Clips x position which is called ball is greater than an array of objects x position which is called car. So I have an array setup with the car the car's main timeline consist of 4 frames that holds the same movie Clip just different sizes on each frame to make the game harder.
So what I want to do is check whenever the ball has jumped over the current car then add plus one to the score. But I am having trouble trying to figure this out. Here is what I have so far:
In my Enter Frame Game Loop I have the Function checkAddPoints:
private function gameLoop(e:Event):void
{
checkBunnyHitObstacles();
checkAddPoints();
}
Then The function:
private function checkAddPoints():void
{
for (var i:int = 0; i < aCarArray.length; i++)
{
var currentCar:car = aCarArray[i];
if (ball.x > currentCar.x)
{
nScore ++;
updateCurrentScore();
}
}
}
So I am getting the current car in the loop and trying to check if the ball has jumped over it. I thought the most logical thing to do was to check if the ball x position was greater than the car x position then it would work. But it just adds a endless amount of numbers and not just one like I want.
Does anyone know what i need to do?
UPDATE: This seems to work haven't had any errors yet
if (currentcar.x - 15 < ball.x && currentCar.x + 15 > ball.x)
{
trace("AddPoint Success");
nScore++;
updateCurrentScore();
}
Storing flag aboud award status in the car should help in your task. If you add flag to the car, let's say car.isAwarded, by default isAwarded is false, you will be able award only once for every car:
private function checkAddPoints():void {
var i:uint, len:uint = aCarArray.length, currentCar:car;
for (i; i < len; ++i) {
currentCar = aCarArray[i];
//Award only once
if (!currentCar.isAwarded && ball.x > currentCar.x) {
nScore++;
currentCar.isAwarded = true;
updateCurrentScore();
}
}
}

How can I work this out?

I am making a game where insects from above the screen move downwards to the bottom. The object is for the player to kill these insects with his/her mouse. When killed the insect should show a kill frame, where I have put in. The kill frame will stay there for 3 seconds and the object will be removed. This will also increase the player's score.
This code is written inside the insect:
function kill(event:MouseEvent):void
{
this.dead = true;
}
This code is written inside the background movieclip frame.
function moveEnemies():void
{
var tempEnemy:MovieClip;
for (var i:int =enemies.length-1; i>=0; i--)
{
tempEnemy = enemies[i];
if (tempEnemy.dead)
{
tempEnemy.gotoAndStop(21);
var myTimer:Timer = new Timer(3000);
myTimer.addEventListener(TimerEvent.TIMER, timerListener);
myTimer.start();
}
}
}
function timerListener (e:TimerEvent):void
{
for (var i:int =enemies.length-1; i>=0; i--)
{
if (tempEnemy.dead)
{
score++;
roachLevel.score_txt.text = String(score);
removeEnemy(i);
}
}
}
function removeEnemy(id:int)
{
removeChild(enemies[id]);
enemies.splice(id,1);
}
The problem that I am experiencing with this, is that whenever I click on the insects they stay there. I click on another one it stays there. Then another, then after a while they disappear and the score increases. Sometimes the death frame doesn't appear and they die as soon as I touch them. Could you please tell me how I can solve this?
You have to relocate score adding to enemy class, and assign a timeout to the particular squished enemy instead of the base class. The best place to do it is in kill() function.
function kill(e:MouseEvent):void {
this.dead=true;
gotoAndStop(21);
flash.utils.setTimeout(removeSelf,3000);
}
function removeSelf():void {
this.parent.removeChild(this);
}
Now, adjust the mechanics for scoring and tracking objects. First, if an object becomes dead, you immediately remove it from the array of enemies (it will handle its removal on its own) and give score for it. And second, all the other functional should be preserved.
for (var i:int=enemies.length-1;i>=0;i--) {
var tempEnemy=enemies[i];
if (tempEnemy.dead) {
score++;
enemies.splice(i,1);
} else {
tempEnemy.y++; // or other move function
}
}
This loop should be in an enterframe listener, so it'll be called once per frame. Also combining splicing and moving like here eliminates the need of placing movement code elsewhere, as you iterate through the array anyway.

Error #1010 after splicing from array

I have an issue here. Every five seconds the spawnEnemies is fired. It creates a movieclip, sets it position and adds it to the stage and an array. In the function checkBullet I check if the bullet hits one of the enemies. If it does, the bullet and the enemy are removed from the stage, and the enemy is spliced from the array. But an error is thrown once I hit an enemy:
TypeError: Error #1010: A term is undefined and has no properties.
If I comment out the arrayEnemies.splice(i,1) line, it works fine but then it isn't removed from the array. This is in itself not an issue, but naturally I don't like to keep unnecessary data in my array. Any help on this?
function checkBullet(event:Event) {
if(stage.contains(mcBullet)) {
for(var i:int = 0; i < arrayEnemies.length; i++) {
if(arrayEnemies[i].hitTestPoint(mcBullet.x, mcBullet.y, true)) {
stage.removeChild(mcBullet);
this.removeChild(arrayEnemies[i]);
arrayEnemies.splice(i,1);
bulletOnStage = false;
}
}
if(mcBullet.x > 800) {
stage.removeChild(mcBullet);
bulletOnStage = false;
}
}
}
function spawnEnemies(event:TimerEvent) {
var enemie:MovieClip = new box_mc();
enemie.name = "mcBox" + event.currentTarget.currentCount;
enemie.x = 850;
enemie.y = Math.floor(Math.random()*(1+155)+255);
addChild(enemie);
arrayEnemies.push(enemie);
}
function moveEnemies(event:Event) {
for(var i:int = 0; i < arrayEnemies.length; i++) {
arrayEnemies[i].x -= 5;
}
}
This will be caused by working on an array that you are interating through, you should hold a ref to the stuff you want to remove then do it after the loop.
Your problem is that if the bullet hits two enemies, you try to remove it from the stage twice. This will throw an ArgumentError.
If you need to test against all enemies (assuming multiple enemies can be hit by the same bullet), you also need to decrement i when you remove an element from your enemy array.
function checkBullet(event:Event) {
if(stage.contains(mcBullet)) {
if(mcBullet.x > 800) {
stage.removeChild(mcBullet);
bulletOnStage = false;
}
for(var i:int = 0; i < arrayEnemies.length; i++) {
if(arrayEnemies[i].hitTestPoint(mcBullet.x, mcBullet.y, true)) {
if(stage.contains(mcBullet)){
stage.removeChild(mcBullet);
}
this.removeChild(arrayEnemies[i]);
arrayEnemies.splice(i,1);
bulletOnStage = false;
i--;
}
}
}
}
Bit of an older question but thought I'd throw in my answer too for anyone that might end up here.
Like Neil said, editing an array that you're itterating through (in this case arrayEnemies) can cause concurrent update problems.
My prefered solution is to use a seperate toRemove array and remove them after the update, for example:
var enemies:Array();
//Lets assume at some point this is populated with Enemy objects
function update():void
{
var toRemove:Array = new Array();
//May want to keep and clear an array instead to avoid creating a new one each update
foreach(var enemy:Enemy in enemies)
{
enemy.update();
if(enemy.markedForRemoval())
toRemove.push(enemy);
}
foreach(var enemy:Enemy in toRemove)
enemies.splice(enemies.indexOf(enemy), 1);
}