Le blog d'Arcade Village

Un jeu en javascript 3 : Le moteur

Un peu de théorie personnelle du jeu


Nous avons écrit notre première classe en javascript. Continuons avec un peu de théorie des jeux ! Je me limite aux jeux en 2D. D'abord, parce que tout comme toi, lecteur, je fais des jeux tout seul. Ensuite, parce que le temps passé à modéliser 3D est trop long. Pour mes jeux, j'applique donc un mécanisme vieux comme le monde : Un moteur de jeux et des sprites.
Pour ceux qui ne sont pas à l'aise avec ce dernier terme, le sprite est un élément qui va interagir avec l’environnement du jeu.
- Le personnage principal d'un jeu 2D est un sprite.
- Des personnages non dirigés par le joueur sont des sprites
- Les éléments du décors qui interagissent avec les personnages du jeu sont des sprites.
- Dans les jeux Nitendo, Mario est un sprite.
- Un nuage du ciel est un sprite uniquement s'il est animé et a une « vie ».

Je présente d'abord chacune des classes, puis je vous montre le code et enfin, je commente les parties importantes de celui-ci.

Le moteur de jeu


Un moteur de jeu est relativement simple. Il s'agit d'une boucle qui effectue à l'infini les même processus :

Il effectue des calculs généraux, comme les collisions ou la gestion des événements déclenchés par le jour (clic souris, touches de clavier, joystick...)
Il demande à ses sprites de réfléchir à leurs mouvements.
Il dessine le décors du jeu
Il demande aux sprites de se dessiner.

A partir de ce moteur très simple, j'ai peu créé tous les jeux d'ArcadeVillage.com et je pense pouvoir créer la majorité des jeux en 2D qui existent.

La durée des calculs ou de la phase de dessin varie suivant la complexité du jeu et des autres processus tournant sur le système, aussi un moteur de jeu doit implémenter un mécanisme simple permettant de maintenir de préserver la même vitesse de jeu au cours de la partie.

La classe du moteur de jeu s'appelle CCAnimeEngine. Toutes mes classes commencent par CC parce que ce sont mes initiales.

La classe utilise la notation par prototype.
CCAnimeEngine.functionname = function()
{

}

Place au code !

La classe CCAnimeEngine



// CCAnime Engine ArcadeVillage
function CCAnimeEngine()
{
this.ae_running = false;
this.ae_actid = 0;

this.ae_sprites = [];
this.ae_sprdico = [];

this.ae_wait = 40; // Temps entre deux
this.ae_paint = false;
this.idle = 0;

this.ctx = null;
this.g = null;


this.ae_l1 = Date.now();
this.ae_l2 = Date.now();
this.ae_ld = Date.now();
this.ae_lw = 0;

this.ae_RequestAnimationFrame = this.getRequestAnimationFrame();
this.ae_CancelAnimationFrame = this.getCancelAnimationFrame();

// Variables de travail
this.ae_is = 0;
this.ae_st = null;

this.ae_events = [];
}

//--------------------------------------------
//
// Task function
//
//--------------------------------------------

CCAnimeEngine.prototype.run = function()
{
if ( !this.ae_running ) return;
this.ae_l1 = Date.now();
this.onTip();
if ( !this.ae_paint )
{
if ( window.requestAnimationFrame )
{
requestAnimationFrame (()=>{this.repaint()});
}
else
this.repaint();
}
this.idle++;
this.ae_l2 = Date.now();
this.ae_lw = this.ae_l2 - this.ae_l1;
if ( this.ae_lw > this.ae_wait ) // On a mis plus de temps que demander
this.ae_lw = 1;
else
this.ae_lw = this.ae_wait - this.ae_lw;

setTimeout(()=>{this.run()}, this.ae_lw);
}


CCAnimeEngine.prototype.start = function()
{
if (this.ae_running) return;

this.ae_running = true;
this.ae_current = Date.now();
this.ae_paint = false;
this.run();
};


CCAnimeEngine.prototype.stop = function()
{
if ( this.actid > -1 )
this.ae_CancelAnimationFrame( this.actid );
this.ae_paint = false;

this.ae_running = false;
};


CCAnimeEngine.prototype.isRunning = function()
{
return this.ae_running;
};

CCAnimeEngine.prototype.setWait = function(w)
{
return this.ae_wait = w;
};

CCAnimeEngine.prototype.repaint = function()
{
if ( this.ae_paint ) return;
this.ae_paint = true;
this.drawOff();
this.ae_paint = false;
};

//--------------------------------------------
//
// Managment functions
//
//--------------------------------------------

CCAnimeEngine.prototype.onTip = function()
{
this.onBeforeSprites();
for (this.ae_is = 0 ; this.ae_is < this.ae_sprites.length ; this.ae_is++ )
{
this.ae_st = this.ae_sprites[this.ae_is];
this.ae_st.prepareMove();
this.ae_st.think();
}
for (this.ae_is = 0 ; this.ae_is < this.ae_sprites.length ; this.ae_is++ )
{
this.ae_st = this.ae_sprites[this.ae_is];
this.ae_st.move();
}
};

CCAnimeEngine.prototype.think = function()
{

};

CCAnimeEngine.prototype.onBeforeSprites = function()
{
};

//--------------------------------------------
//
// Paint functions
//
//--------------------------------------------


CCAnimeEngine.prototype.defineCanvas = function(ictx)
{
this.ctx = ictx;
this.g = new CCGraphics(ae.ctx);
}


CCAnimeEngine.prototype.drawBackOff = function()
{

}


CCAnimeEngine.prototype.drawOff = function()
{
this.drawBackOff();
for (this.ae_is = 0 ; this.ae_is < this.ae_sprites.length ; this.ae_is++ )
{
this.ae_st = this.ae_sprites[this.ae_is];
this.ae_st.draw(this.g)
}
};


//--------------------------------------------
//
// System specifics functions
//
//--------------------------------------------


CCAnimeEngine.prototype.getRequestAnimationFrame = function()
{
return window.requestAnimationFrame
|| window.mozRequestAnimationFrame
|| window.webkitRequestAnimationFrame
|| window.msRequestAnimationFrame;
}

CCAnimeEngine.prototype.getCancelAnimationFrame = function()
{
return window.cancelAnimationFrame
|| window.cancelRequestAnimationFrame
|| window.webkitCancelAnimationFrame
|| window.webkitCancelRequestAnimationFrame
|| window.mozCancelAnimationFrame
|| window.mozCancelRequestAnimationFrame
|| window.msCancelAnimationFrame
|| window.msCancelRequestAnimationFrame
|| window.oCancelAnimationFrame
|| window.oCancelRequestAnimationFrame;
}

//--------------------------------------------
//
// Sprites Functions
//
//--------------------------------------------
CCAnimeEngine.prototype.addSprite = function( spr )
{
var i = 0;
if ( this.ae_sprdico[spr.getName()] != null ) // Est-ce une mise à jour
{
while ( i < this.ae_sprites.length )
{
if ( this.ae_sprites[i].getName() == s )
{
this.ae_sprites[i] = spr;
i = this.ae_sprites.length;
}
else
i++;
}
}
else
this.ae_sprites.push( spr );
this.ae_sprdico[spr.getName()] = spr;
}

CCAnimeEngine.prototype.getSprite = function( s )
{
return this.ae_sprdico[s];
}


CCAnimeEngine.prototype.getSprites = function()
{
return this.ae_sprites;
}

CCAnimeEngine.prototype.removeSprite = function( s )
{
var i;
if ( this.ae_sprdico[spr.getName()] != null ) return; // Inconnu
delete this.ae_sprdico[s];
for ( i = 0 ; i < this.ae_sprites.length ; i++ )
{
if ( this.ae_sprites[i].getName() == s )
{
delete this.ae_sprites[i];
return;
}
}
}

CCAnimeEngine.prototype.removeSprites = function()
{
this.ae_sprdico = [];
this.ae_sprites = [];
}

//--------------------------------------------
//
// Events functions
//
//--------------------------------------------

CCAnimeEngine.prototype.clearEvents = function()
{
this.ae_events = [];
}

CCAnimeEngine.prototype.addEvent = function(evt)
{
this.ae_events.push( evt );
}

CCAnimeEngine.prototype.nextEvent = function()
{
return this.ae_events.shift();
}


Quelques commentaires sur CCAnimeEngine


La première chose que vous pourrez noter, est l'utilisation minimes de variables locales dans les fonctions. Cette classe sert de boucle principale au jeu. Or, je vous rappelle que le ramasse-miette du javascript est dégueulasse et qu'il vaut mieux lui éviter trop de travail.

Vous noterez aussi que des commentaires français et anglais sont mélangés. C'est parce que je suis Français.

Ces considérations oiseuses énumérées, nous regarderons la prochaine fois plus en détail le code.
ArcadeVillage.com 1999 - 2024