Create a game with KineticJS
Canvas has been one of the major breakthroughs of HTML’s evolution. In this tutorial Andrew Croxall demonstrates using it to make a whack-a-mole-style game via KineticJS
Download the source files for this JavaScript tutorial.
Canvas heralded a new dawn for dynamic, scriptable imaging, previously the reserve of Flash or server-side languages. But the excitement can lead to misconceptions among developers new to it, with questions such as “how do I target a shape once I’ve drawn it?” or “how do I do hit-testing?” being common.
The answer to both is: you can’t, not natively at least. Canvas generates a single, static bitmap. Once you draw a shape, it is subsumed into the collective – it is no longer targetable as an individual entity in the way a DOM element is. To simulate effects like these there are workarounds, but they are non-trivial and often require a lot of complex code.
Enter KineticJS
With this in mind it’s a good job we have KineticJS, which trivialises stuff like this. This article is a crash course, and to that end we’ll be making a whack-a-mole-style game. I say ‘style’, because as a vegetarian and animal lover I’ve never ‘got’ why moles should be bopped on the head for surfacing from holes. Our aliens, however, are certified evil. They’ve invaded a hill in Derbyshire and are threatening to destroy all outlets of Prêt. This is war!
KineticJS works by using multiple canvas elements – one per created layer - and stacking them. It excels at making canvas something it’s natively not – an interactive medium. It has a friendly API, and almost everything is done by instantiating methods of the base Kinetic object.
Getting set up
Set up an HTML and a JS file. Link the JS file, and a copy of the latest KineticJS, into your HTML. Grab the source code for the page template, including a sprinkling of CSS; the key part is the following, which constitutes our game container (Kinetic will store its canvases here) and our UI.
<section id='game'></section>
<ul id='ui'>
<li class='disabled' id='score'>score: <span></span></li>
<li class='disabled' id='timer'>time remaining: <span></span></li>
<li class='disabled' id='quit'><a>quit</a></li>
<li id='start'><a>start</a></li>
</ul>
Starting the JavaScript
A quick pointer about the JS before we start: we’ll be drawing the game in shapes: no images. We’ll be setting our dimensions and positions relative to the stage dimensions, so we’ll often be using expressions such as stage_width * .3.
Get top Black Friday deals sent straight to your inbox: Sign up now!
We curate the best offers on creative kit and give our expert recommendations to save you time this Black Friday. Upgrade your setup for less with Creative Bloq.
As we’re dealing with the DOM, make a document ready handler (DRH) for our JS to live in. If you’ve only used DRH’s via jQuery, they natively look like this:
document.addEventListener('DOMContentLoaded', function() {
//all our code here
}, false);
Now some prep:
var game_in_progress = false,
game_el = document.querySelector('#game'),
game_duration = 30,
transition_duration = 0.7,
A tracker var logs whether the game is in progress; we’ll toggle this as the game starts or ends. Next we select the container element and declare some settings. Now, our first dealings with the Kinetic API, to make our stage, and make and add some layers to it.
stage = new Kinetic.Stage({container: game_el, width: 800, height: 500}),
layer_names = ['scene', 'ship_interior', 'ship', 'alien1'],
layers = {};
layer_names.forEach(function(layer_name) {
stage.add(layers[layer_name] = new Kinetic.Layer({id: layer_name}));
});
Your approach to layers will depend on what you’re doing, but one key optimisation is to ensure that any animated elements live on their own layers.
Next, we’ll set up an interval to be used for the timer once the game’s begun. We also log the stage dimensions in local vars, and grab references to our UI and feedback elements – the elements in our ul list.
var timer_interval,
alien_timeouts = [],
stage_width = stage.attrs.width,
stage_height = stage.attrs.height,
ui = {score: null, timer: null, start: null, quit: null};
for (var i in ui) ui[i] = document.querySelector('#'+i);
Finally, we’ll set up a mini extension to Kinetic’s API to enable deep-cloning of layers. This will make more sense later; in short, cloning a layer does not also clone its children (shapes, groups and so on). Since Kinetic utilises JavaScript’s prototypal inheritance, extending it is just a means of declaring methods on one of its constructor prototypes:
Kinetic.Layer.prototype.clone_deep = function(overrides) {
var clone = this.clone(overrides);
this.children.forEach(function(child) { clone.add(child.clone()); });
return clone;
};
Get out the crayons
I’m not going to walk through drawing every shape, since I’d soon run out of article space and things would get repetitive. I’ll get the ball rolling with the initial few shapes, then skip to the aliens. First, the sky – which will encompass the entire canvas.
layers.scene.add(new Kinetic.Rect({
width: stage_width,
height: stage_height,
fill: {
start: {x: 0, y: 0},
end: {x: 0, y: stage_height},
colorStops: [0, '#001', 1, '#445']
}
}));
We instantiate Kinetic’s rectangle constructor and set some predictable properties on it. I omit X and Y position properties, implying that both are 0, and set its dimensions equal to the stage dimensions. I fill it with a gradient from dark to slightly lighter blue: two colour stops, though we could add as many as we liked. We add the whole thing to the scene layer via the generic add() method.
Let’s add some stars. Hell, this could be a veritable romantic evening if it wasn’t for this invasion lark. We’ll add a number of stars equal to the width of the stage and we’ll randomise their positions and radius:
for (var i=0; i<stage_width; i++) {
var y_pos = Math.floor(Math.random() * (stage_height * 0.6))
To create each star we’ll use Kinetic’s circle constructor.
layers.scene.add(new Kinetic.Circle({
x: Math.floor(Math.random() * stage_width),
y: y_pos,
radius: Math.floor(Math. random() * 10) / 8,
fill: "#eee",
opacity: 1 - (y_pos / (stage_height * 0.6))
}));
}
Note the lower the star sits in the sky, the lower opacity we give it, producing a nice effect.
Men from Mars
Having introduced basic drawing, I’m now going to skip straight to creating the aliens. The code behind drawing the rest of the scene and ship is in your downloadable files.
We’ll create one alien, then clone him to conjure the other three into existence. First up, the head, via Kinetic’s ellipse constructor. We’ll fill it with a purple gradient.
var head, ear, eye;
layers.alien1.add(head = new Kinetic.Ellipse({
radius: {x: stage_width * 0.05, y: stage_height * 0.1},
fill: {
start: {x: 0, y: -(stage_height * 0.1)},
end: {x: 0, y: stage_height * 0.1},
colorStops: [0, '#9275AF', 1, '#62457F']
}
}));
For the ears, we’ll clone the head (since it’s already the right shape and colour). Any time you clone, you can pass an object of property overrides. In this case, I override the radius (to change the size) and set some X/Y positions – we keep the inherited fill.
layers.alien1.add(ear = head.clone({
radius: {x: head.attrs.radius.x * 0.2, y: head.attrs.radius.y * 0.13},
x: head.attrs.x - head.attrs.radius.x
}));
layers.alien1.add(ear.clone({x: head.attrs.x + head.attrs.radius.x}));
Creating the second ear is even more straightforward: we clone the first one and merely move it to the right side of the head. And for the eyes we’ll again use an ellipse, filling it dark grey. In order to make the second eye … yes, we’ll just clone the first and override its X position. We’ll also give the eyes (as well as the mouth) a white, hazy shadow, to provide our aliens with some mysteriouslooking definition.
layers.alien1.add(eye = new Kinetic.Ellipse({
x: -(stage_width * 0.025),
y: stage_height * 0.025,
fill: '#222',
radius: {x: stage_width * 0.015, y: stage_height * 0.015},
shadow: {color: '#fff', blur: 10}
}));
layers.alien1.add(eye.clone({x: stage_width * 0.025}));
For the mouth we’ll use the line constructor. This takes an array of successive X and Y points, so we specify four array keys – X1, Y1, X2, Y2.
layers.alien1.add(new Kinetic.Line({
points: [-10, stage_height * 0.06, 10, stage_height * 0.06],
stroke: '#222',
strokeWidth: 4,
shadow: {color: '#fff', blur: 10}
}));
Lines don’t have fills – they have strokes, so we set a stroke width (the thickness) and stroke colour.
So there’s our first alien built and ready to wage war on a popular chain of sandwich shops. All right, he doesn’t have a body or legs – but these are advanced beings, unhindered by the trivialities of physiology. But we still have only one. Cue some more cloning: this time an entire layer.
for (var i=0; i<3; i++) {
var cloned_alien = layers.alien1.clone_deep();
layers['alien'+(i+1)] = cloned_alien;
stage.add(cloned_alien);
}
This is where our deep clone extension comes in handy. In a loop, we create the other three aliens by cloning the one we made, and add them to the stage.
Action stations
We need to ready our aliens in their starting positions. One will appear at each of the two ship windows (we’ll position them in the centre of the craft so they’re initially hidden), while the other two will appear from offscreen in the bottom corners. First, the window aliens (aliens 1 and 2):
layers.alien1.setAttrs({x: stage_width * 0.5, y: stage_height * 0.4});
layers.alien2.setAttrs({x: stage_width * 0.5, y: stage_height * 0.4});
And the corner aliens (3 and 4):
layers.alien3.setAttrs({
x: -(stage_width * 0.06), y: stage_height - (stage_height * 0.2)
});
layers.alien4.setAttrs({
x: stage_width + (stage_width * 0.06), y: stage_height - (stage_height * 0.2)
});
While we’re at it, we’ll log the starting X position of each alien, so that, when they need to retreat after appearing, we can easily return them to their start positions. We’ll also log the aliens’ width, since we’ll use this when calculating their new positions when we animate them in.
for (var i=0; i<4; i++)
layers['alien'+(i+1)].start_x = layers['alien'+(i+1)].attrs.x;
var alien_width = head.attrs.radius.x;
Shuffle and draw
Two quick things: first, to rejig the stack order of some of our layers. By default, the stack order is dictated by the order in which the layers are created. We could have created our layers in precisely the stack order that we wanted them; but as much as anything, in a more dynamic situation this is not always feasible, and in any case I prefer to create layers more liberally and then shuffle things around later. Kinetic makes this process trivial thanks to its move[...]() methods.
stage.get('#disc')[0].moveToTop();
stage.get('#hull')[0].moveToTop();
layers.alien1.moveDown();
layers.alien2.moveDown();
This code also demonstrates Kinetic’s ID/name system. Anything you create, layers, shapes or whatever, can have an id (unique) or name (reusable) property. You can then quickly retrieve items via Kinetic’s get() method, passing it a string – #item to get an item by ID, or .items to get items by name.
The second thing we do is to draw the layers. This tells Kinetic to actually create and output our shapes to the page (until this point, they have existed only as stored instructions).
or (var i in layers) layers[i].draw();
Starting the game
Nice picture, but we don’t have a game yet – time to add some interactivity. First we’ll add some handlers for the start/quit links, references to which we grabbed earlier on in our ui object.
ui.start.addEventListener('click', function() {
First we check the click didn’t come while the game was already in progress, by checking our game_in_progress var. If it didn’t, we alter the variable to true.
if (game_in_progress) return false;
game_in_progress = true;
Next we update the UI. We disable the start link and enable everything else. In effect, our UI visibly wakes up. We set the timer to the number of seconds stored in our game_duration var, and set the score to 0.
for (var i in ui) ui[i].className = i != 'start' ? '' : 'disabled';
ui.timer.querySelector('span').textContent = game_duration;
ui.score.querySelector('span').textContent = 0;
Let’s start the clock, by decrementing the number in our timer element once per second. When it reaches 0, the game is over and we call our quit function.
timer_interval = setInterval(function() {
ui.timer. querySelector('span').textContent--;
if (ui.timer.querySelector('span').textContent === '0') quit_game();
}, 1000);
We now need to set the aliens on course for their first appearance, after a random number of seconds between 0 and 5. We’ll define the actual function that handles this, alien_appearance(), shortly.
for (var i=0; i<4; i++)
setTimeout((function(i) {
return function() { alien_appearance(i+1); };
})(i), Math.floor(Math.random() * 6) * 1000);
}, false);
Finally, let’s listen for hits to the aliens, via mouse clicks or, on mobile devices, screen taps, and increment our score each time.
for (var i=0; i<4; i++)
layers['alien'+(i+1)].on('click tap', function(){
ui.score.querySelector('span').textContent++;
});
I’m going to skip the game quit/end function, so for that refer to the downloadable source code. Let’s get on with the more interesting business of animating the aliens.
Animating the aliens
Our alien appearance/retreat function is called initially by our start function, and then, once the alien has moved into and then out of view, by itself, after a random period of time (0-5 seconds), such that it’s self-perpetuating. The function works for a single alien at a time, denoted by the number from 1 to 4 that is passed to its alien_id argument.
function alien_appearance(alien_id) {
if (!game_in_progress) return;
var layer = layers['alien'+alien_id];
After establishing the layer we’re dealing with, let’s work out the alien’s new X position. Instead of him always coming into full view, we’ll throw in a random modifier, so he may sometimes appear only partially, making things trickier.
var modifier = Math.floor(Math.random() * alien_width);
var new_x = (function() {
switch (alien_id) {
case 1: return (stage_width * 0.35) + modifier;
case 2: return (stage_width * 0.65) - modifier;
case 3: return alien_width - modifier;
case 4: return (stage_width - alien_width) + modifier;
}
})();
We determine the alien’s new position via a switch statement, as it depends on which alien we’re dealing with.
Next comes the actual animation, via Kinetic’s transitionTo() method. This takes a configuration object denoting the new X coordinate, the transition duration, any easing effect and a callback – all of which will be very familiar if you’re used to jQuery’s animate(). We set the transition duration in a var, transition_duration, in the prep section.
layer.transitionTo({
x: new_x,
duration: transition_duration,
easing: 'ease-in-out',
callback: function() {
setTimeout(animate_out, Math.floor(Math.random() * 1000)); }
});
Once our alien animated in, we’ll set him to retreat again after 0-1 seconds. This is handled by the animate_out function, which we’ll define now. The alien will retreat to his starting X position – we purposely logged this (as a property of the layer) when we drew each alien, for this reason.
function animate_out() {
layer.transitionTo({
x: layer.start_coords.x,
duration: transition_duration,
easing: 'ease-in-out',
callback: function() {
setTimeout(function() {
alien_appearance(alien_id);
}, Math.floor(Math.random() * 6) * 1000);
}
});
}
As you can see, this is just a reverse transition, again utilising the transitionTo() method. We have therefore set up a self-perpetuating system in which each alien appears and retreats. On appearance, a timeout is set for the alien to retreat in the next 0-1 seconds, and on retreat, a timeout is set for it to reappear at some time in the next 0-5 seconds. As our alien_appearance() function opens with a check to ensure the game is in progress, we can be sure that, if they retreat and the game is over, they will not reappear thereafter.
Conclusion
OK, this might not get me a job at Valve just yet. There are whistles and bells we could add: alien bodies, a sound on fire, a facial expression when the aliens are hit and so on. But all these things are easy to add once you get Kinetic’s fundamentals, and these are what I’ve showcased in this tutorial. So go forth and create!
Thanks to Eric Rowell for his peer review of this tutorial
Thank you for reading 5 articles this month* Join now for unlimited access
Enjoy your first month for just £1 / $1 / €1
*Read 5 free articles per month without a subscription
Join now for unlimited access
Try first month for just £1 / $1 / €1
The Creative Bloq team is made up of a group of design fans, and has changed and evolved since Creative Bloq began back in 2012. The current website team consists of eight full-time members of staff: Editor Georgia Coggan, Deputy Editor Rosie Hilder, Ecommerce Editor Beren Neale, Senior News Editor Daniel Piper, Editor, Digital Art and 3D Ian Dean, Tech Reviews Editor Erlingur Einarsson and Ecommerce Writer Beth Nicholls and Staff Writer Natalie Fear, as well as a roster of freelancers from around the world. The 3D World and ImagineFX magazine teams also pitch in, ensuring that content from 3D World and ImagineFX is represented on Creative Bloq.