dimanche 12 septembre 2010

08 - Tests de Collision

Maintenant que j'ai placé mes monstres, la prochaine étape devrait en toute logique consister à les faire se déplacer dans le Donjon.
Néanmoins j'ai décidé aujourd'hui de profiter de leur immobilité pour m'occuper de tout autre chose : les tests de collision.

Un test de collision, c'est ce qui permet de savoir que deux sprites de mon choix sont entrés en contact ; c'est une donnée importante, car à chaque fois que le Héros entre en contact avec un ennemi, la phase d'exploration est remplacée par une phase de combat.

Étant donné que le sprite du Héros dépasse un peu du cadre de 16 pixels de côté qui forme chaque tile, je m'attendais à avoir quelques soucis lors des tests de collision :

Sur l'image ci-dessus, on voit que la coiffure du Héros touche le monstre "Goblin" alors que les deux personnages ne sont pas sur la même tile. Comme je n'ai pas envie qu'une telle situation ne débouche sur un combat, je dois donc faire en sorte que le test de collision soit négatif (même si à l'image les deux sprites sont en contact).

Finalement la méthode à utiliser s'est avérée bien plus simple que prévue ; j'ai juste placé le code suivant dans le Clip du monstre :

onSelfEvent (enterFrame) {
if (_root.hero.isNearThis(0)) {
trace ("collision avec " add identiteClip);
}
}

"isNearThis" vérifie la collision non pas avec les sprites en entier mais seulement avec un point précis de chaque sprite (le point "central", qui est en fait situé en haut à gauche de la tile). La collision est détectée si la distance entre ces deux points est inférieure ou égale à une certaine valeur (que j'ai ici fixée à 0 pixel, mais qui aurait pu être n'importe quel autre nombre strictement inférieur à 16).

Bref tout marche comme prévu, je vais donc pouvoir m'occuper la prochaine fois du déplacement des ennemis...

samedi 11 septembre 2010

07 - Génération des Ennemis

Au moment de la génération du Donjon, un ennemi est créé dans chacune des salles qui composent l'étage.
Pour chaque ennemi créé, je dois déterminer deux choses : Sa position initiale et sa nature.

Chaque type de salle possède plusieurs positions initiales potentielles pour les ennemis ; par exemple dans une salle en forme de "9" les ennemis apparaitront soit en haut à gauche, soit au milieu, soit en bas à droite.
Pour déterminer la position initiale réelle de l'ennemi, j'utilise une méthode similaire à celle que j'ai utilisée pour créer les jonctions entre les salles (billet n° 04) : J'étudie chaque salle une à une, et à chaque fois je fais la liste de toutes les possibilités avant d'en tirer une au sort.

Chacun des 7 environnements graphiques du jeu propose son propre bestiaire ; ce dernier est constitué de 2 types de monstres : Un monstre commun (2 chance sur 3) et un monstre rare (1 chance sur 3).
Au moment de créer l'ennemi j'effectue donc un tirage au sort pour déterminer s'il s'agit d'un monstre commun ou rare, et en fonction de l'environnement graphique j'affiche le sprite adéquat ; tous ces sprites ont été regroupés dans un seul et même Clip, et chaque frame de ce Clip correspond à un type d'ennemi.

Contrairement au Héros qui possède 8 positions différentes (4 statiques et 4 déplacements), les ennemis n'en ont qu'une seule : En effet ces derniers sont toujours vus de face, quelle que soit la direction dans laquelle ils se déplacent ; comme ils sont constamment en mouvement, ils n'ont pas besoin de positions de repos.
Ce parti-pris minimaliste me permet de réduire considérablement le travail graphique (3 sprites à dessiner au lieu de 10, ou plus précisément 2 au lieu de 6 - certains sprites sont en effet de simples copiers-collers retournés par symétrie horizontale) tout en accentuant le côté rétro du jeu.


Sur la capture d'écran ci-dessus, on voit bien les 3 différentes positions initiales disponibles pour une salle en forme de "6" (en haut à gauche, au milieu, en bas à droite), ainsi que la rareté du monstre "Minotaur"(marron) par rapport au monstre "Demon Knight" (vert).

jeudi 9 septembre 2010

06 - Déplacement du Héros

Le déplacement du Héros fait intervenir 2 variables :
  • une variable de direction (_global.directionHeros) qui indique la direction du Héros (et permet d'afficher la bonne frame du Clip sprite_hero dont je vous parlais dans mon billet précédent).
  • une variable de déplacement (_global.deplacementHéros) qui indique le nombre de pixels à parcourir avant que le Héros n'ait fini son déplacement.
Quand le joueur appuie sur une touche de direction du clavier, si la variable de déplacement est nulle, alors on regarde si le Héros peut se déplacer dans cette direction ou s'il y a un obstacle qui l'en empêche. Si le Héros peut passer, alors la variable déplacement prend la valeur 16 (je rappelle que mes tiles sont des carrés de 16 pixels de côté, et que le Héros doit donc se déplacer de 16 pixels pour passer d'une tile à l'autre).

Pour les extraits de code ci-dessous, je ne vais considérer que le bouton BAS :

directionVerticale = Key.isDown(Key.DOWN) - Key.isDown(Key.UP);
//si on appuie sur BAS, directionVerticale a pour valeur 1
//si on appuie sur HAUT, directionVerticale a pour valeur -1
//si on appuie sur BAS et HAUT, directionVerticale a pour valeur 0
directionHorizontale = Key.isDown(Key.RIGHT) - Key.isDown(Key.LEFT);
// même principe pour GAUCHE et DROITE
if (directionVerticale == 1 && directionHorizontale ==0){
// Le joueur a seulement appuyé sur BAS
if (_global.deplacementHeros ==0){
// la pression sur cette touche n'a d'effet que si le Héros n'est pas déjà en train de se déplacer
_global.directionHeros = 1;
if (_global.donjon[_global.positionHeros[0]][_global.positionHeros[1]+1] ==1){
// la tile en bas du Héros est de type "dalle" : le déplacement a lieu
_global.deplacementHeros += 16;
_global.positionHeros[1]++;
}
}
}

Si la variable déplacement n'est pas nulle, alors à chaque frame de la timeline le Héros se déplace à une vitesse que j'ai fixée à 2 pixels par frame : il faut donc 8 frames pour que le Héros passe d'une tile à l'autre, ce qui correspond à 0,32 secondes (le jeu tourne à 25 frames par seconde).

if (_global.deplacementHeros > 0){
// le Héros de déplace
if (_global.directionHeros == 1){
// Le Héros se déplace vers le BAS
hero._y += _global.vitesseHeros;
_global.deplacementHeros -= _global.vitesseHeros;
}
hero.gotoAndStop(_global.elementActuel * 10 + _global.directionHeros + 4);
//on affiche l'animation "déplacement vers le bas"
} else {
// déplacement nul : le Héros reprend une position statique
hero.gotoAndStop(_global.elementActuel * 10 + _global.directionHeros);
}

Ça y est, j'ai donc un personnage entièrement contrôlable au clavier qui se déplace dans le donjon sans passer à travers les murs !
Prochaine étape : Ajouter des monstres dans le Donjon...

mardi 7 septembre 2010

05 - Le Sprite du Héros

Le Héros de Math & Magic peut être vu sous 4 angles différents : de face, de dos, et de profil (gauche et droit).

Lorsque le Héros se déplace, j'utilise un système d'animation à quatre temps (rudimentaire, mais totalement dans l'esprit rétro que je cherche à donner à mon jeu) ; si par exemple j'applique cette méthode au cas du Héros se déplaçant vers le bas cela donne :
  1. Le Héros est vu de face, dans sa position de repos ;
  2. Le Héros met un pied en avant ;
  3. Le Héros reprend sa position de repos (sprite identique à celui de l'étape 1) ;
  4. Le Héros met l'autre pied en avant.
Ces quatre étapes d'animation se répètent en boucle aussi longtemps que le personnage se déplace dans cette direction.


Quand le déplacement s'arrête, le Héros reprend sa position de repos.

Pour les déplacements vers la gauche ou vers la droite, cette animation est simplifiée : J'alterne juste entre "le Héros est dans sa position de repos" et "le Héros écarte les jambes" ; en effet, vu le manque volontaire de détail de mes sprites 8-bits, je peux tout à fait me permettre d'utiliser la même image pour l'étape 2 et pour l'étape 4.

Donc pour un seul personnage, il existe en tout huit représentations différentes que je vais associer à un numéro : quatre positions statiques (1: de face, 2: de profil regardant à gauche, 3: de dos, 4: de profil regardant à droite) et quatre mouvements de déplacement (5: vers le bas, 6: vers la gauche, 7: vers le haut , 8: vers la droite).
Chaque Héros est lui même associé à un numéro (1: Chevalier de Feu, 2: Chevalier de Glace, 3: Chevalier de Terre).

Je place toutes ces représentations dans un seul et même Clip (appelé "sprite_hero"), en utilisant la formule suivante :

Numéro de la frame = 10 x Numéro du Héros + Numéro de la représentation

Ainsi la frame 11 de mon Clip sprite_hero représente le Chevalier de Feu en position statique vu de face, tandis que la frame 36 représente le Chevalier de Terre se déplaçant vers la gauche.


Pour finir, une petite image pour illustrer ce que je racontais hier au sujet des positions initiales du Héros en fonction de la nature de la dernière salle :

Comme vous le voyez, si la salle la plus à droite a la forme d'un "2", le Héros commencera en bas à gauche de cette salle ; si elle a la forme d'un "9", il commencera plus haut et tout à droite de la salle.


Ah, et un dernier mot sur le sprite du Héros :
Pour accentuer le côté rétro, je me suis limité à une palette de 4 couleurs pour chaque sprite et j'ai opté pour une résolution plus grossière : Alors que les sprites des dalles et des briques ont une dimension de 16 pixels de côté, les sprites des personnages semblent eux mesurer à peu près 8 pixels de côté (chaque "pixel" du Héros est en réalité un groupe de 4 pixels). Je dis "à peu près", car je me suis permis quelques dépassements (ne serait-ce qu'à cause de la coiffure).

dimanche 5 septembre 2010

04 - Jonction des salles - Position initiale du Héros

Aujourd'hui j'ai poursuivi la création du Donjon en me penchant sur la résolution du problème évoqué la dernière fois : Comment joindre les différentes salles entre elles ?

Chacune de ces salles est séparée par une colonne de briques. Ce que je souhaite, c'est assurer la jonction entre deux salles en remplaçant une de ces briques par une dalle.
J'ai suivi la méthode suivante : Pour chaque colonne de séparation de salles, j'étudie ligne par ligne la colonne précédente et la colonne suivante ; si, pour une ligne donnée, il y a une dalle dans la colonne précédente ET dans la colonne suivante, alors il est possible de joindre ces deux salles en remplaçant une brique de la colonne de séparation par une dalle. J'ajoute donc les coordonnées de cette brique à la liste des jonctions potentielles. Une fois toutes les lignes possibles étudiées, je tire au sort une valeur dans la liste des jonctions potentielles afin de déterminer quelle brique je vais remplacer par une dalle.

Exemple :
  • Pour la colonne 4 il n'existe qu'une seule possibilité : remplacer la brique de la ligne 3 par une dalle ;
  • Pour la colonne 8 il y a 2 possibilités : la ligne 1 et la ligne 3 ;
  • Pour la colonne 12 il y a 4 possibilités : les lignes 1, 3, 4 et 5 ;
  • Pour la colonne 16 il y a 3 possibilités : les lignes 1, 3 et 5.

Après tirage au sort, j'obtiens le tracé suivant :

Si j'effectue un nouveau tirage au sort, il y a de grandes chances pour que je tombe sur un tracé différent, comme celui-ci par exemple :

La question des jonctions entre les salles semble donc réglée... Pourtant, il reste encore un cas à envisager : Et si le nombre de jonctions potentielles entre deux salles est égal à zéro ?

L'image ci-dessus présente plusieurs paires de salles pour lesquelles la jonction sera impossible si on applique la méthode que je viens de vous présenter.

Il existe en fait 4 paires de salles concernées par ce problème : -1, -7, 14 et 17.
Pour ces 4 cas particuliers, je décide que la jonction s'effectuera selon un chemin bien précis que j'aurai déterminé à l'avance et qui nécessitera de transformer deux briques en dalles et non une seule.

Ce qui donne au final le tracé suivant :


La question du tracé est maintenant quasi réglée : Il ne reste plus qu'à s'occuper de la position initiale du Héros.
Quand le Héros arrive dans un nouvel étage, on est confronté à une des deux situations suivantes :
  • La position du Héros est la même qu'à l'étage précédent ; dans ce cas on doit s'assurer que la tile sur laquelle il se trouve n'est pas une brique, et pour ce faire on impose qu'elle soit de type dalle.
  • Le Héros commence son exploration dans la salle la plus à droite de l'étage ; dans ce cas la position initiale du Héros dépend de la nature de la salle (j'illustrerai ce point dans le billet suivant).