0

I'm sorry if it's asking for too much, but I'm too confused by now. I'm making this really simple shooter game for my nephew in AS3. It all seems to be working just fine, except for one really annoying error that keeps popping up every second or third time the game is launched.

IT is Error #1009: Cannot access a property or method of a null object reference. The problem is always with parent.removeChild(this) command in the relevant class (EnemyClass, BulletClass or MissileClass). This happens in two cases: either when checkFinishConditions method in Main is called and the EnemyClass instance needs to be deleted. So if I get the #1009 error does this mean the instance has already been deleted? The second situation is when inst.hitTestObject(enemyInstance) is checked in Main class. Does this mean the EnemyClass instance has already been deleted somehow? I'm totally lost here to be honest.

private function checkCollision():void 
        {
            //loop through missiles
            for (var i:int = 0; i < aMissileArray.length; i++) {
                //get the current missile
                var currentMissile:missileClass = aMissileArray[i];
                //loop through enemies
                for (var j:int = 0; j < aEnemyArray.length; j++) {
                    var thisEnemy:EnemyClass = aEnemyArray[j];                                      
                    if (currentMissile.hitTestObject(thisEnemy)) {                          
                        var thisExplode:ExplosionClass = new ExplosionClass(thisEnemy.x,thisEnemy.y);                       
                        addChild(thisExplode);
                        currentMissile.destroyThis();
                        aMissileArray.splice(i,1);
                        thisEnemy.deleteEnemy();
                        aEnemyArray.splice(j, 1);                       
                        aDamageArray.splice(j, 1);  
                        scoreValueText += 1;    
                        j--;
                        i--;
                    }
                    //break;
                }
            }
            //loop through bullets
            for (var l:int = 0; l < aBulletArray.length; l++) {
                //get the current missile
                var currentBullet:BulletClass = aBulletArray[l];
                //loop through enemies
                for (var k:int = 0; k < aEnemyArray.length; k++) {
                    var currentEnemy:EnemyClass = aEnemyArray[k];                                       
                    if (currentBullet.hitTestObject(currentEnemy)) {                        
                        currentBullet.destroyThis();
                        aBulletArray.splice(l, 1);                          
                        aDamageArray[k] -= 1;   
                        l--;
                        if (aDamageArray[k] < 1) {              
                            //create an explosion           
                            var thisBulletExplode:ExplosionClass = new ExplosionClass(currentEnemy.x,currentEnemy.y);                       
                            addChild(thisBulletExplode);                    
                            currentEnemy.deleteEnemy();
                            aEnemyArray.splice(k, 1);                           
                            aDamageArray.splice(k, 1);
                            scoreValueText += 1;    
                            k--;

                        }
                        break;
                    }
                }

            }

        }
Alex
  • 944
  • 4
  • 15
  • 28
  • That is quite a lot of code! A hacky way to do fix this might be to replace all instances of `parent.removeChild(this)` with `if (parent) {parent.removeChild(this)}`, which should get rid of the error (although it isn't dealing with the root cause - that your code is causing these objects to be removed from the display list more than once). – xdl Oct 20 '13 at 01:07

2 Answers2

1

There is alot of code to look over, but one potential issue I see is here :

        for each(var currentDieEnemy:EnemyClass in aEnemyArray) {                                                                               
            aEnemyArray.splice(0, 1);
            currentDieEnemy.deleteEnemy();                                  
        }  

The potential issue is that you are 'assuming' that the order of this loop is sequential based on the actual index order of the Array. You might want to stick a trace in there to verify that is actually what is happening, because I believe it's possible that they can be out of order.

See this question for details -- For-Each Loop AS3: Is the direction guaranteed?

So imagine the scenario where the 3rd item in the array is first and you splice the item at the 0 index. Now, you have an item in the array that is removed from the display list, but not from the array. So what happens when you get to that item ? Pretty much what you are describing will happen since the parent property is null, since you already removed it from the display list.

The way to fix that is to do something like this :

while (aEnemyArray.length > 0)
{
   var currentDieEnemy:EnemyClass = aEnemyArray[0];
   currentDieEnemy.deleteEnemy();
   aEnemyArray.splice(0,1);
}

I found another issue in this block of code :

        for (var j:int = 0; j < aEnemyArray.length; j++) {
            var thisEnemy:EnemyClass = aEnemyArray[j];                                      
            if (currentMissile.hitTestObject(thisEnemy)) {                          
                var thisExplode:ExplosionClass = new ExplosionClass(thisEnemy.x,thisEnemy.y);                       
                addChild(thisExplode);
                currentMissile.destroyThis();
                aMissileArray.splice(i,1);
                thisEnemy.deleteEnemy();
                aEnemyArray.splice(j, 1); // now array composition is different.                       
                aDamageArray.splice(j, 1);  
                scoreValueText += 1;                    
            }
        }

When you loop through an Array and splice an item at the loop index, you have to realize what happens to the composition of the array. Here's an example

Suppose enemy3 needs to be spliced because of a collision. Given your code, this is what happens :

Before (j == 2)
(0) enemy1
(1) enemy2
(2) enemy3
(3) enemy4
(4) enemy5

AFTER
(0) enemy1
(1) enemy2
(2) enemy4
(3) enemy5

So when the loop continues, j will now increment and equal 3.

The result is that enemy4 doesn't get evaluated as the composition of the array collapses to fill the hole created by the splice and you didn't adjust your loop index variable.

So, what you can do in that situation is simply decrement the loop index variable at the end of the block like so :

 aEnemyArray.splice(j, 1); // now array composition is different.                       
 aDamageArray.splice(j, 1);  
 scoreValueText += 1;   
 j--;   // decrement loop index variable.

So while this is one solution, the main point to take away here is the change in the composition of your Array after a splice.

Community
  • 1
  • 1
prototypical
  • 6,731
  • 3
  • 24
  • 34
  • thanks, so splice(j,1) command deletes the j'th entry in he array and reduce the array length by 1, is this correct? – Alex Oct 21 '13 at 01:38
  • Yes, and the composition of the array changes. The hole that is created is filled as all items after that move up to fill that hole. – prototypical Oct 21 '13 at 02:24
  • Also, keep in mind that splice doesn't delete those items, but rather returns an Array containing whatever was spliced. That is optional, but it is valid to do this - `var splicedItems:Array = aEnemeyArray.splice(j,1);` which would result in splicedItems containing the item that was removed from the Array. – prototypical Oct 21 '13 at 02:26
  • Thanks. Also, if I understand the logic correctly, while looping through aMisileArray I need to decrease the index too? So essentially when an entry is deleted from the array by splicing I always need to decrease the index by 1! – Alex Oct 21 '13 at 02:43
  • I was just pointing out the conceptual issues, anywhere you are following the same logic, you need to make adjustments accordingly. – prototypical Oct 21 '13 at 03:17
  • Could you please look at the code again? Although it works much better than before, the same error keeps coming up. I've left only the relevant piece. I think I've followed your suggestions quite closely. – Alex Oct 21 '13 at 18:34
  • What works better ? I don't know that these were the only issues, they are just two that I spotted. Your question amounts to "debug my code". I am willing to help, but honestly this is where you need to take some time to develop your debugging skills and troubleshoot. Use traces and the debugger/breakpoints to track down WHY an instance might be null and WHY it's not what you expect it to be. That is what I personally would do. Approaching it from this angle is kind of like programming charades. – prototypical Oct 21 '13 at 19:14
1

Yep, you are splicing your objects from arrays in a weird way. First, you are traversing the array forwards and splicing in the loop. This can lead to issues of several bullets not trigger collisions when they should, or say not move. See, if you are running your loop for bullets at index i, once you call bullets.splice(i,1); the bullet that was to be iterated next becomes at position i, then you increment i and that bullet remains unprocessed.

Next, you have nested loops, and in the inner loop you are removing an object from OUTER loop. This means once you did a removal of the outer loop's object, your inner loop is now invalidated, you have to preemptively terminate inner loop.

        //loop through bullets
        for (var l:int = aBulletArray.length-1; l>=0; l--) {
            // first, traverse both arrays backwards
            //get the current missile
            var currentBullet:BulletClass = aBulletArray[l];
            //loop through enemies
            for (var k:int = aEnemyArray.length-1; k>=0; k--) {
                var currentEnemy:EnemyClass = aEnemyArray[k];                                       
                if (currentBullet.hitTestObject(currentEnemy)) {                        
                    currentBullet.destroyThis();
                    aBulletArray.splice(l, 1);                          
                    aDamageArray[k] -= 1;   
                    if (aDamageArray[k] < 1) {              
                        //create an explosion           
                        var thisExplode:ExplosionClass = new ExplosionClass(currentEnemy.x,currentEnemy.y);                     
                        addChild(thisExplode);                  
                        currentEnemy.deleteEnemy();
                        aEnemyArray.splice(k, 1);                           
                        aDamageArray.splice(k, 1);
                        scoreValueText += 1;                            
                    }
                    // and since we don't have "currentBullet" anymore, do this
                    break;
                }
            }
        }

Fix all the other iterations through your arrays where you do splicing, like I did in this part of code, and you should avoid 1009 errors in these loops.

Also, you should not post your entire project code, but instead post only relevant parts, say an entire function that's reported as throwing an error, and explain which line is producing the error - it's written as number in the error's stack trace.

Vesper
  • 18,599
  • 6
  • 39
  • 61
  • thanks, I followed the tutorial, that's how it handled the array manipulation. I'll have a closer look at the splice command – Alex Oct 21 '13 at 01:47