📜 ⬆️ ⬇️

Creating a Javascript Canvas Game


Hello! I invite you to create with me a small casual game for several people with a single Javascript Canvas computer.
In the article, I step by step dismantled the process of creating such a game with the help of MooTools and LibCanvas , dwelling on every small action, explaining the reasons and logic for adding new and refactoring existing code.


ps Unfortunately, Habr cuts off large colored articles somewhere on the sixty thousandth character, because I was forced to take a couple of sections of code from the article on pastebin. If you want to read the article, not running on the links in search of the code - you can use a mirror .

rules


We control the player (Player), who must catch the bait (Bait) - and at the same time dodge the emerging "predators" (Barrier).
The goal of the game is to catch the maximum number of baits without contacting predators.
In contact with one of the predators, they (the predators) all disappear, and the points are reset, which, in fact, is equivalent to starting the game from scratch.
')

Html file


We create the initial file, which will be our canvas application. I used the files hosted on the libcanvas website, but this is optional. JavaScript files were added during the game creation process, but I don’t want to go back to this file anymore, so I’ll announce them immediately.

[[ Code ./index.html on pastebin ]]



Create a project


To begin with we will create the project. We need to do this only when the document is ready - we will use the " domready " event provided by domready .
We will 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 =



It can be noted that the result is not exactly what we expected, because after each frame the canvas is not automatically cleared.
You need to add a black flush and black preprocessor to ./js/start.js

libcanvas . addProcessor ( 'pre' ,
new
LibCanvas . Processors . Clearer ( '#000' )
);

= Step 2 =



Add the bait


[[ Code ./js/Bait.js on pastebin ]]



Add to ./js/start.js :
// ,
var bait = new Bait (). setZIndex ( 20 );
libcanvas . addElement ( bait );


Refactoring - create parent class


We have very similar classes Bait and Player. Let's create a class GameObject from which they will inherit from us.

First, 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 class GameObject

[[ Code ./js/GameObject.js on pastebin ]]



After that, you can ease the other classes:

./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, nothing broke:

= Step 3 =



Hooray! Everything works everywhere, and the code has become much easier.

Friends of the baited player


Everything is moving on the screen, but there is really no reaction to our actions.
Let's start by making friends with the bait and the player - when running onto it, the bait must move to another random place.
To do this, we create a separate timeout from the rendering, which will check the contact.

We write to 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 fine, but we see that moving is rude and annoying. It would be better if the bait ran smoothly.
To do this, you can use one of the Libkanvas behavior. Just 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 a lot 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 ));
}
});


We will also slightly modify ./js/start.js so that predators will 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 each 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 over the playing field - you need to make a "beat off the walls."
2. Sometimes the bait has time to grab twice while flying away - you need to make a short timeout of "invulnerability."
3. The case of death is not processed.

Predators beat off the walls, the bait gets a little time "invulnerability"


Implement beat off the walls easier than ever. Slightly change the move method of class Barrier, in the file ./js/Barrier.js :

[[ Barrier.move code on pastebin ]]



Correcting 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 are how many times the bait is caught and it is equal to the number of predators on the screen - it is very easy to score points:
Let's slightly expand the draw method in the Player class, the ./js/Player.js file:
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 - over!

= Step 8 - Single player =



We realize a multiplayer game with a single computer


Keyboard Motion


To begin with, we will transfer control from the mouse to the keyboard. In ./js/start.js change libcanvas.listenMouse() to libcanvas.listenKeyboard()
In it we add player.checkMovement(); to the timeout player.checkMovement(); .
In ./js/Player.js, we delete the createPosition override, in the draw method, we 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 =



Unpleasant nuance - the player crawls over the screen and there may get lost.
Let's restrict its movement and a little refactor the code, putting out the receipt of the key state in 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 will also slightly change 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 ./js/start.js file:

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'
};


The contents of the timer are wrapped in players . each (function ( player ) { /* * */ }); players . each (function ( player ) { /* * */ });

= Step 11 =



It remains to make minor corrections:
1. Move the second player's account below the first player's account.
2. Color the predators of different players in different colors.
3. For the sake of statistics, enter "Record" - what is the maximum score by which player was reached.

We make the appropriate changes in ./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
});
}
});


Making adjustments to ./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 - a game for two =



Add the third and fourth player


If desired, it is very easy 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 : '"'
};

= Step 13 - game for four =

Source: https://habr.com/ru/post/102010/


All Articles