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.
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.
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.
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.
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.
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.
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.
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!
Mike Cluck is a software developer interested in game development. He can be found on Github at MCluck90.
I remember deciding to pursue my first IT certification, the CompTIA A+. I had signed…
Key takeaways The transformer architecture has proved to be revolutionary in outperforming the classical RNN…
Once we learn how to deploy an Ubuntu server, how to manage users, and how…
Key-takeaways: Clean code isn’t just a nice thing to have or a luxury in software projects; it's a necessity. If we…
While developing a web application, or setting dynamic pages and meta tags we need to deal with…
Software architecture is one of the most discussed topics in the software industry today, and…