Arcade Village Blog

A javascript game 3 : The engine

A little personal game theory


We wrote our first class in javascript. Let's continue with a little game theory! I limit myself to 2D games. First, because just like you, reader, I play games on my own. Second, because the time spent modeling 3D is too long. For my games, I therefore apply a mechanism as old as the world: A game engine and sprites.
For those who are not comfortable with the latter term, the sprite is an element that will interact with the game environment.
- The main character of a 2D game is a sprite.
- Non-player-led characters are sprites
- The elements of the scenery that interact with the characters in the game are sprites.
- In Nitendo games, Mario is a sprite.
- A sky cloud is a sprite only if it is animated and has a "life".

I introduce each of the classes first, then I show you the code and finally I comment on the important parts of it.

The game engine


A game engine is relatively simple. This is a loop that performs the same processes over and over again:

It performs general calculations, such as collisions or the management of events triggered by the day (mouse click, keyboard keys, joystick, etc.)
He asks his sprites to think about their movements.
He designs the scenery for the game
He asks the sprites to draw themselves.

From this very simple engine, I have not created all the games at ArcadeVillage.com and I think I can create most of the 2D games that exist.

The duration of the calculations or the drawing phase varies depending on the complexity of the game and other processes running on the system, so a game engine must implement a simple mechanism to maintain the same game speed during the game. .

The game engine class is called CCAnimeEngine. All my classes start with CC because those are my initials.

The class uses prototype notation.
CCAnimeEngine.functionname = function ()
{
...
}

Make way for the code!

The CCAnimeEngine class


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();
}

Some comments on CCAnimeEngine


The first thing you will notice is the minimal use of local variables in functions. This class serves as the main loop for the game. However, I remind you that the javascript garbage collector is disgusting and that it is better to save it too much work.

You will also notice that French and English comments are mixed. It is because I am French.

With these idle considerations enumerated, we will look at the code in more detail next time.