Action Script 3 : Mini-jeu en isométrique

Action Script 3 : Mini-jeu en isométrique

Programme : Flash

Langage : Action Script 3

Difficulté : Simple

Temps estimé : 2 à  4 heures

Télécharger les fichiers sources
Vous devez être logués ou enregistrés pour télécharger !

Arcade (Sebastien Geelen) explique comment coder un mini-jeu en isométrique en Flash.

Vous aimez les jeux vidéo avec des gentils vaisseaux et des méchants sur lesquels il faut tirer?
Vous aimé aussi réaliser des jeux en Flash et vous voulez découvrir l’isométrique?
Ce tutoriel est fait pour vous !


Demo Flash


Dans ce tutoriel nous allons créer un mini jeu Flash qui va nous permettre d’appréhender les bases de l’isométrique en programmation. Aucune connaissance préalable en isométrie n’est donc nécessaire.

Avec ces techniques et un peu de bonne volonté on va facilement pouvoir apprendre le fonctionnement de l’isométrique à  plus grande échelle.

NB : Attention, les concepts de programmation ActionScript courants ne sont pas expliqués dans ce tutoriel et vous devez donc avoir des bases correctes dans ce domaine.


L’idée

Pour avoir un jeu assez simple à  coder, mais qui nous permettra tout de même d’utiliser l’isométrique, nous allons développer un bon vieux jeu de shoot.
Prenons rapidement le temps de définir ce que nous devrons implémenter dans celui-ci:

  • Nous aurons un petit vaisseau isométrique à  notre disposition (le gentil)
  • Ce vaisseau devra pouvoir se déplacer et tirer sur les ennemis
  • Un certain nombre d’ennemis (les méchants) devront attaquer en se déplaçant vers le vaisseau
  • La difficulté doit augmenter avec le temps.

Voila les grands principes à  garder en tète, rien de bien compliqué donc.

NB : Le jeu ici codé est largement améliorable, il vous sert de base d’apprentissage. A vous de l’améliorer par la suite !


L’architecture

Nous pouvons à  présent créer l’architecture de notre petit projet, qui devra bien entendu répondre aux exigences que nous avons définies plus haut.

Base.as :
Cette classe sera le stage de l’application, notre classe mère. Elle sera aussi chargée de relayer les messages de debug émis par le système, qui nous permettrons de tester notre jeu.

Engine.as :
Celle-ci sera la classe principale de notre jeu, le coeur du jeu, elle centralisera et gèrera tous les objets et simulera tout les fonctionnements de l’univers du jeu.

Cell.as :
Cette classe correspondra à  une cellule de la grille. Grille qui servira de terrain de jeu a notre vaisseau ainsi qu’aux méchants.

Bullet.as :
Cette classe représente un objet bullet (en gros, un projectile).

Creep.as :
Cette classe représente un objet creep (un méchant).

Ship.as :
Cette classe représente un objet ship (le gentil).


L’isométrique

Bon penser au jeu c’est bien beau mais on est ici pour apprendre l’isométrique boudiou… ça vient, ça vient.

Déjà , l’isométrique c’est quoi?

L’isométrique c’est un grand mot qui ne veut pas dire grand chose mais qu’on utilise pour se la péter alors que ça existe depuis des dizaines d’années.

Non mais sérieux…

L’isométrique n’est en réalité qu’une illusion incomplète de la 3D : c’est de la fausse perspective.

En isométrique on oublie la profondeur de champ et les points ou lignes de fuite. Ce qui a l’avantage de réduire énormément les calculs nécessaires pour simuler de la 3D. Tout le monde peut facilement dessiner de l’isométrique avec un crayon et une latte sur une feuille de papier quadrillée.

Voici le principe de base on prend un carré, on le fait pivoter de 90 ° et on « l’aplatit » de moitié. Et, magie, on a une impression de perspective.

Pour reproduire ceci en AS3 on trace une ligne qui part de 0,0 (point 1) et passe par les points 2, 3 et 4 du schéma pour terminer au point 1.

Le petit bout de code :

private function _draw(color:int=0xcccccc, size:int=5):void {
var graphics_tmp:Graphics = this.graphics;
graphics_tmp.lineStyle(1, 0x666666);
graphics_tmp.beginFill(color); //est au point 1
graphics_tmp.lineTo( -size, size / 2); // trace au point 2
graphics_tmp.lineTo( 0, size) ;// trace au point 3
graphics_tmp.lineTo( size, size / 2); // trace au point 4
graphics_tmp.lineTo( 0, 0); // trace au point 1 pour fermer la forme
graphics_tmp.endFill(); // fin
}

Et pour donner un peu de relief on va faire comme ceci:

En AS3 on va cette fois-ci déplacer la mine de notre crayon virtuel vers le point 1 ensuite nous allons tracer l’extérieur de la forme en suivant les points 2, 3, 4, 5, 6 et ensuite revenir au point 1. Les arêtes intérieures seront tracées séparément après la forme.
Vu que la mine est au point 1 à  la fin de la forme, nous traçons d’abord vers le point 7 puis le point 3.
Après un déplacement au point 5 nous traçons la dernière arête vers le point 7 qui finalise le dessin.

Code:

private function _draw(color:int=0xcccccc, height:int = 5):void {
var _size:int =  5;
var graphics_tmp:Graphics = this.graphics;
graphics_tmp.clear();
graphics_tmp.lineStyle(1, 0x666666);
graphics_tmp.moveTo(0, _size); // va au point 1
graphics_tmp.beginFill(color);
graphics_tmp.lineTo(_size, _size / 2); // trace au point 2
graphics_tmp.lineTo(_size, (_size / 2) - height); // trace au point 3
graphics_tmp.lineTo(0, -height); // trace au point 4
graphics_tmp.lineTo(-_size, (_size / 2) - height); // trace au point 5
graphics_tmp.lineTo( -_size, _size / 2); // trace au point 6
graphics_tmp.lineTo(0, _size); // trace au point 1 pour terminer la forme
graphics_tmp.endFill(); // fin de la forme remplie
graphics_tmp.lineTo(0, _size-height); // trace au point 7
graphics_tmp.lineTo(_size, (_size / 2) - height); // trace au point 3
graphics_tmp.moveTo(-_size, (_size / 2) - height); // va au point 5
graphics_tmp.lineTo(0, _size-height); // trace au point 7
}

La grille

La grille c’est quoi?

La grille c’est l’élément qui va faire la liaison entre ce que le joueur va voir et ce que le programme va comprendre et interpréter. En gros c’est ce qui va nous permettre de représenter l’isométrique sous forme de variables et d’états.
La grille est en fait formée d’une série de cellules, x en largeur et y en longueur.
Ici x et y ne correspondent pas aux x et y de l’application, mais bien au x et y de notre grille.
C’est donc à  nous de les imaginer.

Toute ces cellules sont enregistrées dans un tableau à  2 dimensions (TableauDeCellule[x][y]) lors de leur création.
Cela nous permettra donc de naviguer dans la grille.

En ajoutant (ou en retirant) 1 à  une de ces 2 valeurs nous pouvons simuler un déplacement.
C’est en répétant cette opération que nous déplaceront notre vaisseau, mais aussi les projectiles et les ennemis.
Le tout est de savoir quand et ou les déplacer.

Un petit exemple parce que, quand même, l’auteur il ne sait pas s’exprimer correctement:

Quand le joueur va pousser sur la flèche de gauche, nous allons juste changer la cellule qui détient le vaisseau gentil.
Nous déterminons le x de la nouvelle cellule en prenant le x de la cellule actuelle +1.
Après nous demandons à  la nouvelle cellule d’ajouter le vaisseau comme enfant d’elle même (addChild).

Cela donnera donc l’impression que le vaisseau s’est déplacé.



Le jeu

Maintenant que nous savons dessiner de l’isométrique en AS3, utilisons ce nouveau super pouvoir dans notre jeu.

NB : Pour programmer ce jeu je n’ai pas réellement utilisé Flash. J’ai compilé du Flash avec le programme gratuit FlashDevelop et les librairies gratuites du Flex SDK et du components.swc de Flash. Vous pouvez donc compiler et coder ce jeu sans avoir besoin d’une licence de Flash.

Je vous conseille de vous munir des sources du jeux pour suivre ce que je vais expliquer ci-dessous, ça sera plus simple pour vous. Le sources sont disponibles au début de l’article.

Revenons à  ce qui nous intéresse, à  savoir : tirer sur les méchants.

Nous allons donc commencer notre petit jeu par:

Définir la classe mère (Base.as)

Cette classe est très simple. Tout d’abord nous allons définir nos variables et appeler nos méthodes d’initialisation dans notre constructeur (qui sera lui même appelé par défaut au lancement de l’application flash).

Voici ce que ça donne:

private var _debugText:TextField = new TextField();
private var _stepText:TextField = new TextField();
private var _engine:Engine;

public function Base()
{
_initEngine();
_initDebugTrace();
_initStepTrace();
}

Je pense que le code se suffit à  lui même.

Passons donc à  la définition des dites méthodes d’initialisation. à€ savoir :

  • _initEngine
  • _initDebugTrace
  • _initStepTrace

_initEngine :

private function _initEngine():void {
_engine = new Engine(this);
_engine.x = 300;
_engine.y = 100;
addChild(_engine);
}

Cette méthode se charge de créer une nouvelle instance de l’objet engine ( _engine = new Engine() )   en lui spécifiant un paramètre (this) qui lui sera utile. Et ensuite de le placer correctement sur la scène.
L’objet engine sera en fait une instance du jeu que nous allons créer, Le cerveaux pour ainsi dire.

DebugTrace :

private function _initDebugTrace():void {
_debugText.htmlText = "initialisation";
_debugText.width = 100;
_debugText.height = 500;
_debugText.multiline = true;
_debugText.wordWrap = true;
addChild(_debugText);
}
public function debugTrace(t:String):void {
_debugText.htmlText += t;
_debugText.scrollV = _debugText.maxScrollV;
}

Le debugTrace sera utilisé pour avoir un affichage des messages de debug directement dans notre application Flash.
Le textField est initialisé dans _initDebugTrace, et la méthode debugTrace est appelée pour ajouter un message.

Lors de l’ajout d’un message nous forçons le texte à  scroller à  son maximum ( _debugText.scrollV = _debugText.maxScrollV )   dans le but de toujours voir les derniers messages.

StepTrace :

private function _initStepTrace():void {
_stepText.htmlText = "----";
_stepText.width = 100;
_stepText.x = 500
addChild(_stepText);
}
public function stepTrace(t:String):void {
_stepText.htmlText = t;
}

Même chose ici, à  part que _stepText va afficher des messages envoyés par le jeu à  chaque étape.

Rien de bien compliqué dans tout ceci et donc, nous en avons déjà  fini avec cette classe.
Le jeu a un environnement près à  l’accueillir !


Poursuivons donc notre palpitante aventure de programmation en :

Créant les gentils et les méchants (ship.as + Bullet.as et Creep.as)

Ici aussi c’est très simple.

Nos 3 classes ont une méthode _draw() qui va tout simplement dessiner la représentation isométrique de l’objet. Comme nous avons vu ce procédé ci-dessus, je ne vais pas m’appesantir sur la question.

La classe des méchants (Creep.as) a quand même une petite spécificité bien à  elle, elle a une autre méthode : youAreTheKiller(). Cette méthode est appelée quand un méchant vient de vous tuer, elle permet de mettre le meurtrieur en évidence.

Il nous reste une dernière classe qui à  son importance visuellement, mais pas que :


Cell.as

Dans cette classe nous retrouvons la méthode _draw(), qui comme vous l’imaginez trace visuellement la cellule sur la scène, mais nous trouvons également les méthodes youHaveTheShip(), youHaveACreep(), youHaveABullet(), dropCreep() et dropBullet().

Comme tout ceci est simple et clair, passons à  la suite … noooon je rigole. Je ne vais pas vous laissez tomber maintenant, hein.


La méthode youHaveTheShip() est appelée par le jeu quand notre vaisseau va se déplacer vers une cellule bien précise. Lors de ce déplacement nous allons vérifier que la case est sure. Si oui, tout se passe bien, le vaisseau se déplace. Si non rien ne va plus et nous mourrons.

La méthode youHaveACreep() à  le même type de fonction mais pour un méchant cette fois. Nous vérifions également si la case est libre. Si oui, tout baigne. Si non, nous allons être confrontés à  deux cas bien différents.

  1. La case est occupée par un autre méchant et donc nous devons choisir une autre destination.
  2. La case est occupée par le vaisseau et nous allons donc le trucider!
Passons à  la méthode youHaveABullet(). On appelle cette méthode ci quand on veut nous transférer un projectile. Si la case est libre, le missile fait son petit bonhomme de chemin, si elle est occupée par un ennemi, celui-ci est détruit.
Reste donc dropCreep() et dropBullet() qui sont appelées quand un méchant se fait tirer dessus. Elle servent a détruire le méchant ainsi que le projectile.

L’IA (ou incompétence archificielle)

Nous voila à  l’apothéose de notre jeu.

Nous allons enfin créer la classe ultime qui va régir tout le bordel, notre Big Brother rien qu’a nous! Vous êtes près?

Allez, c’est parti, créons ensemble la classe Engine.as (plus communément appelée GG pour Game’s God).

GG va en fait nous servir a relier toutes les classes que nous venons de créer. C’est elle qui va appeler les méthodes publiques des différents objets dans le but d’organiser une partie cohérente où tout s’entremêle en un savant mélange (enfin tout ça est théorique…). Je vais passer sous silence les différentes propriétés de notre classe (variables) car leur utilité deviendra logique lors de la création des méthodes. Attelons-nous donc a la création de celles-ci.

Tout d’abord _initMap(). Cette méthode va initialiser l’environnement de jeu en créant la grille. Un système de tableaux à  deux dimensions (boucles for imbriquées) est ici utilisé pour représenter sous forme de variables ce que nous avons sur notre écran. Tout ceci dans le but de pouvoir retrouver chaque cellule individuellement.

La première boucle représente les colonnes et la seconde, les lignes.

_mapArray = new Array();
 for (var i:int = 0; i < CELL_NBR_X ; i++ ) {
_mapArray[i] = new Array();
for (var j:int = 0; j < CELL_NBR_Y ; j++) {
_mapArray[i][j] = new Cell(this, CELL_SIZE, i, j);
Sprite(_mapArray[i][j]).x = (-i*CELL_SIZE)+(j*CELL_SIZE);
Sprite(_mapArray[i][j]).y = (j*CELL_SIZE/2)+(i*CELL_SIZE/2);

Sprite(_mapArray[i][j]).name = "Cell_"+String(j*CELL_NBR_X+i);

addChild(_mapArray[i][j]);
}
 }

Viennent ensuite ces quatre lignes qui semblent ne contenir rien de logique, au contraire :

La première sert a ajouter une nouvelle cellule dans l’index actuel de notre tableau à  deux dimensions, tout en spécifiant à  cette nouvelle cellule sa position.

La seconde ainsi que la troisième ne sont rien d’autre que le calcul nécessaire au placement visuel de cette nouvelle cellule en X et en Y. En fait grâce à  l’index actuel (i et j) nous calculons sa position suivant la taille des cellules définie dans une constante ( CELL_SIZE ).

La dernière ligne n’est rien d’autre que l’assignement d’un nom unique a cette nouvelle cellule.

_mapArray[i][j] = new Cell(this, CELL_SIZE, i, j);
sprite(_mapArray[i][j]).x = (-i*CELL_SIZE)+(j*CELL_SIZE);
Sprite(_mapArray[i][j]).y = (j*CELL_SIZE/2)+(i*CELL_SIZE/2);
Sprite(_mapArray[i][j]).name = "Cell_"+String(j*CELL_NBR_X+i);

La méthode _initControls() se charge juste de créer et de positionner les bouton de l’interface. Rien de bien sorcier.


_startGame() par contre est cruciale. En effet, elle sert à  lancer le jeu. Elle doit donc mettre tout en place pour que le jeu tourne sans accroc et que chacun soit à  sa place.

Voici donc dans l’ordre les choses importantes qu’elle effectue. On va tout d’abord supprimer et désactiver le bouton de lancement du jeu.

private function _startGame(e:MouseEvent):void {
_startButton.removeEventListener(MouseEvent.CLICK, _startGame)
_startButton.enabled = false;
_daddy.debugTrace("Game starting");

Ensuite nous allons créer notre vaisseau de combat (new Ship) en lui donnant sa taille (CELL_SIZE) et le nommer.

_userShip = new Ship(this, CELL_SIZE);
_userShip.name = "ship";

Ensuite nous allons déterminer la cellule qui va accueillir le vaisseau, à  savoir la cellule au centre de la 2ème ligne. Et lui signifier grâce à  la méthode ( youHaveTheShip(_userShip) ).

_currentShipCellX = Math.round(CELL_NBR_X / 2);
_currentShipCellY = CELL_NBR_Y - 2;
Cell(_mapArray[_currentShipCellX][_currentShipCellY]).youHaveTheShip(_userShip);

Nous activons l’écouteur du clavier, utile pour les déplacements et tirs.

_daddy.stage.addEventListener(KeyboardEvent.KEY_DOWN, _keyPress);

Puis nous créons et lançons les compteurs pour les vagues de monstres et pour la vitesse des missiles. Sans oublier de leur assigner un écouteur.


_gameTimer = new Timer(CREEPS_DELAY);

_gameTimer.addEventListener(TimerEvent.TIMER, _stepCreep);

_gameTimer.start();

_bulletTimer = new Timer(BULLETS_DELAY);

_bulletTimer.addEventListener(TimerEvent.TIMER, _stepBullet);

_bulletTimer.start();

Et enfin nous créons des tableaux récapitulatifs pour les bullets et pour les creeps. Ceux-ci contiendront toutes les cellules qui ont en ce moment un monstre ou un missile chez elles.

Et bien sur un petit lancement de la 1ère vague de monstres avec _createCreep().

_cellsHavingBullet = new Array();
_cellsHavingCreep = new Array();
_createCreep(_nbrCreepsSpawn);

_daddy.debugTrace("Game started");
 }

Viennent ensuite 6 classes qui peuvent se résumer à  3 classes. En effet nous avons deux classes _step, deux classes _move, et deux classes _create. Chaque paire est liée l’une au creeps, l’autre aux bullets.

Elles servent respectivement à  :

_stepCreep() et _stepBullet()

Elles gèrent les fonctionnements déclenchés par les timers. Pour les bullets, ça se résume à  les déplacer vers le haut.

Pour les méchants par contre nous avons des fonctionnements plus élaborés.

Pour tout les méchants, comme pour les missiles, nous allons demander un déplacement grâce à  la fonction forEach. Elle appellera la fonction _move… une fois pour chaque objet du tableau récapitulatif.

Mais nous allons aussi effectuer plusieurs tests.

  1. si le temps entre chaque vague de monstre est atteint, nous lançons une nouvelle vague de monstre
  2. si le temps entre chaque augmentation des ennemis est atteint, nous augmentons le nombre de méchants.
  3. toutes les 30 secondes nous augmentons la vitesse du jeu.
_cellsHavingCreep_tmp = new Array();
_cellsHavingCreep.forEach(_moveCreep);
_cellsHavingCreep = _cellsHavingCreep_tmp;

if (_gameTimer.currentCount / _stepsBetweenSpawn is int) { //test si on doit lancer une nouvelle vague
_createCreep(_nbrCreepsSpawn);
_daddy.debugTrace(_nbrCreepsSpawn+" creeps spawn");
}

if (_gameTimer.currentCount / NBR_CREEPS_RAISE is int && _nbrCreepsSpawn<CELL_NBR_X) { // test si on doit augmenter le nombre de monstre par vague
_nbrCreepsSpawn++
_daddy.debugTrace("Creeps per spawn raise to "+_nbrCreepsSpawn);
}

if (_gameTimer.currentCount / 30 is int) { // test si on doit augmenter la vistesse du jeu
_gameTimer.delay /= ACCELERATION;
_daddy.debugTrace("delay decrase to"+Math.round(_gameTimer.delay));
}

_moveBullet() et _moveCreep()

Ces 2 méthodes servent a déplacer les missiles et les méchants de cellule en cellule, de prévenir les cellules concernées, et d’effectuer certains tests de sécurité pour ne pas avoir de bug. La méthode _moveCreep() a néanmoins une petite spécificité : à  partir d’un certain moment, elle va autoriser les méchants a se déplacer latéralement et plus uniquement en ligne droite, histoire de corser un peu l’affaire ( et de rendre le tout complètement incompréhensible).

_createBullet() et _createCreep()

Ces méthodes sont elles utilisées pour créer des méchants et des missiles. Les méchants lors des nouvelles vagues de monstres et les missiles lors d’un tir du joueur. Rien de bien compliqué. Les petites dernières Nous en arrivons à  la fonction _keyPress() qui comme vous l’aurez deviné gère tout les appels qu’effectue le joueur en appuyant sur le clavier. à€ savoir, les déplacements et tirs du vaisseau. Et la petite dernière : gameOver(). je vous laisse deviner son fonctionnement. ;) Voila nous en avons fini avec le code.

Les graphismes

Ah ben non, c’est vrai, y en a pas.

Le reste

Je suis bien au fait des limitations de mon petit jeu, et j’entends déjà  les critiques poindre, à  savoir :

  •  » Mais, c’est codé comme un cochon !  » ( oui mais le but est d’appréhender l’isométrique, à  la base )
  •  » Dis, t’es sûr que t’es infographiste toi ?  » ( oui, c’est de l’art comptant pour rien )
  •  » Euh, ça par en coui*** après 30 secondes ton jeu.  » ( j’avoue que mon jeu n’est pas viable a long terme, il ne tient qu’à  vous de l’améliorer)
  •  » C’est où que t’a appris à  écrire ? (no comment)
Et bien sur cette liste n’est pas exhaustive.
Pour ce qui se sont vraiment pris au jeu ( jeu de mot ahaha ), je vous invite à  continuer à  coder dedans et à  nous montrer ce que ça donne. Je suis bien entendu à  l’écoute de toutes vos questions, n’ayez pas peur, je ne mors pas. Ah oui, j’oubliais. Les sources sont disponibles sous licence Creative commons.BY-NC-SA Bon développement.

5 Comments to “Action Script 3 : Mini-jeu en isométrique”

Laisser un commentaire