
Creating a Javascript Canvas Game

Hello! I suggest that you and me create a small casual game for several people at one computer on a Javascript Canvas.
In the article, I step-by-step examined the process of creating such a game using MooTools and LibCanvas , stopping at each small action, explaining the reasons and logic of adding new and refactoring existing code.
ps Unfortunately, Habr cuts off large colored articles somewhere in the sixty thousandth character, because I was forced to remove a couple of sections of code from the article on pastebin. If you want to read the article without running through the links in search of code - you can use the mirror .
rules
We control the player (Player), which must catch the bait (Bait) - and at the same time dodge the appearing "predators" (Barrier).
The goal of the game is to catch the maximum number of bait without touching the predators.
In contact with one of the predators, all of them (predators) disappear, and the points are reset to zero, which, in fact, is equivalent to starting the game from scratch.
HTML file
We create the initial file on which our canvas application will be. I used the files hosted on the libcanvas website, but this is not necessary. JavaScript files were added during the creation of the game, but I do not want to return to this file anymore, therefore I will declare them immediately.
[[ Code ./index.html on pastebin ]]
Create a project
First, create the project itself. We need to do this only when the document is ready - we will use the event provided by mootools "
domready
" We also create a LibCanvas.Canvas2D object that will help us with the animation.
./js/start.js
window.addEvent('domready', function () {
// с помощью Мутулз выберем первый елемент канвас на странице
var elem = $$('canvas')[0];
// На его основе создадим елемент LibCanvas
var libcanvas = new LibCanvas.Canvas2D(elem);
// Перерисовка будет осуществлятся каждый кадр, несмотря на наличие или отсутствие изменений
libcanvas.autoUpdate = true;
// Будем стремится к 60 fps
libcanvas.fps = 60;
// Стартуем наше приложение
libcanvas.start();
});
Add user
Add a new object - Player, which will be controlled by the mouse - its coordinates will be equal to the coordinates of the mouse cursor.
This object will look like a circle of a certain color and size (specified in the property)
[[ Code ./js/Player.js on pastebin ]]
Add to ./js/start.js before
libcanvas.start();
:libcanvas.listenMouse();
var player = new Player().setZIndex(30);
libcanvas.addElement(player);
= Step 1 =
You may notice that the result is not quite the same as we expected, because after each frame the canvas does not automatically clear.
You need to add a cleansing and black fill preprocessor to ./js/start.js
libcanvas.addProcessor('pre',
new LibCanvas.Processors.Clearer('#000')
);
= Step 2 =
Add bait
[[ Code ./js/Bait.js on pastebin ]]
Add to ./js/start.js :
// Возьмем индекс поменьше, чтобы наживка рисовалась под игроком
var bait = new Bait().setZIndex(20);
libcanvas.addElement(bait);
Refactoring - create a parent class
We have very similar Bait and Player classes. Let's create a GameObject class from which they will be inherited from us.
To get started, let's create createPosition from the constructor of the Player class:
./js/Player.js
var Player = new Class({
// ...
initialize : function () {
// ..
this.shape = new LibCanvas.Shapes.Circle({
center : this.createPosition()
// ...
},
createPosition : function () {
return this.libcanvas.mouse.point;
},
Now create a GameObject class
[[ Code ./js/GameObject.js on pastebin ]]
After that, other classes can be lightened :
./js/Bait.js
var Bait = new Class({
Extends : GameObject,
radius : 15,
color : '#f0f'
});
./js/Player.js
var Player = new Class({
Extends : GameObject,
radius : 15,
color : '#080',
createPosition : function () {
return this.libcanvas.mouse.point;
},
draw : function () {
if (this.libcanvas.mouse.inCanvas) {
this.parent();
}
}
});
We look, if nothing broke:
= Step 3 =
Hurrah! Everything works everywhere, and the code has become much easier.
Friends of a bait player
Everything on the screen is moving, but there really is no reaction to our actions.
Let's start by making friends of the bait and the player - when they run into it, the bait should move to another random place.
To do this, create a timeout separate from rendering, which will check for contact.
We write at the end ./js/start.js :
(function(){
bait.isCatched(player);
}.periodical(30));
Now we need to implement the isCatched method in ./js/Bait.js :
isCatched : function (player) {
if (player.shape.intersect(this.shape)) {
this.move();
return true;
}
return false;
},
move : function () {
// перемещаем в случайное место
this.shape.center = this.createPosition();
}
= Step 4 =
Almost perfect, but we see that moving is rude and annoying. It would be better if the bait smoothly ran away.
To do this, you can use one of the LibCanvas behavior. It is enough to add one line and slightly change the move method:
Now we need to implement the isCatched method in ./js/Bait.js :
var Bait = new Class({
Extends : GameObject,
Implements : [LibCanvas.Behaviors.Moveable],
// ...
move : function () {
// быстро (800), но плавно перемещаем в случайное место
this.moveTo(this.createPosition(), 800);
}
});
Very simple, right? And I like the result much more:
= Step 5 =
Add predators
./js/Barrier.js :
var Barrier = new Class({
Extends : GameObject,
full : null,
speed : null,
radius : 8,
color : '#0ff',
initialize : function () {
this.parent();
this.speed = new LibCanvas.Point(
$random(2,5), $random(2,5)
);
// Через раз летим влево, а не вправо
$random(0,1) && (this.speed.x *= -1);
// Через раз летим вверх, а не вниз
$random(0,1) && (this.speed.y *= -1);
},
move : function () {
this.shape.center.move(this.speed);
return this;
},
intersect : function (player) {
return (player.shape.intersect(this.shape));
}
});
Also, we will slightly modify ./js/start.js so that predators appear when fishing for bait:
bait.isCatched(player);
// меняем на
if (bait.isCatched(player)) {
player.createBarrier();
}
player.checkBarriers();
We implement the addition of barriers for the player, ./js/Player.js and move them all every check:
barriers : [],
createBarrier : function () {
var barrier = new Barrier().setZIndex(10);
this.barriers.push(barrier);
// Надо не забыть добавить его в наш объект libcanvas, чтобы хищник рендерился
this.libcanvas.addElement(barrier);
return barrier;
},
checkBarriers : function () {
for (var i = this.barriers.length; i--;) {
if (this.barriers[i].move().intersect(this)) {
this.die();
return true;
}
}
return false;
},
die : function () { },;
= Step 6 =
Great, there was movement in the game. But we see three problems:
1. Predators fly away from the playing field - you need to do a “fight off the walls”.
2. Sometimes the bait manages to catch twice before it flies away - you need to make a short timeout of "invulnerability".
3. The death case is not processed.
Predators fight off walls, bait gets a short time of “invulnerability”
Realizing wall strikes is easy. Slightly change the move method of the Barrier class in the file ./js/Barrier.js :
[[ Barrier.move code on pastebin ]]
Fixing the bait problem is also not very difficult - we make changes to the Bait class in the ./js/Bait.js file
[[ Bait.makeInvulnerable code on pastebin ]]
= Step 7 =
We realize death and scoring
Because points - this is how many times the bait is caught and it is equal to the number of predators on the screen - it’s very easy to make points counting: Let's
slightly extend the draw method in the Player class, the file ./js/Player.js :
draw : function () {
// ...
this.libcanvas.ctx.text({
text : 'Score : ' + this.barriers.length,
to : [20, 10, 200, 40],
color : this.color
});
},
// Т.к. очки - это всего-лишь количество хищников - при смерти достаточно удалить всех хищников
die : function () {
for (var i = this.barriers.length; i--;) {
this.libcanvas.rmElement(this.barriers[i]);
}
this.barriers = [];
}
Single player game is over!
= Step 8 - Single Player =
We realize a multiplayer game on one computer
Keyboard movement
To begin with - we will transfer control from the mouse to the keyboard. In ./js/start.js we change
libcanvas.listenMouse()
to libcanvas.listenKeyboard()
In it we add to the timeout
player.checkMovement();
. In ./js/Player.js we remove the override
createPosition
, in the method we draw
remove the mouse check and implement the movement using the arrows:speed : 8,
checkMovement : function () {
var pos = this.shape.center;
if (this.libcanvas.getKey('left')) pos.x -= this.speed;
if (this.libcanvas.getKey('right')) pos.x += this.speed;
if (this.libcanvas.getKey('up')) pos.y -= this.speed;
if (this.libcanvas.getKey('down')) pos.y += this.speed;
},
= Step 9 =
An unpleasant nuance - the player crawls behind the screen and there it can get lost.
Let's restrict its movement and refactor the code a little, taking the key state to a separate method
isMoveTo : function (dir) {
return this.libcanvas.getKey(dir);
},
checkMovement : function () {
var pos = this.shape.center;
var full = this.getFull();
if (this.isMoveTo('left') && pos.x > 0 ) pos.x -= this.speed;
if (this.isMoveTo('right') && pos.x < full.width ) pos.x += this.speed;
if (this.isMoveTo('up') && pos.y > 0 ) pos.y -= this.speed;
if (this.isMoveTo('down') && pos.y < full.height) pos.y += this.speed;
},
We’ll also slightly modify the isMoveTo method - so that you can easily change the keys to control the player:
control : {
up : 'up',
down : 'down',
left : 'left',
right : 'right'
},
isMoveTo : function (dir) {
return this.libcanvas.getKey(this.control[dir]);
},
= Step 10 =
Enter the second player
Modify the file ./js/start.js :
var player = new Player().setZIndex(30);
libcanvas.addElement(player);
// =>
var players = [];
(2).times(function (i) {
var player = new Player().setZIndex(30 + i);
libcanvas.addElement(player);
players.push(player);
});
// Меняем стиль и управление второго игрока
players[1].color = '#ff0';
players[1].control = {
up : 'w',
down : 's',
left : 'a',
right : 'd'
};
Wrap the contents of the timer in
players.each(function (player) { /* * */ });
= Step 11 =
It remains to make small amendments:
1. Move the second player’s score lower than the first player’s.
2. Color the predators of different players in different colors.
3. For the sake of statistics, enter the “Record” - what is the maximum score by which player was achieved.
Make the appropriate changes to ./js/Player.js :
var Player = new Class({
// ...
// Красим хищников в соответствующий игроку цвет:
createBarrier : function () {
// ...
barrier.color = this.barrierColor || this.color;
// ...
},
// Реализуем подсчет максимального рекорда
maxScore : 0,
die : function () {
this.maxScore = Math.max(this.maxScore, this.barriers.length);
// ...
},
index : 0,
draw : function () {
this.parent();
this.libcanvas.ctx.text({
// Выводим максимальный рекорд:
text : 'Score : ' + this.barriers.length + ' (' + this.maxScore + ')',
// Смещаем очки игрока на 20 пикселей вниз зависимо от его индекса:
to : [20, 10 + 20*this.index, 200, 40],
color : this.color
});
}
});
We make corrections in ./js/start.js :
(2).times(function (i) {
var player = new Player().setZIndex(30 + i);
player.index = i;
// ...
});
players[0].color = '#09f';
players[0].barrierColor = '#069';
// Меняем стиль и управление второго игрока
players[1].color = '#ff0';
players[1].barrierColor = '#960';
players[1].control = {
up : 'w',
down : 's',
left : 'a',
right : 'd'
};
Congratulations, the game is done!
= Step 12 - play for two =
Add a third and fourth player
If you wish, it’s very simple to add a third and fourth player:
players[2].color = '#f30';
players[2].barrierColor = '#900';
players[2].control = {
up : 'i',
down : 'k',
left : 'j',
right : 'l'
};
// players[0] uses numpad
// players[3] uses home end delete & pagedown
players[3].color = '#3f0';
players[3].barrierColor = '#090';
players[3].control = {
up : '$',
down : '#',
left : 'delete',
right : '"'
};