Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I'm coding a 2D sidescroller using only JavaScript and HTML5 canvas, and in my game I have two problems with shooting:

1) Player shoots continous stream of bullets. I want that player can shoot only a single bullet even though the shoot-button is being held down.

2) Also, I get an error "Uncaught TypeError: Cannot call method 'draw' of undefined" when all the bullets are removed. My shooting code goes like this: When player shoots, I do game.bullets.push(new Bullet(this, this.scale)); and after that:

    function Bullet(source, dir)
    {
        this.id = "bullet";
        this.width = 10;
        this.height = 3;
        this.dir = dir;

        if (this.dir == 1)
        {
            this.x = source.x + source.width - 5;
            this.y = source.y + 16;
        }
        if (this.dir == -1)
        {
            this.x = source.x;
            this.y = source.y + 16;
        }
    }
    Bullet.prototype.update = function()
    {
        if (this.dir == 1) this.x += 8;
        if (this.dir == -1) this.x -= 8;

        for (var i in game.enemies)
        {
            checkCollisions(this, game.enemies[i]);
        }
        // Check if bullet leaves the viewport
        if (this.x < game.viewX * 32 || this.x > (game.viewX + game.tilesX) * 32)
        {
            removeFromList(game.bullets, this);
        }
    }
    Bullet.prototype.draw = function()
    {
        // bullet flipping uses orientation of the player
        var posX = game.player.scale == 1 ? this.x : (this.x + this.width) * -1;
        game.ctx.scale(game.player.scale, 1); 
        game.ctx.drawImage(gameData.getGfx("bullet"), posX, this.y);
    }

I handle removing with this function:

    function removeFromList(list, object)
    {
        for (i in list)
        {
            if (object == list[i])
            {
                list.splice(i, 1);
                break;
            }
        }
    }

And finally, in the main game loop I have this:

for (var i in game.bullets)
{
    game.bullets[i].update();
    game.bullets[i].draw();
}

I have tried adding if (game.bullets.length > 0) to the main game loop before the above draw&update calls, but I still get the same error.

share|improve this question
add comment

3 Answers

up vote 6 down vote accepted

As far as 1) you'll need to use a toggle state. Fire ONLY if the user did not click the previous tick, and is registered as clicking this tick. This can be thought of as the rising edge of the click state.

                 here should the fire logic be called
                 ^
    clicked       ___  
not clicked   ___|

For 2) it seems that your update() causes the item to be removed from the list. The easiest fix is to draw() before you update().


I'm new here so I did not realise I could edit, so here it is:

As for 2 in pseudocode:

Assuming you have two booleans: isFiring, wasFiring update as such:

isFiring = isPlayerFiring(); //whatever method you use if (isFiring && !wasFiring) { //do fire logic //will only be true exactly once each fire press } wasFiring = isFiring;

share|improve this answer
    
Wow, such an easy fix for 2), thank you! And as for 1), to know the states from the previous tick, I need to create my own game timer, am I right? –  fnx Nov 20 '12 at 14:41
    
just keep a boolean somewhere, when the user click, if it's false, then turn it true and shoot, when they release the mouse button you set it to false again. –  Luke B. Nov 20 '12 at 15:44
    
if your API does not support a function that returns true just when the fire button is pressed you can have a look at my other answer with pseudo code. If it does, then it's much better to use it directly –  AlexanderBrevig Nov 20 '12 at 15:47
1  
@AlexanderBrevig why not edit this answer to include both? –  Lunin Nov 20 '12 at 21:26
    
I actually did not know I could edit, it's done now –  AlexanderBrevig Nov 20 '12 at 22:36
add comment

Your problem with removing bullets from the list while iterating the list is a common one. It happens in many languages (not just Javascript).

If you want to remove an object from the list while you're iterating it - don't.

Instead, set a flag (I normally use "dead") to say that this object is "dead". Then have some code which checks the "dead" flags separately.

If it's an indexed container (e.g. Javascript array) you will probably want to iterate backwards through the list, so you can remove elements without the remaining elements changing their index.

So it'll look like this pseudocode:

for i in game.bullets {
  game.bullets[i].update(); // sets dead flag if bullet collides, leaves the screen, etc.
}

for (i=game.bullets.length -1; i >=0; i --) {
  var bullet = game.bullets[i];
  if (bullet.dead) {
    removeFromListByIndex(game.bullets, i);
  }
}
share|improve this answer
add comment

The keypress issue

Apart from solving it manually which is already brilliantly answered, I recommend the jquery.hotkeys library, as it will also help with other game-related keyboard handling.

The undefined issue

There are at least two issues with the last part, the first is the already noted one by AlexanderBrevig that you're removing bullets in update and then trying to call draw() on a removed bullet. The second is that you're using the for...in enumeration construct for iterating an array, which really should only be used to enumerate property names on objects¹.

So use a for-loop instead, like this:

for (var i = 0; i < game.bullets.length; i++)
{
     game.bullets[i].draw();        
     game.bullets[i].update(); // only remove bullets at the end as AlexanderBrevig wrote
}

This will check the bullets.length on each iteration, so if you remove a bullet the length will change and the iteration will stop when needed.

An unexpected problem

However, this still causes problems that might not be easy to notice or even cause any obvious trouble in a game loop: now when a bullet is removed and the array shrinks, the next bullet gets the deleted bullet's index and will never get its draw or update function called during that game tick.

One common solution is to iterate backwards:

for (var i = game.bullets.length-1; i >= 0; i--)
{
     game.bullets[i].draw();        
     game.bullets[i].update();
}

Overdone

For further fun you could make it more robust, like say you've got 5 bullets in the array and then somehow happen to do this somewhere in the code:

game.bullets[325] = myBullet;

...you will end up with a 325 long array with mostly undefined values inbetween the first five and the 325th. So check for that.

for (var i = game.bullets.length-1; i >= 0; i--)
{
     if (game.bullets[i] !== undefined) {
         game.bullets[i].draw();        
         game.bullets[i].update();
     }
}

To go further you could check that the functions are actually functions, check the draw and update call separately to keep doing update before draw and so on.

¹Yes arrays are objects and the indices are property names, but the order of for...in enumeration may change, you might get other object properties apart from your array elements and it's also slower. See this SO question for more information.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.