Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox

Fiche pratique : la librairie Flixel

Compatible ActionScript 3. Cliquer pour en savoir plus sur les compatibilités.Par Monsieur Spi, le 14 avril 2013

Flixel, site officiel : http://flixel.org/

Flixel est une librairie gratuite à l'attention des développeurs de jeux AS3. Elle permet de créer des jeux “old school”, à base de tuiles ou non, et offre une grande gamme d'outils pratiques pour vous faciliter la tâche et dont voici une liste non exhaustive :

  • gestion des maps
  • découpe des feuilles de sprites
  • animation des sprites
  • gestion des collisions
  • gestion des commandes
  • scrolling
  • déplacements des objets
  • gestion de l'affichage
  • gestion des textes

J'ai pensé qu'une petite fiche pratique sur la prise en main de cette librairie et son utilisation sur un petit exemple de jeu serait utile, même si, et nous en parlerons en conclusion, je ne suis pas encore tout à fait persuadé de l'utilité d'un tel moteur selon le type de jeu que l'on souhaite faire.

Etape 1 - Installation et premiers pas

Mauvaise nouvelle pour tous ceux qui ont l'habitude de travailler avec l'IDE de Flash, Flixel est prévu pour fonctionner avec Flash Develop ou Flash Builder, mais pas avec l'interface du logiciel Flash. Il va donc falloir abandonner les vieilles habitudes et travailler en POO avec un éditeur externe. Rassurez-vous ce n'est pas beaucoup plus compliqué pour autant. Cette première étape s'adresse à ceux qui n'ont jamais utilisé un éditeur externe et un compilateur adéquat, elle traitera également de l'import de la librairie dans votre projet. Pour tous ceux qui sont à l'aise avec ça, passez directement à l'étape 2 pour voir comment démarrer avec Flixel.

Installation

Flixel est donc prévu pour fonctionner sans Flash (IDE), pour ce petit exercice, qui sera donc orienté POO, j'ai choisi Flash Develop (FD) avec lequel je suis plus à l'aise.

  • Commencez donc par télécharger FD (ici : http://www.flashdevelop.org/ ), et installez-le.
  • Créez un nouveau dossier “monProjet” sur votre ordinateur puis ouvrez FD.
  • Créez un nouveau projet et choisissez “AS3 Project” (nous compilerons directement avec FD).
  • Donnez un nom à votre projet et indiquez le dossier que vous avez créé pour votre projet.
  • Validez en appuyant sur “OK”.

De base Flash Develop à créé 3 dossiers dans votre projet, pour faire simple :

  • bin : contient le résultat du projet
  • lib : contient les librairies utiles au projet
  • src : contient les classes de votre programme

Bien à présent il nous faut un moyen de compiler notre programme (en faire un SWF si vous préférez), FD n'est qu'un éditeur ce n'est pas lui qui compile, pour cela on va utiliser le compilateur proposé par AIR 3.5.

Pour commencer FD à besoin de quelques informations, allez dans “Project/Properties…”

Dans l'onglet “Output”, choisissez la version du player dans laquelle vous voulez compiler le jeu, ainsi que la taille de la scène, sa couleur de fond et son framerate, exactement de la même manière que vous l'auriez fait avec l'IDE.

Dans l'onglet “SDK”, nous allons choisir le compilateur, à savoir ici le framework AIR 3.5, normalement il est installé de base avec FD, si ce n'est pas le cas vous devrez le télécharger et l'installer.

Dans l'onglet “Classpaths”, on choisi le chemin des classes du programme, par défaut FD intègre le dossier “src”. Vous pouvez bien sur ajouter de nouveaux chemins, par exemple si vous souhaitez placer la librairie de Flixel (ou une autre) dans le dossier “lib”, cela vous permet de mieux organiser le programme en séparant les classes de votre projet des différentes librairies utilisées.

Nous allons nous passer pour cet exercice des deux onglets “Build” et “Compiler Options”, je vous laisse le loisir de découvrir à quoi ça sert ;-)

Notre structure de base est prête, pour ceux qui n'ont jamais fait de POO ou travaillé avec un éditeur externe, cela change pas mal d'habitudes, mais globalement on s'y retrouve, la grosse différence c'est que nous n'avons pas de timeline et que nous devons configurer des petites choses que Flash IDE fait tout seul normalement.

Faites un tour des trois dossiers créés par Flash Develop, dans le dossier “bin” vous trouvez tout ce qui sert à l'intégration de votre programme dans une page HTML, c'est le dossier d'export.

Dans le dossier “src” vous trouvez un fichier nommé “Main.as”, si vous avez suivit les exercices précédent cela devrait vous mettre la puce à l'oreille, en effet j'utilise toujours une fonction “main” pour piloter le programme, même dans l'IDE. Il s'agit donc de la classe principale du programme, son point d'entrée si vous préférez. Vous remarquerez que ce fichier comporte une petite flèche verte, cela signifie que FD considère ce fichier comme la classe principale du programme. Si vous n'avez pas cette petite flèche verte faites un clic droit sur votre fichier et choisissez “Document Class” autrement dit “Classe du document”. Pour plus d'infos sur la classe document, je vous renvoie au tutorial de Nataly sur le passage de l'IDE à la POO ici : http://forums.mediabox.fr/wiki/tutoriaux/flashplatform/programmation/poo_bases/classesqqc

Il nous reste donc le dossier “lib” qui est sensé contenir toutes les librairies supplémentaires que vous souhaitez utiliser dans votre programme. On pourrait par exemple y installer Flixel, mais pour cet exercice on va choisir une méthode plus simple, puisque FD à déjà configuré le ClassPath sur le dossier “src” on va tout simplement ajouter Flixel à un nouveau sous dossier “org” que vous devez créer.. C'est plus simple et vous évites de toucher au “ClassPath”.

Décompressez l'archive de Flixel dans le dossier “org” de votre projet, vous devez vous retrouver avec un fichier “Main.as” et un dossier “org/Flixel” contenant toutes les classes de cette librairie.

Ca y est, nous sommes prêts à travailler.

Créer un point d'entrée pour Flixel

Je ne vais pas vous faire le détail du fonctionnement de Flixel, ça prendrait bien trop de temps et vous avez des manuels fournis avec Flixel pour étudier son code, je vais juste me contenter de vous montrer la base.

Tout jeu utilisant Flixel à besoin d'un point d'entrée ou conteneur, autrement dit d'une classe qui étend un objet jeu de Flixel. Pour cela c'est très simple, il suffit d'éditer la classe “Main.as” de la manière suivante :

package {
 
	import org.flixel.FlxGame;
 
	public class Main extends FlxGame {
 
		public function Main():void {
			super(800, 600, View, 1);
		}
	}
}

Pour ceux qui ne sont pas habitués à la POO, allez faire un tour sur le tutorial de Nataly avant d'aller plus loin.

Dans Flash IDE vous utilisez déjà tout un tas de classes et d'objets, parfois sans même vous en rendre compte, par exemple lorsque vous créez un nouveau MovieClip, ou un nouveau TextField. Pour celui qui est curieux il est facile de voir comment ces classes s'utilisent en allant tout simplement consulter la doc de Flash. En dehors du fait que votre environnement de travail change, c'est le même principe lorsque vous souhaitez utiliser des classes perso ou des librairies externes comme Flixel dans FD, il vous suffit d'aller consulter la notice pour voir comment les classes sont implémentées (voir notice de Flixel ici : http://flixel.org/docs/ ) et apprendre à vous en servir.

La classe “Main” étant à la racine du projet on se contente de déclarer le package de base. Puis on importe la classe que nous souhaitons utiliser, elle se trouve dans le dossier “org”, dans le sous dossier “flixel” et se nomme “FlxGame”.

Voici sa définition : http://flixel.org/docs/org/flixel/FlxGame.html

Cet objet est le conteneur de tout le jeu, il ne peut y avoir qu'un seul par jeu.

La classe “Main” étend la classe mère “FlxGame”, on le signale donc. Puis on crée le constructeur de la classe, et à l'aide de la commande “super” on appelle le constructeur de la classe mère (FlxGame), en lui passant les paramètres utiles de notre projet, ici :

super(800, 600, View, 1);
  • Largeur de la scène
  • Hauteur de la scène
  • La classe à instancier lors du lancement du jeu
  • Le facteur de zoom du jeu (on y reviendra par la suite).

Il s'agit là des principaux paramètres utiles mais vous avez la possibilité d'en ajouter d'autres comme le framerate par exemple, à vous d'aller étudier la classe mère pour connaître les différents paramètres possibles.

Intéressons-nous à la classe “View”, il s'agit en fait de l'objet principal de notre jeu qui doit être appellé à la création du jeu. En un sens on peut le considérer comme un “objet d'affichage de Flixel”. Pour créer cet objet d'affichage on va donc créer arbitrairement une nouvelle classe “View” qui étendra l'objet correspondant dans Flixel à savoir “FlxState”.

Vous remarquerez que les classes principales de Flixel commencent par “Flx” pour “Flixel” et un nom clairement identifiable, ici “State” qui signifie “état”, nous reviendrons sur la notion d'états dans Flixel un peu plus tard.

Si vous lancez une compilation du projet (CTRL+ENTRER) à ce stade, le programme plante, c'est normal car nous n'avons pas encore créé notre objet “View”.

Créer un nouvel état

Nous venons de le voir, l'objet que nous avons appelé “View” va servir à l'affichage, nous allons donc devoir le déclarer, pour cela créez un nouveau fichier nommé “View.as” dans le dossier “src” de votre projet.

Par défaut Flash Develop vous propose quelques options, n'en tenez pas compte pour le moment et donnez juste le nom “View.as” à votre nouvelle classe.

Editez cette nouvelle classe et écrivez ce qui suit :

package {
 
	import org.flixel.FlxState;
	import org.flixel.FlxText;
	import org.flixel.FlxG;
 
	public class View extends FlxState {
 
		private var _titre:FlxText;
		private var _texte:String;
 
		public function View() {
			_texte = "Hello World !";
		}
 
		override public function create():void {
			_titre = new FlxText(0, FlxG.height*.5, FlxG.width, _texte);
			_titre.setFormat(null, 24, 0x00ff00, "center", 0x005500);
			this.add(_titre);
		}
 
		override public function update():void {
			super.update();
		}
 
		override public function draw():void {
			super.draw();
		}
	}
}

On commence par importer les classes qui vont nous être utiles (on reviendra dessus juste après), puis on crée le constructeur de la classe “View”, elle étend “FlxState”.

Voici la définition de FlxState : http://flixel.org/docs/org/flixel/FlxState.html

Revenons deux minutes sur la notion d'“états” dont je vous ai parlé précédemment. Dans Flixel le système de gestion repose sur des “états”, chaque “état” représente une partie visible de votre jeu, comme par exemple le menu, le score, la zone de jeu, … Ce mécanisme repose sur le desing pattern “Game State Pattern”, je vous laisse lire sa définition, bien mieux expliquée que je ne le ferait, ici : http://gamedev.dreamnoid.com/2009/01/06/game-state-pattern/

On peut considérer chaque “état” comme une couche, un conteneur, ou plus précisément une partie logique du jeu, un jeu peut avoir de nombreux “états” différents, par exemple :

Notez que deux méthodes sont utiles, “Update” et “Draw. “Update” sert principalement aux calculs comme les collisions ou les déplacements, quand à “Draw” il s'agit en fait tout simplement de l'affichage à l'écran du résultat des calculs effectués.

Je n'entrerai pas plus loin dans les explications à ce niveau pour éviter d'embrouiller les débutants. Notez simplement que tout nouvel “état” dans Flixel est donc un objet qui étend “FlxState”, c'est sans doute la chose la plus importante que vous ayez à retenir à ce stade, avec le fait que cet objet est mis à jour à l'aide de la méthode “update” et affiché à l'aide de la méthode “draw” de la classe “FlxState”. Lors de l'instanciation d'un “état”, la méthode “create” est appelée (nous y reviendrons plus tard) puis les méthodes “update” et “draw” sont appelée en boucle indéfiniment.

Etudions ce petit programme pas à pas :

import org.flixel.FlxState;
import org.flixel.FlxText;
import org.flixel.FlxG;

Ici j'importe trois classes de Flixel, le première est l'objet “etat”, la seconde un objet “texte” et la troisième “FlxG”, certainement la plus utile, nous permet d'accéder aux informations globales du jeu comme le stage, les touches enfoncées, la position de la souris, les paramètres de la scène, etc… bref toutes les informations importantes du jeu.

Voici leurs définitions :

http://flixel.org/docs/org/flixel/FlxState.html
http://flixel.org/docs/org/flixel/FlxText.html
http://flixel.org/docs/org/flixel/FlxG.html

public class View extends FlxState {

Je crée un nouvel “état”.

private var _texte:String;
private var _titre:FlxText;

Je crée deux variables, une qui représente le texte que je vais vouloir afficher, et une autre qui représente un objet “texte” dans Flixel. Nous n'utilisons pas l'IDE de Flash il va donc falloir gérer l'affichage des textes, d'ordinaire on déclarerait un objet text via la classe TextField de Flash, mais ici Flixel le fait pour nous à l'aide de la classe “FlxText”, elle comprend de nombreux paramètres utiles comme la position du texte, sa taille, etc… et aussi des méthodes qui permettent de formater le texte selon vos souhaits (nous y reviendrons).

public function View() {
	_texte = "Hello World !";
}

La variable “_texte” n'est pas spécifique à Flixel, c'est un simple texte libre que nous souhaitons afficher, elle est donc initialisée dans le constructeur de la classe “View”.

override public function create():void {
	_titre = new FlxText(0, FlxG.height*.5, FlxG.width, _texte);
	_titre.setFormat(null, 24, 0x00ff00, "center", 0x005500);
	this.add(_titre);
}

Ha, voilà que ça se complique….

Pour ceux qui ne font pas de POO et découvrent le fonctionnement avec cet exercice, je dois ajouter quelques précisions. “override” permet à une sous-classe (ici View) de ne pas prendre en compte une méthode utilisée par la classe mère (ici FlxState) et de la remplacer par une méthode propre à la sous-classe. En gros “View” étend “FlxState” et hérite donc aveuglément de toutes les méthodes de “FlxState”, sa classe mère, et nous souhaitons modifier cette méthode pour créer un objet spécifique.

La méthode “create” est appelée à l'initialisation de l'“état” qu'on crée, elle permet de construire les éléments utiles que ce soient des variables, du texte, des tuiles, …

Bien, “_titre” est un objet propre à Flixel, il s'agit d'un texte, nous allons donc le créer via la méthode “create” de Flixel,

_titre = new FlxText(0, FlxG.height*.5, FlxG.width, _texte);

Ici j'instancie tout simplement la variable “_titre”, comme je pourrais le faire avec un “TextField” dans Flash (voir la doc pour les paramètres utilisés). Ici mon texte sera donc placé à 0 sur X, la moitié de la hauteur de la scène sur Y, aura une taille de la largeur de la scène et comprendra le texte “_texte”.

_titre.setFormat(null, 24, 0x00ff00, "center", 0x005500);

La méthode “setFormat” me permet d'appliquer un formatage au texte que je veux afficher, ici le nom de la police utilisée (par défaut aucun), sa taille en pixels, sa couleur, son alignement, et la couleur de son ombre (si il y en a une).

this.add(_titre);

Et on affiche le texte final, attention comme vous pouvez le remarquer Flixel remplace le bon vieux “addChild” par un simple “add”. Tous les objets Flixel que nous souhaitons ajouter utilisent cette commande en remplacement de “addChild”.

override public function update():void {
	super.update();
}
 
override public function draw():void {
	super.draw();
}

On met à jour l'objet et on l'affiche à l'écran.

Testez votre programme (CTRL+ENTER) et normalement vous devriez voir un texte apparaître à l'écran.

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

Nous en avons fini avec la phase découverte de Flixel, à présent nous allons passer à la création d'un petit jeu d'exemple.

Etape 2 - Un premier jeu

Je vous propose le démarrage d'un jeu de type “Shoot'em up”, c'est encore ce qu'il y a de plus simple et de plus rapide à prendre en main.

Le point d'entrée

Nous allons créer un nouveau projet en suivant la même méthode que précédemment.

Vous devez avoir une classe “Main.as” dans laquelle vous allez écrire :

package {
 
	import org.flixel.FlxGame;
 
	public class Main extends FlxGame {
		public function Main():void	{
			super(640, 480, Jeu, 1, 60, 60);
			forceDebugger = true;
		}
	}
}

Je crée un nouveau point d'entrée de Flixel (le conteneur du jeu), ayant une taille de 640*480, qui appelle “Jeu” au démarrage, avec un facteur de zoom de 1, un framerate Flixel de 60 et un framerate Player de 60 également.

Jusque là rien de bien compliqué si vous avez bien suivit les explications précédentes.

forceDebugger = true;

Flixel propose un débogueur visible à l'écran, pourquoi s'en priver ? Cette commande vous permet de faire apparaître les infos de débogage en appuyant sur la touche “ù” de votre clavier, voici ce que renvoie le débogueur :

Le premier état

Il nous faut un premier état “Jeu” à lancer au démarrage du programme, créez un nouveau fichier “Jeu.as” au même endroit que “Main.as” et écrivez :

package {
 
	import org.flixel.*;
 
	public class Jeu extends FlxState {
 
		private var _joueur:Vaisseau;
		private var _ennemis:FlxGroup;
		private var _tirs:FlxGroup;
		private var _timer:Number;
		private var _score:FlxText;
		private var _finPartie:FlxText;
		private var _frequence:Number = 2.5;
		private var _texteFin:String = "PERDU \n APPUYEZ SUR ENTER POUR REJOUER !";
 
		override public function create():void	{
			_joueur = 		new Vaisseau();
			_ennemis = 		new FlxGroup();
			_tirs = 		new FlxGroup();
			_score = 		new FlxText(10, 8, 200, "0");
			FlxG.score = 		0;
			add(_joueur);
			add(_ennemis);
			add(_tirs);
			add(_score);
			_timer = _frequence;
			super.create();
		}
 
		override public function update():void {
			if(FlxG.keys.justPressed("SPACE")) tirer(new FlxPoint(_joueur.x+36, _joueur.y+12));
			_timer -= FlxG.elapsed;
			if(_timer<0) creeEnnemi();
			FlxG.overlap (_ennemis, _tirs, tirToucheEnnemi);
			FlxG.overlap (_ennemis, _joueur, ennemiToucheJoueur);
			super.update();
		}
 
		private function creeEnnemi():void {
			_ennemis.add(new Ennemis(FlxG.width, Math.random()*(FlxG.height-100)+50));
			_timer = _frequence;
		}
 
		private function tirer(p:FlxPoint):void{
			_tirs.add(new Tir(p.x, p.y));
		}
 
		private function tirToucheEnnemi(e:Ennemis, t:Tir):void{
			e.kill();
			t.kill();
			FlxG.score += 1;
			_score.text = FlxG.score.toString();
		}
 
		private function ennemiToucheJoueur(e:Ennemis, j:Vaisseau):void {
			j.kill();
			e.kill();
			_finPartie = new FlxText(0, FlxG.height*.5, FlxG.width, _texteFin);
			_finPartie.setFormat(null, 10, 0xFFFFFFFF, "center");
			add(_finPartie);
		}
	}
}
import org.flixel.*;

On importe toutes les classes de Flixel, elles pourront nous servir, notez que vous pouvez limiter l'import aux uniques classes utiles si vous le souhaitez.

public class Jeu extends FlxState {

On déclare notre état “Jeu”, il étend “FlxState”, normal.

private var _joueur:Vaisseau;
private var _ennemis:FlxGroup;
private var _tirs:FlxGroup;
private var _timer:Number;
private var _score:FlxText;
private var _finPartie:FlxText;
private var _frequence:Number = 2.5; 
private var _texteFin:String = "PERDU \n APPUYEZ SUR ENTER POUR REJOUER !";

Je déclare toutes mes variables et objets utiles pour le jeu, là c'est livré un peu en vrac sans explications intermédiaires, mais vous allez voir que c'est très facile, je crée un joueur qui correspond à la classe “Vaisseau” que nous n'avons pas encore écrit, puis je crée deux groupes “ennemis” et “tirs”. Les groupes dans Flixel peuvent se comparer à des conteneurs ou des tableaux, si vous avez suivit mes exercices précédents où on travaillait à l'IDE cela correspond aux tableaux de stockage utilisés pour réunir plusieurs éléments d'un même type tels que les tirs et les ennemis, ce qui facilite la gestion des collisions par exemple. Je crée ensuite une variable “timer” qui va servir à déclencher les actions du jeu, ici principalement l'apparition des ennemis. Deux textes Flixel qui vont me servir à afficher le score et les informations de fin de partie, et enfin on termine par une variable “frequence” qui sert à réinitialiser le timer et donc faire apparaître un nouvel ennemi et le texte que je veux afficher en fin de partie, qui est une simple String.

override public function create():void	{
	_joueur = 		new Vaisseau();
	_ennemis = 		new FlxGroup();
	_tirs = 		new FlxGroup();
	_score = 		new FlxText(10, 8, 200, "0");
	FlxG.score = 		0;
	add(_joueur);
	add(_ennemis);
	add(_tirs);
	add(_score);
	initTimer();
	super.create();
}

La fonction “create” nous sert à créer les différents éléments du jeu, nous avons vu ça en introduction de cette fiche. On crée donc tous nos éléments à savoir le joueur, les groupes ennemis et tirs, et le score qu'on s'empresse d'initialiser tout de suite. Notez l'apparition d'une nouvelle variable “FlxG.score”, il s'agit du stockage du score à la racine du jeu, on peut donc y accéder par l'intermédiaire de “FlxG” qui est la méthode pour accéder aux informations globales de notre projet.

J'ajoute enfin tous les éléments à la liste d'affichage de Flixel, puis j'initialise le timer. Comme vous pouvez le constater il n'y a pas beaucoup de différences avec une création de jeu classique, la méthode “create” correspond à la fonction “init” que j'utilisait dans les exercices à l'IDE.

override public function update():void {
	if(FlxG.keys.justPressed("SPACE")) tirer(new FlxPoint(_joueur.x+36, _joueur.y+12));
	timer -= FlxG.elapsed;
	if(_timer<0) creeEnnemi();
	FlxG.overlap (_ennemis, _tirs, tirToucheEnnemi);
	FlxG.overlap (_ennemis, _joueur, ennemiToucheJoueur);
	super.update();
}

On se réécrit la méthode update de notre état, là il s'agit de mettre à jour les éléments du jeu. Il faut se poser deux minutes pour réfléchir aux éléments que nous devons manipuler ici. Nous travaillons en POO, ce qui veut dire que chacun de nos objets (ennemis, tirs, joueur) à sa propre petite classe qui le défini et lui permet de faire des choses qui lui sont propres, par exemple le joueur aussi bien que les ennemis ou les tirs peuvent se déplacer, c'est de la tambouille interne à la classe de chaque objet, en revanche certaines actions comme le fait de faire apparaître un nouveau tir pour le joueur ou bien les tests de collisions entre les différents éléments du jeu, ne concernent plus seulement l'objet en question mais plusieurs. C'est à ce moment là qu'on va sortir les commandes de la classe de chaque objet et les mettre dans notre objet de jeu global (notre état “Jeu”), c'est donc lui qui pilote les interactions entre les objets alors que chaque objet se gère tout seul pour les actions qui lui sont propres.

Ici je vais donc commencer par regarder si le joueur appuie sur la barre espace, notez la définition simplifiée pour récupérer la pression d'une touche, c'est assez pratique même si ça change un peu nos habitudes. Lorsque le joueur tire, j'appelle la fonction correspondante en lui passant en paramètre la position que doit avoir le tir par rapport au joueur.

Je mets ensuite le timer à jour, pour cela je fait appel à une méthode de la classe “FlxG” qui décompte le temps passé, une fois de plus notre classe “FlxG” est bien utile puisqu'elle nous donne accès naturellement à une information que nous aurions du coder autrement. A chaque frame je décrémente le timer du temps passé, si le timer est inférieur à zéro je crée un nouvel ennemi, ceci me permet de gérer la cadence d'apparition des ennemis.

Puis on attaque les tests de collisions, je vais faire une fois de plus appel à la classe “FlxG” et à sa méthode “overlap” qui détermine lorsque deux objets sont en contact, cela correspond sommairement à un “hitTestObject”, Flixel compare les deux hitBox et regarde si elles sont en contact. La petite différence c'est que nous lui demandons de vérifier deux groupes et non pas deux éléments indépendants. Le premier test de collision est fait entre le groupe des ennemis et le groupe des tirs, ainsi si un ennemi du groupe touche un tir du groupe on déclenche la fonction “tirToucheEnnemi”, et vous allez voir lorsqu'on va étudier cette fonction que Flixel nous apporte un gros plus à ce niveau.

On fait la même détection entre le joueur (qui cette fois n'est pas un groupe) et le groupe des ennemis, si il y a contact on déclenche la fonction “ennemiToucheJoueur”.

private function creeEnnemi():void {
	_ennemis.add(new Ennemis(FlxG.width, Math.random()*(FlxG.height-100)+50));
	_timer = _frequence;
}

Si le timer est à zéro on crée un nouvel ennemi, pour cela on va tout ajouter un nouvel ennemi au groupe des ennemis, sa position sur X est de la taille de la scène (donc à droite en dehors de la scène) et sa position sur Y est aléatoire sur la hauteur de la scène avec une restriction de la taille d'un ennemi pour éviter qu'il se retrouve à moitié dehors. Puis on réinitialise le timer afin de que l'ennemi suivant puisse apparaître à son tour.

private function tirer(p:FlxPoint):void{
	_tirs.add(new Tir(p.x, p.y));
}

Lorsque le joueur appuie sur la barre espace, on crée un nouveau tir, sa position est donnée par la classe “Vaisseau” (que nous n'avons pas encore écrit), notez l'utilisation de “FlxPoint”, qui correspond tout simplement à un bête “Point”' de la classe “Geom” de Flash, mais avec les petits plus de Flixel (dont nous ne nous servons pas ici). Isoler la création des tirs dans une petite fonction, même si pour le moment cela tient en une ligne, nous servira par la suite si on veut donner la possibilité aux ennemis de tirer par exemple.

private function tirToucheEnnemi(e:Ennemis, t:Tir):void{
	e.kill();
	t.kill();
	FlxG.score += 1;
	_score.text = FlxG.score.toString();
}

Revenons à nos tests de collisions, ici on va vérifier si un tir touche un ennemi, je vous en avait parlé un peu plus haut, la vérification se fait sur les groupes entiers, or comme vous pouvez le constater le test de collision se fait entre deux entités de ces groupes et non sur le groupe entier. C'est un petit bonus apporté par Flixel, il suffit de lui indiquer deux groupes et les tests de collisions s'effectuerons entre deux entités uniques de ces groupes. Par exemple ici je veux tester la collision entre les objets de type “Ennemis” et les objets de type “Tir” des groupes “_ennemis” et “_tirs”. Flixel prend en compte les deux groupes et teste la collision un à un entre tous les éléments des deux groupes. Si la collision à bien lieu, on retire l'ennemi touché et le tir concerné, puis on incrémente le score (celui de l'objet “FlxG”) et on met à jour l'affichage du texte.

private function ennemiToucheJoueur(e:Ennemis, j:Vaisseau):void {
	j.kill();
	e.kill();
	_finPartie = new FlxText(0, FlxG.height*.5, FlxG.width, _texteFin);
	_finPartie.setFormat(null, 10, 0xFFFFFFFF, "center");
	add(_finPartie);
}

On remet le couvert pour la collision entre le joueur et les ennemis, comme vous pouvez le constater qu'il s'agisse d'un groupe ou d'un élément isolé la démarche est exactement la même. Si il y a collision on supprime le joueur et l'ennemi concerné. Puis j'affiche le texte de fin et lui donne un formatage de base.

C'est terminé pour notre programme principal, à présent nous allons nous attaquer au reste… Jusqu'à présent nous avons manipulé des objets lambda, mais nous n'avons pas créé les classes qui définissent ces objets or notre programme ne peut bien sur pas fonctionner sans. Pour simplifier pour ceux qui ne font pas de POO, c'est comme si on disait au programme de travailler avec le clip “Vaisseau” et que nous n'avions pas créé ce clip et ne l'avons pas exporté pour AS, pour le programme l'objet n'existe pas, il faut donc le créer.

Créer le joueur

Créez une nouvelle classe nommée “Vaisseau.as”, toujours au même endroit dans le dossier “src” du projet, et écrivez :

package  {
 
	import org.flixel.*;
 
	public class Vaisseau extends FlxSprite {
 
		[Embed(source="../assets/Vaisseau.png")] private var ImgVaisseau:Class;
 
		public function Vaisseau():void {
			super(50, 50, ImgVaisseau);
		}
 
		override public function update():void {
 
			velocity.x = 0;
			velocity.y = 0;
 
			if (FlxG.keys.LEFT) 	 velocity.x = -250; 
			if(FlxG.keys.RIGHT) 	 velocity.x = 250;
			if(FlxG.keys.UP) 	 velocity.y = -250;
			if(FlxG.keys.DOWN)  	 velocity.y = 250;
			if(x>FlxG.width-width)	 x = FlxG.width - width;
			if(x<0)	 		 x = 0;
			if(y>FlxG.height-height) y = FlxG.height - height;
			if(y<0) 		 y = 0;
 
			super.update();
		}
	}
}

Etudions cette classe pas à pas :

public class Vaisseau extends FlxSprite {

Je crée la classe “Vaisseau” qui représente donc mon joueur, elle étend “FlxSprite” dont vous trouverez la définition ici : http://flixel.org/docs/org/flixel/FlxSprite.html

Il s'agit d'un objet Flixel qui a des fonctionnalités étendues pour les graphismes.

[Embed(source="../assets/Vaisseau.png")] private var ImgVaisseau:Class;

Ha ! Qu'est ce que c'est que cette chose étrange ?

Nous n'utilisons pas de bibliothèque car nous ne travaillons pas avec l'IDE de Flash, du coup on ne peut pas embarquer des ressources (clips, bitmaps, sons, video,… ) facilement, il faut le faire à la main. Fort heureusement AS3 nous propose le tag [Embed] pour spécifier au compilateur qu'un fichier externe doit être intégré. Il suffit ensuite d'en faire une classe pour pouvoir l'utiliser comme n'importe quel autre objet dans notre programme. Cela se passe en deux étapes, on indique d'abord où se trouve la ressource externe à importer, puis on y associe une définition de classe, notez qu'il n'y a pas de point virgule qui sépare les deux instructions, on peut traduire le tout par “cette ressource est une variable privée nommée ImgVaisseau et correspond à une classe”.

Pensez à créer un dossier “assets” à la racine de votre projet et y poser un élément graphique (en PNG pour conserver la transparence) correspondant au vaisseau de votre joueur. Je ne choisi pas le nom du dossier de stockage des graphismes au hasard, “assets” est utilisé dans beaucoup de moteurs de jeux pour désigner les ressources tels que les graphismes, les sons, les vidéos, les éléments 3D, bref tout ce qui n'est pas du programme et est utile au sein du jeu.

public function Vaisseau():void {
	super(50, 50, ImgVaisseau);
}

Dans le constructeur de la classe “Vaisseau” on spécifie la position de l'objet que l'on crée et l'image qui le représente, attention jetez un oeil au constructeur de la classe mère “FlxSprite” (voir : http://flixel.org/docs/org/flixel/FlxSprite.html#FlxSprite%28%29 ), vous ne pouvez spécifier une image à cet endroit que si elle n'est pas animée, pour les sprites animés il faut passer par une autre méthode que nous étudierons sans doute plus tard (jetez un oeil à la méthode “addAnimation()” de cette même classe).

override public function update():void {
 
	velocity.x = 0;
	velocity.y = 0;
 
	if (FlxG.keys.LEFT) 	 velocity.x = -250; 
	if(FlxG.keys.RIGHT) 	 velocity.x = 250;
	if(FlxG.keys.UP) 	 velocity.y = -250;
	if(FlxG.keys.DOWN)  	 velocity.y = 250;
	if(x>FlxG.width-width)	 x = FlxG.width - width;
	if(x<0)	 		 x = 0;
	if(y>FlxG.height-height) y = FlxG.height - height;
	if(y<0) 		 y = 0;
 
	super.update();
}

On modifie la fonction update pour effectuer les calculs propres au joueur. Le paramètre “velocity” est propre à la classe “FlxObject” dont découle la classe “FlxSprite” que nous utilisons ici (voir : http://flixel.org/docs/org/flixel/FlxObject.html#velocity ), il s'agit d'un Point qui représente la vitesse de l'objet sur les deux axes. Pour faire simple en modifiant la vélocité de l'objet on le déplace ;-)

Je commence donc par donner une vélocité de zéro sur chaque axe, puis je vais regarder si le joueur appuie sur une touche et en fonction de la touche je modifie la vélocité sur chaque axe. Puis je limite le déplacement de l'objet sur chaque axe afin qu'il ne sorte pas de l'écran.

Résumons, la fonction “update” est lancée à chaque frame, la vitesse de l'objet sur les deux axes est remise à zéro (l'objet ne bouge plus), puis si le joueur appuie sur une touche de direction la vitesse de l'objet est modifiée (l'objet bouge). Si l'objet sort de la zone de jeu il est replacé dans la zone de jeu.

C'est tout pour le joueur pour le moment, on s'attaque aux ennemis.

Créer les ennemis

Créez une nouvelle classe nommée “Ennemis.as”, toujours au même endroit dans le dossier “src” du projet, et écrivez :

package {
 
	import org.flixel.*;
 
	public class Ennemis extends FlxSprite {
 
		[Embed(source="../assets/Ennemi.png")] private var ImgEnnemi:Class;
 
		public function Ennemis(x:Number, y:Number):void {
			super(x, y, ImgEnnemi);
			velocity.x = -200;
		}
 
		override public function update():void	{
			velocity.y = Math.cos(x/50)*50;
			super.update();
		}
	}
}

Basiquement c'est la même chose que pour le joueur, de nouveau un “FlxSprite”, de nouveau on importe des graphismes externes. Dans le constructeur on place l'ennemi à la position qu'il doit occuper lorsqu'on le crée, et on lui donne une vélocité de -200 sur l'axe X.

La mise à jour se fait à chaque frame, c'est là qu'on modifie la vélocité sur la position Y, ce qui nous permet de transformer un déplacement linéaire en déplacement sinusoïdal ;-)

Créer les tirs

Créez une nouvelle classe nommée “Tir.as”, toujours au même endroit dans le dossier “src” du projet, et écrivez :

package {
 
	import org.flixel.*;
 
	public class Tir extends FlxSprite {
		public function Tir(x: Number, y: Number):void {
			super(x, y);
			makeGraphic(16, 4, 0xFF597137);
			velocity.x = 1000;
		}
	}
}

De nouveau un “FlxSprite”, cette fois cependant on ne fait pas appel à une ressource externe, en effet Flixel est aussi capable de dessiner avec une simple commande. Je commence par placer le tir à l'endroit voulu (le nez du vaisseau du joueur pour le moment) puis je demande à Flixel de créer un nouvel élément graphique (“makeGraphic”) dont je spécifie la taille et la couleur, puis je lui donne une vélocité de 1000 sur l'axe X.

Et nous avons terminé notre petit exercice, testez le jeu, si tout s'est bien passé vous avez un début de shoot'em up jouable.

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

Conclusion

Voilà pour une petite prise en main rapide de Flixel, comme vous pouvez le constater ce n'est pas si différent de ce à quoi je vous ai habitué avec les exercices sur l'IDE. Certes, vous n'avez pas de bibliothèque, mais finalement on s'en servait déjà assez peu dans les exercices précédents, ok c'est de la POO et ça peut en rebuter plus d'un, mais à la fois est-ce si difficile que ça à comprendre si on fait un petit effort pour changer nos habitudes ?

Les gros avantages de Flixels sont sa simplicité, son accessibilité, sa communauté très active et surtout la disponibilité de nombreux plugins (car Flixel est pluggable) qui étendent grandement ses possibilités. Cependant tout n'est pas rose non plus, on est obligé d'apprendre un nouveau vocabulaire (celui de Flixel), et les performances ne sont pas toujours au rendez-vous selon les jeux que vous allez créer.

Tout dépend donc de vos projets et des vos envies, Flixel est un bon moteur pour créer des jeux “old school” variés, il demande un certain temps d'adaptation mais sa prise en main est accessible au plus grand nombre, et il propose une gamme d'outils intéressante pour gérer aussi bien les collisions que les animations, la découpe des feuilles de sprites, la création d'éléments graphiques, les déplacements, les préchargements, le scrolling, etc…. Mais il ne faut pas perdre de vue que bien souvent un moteur maison répondant à vos besoins tient en une petite centaine de lignes de code bien plus faciles à optimiser et à maîtriser lorsque vous faites évoluer votre jeu qu'un gros framework écrit par d'autres et que vous ne maîtrisez pas totalement.

Les sources

etape1.zip Etape 1
etape2.zip Etape 2