Making a Space Invaders Game

Mike Cluck

December 04th, 2015

In just 6 quick steps, we're going to make our own Space Invaders game using Psykick2D. All of the code for this can be found here.

What is Psykick2D?

Psykick2D is a 2D game engine built with [Pixi.js] and designed with modularity in mind. Game objects are entities which are made up of components, systems contain and act on entities, and layers run systems.

Getting Started

After you download the latest Psykick2D build create an HTML page referencing Psykick2D. Your page should look something like this. Psykick2D will be looking for a container with a psykick id to place the game in.

Now create a main.js and initialize the game world.

var World = Psykick2D.World;

World.init({
  width: 400,
  height: 500,
  backgroundColor: '#000'
});

Reload the page and you should see something like this. Blank screens aren't very exciting so let's add some sprites.

Drawing Sprites

You can find the graphics used here (special thanks to Jacob Zinman-Jeanes for providing these). Before we can use them though, we need to preload them. Add a preload option to the world initialization like so:

World.init({
  ...
  preload: [
    'sprites/player.json',
    'sprites/enemy1.json',
    'sprites/enemy2.json',
    'sprites/enemy3.json',
    'sprites/enemy4.json'
  ]
});

Accessing sprites is as easy as referencing their frame name (given in the .json files) in a sprite component. To make an animated player, we just have to assemble the right parts in an entity. (I suggest placing these in a factory.js file)

var Sprite = Psykick2D.Components.GFX.Sprite,
    Animation = Psykick2D.Components.GFX.Animation;

function createPlayer(x, y) {
  var player = World.createEntity(), // Generate a new entity
      sprite = new Sprite({
        frameName: 'player-1',
        x: x,
        y: y,
        width: 64,
        height: 29,
        pivot: { // The original image is sideways
          x: 64,
          y: 29
        },
        rotation: (270 * Math.PI) / 180 // 270 degrees in radians
      }),
      animation = new Animation({
        maxFrames: 3, // zero-indexed
        frames: [
          'player-1',
          'player-2',
          'player-3',
          'player-4'
        ]
      });

  player.addComponent(sprite);
  player.addComponent(animation);
  return player;
}

Entities are comprised of components. All an entity needs is the right components and they'll work with any system. Because of thise, creating enemies looks almost exactly the same just using the enemy sprites. Once those components are attached, we just add the entities to a system then the system to a layer. The rest is taken care of automatically.

var mainLayer = World.createLayer(),
    spriteSystem = new Psykick2D.Systems.Render.Sprite(),
    animationSystem = new Psykick2D.Systems.Behavior.Animate();

var player = createPlayer(210, 430);
spriteSystem.addEntity(player);
animationSystem.addEntity(player);

mainLayer.addSystem(spriteSystem);
mainLayer.addSystem(animationSystem);

World.pushLayer(mainLayer);

If you repeat the process for the enemies then you'll end up with a result like what you see here. With your ship on screen, let's add some controls.

source

Ship Controls

To control the ship, we'll want to extend the BehaviorSystem to update the player's position on every update. This is made easier using the Keyboard module.

var BehaviorSystem = Psykick2D.BehaviorSystem,
    Keyboard = Psykick2D.Input.Keyboard,
    Keys = Psykick2D.Keys,
    SPEED = 100;

var PlayerControl = function() {
  this.player = null;
  BehaviorSystem.call(this);
};

Psykick2D.Helper.inherit(PlayerControl, BehaviorSystem);

PlayerControl.prototype.update = function(delta) {
  var velocity = 0;
  // Give smooth movement by using the change in time
  if (Keyboard.isKeyDown(Keys.Left)) {
    velocity = -SPEED * delta;
  } else if (Keyboard.isKeyDown(Keys.Right)) {
    velocity = SPEED * delta;
  }

  var player = this.player.getComponent('Sprite');
  player.x += velocity;

  // Don't leave the screen
  if (player.x < 15) {
    player.x = 15;
  } else if (player.x > 340) {
    player.x = 340;
  }
};

Since there's only one player we'll just set them directly instead of using the addEntity method.

// main.js
...
var controls = new PlayerControl();
controls.player = player;

mainLayer.addSystem(controls);
...

Now that the player can move we should level the playing field a little bit and give the enemies some brains.

source

Enemy AI

In the original Space Invaders, the group of aliens would move left to right and then move closer to the player whenever they hit the edge. Since systems only accept entities with the right components, let's tag the enemies as enemies.

function createEnemy(x, y) {
  var enemy = World.createEntity();
  ...
  enemy.addComponentAs(true, 'Enemy');
  return enemy;
}

Creating the enemy AI itself is pretty easy.

var EnemyAI = function() {
  BehaviorSystem.call(this);
  this.requiredComponents = ['Enemy'];
  this.speed = 30;
  this.direction = 1;
};

Psykick2D.Helper.inherit(EnemyAI, BehaviorSystem);

EnemyAI.prototype.update = function(delta) {
  var minX = 1000,
      maxX = -1000,
      velocity = this.speed * this.direction * delta;
  for (var i = 0; i < this.actionOrder.length; i++) {
    var enemy = this.actionOrder[i].getComponent('Sprite');
    enemy.x += velocity;

    // Prevent it from going outside the bounds
    if (enemy.x < 15) {
      enemy.x = 15;
    } else if (enemy.x > 340) {
      enemy.x = 340;
    }

    // Track the min/max
    minX = Math.min(minX, enemy.x);
    maxX = Math.max(maxX, enemy.x);
  }

  // If they hit the boundary
  if (minX <= 15 || maxX >= 340) {
    // Flip around and speed up
    this.direction = this.direction * -1;
    this.speed += 1;

    // Move the row down
    for (var i = 0; i < this.actionOrder.length; i++) {
      var enemy = this.actionOrder[i].getComponent('Sprite');
      enemy.y += enemy.height / 2;
    }
  }
};

Like before, we just add the correct entities to the system and add the system to the layer.

var enemyAI = new EnemyAI();
enemyAI.addEntity(enemy1);
enemyAI.addEntity(enemy2);
...

mainLayer.addSystem(enemyAI);

Incoming! Aliens are now raining down from the sky. We need a way to stop these invaders from space.

source

Set phasers to kill

To start shooting alien scum, add the bullet sprite to the preload list

preload: [
  ...
  'sprites/bullet.json'
]

then generate a bullet just like you did the player. Since the original only let one bullet exist on screen at once, we're going to do the same. So in your PlayerControl system give it a new property for bullet and we'll add some shooting ability.

var PlayerControl = function() {
  BehaviorSystem.call(this);
  this.player = null;
  this.bullet = null;
};

...

PlayerControl.prototype.update = function(delta) {
  ...

  var bullet = this.bullet.getComponent('Sprite');
  // If the bullet is off-screen and the player pressed the spacebar
  if (bullet.y <= -bullet.height && Keyboard.isKeyDown(Keys.Space)) {
    // Fire!
    bullet.y = player.x - 18;
    bullet.y = player.y;
  } else if (bullet.y > -bullet.height) {
    // Move the bullet up
    bullet.y -= 250 * delta;
  }
};

Now we just need to draw the bullet and attach it to the PlayerControl system.

var bullet = createBullet(0, -100);
spriteSystem.addEntity(bullet);
controls.bullet = bullet;

And just like that you've got yourself a working gun. But you can't quite destroy those aliens yet. We need a way of making the bullet collide with the aliens.

source

Final Step

Psykick2D has a couple of different collision structures built in. For our purposes we're going to use a grid. But in order to keep everything in sync, we want a dedicated physics system. So we're going to give our sprite components new properties, newX and newY, and set the new position there. Example:

player.newX += velocity;

To create a physics system, simply extend the BehaviorSystem and give it a collision structure.

var Physics = function() {
  BehaviorSystem.call(this);
  this.requiredComponents = ['Sprite'];
  this.grid = new Psykick2D.DataStructures.CollisionGrid({
    width: 400,
    height: 500,
    cellSize: 100,
    componentType: 'Sprite' // Do all collision checks using the sprite
  });
};

Psykick2D.Helper.inherit(Physics, BehaviorSystem);

There's a little bit of work involved so you can view the full source here. What's important is that we check what kind of entity we're colliding with (entity.hasComponent('Bullet')) then we can destroy it by removing it from the layer.

Here's the final product of all of your hard work. A fully functional space invaders-like game! Psykick2D has a lot more built in. Go ahead and really polish it up!

final source

About the Author

Mike Cluck is a software developer interested in game development. He can be found on Github at MCluck90.

Get your free eBook today

Continue on your journey as a game developer by deciding which game engine and language suits you best with the help of our free eBook.