Frame-Engine - Créer un premier jeu-vidéo

Installation et introduction du Frame-Engine

Aymeric Dourthe

Frame-Engine est un moteur permettant le développement d'applications et de jeux vidéo. Voici un tutoriel pour expliquer son architecture, comment l'installer et comment créer un premier jeu avec cette bibliothèque.

Commentez

- Titre : Frame-Engine - Créer un premier jeu-vidéo

- Auteur : Aymeric Dourthe

- Parution : 3 mars 2013

- Mise à jour : 3 mars 2013

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Frame-Engine. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Préliminaires

- II-A. Mode de rendu

- II-B. JavaScript

- II-C. Installation du moteur

- III. Concept de base

- III-A. Engine

- III-B. Area

- III-C. Drawable

- III-D. Entity

- III-E. Fonctionnement du Frame-Engine

- IV. Environnement de base

- V. Le jeu

- V-A. Surface de jeu

- V-B. Le joueur

- V-C. Les graines

- V-D. Finalisation

- V-E. Sauvegarde du meilleur score

- VI. Conclusion

- VII. Remerciements

I - Introduction

Frame-Engine est un moteur permettant le développement d'applications et de jeux vidéos. Il a pour avantage de fournir une solution de développement unique avec l'ensemble des plates-formes existantes compatibles HTML5.

Dans ce tutoriel, je vais vous expliquer les différents éléments permettant de réaliser un petit jeu vidéo de A à Z en quelques minutes. Vous pouvez déjà avoir un aperçu du petit jeu que nous allons réaliser à cette adresse :

http://www.frame-engine.com/hamtaro/ ou directement ici.

Vous pouvez télécharger le code source ici, ou alors essayer l'application finale en l'installant sur vos mobiles via l'App Store, Google Play, ou simplement sur votre navigateur favori.

II - Préliminaires

Avant d'attaquer la phase de conception et de développement de notre jeu, il est important d'avoir quelques notions préliminaires. Celles-ci vous permettront de mieux appréhender la philosophie du moteur.

II-A - Mode de rendu

Frame-Engine est construit autour d'un système de rendu par image, il faut donc raisonner en terme de rafraîchissement et de boucle synchrone. Toutes les x FPS, votre application va être rafraîchie, une image va être calculée par le moteur puis affichée. C'est pourquoi il est important de connaître comment le moteur va afficher vos éléments afin de pouvoir jouer sur les effets de profondeur, de vitesse de déplacement et d'interaction entre éléments. À cela viennent s'ajouter des appels asynchrones qui sont les appels clavier, souris, réseau… Il faut prendre en compte que si vous modifiez une propriété interne à un élément lors d'un appel asynchrone vous n'avez aucun moyen de savoir comment votre modification va affecter le moteur et les éléments.

II-B - JavaScript

Comme le moteur fonctionne avec JavaScript, je vous recommande d'avoir un minimum de connaissance dans ce langage. De plus, nous utilisons la bibliothèque Base.js pour l'implémentation de l'héritage. Cette bibliothèque est simple à prendre en main mais comme pour JavaScript, je vous recommande de vous renseigner sur son utilisation.

II-C - Installation du moteur

Afin d'utiliser le moteur dans vos applications vous devez inclure la bibliothèque frame-engine.js dans votre page.

Pour l'utiliser vous devez copier le fichier dans le répertoire de votre application puis inclure la balise suivante dans le <head> de votre fichier HTML.

<script type="text/javascript" src="frame-engine.0.1.2b.js" ></script>

Une fois la bibliothèque ajoutée dans l'entête de votre fichier HTML, il ne reste plus qu'à créer une instance du moteur avec pour paramètre l'id de votre canvas.

<script type="text/javascript">
window.onload = function(){
new frameEngine("canvas");
}
</script>

Vous pouvez directement utiliser le paquetage prêt à l'utilisation disponible sur la page de téléchargement du site.

III - Concept de base

Voici un diagramme présentant les classes actuellement disponibles dans la bibliothèque. Certaines classes présentent un lien d'héritage. Les méthodes et variables membres ne sont pas présentes sur ce diagramme pour essayer de le garder aussi clair que possible. Il est important de prendre en compte que la plupart des méthodes sont statiques afin d'alléger les objets instanciés par le moteur JavaScript.

On peut constater que le moteur (Engine) ne peut contenir qu'une et une seule Area à un instant t. Tandis qu'une Area peut contenir plusieurs Drawable. Tous les Drawables héritent de BaseElement qui apporte la logique d'affichage (calcul des positions, des limites d'affichage, des événements…).

Le paquetage Entity qui dérive directement du paquetage DrawableObjects apporte une notion supplémentaire d'entité, tel qu'un personnage ou un objet réagissant dans le jeu.

Le paquetage Containers vous apporte des classes facilitant la gestion et le stockage de vos données.

Le paquetage Useful fournit un ensemble de classes utiles dans le domaine du jeu vidéo. On y trouve des timers afin de réaliser des actions à un moment précis, un système de particule ou encore des méthodes pour jouer une musique de fond ou des effets sonores. Une classe de filtre d'image est aussi disponible, elle vous permet d'appliquer des effets et correction (gamma, saturation…) sur vos images ou l'écran de manière simplifiée. La classe Input quant à elle vous donne un accès au clavier et à la souris.

Un paquetage Network est fourni à l'état expérimental afin de faciliter les communications entre le client et le serveur mais aussi entre plusieurs clients via la classe Room.

Le paquetage UI est aussi fourni à titre expérimental afin de faciliter l'interaction avec le joueur dans la saisie de données et la validation d'action.

III-A - Engine

Engine est la classe de base du moteur. C'est elle qui contient tout le processus d'affichage et de gestion des événements. Le plus important pour vous est de savoir initialiser le moteur et de charger une zone (méthode init du moteur et setCurrentArea. Néanmoins le moteur offre d'autres fonctionnalités, comme l'affichage d'information pendant quelques secondes (méthode displayInfo), ou encore l'affichage de flash lumineux (méthode flash).

III-B - Area

Cette classe définit des zones à dessiner. Elle contient des éléments qui sont tous issus du même parent BaseElement.

BaseElement fournit un grand nombre de fonctions pour modifier vos éléments. La classe Area organise les éléments en pseudo couches, pour donner une impression de profondeur, cela ressemble fortement au z-index du CSS.

III-C - Drawable

Les drawables sont les éléments qui peuvent être affichés à l'écran, par exemple des images, des formes géométriques…

Il y a 7 drawables principaux :

- Drawable : pour afficher une image ;

- AnimatedDrawable : pour afficher un sprite (image animée) ;

- ColorDrawable : pour dessiner une forme géométrique ;

- TouchDrawable : pour dessiner des brosses aux événements click/touch;

- VideoDrawable : pour afficher une vidéo dans votre application ;

- UiDrawable : pour faciliter la création d'élément d'interface utilisateur ;

- BufferedDrawable : permet la manipulation des pixels de l'image.

III-D - Entity

Les entités sont les formes évoluées des drawables. Elles offrent des fonctionnalités supplémentaires telles que le déplacement par point de contrôle ou encore le déplacement simple de façon lissée. Nous offrons aussi une entité respectant des règles de physique simulées basiques. Vous pouvez en trouver un exemple sur le site http://www.frame-engine.com/fr/showcase.

Nous utilisons généralement ces entités pour des ennemis, le joueur ou encore des éléments interactifs.

III-E - Fonctionnement du Frame-Engine

Frame-Engine utilise une boucle de rendu comme on en trouve dans les jeux vidéos. Le moteur se synchronise avec le rafraîchissement de votre navigateur si cela est possible, ensuite à chaque rafraîchissement (de 30 à 180 fois par secondes) une image est calculée et affichée. Il est important de connaître l'ordre de rendu des éléments dans le moteur afin de comprendre comment va être construite l'image à afficher. Certaines fonctions sont automatiquement appelées par le moteur. Celles-ci vous permettent de réaliser des modifications s'appliquant aux éléments.

Voici un schéma expliquant la boucle de rendu du moteur :

Les fonctions beforeRender et afterRender vous permettent de réaliser des tests sur l'ensemble de votre Area actuellement affichée par le moteur, tandis que les fonctions beforeProcess et afterProcess permettent de réaliser des modifications sur les éléments et entre éléments. Par exemple, il est recommandé de tester si votre ennemi touche un personnage dans une de ces deux fonctions.

Maintenant que vous avez les bases, nous allons passer à la création d'un jeu basique.

IV - Environnement de base

L'environnement de base d'un projet Frame-Engine est en règle générale constitué d'une page HTML avec un canvas, puis de fichiers JavaScript et médias.

Nous nommerons ici notre fichier de base index.html ce qui permet d'être compatible avec les conteneurs mobiles fournis sur le site.

Notre fichier index.html contient un canvas et les liens vers les fichiers JavaScript :

<!DOCTYPE html>
<html>
<head>
<link href="../engine/frame.0.1.css" type="text/css" rel="stylesheet" media="screen" />
<script type="text/javascript" src="frame-engine.0.1.js ></script>
<script type="text/javascript" src="entryPoint.js"></script>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
</html>

À cela nous ajoutons l'instanciation du moteur dans l'entête :

<script type="text/javascript">
window.onload = function(){
new frameEngine("canvas");
}
</script>

Et enfin, un point important est le redimensionnement matériel du jeu vidéo :

<style type="text/css">
html, body, canvas{
width:100%;
height:100%;
margin:0;
padding:0;
}
</style>

Le fichier principal de notre application s'appelle entryPoint.js. Il contient les éléments d'initialisation et ici, comme l'application est simple, on l'utilisera pour l'ensemble du jeu.

L'initialisation la plus simple est la suivante :

function setup(){
Engine.init();
Engine.setAlwaysRefresh(true);
Engine.setCurrentArea(area);
}

La fonction setup est une fonction réservée à l'initialisation du moteur.

À ce niveau, nous avons tout le nécessaire pour commencer le développement de notre jeu.

V - Le jeu

Le jeu développé ici est très simple mais permet de présenter les concepts principaux du moteur.

Le personnage principal sera représenté par Hamtaro qui aura pour objectif d'attraper toutes les graines tombant du ciel. Hamtaro sera animé par la technique des sprites (une image contenant plusieurs images formant une ou plusieurs animations).

V-A - Surface de jeu

Tout d'abord nous allons limiter la surface de jeu du moteur à 1000 pixels de large par 722 pixels de haut, ce qui correspond à l'image de fond que nous allons utiliser.

Pour donner une dimension au moteur, on modifie l'appel à la méthode d'initialisation (init) :

Engine.init(false,1000,722);

Le premier paramètre est l'activation du mode debug, le second la largeur de la surface de rendu et le dernier la hauteur de la surface de rendu.

Tous les paramètres de la méthode init sont optionnels, les valeurs par défaut sont visibles dans la documentation.

Hors de toute fonction, on définit une Area qui sera la zone affichée par le moteur :

var area = new Area();

Puis dans le setup, on associe cette area comme élément de départ du jeu :

Engine.setCurrentArea(area);

Chaque area possède 3 méthodes que vous pouvez redéfinir : init, beforeRender, afterRender.

Nous allons utiliser la méthode init pour ajouter des éléments à dessiner. Nous ajoutons un Drawable au deuxième niveau de profondeur pour afficher une image de fond. Le constructeur prend en paramètre la position en x, en y et le chemin vers l'image.

area.init = function(){
this.addDrawable(2, new Drawable(0,0,"images/background.jpg"));
}

V-B - Le joueur

Nous allons maintenant créer le personnage (Hamtaro dans l'exemple). Pour ce faire, nous étendons la classe Entity :

var Player = Entity.extend({
});

La classe Entity étend de la classe AnimatedDrawable, ce qui permet d'afficher des animations comme on en trouve dans les gifs animés.

Notre personnage peut se déplacer de façon latérale et sauter, je rajoute donc deux variables contenant les valeurs de déplacement et surcharge la fonction move.

Nous utilisons trois séquences d'animation, la première est l'état à l'arrêt, la seconde le mouvement vers la droite, et la dernière le mouvement vers la gauche.

Pour réaliser le saut du personnage, nous utilisons un système de timer, ainsi, un seul appui sur la touche espace réalise un saut complet. Pour éviter que le personnage ne saute plusieurs fois en même temps on utilise la variable state présent dans tout élément et on définit un état statique :

Player.JUMP = 1;

//Création de la classe Player qui hérite de Entity
var Player = Entity.extend({

offsetX : 0, //Variable member statique
offsetY : 0, //Variable member statique
//Surcharge de la fonction move de la clase Entity
move : function(){
if (Input.keysDown[KEY_LEFT]){        //Si on appuie sur la touche flèche gauche
if (this.offsetX != -10)
AnimatedDrawable.playSequence(player, 2);    //On lance la 3eme animation (à partir de 0) présente dans le sprite
this.offsetX = -10;                //On change la valeur de l'offset
}
if (Input.keysDown[KEY_RIGHT]){            //Si on appuie sur la touché flèche droite
if (this.offsetX != 10)
AnimatedDrawable.playSequence(player, 1);    //On lance la deuxième animation présente dans le sprite
this.offsetX = 10;
}
//Si on appuie sur aucune touche ou que l'on atteint les bords de l'écran
if ((!Input.keysDown[KEY_RIGHT]  && !Input.keysDown[KEY_LEFT]) || ( this.rightBound > Engine.width && this.offsetX == 10) || ( this.x < 0 && this.offsetX == -10)){
this.offsetX = 0;                    //On remet l'offset à 0
AnimatedDrawable.playSequence(player, 0);    //On lance la première séquence animée
}
//Si on appuie sur la touche espace et que l'on est pas déjà en train de sauter
if (Input.keysDown[KEY_SPACE] && this.state != Player.JUMP){
this.offsetY = -20;        // On change l'offset en y pour faire monter le personnage
this.state = Player.JUMP;    // On change l'état de l'instance
BaseElement.timeOut(this, 250, function(){     // On programme un appel ultérieur provoquant la descente du personnage
this.offsetY = 20;
//On programme un appel ultérieur pour remettre les variables dans leur état initial
BaseElement.timeOut(this, 250, function(){
if (this.y != 550) this.setPosY(550);    // On utilise toujours setPosX/Y pour modifier les coordonnées
this.offsetY = 0;
this.state = 0;
});
});
}
this.base(this.offsetX,this.offsetY,20);    // On appelle la function move de la classe parente
}
});

Pour connaître l'état des touches, on utilise le membre statique keysDown de la classe Input.

La méthode move est appelée via la méthode beforeProcess que nous surchargeons :

beforeProcess : function(){
this.move();
}

La mauvaise idée pour le déplacement aurait été d'utiliser les événements keyDown et keyUp. En effet, ces événements sont déclenchés lors de l'appui sur une touche. Or lorsque vous appuyez longtemps sur une touche du clavier l'événement est déclenché plusieurs fois, mais ce nombre de fois dépend de votre matériel. Par conséquent, le mouvement n'aurait pas été fluide et différent selon les machines.

Il ne reste plus qu'à instancier une version du joueur dans l'initialisation de l'area :

player = new Player(300,550,"images/player.png", 167,156,100);

On définit les séquences animées :

AnimatedDrawable.setSequences(player, [[0,0],[0,7],[8,15]]);

On définit la séquence à jouer à l'état initial :

AnimatedDrawable.playSequence(player, 0);
//On ajoute l'instance à l'area
this.addDrawable(1, player);

Nous ajoutons aussi les séquences de mouvements et définissons la séquence initiale.

V-C - Les graines

Notre personnage va manger des graines qui tomberont et lui fera ainsi gagner des points. Nous créons donc une classe étendant Drawable dans laquelle la méthode beforeProcess fera les actions suivantes :

- si le joueur attrape la graine le score augmente et la graine disparaît ;

- si la graine touche le sol, le jeu est fini et on affiche une image de fin ;

- la graine descend de haut en bas.

En bonus on joue un son lorsque le joueur attrape la graine, la méthode pour jouer un son ne prend pas d'extension dans le chemin du fichier. Le moteur va sélectionner le bon format de fichier selon le navigateur utilisé, c'est pourquoi il est nécessaire d'avoir les fichiers sons au format mp3 et ogg.

Dans l'initialisation de l'area nous ajoutons :

scoreText = new TextElement(20,20, "0", color(0,0,0));    //Création d'un élément texte pour afficher le score
this.addDrawable(1, scoreText);    //Ajout de cet élément dans l'area

//Création de la classe Seed héritant de la classe Drawable
var Seed = Drawable.extend({
//Surcharge de la method beforeProcess pour effectuer une action avant l'affichage par le moteur
beforeProcess : function(){
if (this.hit(player)){    //On test la collision entre la graine et le joueur
this.remove();        //Si il y a bien une collision on supprime cette instance de graine
Sound.play("sounds/eat");    //On joue un son
score += 10;        //On incrémente le score
scoreText.setText(score.toString());     //On met a jour l'élément textuel
}
if (!end)    //Si le jeu n'est pas terminé
BaseElement.advanceBy(this,0,10,50);    //On fais descendre la graine de 10 pixels sur 50ms
if (this.bottomBound > 722){    //Si la graine touche le sol le jeu est fini
end = true;
area.addDrawable(0, new Drawable((Engine.width * 0.5) - (236 * 0.5), (Engine.height * 0.5) - (59 * 0.5), "images/loose.png"));    //On ajoute dynamiquement l'image indiquant la fin du jeu
this.remove();
AnimatedDrawable.playSequence(player,0);
}
}

});

V-D - Finalisation

Il ne reste plus qu'à faire tomber les graines sur la surface de jeu toutes les 500ms. On crée un timer :

var timer = new Timer(500);

Puis dans la méthode beforeRender de l'Area on instancie des graines :

area.beforeRender = function(){
if (!end && timer.itsTime()){    //Si le jeu n'est pas fini et que 500ms se sont écoulées
this.addDrawable(2, new Seed(random(0,900),5,"images/sunflower.png"));    //On ajoute une nouvelle graine
}
}

Nous utilisons la variable end pour définir si le jeu est fini ou non.

V-E - Sauvegarde du meilleur score

Afin de sauvegarder localement le meilleur score on utilise le localStorage d'HTML5.

Pour accéder au localStorage, on utilise la variable storage qui a pour avantage d'être liée au système de stockage fourni dans le paquetage Android Frame-Engine. Ainsi, le système de sauvegarde fonctionne sans changement sous Android, ce qui ne serait pas le cas en utilisant directement le localStorage.

La sauvegarde est assez simple, à la fin de la partie, on ajoute une clé contenant la valeur du meilleur score.

Dans la classe Seed, on modifie dans la fonction beforeProcess, la partie terminant le jeu :

if (this.bottomBound > 722){
end = true;
if (storage.getItem('best_score') < score)    //Si le score présent dans le localstorage est inférieur au score de la partie
storage.setItem('best_score',score);    //On met à jour le score dans le localstorage
area.addDrawable(0, new Drawable((Engine.width * 0.5) - (236 * 0.5), (Engine.height * 0.5) - (59 * 0.5), "images/loose.png"));
this.remove();
AnimatedDrawable.playSequence(player,0);
}

Puis pour afficher le meilleur score, on ajoute à l'initialisation de notre Area l'instanciation d'un TextElement :

if (storage.getItem('best_score')){     //Si le score est présent dans le local storage alors on l'affiche
this.addDrawable(1,new TextElement(20,35, "best : " + storage.getItem('best_score'), color(0,0,0)));
}

VI - Conclusion

Comme vous pouvez le voir il n'y a rien de très compliqué dans ce tutoriel. L'idée est de montrer la logique de développement avec Frame-Engine et ainsi vous simplifier la vie.

N'hésitez pas à nous contacter pour obtenir plus d'information ou encore nous proposer vos idées.

Vous trouverez le mini jeu hamtaro à cette adresse :

http://www.frame-engine.com/hamtaro/.

L'archive du code source ici :

http://www.frame-engine.com/hamtaro/hamtaro.zip.

Vous pouvez retrouver la documentation complète de la bibliothèque ici :

http://www.frame-engine.com/docs.

VII - Remerciements

Merci à LittleWhite pour sa relecture technique et pour le temps qu'il a consacré à la mise en page du présent tutoriel. Merci aussi à FirePrawn et ClaudeLELOUP pour leurs relectures orthographiques.