Object pooling and projectiles for HTML5 Games

The next thing I want to add is the ability for your space ship to shoot. We're not ready for collision detection just yet, but we will need to do some object pooling in order to make sure we don't overwork the garbage collector. When you have a language like JavaScript that manages the memory for you (unlike C++) it needs to periodically free up memory that's not being used by running what is called a garbage collector. If the garbage collector has to remove too many objects at the same time, it can have a serious impact on your frame rate. One of the nice tricks that many game developers use is called object pooling. Instead of using a "new" call to create a new game object, you create a class that manages all object creation. When an object is no longer being used, it becomes available to be recycled by the object pool manager. We're not going to create a dedicated Pool Manager class for this game, but we're going to create an individual pool for the bullets we shoot and for that we're going to use a simple Array.

var bullet_array: Array<cBullet> = new Array<cBullet>();

Defining our bullet class

We also have to define the bullet class. We are going to have it implement iShape, but the draw will need to do a lot more than some of our other shape clases.

class cBullet implements iShape {
 public active: boolean = true;
 public x: number = 0;
 public y: number = 0;
 public lineWidth: number = 5;
 public size: number = 0;
 public color: string = "red";

 public lineWidthAnimVal: number = 0;
 public widthUp: boolean = true;

 public velocity: cVector = new cVector();
 public speed: number = 5;

 public launch = (orientation: cVector): void => {
  this.velocity.copy(orientation);
  this.velocity.multiply(this.speed);
 }

 public draw = (): void => {
  if (this.active == false) {
   return;
  }

  if (this.widthUp == true) {
   this.lineWidthAnimVal += 0.1;

   if (this.lineWidthAnimVal >= 2) {
    this.widthUp = false;
   }
  }
  else {
   this.lineWidthAnimVal -= 0.1;
   if (this.lineWidthAnimVal <= -2) {
    this.widthUp = true;
   }
  }
  this.x += this.velocity.x;
  this.y += this.velocity.y;

  if (this.x < -10 || this.x > 1290 || this.y < -10 || this.y > 730) {
   this.active = false;
  }

  ctx.save();
  ctx.beginPath();
  ctx.strokeStyle = this.color;
  ctx.lineWidth = this.lineWidth + this.lineWidthAnimVal;
  ctx.rect(this.x, this.y, this.size, this.size);
  ctx.stroke();
  ctx.restore();
 }

 public constructor(x: number, y: number, size: number, color: string = "red", lineWidth: number = 5) {
  this.x = x;
  this.y = y;
  this.size = size;
  this.color = color;
  this.lineWidth = lineWidth;
 }
}


One of the keys to pooling the bullets is the "active" attribute. This is a simple true false attribute that will decide if the object will do anything wihin the draw call. When the ship actually fires a bullet, rather than creating a new bullet, we are going to write some source code that loops through the bullet array looking for a bullet that is not active. If we find an inactive bullet we'll activate it and reset the appropriate values. If we can't find an inactive bullet then we will still need to create a new one. When a bulleet runs off the screen, we will deactivate it, putting that bullet back into the pool and available for the next time the player fires a bullet.

function keyboardInput(event: KeyboardEvent) {
 // PRESS LEFT ARROW OR 'A' KEY
 if (event.keyCode == 37 || event.keyCode == 65) {
  space_ship.turnLeft();
 }
 // PRESS UP ARROW OR 'W' KEY
 else if (event.keyCode == 38 || event.keyCode == 87 ) {
  space_ship.accelerate();
 }
 // PRESS RIGHT ARROW OR 'D' KEY
 else if (event.keyCode == 39 || event.keyCode == 68 ) {
  space_ship.turnRight();
 }
 // PRESS DOWN ARROW OR 'S' KEY
 else if (event.keyCode == 40 || event.keyCode == 83 ) {
  space_ship.decelerate();
 }
 // PRESS SPACE BAR
 else if (event.keyCode == 32) {
  var bullet: cBullet;
  for (var i: number = 0; i < bullet_array.length; i++) {
   bullet = bullet_array[i];
   if (bullet.active == false) {
    break;
   }
  }
  if (bullet == null || bullet.active == true) {
   bullet = new cBullet(space_ship.x, space_ship.y, 3);
   bullet_array.push(bullet);
  }
  else {
   bullet.x = space_ship.x;
   bullet.y = space_ship.y;
   bullet.active = true;
  }
  bullet.launch(space_ship.orientation);
 }
}

The above code adds a SPACE BAR keycode check to our keyboardInput function. It loops through the bullet array looking for a bullet where the active flag is set to false. If there is no active bullet, a new bullet will need to be created and added to a bullet array. The bullet will also have to be launched from the space ship in in the direction the space ship is facing. The app is below.

Look at the full source code for this app.

Part 4 - Advanced Keyboard Input

Part 1 - Basic Keyboard Input