lundi 27 septembre 2010

16 - Inventaire du Marchand - Achat d'Objets

Une fois que le marchand a fini son bavardage, il dévoile au Héros la liste des objets qu'il met en vente.
Le marchand propose un objet de chaque type : Une armure, un bandeau, une épée, un bouclier, un anneau, une paire de bottes, un morceau de viande, une potion, et un parchemin permettant d'apprendre un sort optionnel.

La puissance de chacun des équipements est tirée au sort entre deux valeurs ; cette fourchette de valeurs dépend de l'étage actuel, comme c'était le cas pour les trésors, mis à part qu'ici le maximum possède une valeur moindre : Ainsi une Épée trouvée dans un trésor à l'étage -18 donnera entre +12 et +22 Attaque, alorsqu'une Épée vendue par un marchand à ce même étage donnera seulement entre +12 et +16 Attaque.
En outre, si la puissance tirée au sort n'est multiple ni de 3, ni de 4, ni de 5, alors elle est diminuée de 1 jusqu'à ce qu'elle soit multiple d'au moins un de ces trois nombres.

La nature des deux objets de soin est déterminée par tirage au sort de la même manière que lorsqu'on les trouve dans des bourses : En fonction de l'étage, le pourcentage d'obtention de chaque type d'objet est différent (par exemple à l'étage -56 il y a 80% de chances de trouver une Viande+50 et 20% de chances de trouver une Viande+200).

Le parchemin de sort, pour finir, est lié lui aussi à l'étage actuel : Entre les étages 10 et 99 le marchand proposera le sort "Provoke" (provoquer), entre les étages 100 et 999 il proposera le sort "EnemHeal" (soin ennemi), etc.

Une fois la nature des objets vendus déterminée, il faut définir leur prix de vente. Pour les objets de type consommable et parchemin, ce prix est fixe ; en revanche pour les équipements, je dois prendre en compte le prix de revente de l'ancien équipement :
  • Pour les armures, le prix de vente est égal au bonus de PV ;
  • Pour les bandeaux, le prix de vente est égal au bonus de PM x 12 ;
  • Pour les autres pièces d'équipement, le prix de vente est égal au bonus de stat x 10 ;
  • Le marchand rachète le vieil équipement du Héros 1/4 de son prix de vente (arrondi en dessous).
Le prix de vente réel est donc égal au prix de vente moins le prix de rachat du vieil équipement ; ce prix ne peut pas être inférieur à 1 pièce d'or (si c'est le cas, je lui impose de prendre la valeur 1).

Exemple : Le marchand vend une armure +112 PV (prix de vente : 112 pièces d'or) ; Le Héros porte une armure +98 PV (prix neuf : 97 pièces d'or, prix de revente : 24 pièces d'or) ; le prix annoncé par le marchand ne sera donc pas 112 mais 112-24=88 pièces d'or.


Maintenant que je connais l'inventaire du marchand, je dois le communiquer au joueur ; pour ce faire j'ai créé un Clip appelé "cadre_shop" qui rassemble les diverses icones des objets vendus et possède plusieurs textes dynamiques (pour afficher les stats et les prix des objets vendus). Les objets susceptibles d'intéresser le Héros ont leur prix indiqué en bleu, les objets ayant des stats inférieures aux stats actuelles ont leur prix indiqué en rouge.

Je retire certains objets de la vente :
  • Les objets qui sont identiques à ceux déjà équipés par le Héros ;
  • Les objets de soin que le Héros possède déjà en 9 exemplaires ;
  • Les parchemins de sorts que le Héros a déjà appris.
Ces objets sont recouverts par un Clip portant la mention "Sold Out" et leur achat est impossible.


L'étape suivante consiste à permettre au joueur de sélectionner un objet et de l'acheter : J'ai créé un Clip représentant le curseur de sélection (4 triangles roses qui clignotent) et j'ai fait en sorte qu'on puisse le faire se déplacer d'une icone à une autre par simple pression sur une des 4 directions ; un fois l'objet choisi, une pression sur la touche "Entrée" permet de valider l'achat (à condition que le Héros possède suffisamment d'argent) : l'inventaire est alors modifié en conséquence, une sauvegarde automatique est effectuée, et l'affichage des objets vendus est actualisé (ajout d'étiquettes Sold Out si nécessaire).


Une fois la création du marchand terminée, j'ai procédé à quelques réglages afin que les joueurs ne deviennent pas trop riches trop rapidement : J'ai ainsi divisé par 2 les sommes trouvées dans les trésors, et grandement augmenté le prix des objets de soin. Mais il se peut que je modifie une nouvelle fois ces valeurs (dans un sens ou dans l'autre) une fois les combats contre les monstres implémentés...

samedi 25 septembre 2010

15 - Dialogue avec un PNJ

En dehors des trésors et des ennemis, il existe autre chose que le Héros peut trouver dans le Donjon : Des PNJ (Personnages Non Joueurs).

À l'origine je pensais utiliser différents types de PNJ :
  • Des marchands auprès de qui le Héros peut améliorer son équipement et s'approvisionner en objets de soin ;
  • Des vieux sages qui donnent des conseils quant à la bonne utilisation des différents sorts ;
  • Des aventuriers qui donnent des conseils sur la façon de battre les monstres.
Mais après réflexion, j'ai décidé de tout regrouper en un seul et même PNJ : Le marchand.


Quand le Héros entre en contact avec un marchand, il y a d'abord une phase de dialogue avant que le joueur ait accès à la liste des objets vendus ; pour chaque environnement du jeu, il existe 3 dialogues différents : En général il y a un message concernant le sort qui vient d'être appris lors du dernier Level Up, un message concernant le sort qui peut être acheté auprès du marchand, et le troisième concerne l'aventure au sens large (textes d'ambiance renforçant le background, mécaniques du jeu). Un tirage au sort détermine quel texte sera affiché.

Comme le marchand ne peut pas être rencontré dans la première zone, cela fait donc 18 textes au total, que j'avais rédigé un jour avant de me lancer dans la programmation ; je les ai donc intégré au jeu, en tenant compte des limitations de la fenêtre de texte : Celle-ci affiche en effet 6 lignes de 20 caractères, soit 120 caractères en tout (moins qu'un tweet !).

Il m'a donc fallu découper ces textes sur plusieurs pages, et reformuler certaines phrases pour qu'elles rentrent dans la fenêtre ; j'ai aussi mis certains mots-clé en avant en utilisant mes 2 variables "bleu" et "blanc". Au final, il y a donc pas mal de différences entre les textes d'origine et ceux qui seront visibles au cours du jeu.

jeudi 23 septembre 2010

14 - Level Up après ouverture d'un coffre

Comme je l'ai dit dans mon billet précédent, le score du Héros augmente de 1 chaque fois qu'il s'empare du trésor situé à l'extrême gauche de l'étage.
Si le score du Héros dépasse un certain seuil, son niveau augmente et il apprend un nouveau sort :

mardi 21 septembre 2010

13 - Sauvegarde Automatique - Changement d'Étage

Souvenez-vous : Il y a quelques semaines, lorsque je me suis lancé dans la programmation de ce jeu, ma première action a consisté à mettre au point les fichiers de sauvegarde.
Maintenant que les stats et l'inventaire du Héros peuvent être modifiés grâce aux trésors, il est temps que je mette à profit l'existence de ces fichiers afin de garder une trace de toutes ces modifications.
J'ai donc créé une fonction "autoSave" qui est appelée à chaque fois que le Héros ramasse un trésor et qui garde en mémoire la majorité des variables du jeu (stats, inventaire, High Score... ).

Je me suis ensuite occupé du changement d'étage : Quand le Héros s'empare du contenu du coffre situé à l'extrême gauche de l'étage, son score augmente de 1 et il est téléporté à l'entrée de l'étage suivant ; si son nouveau score est supérieur au High Score, je met ce dernier à jour et effectue une sauvegarde automatique.

lundi 20 septembre 2010

12 - Fenêtres de Textes - Modification de l'Inventaire - Choix

Une fois que le contenu du trésor a été tiré au sort, il faut transmettre cette information au joueur ; pour ce faire, j'utilise une fenêtre de texte au design une fois de plus rétro (texte blanc sur fond noir, cadre blanc pixelisé). À l'intérieur du Clip "fenêtre de texte" se trouve un objet "Texte dynamique", c'est à dire un texte que je peux modifier en fonction du message que je souhaite afficher.

Au moment d'effectuer mes recherches graphiques, j'ai opté pour une typo pixelisée appelée "Small Fonts" ; le problème c'est que cette typo est au format .fon, alors que SWISH Max utilise le format .ttf : J'ai donc dû chercher une typo TrueType qui, une fois l'option "police pixelisée" activée, ressemblerait suffisamment à "Small Fonts". Et fort heureusement parmi toutes les polices installées sur mon ordi il y en a une qui répond à ce critère : "Franklin Gothic Medium". La seule différence notable est la présence d'une barre sous le 1.

J'ai donc utilisé cette typo pour l'affichage des stats. Et je comptais bien l'utiliser aussi pour afficher les divers messages du jeu, sauf que...


Il est possible d'intégrer des balises HTML au sein d'un objet "texte dynamique" en activant l'option adéquate. Cela permet de modifier l'apparence de certains éléments du texte (mots en gras, en italique, soulignés, changement de taille ou de couleur), et de mettre ainsi certains mots en avant.
Il se trouve que j'ai justement envie que, lorsque le joueur reçoit le message "Vous avez trouvé une Épée +50 ATT, voulez-vous l'équiper ?", l'expression "Épée +50 ATT" ne soit pas écrite en blanc mais en bleu.

Le changement de couleur se fait à l'aide de la balise <font color ="#B0BCF9"></font>, sauf que je ne peux pas utiliser de guillemets tels quels car ils servent à marquer le début et la fin d'une chaîne de caractères ; je suis donc obligé d'utiliser le code ASCII du caractère guillemet à la place : chr(34).

message = "Le début est en blanc <font color=" add chr(34) add "#B0BCF9" add chr(34) add ">puis en bleu</font> et de nouveau en blanc !";
monTexteDynamique.text = message;

edit : suite à une remarque judicieuse de Fitz (voir les commentaires), j'ai découvert une autre façon d'intégrer des guillemets à ma chaîne sans pour autant passer par le code ASCII : Il me suffit juste de marquer le début et la fin de la chaîne avec des apostrophes et non des guillemets !
Le code ci-dessus aurait en fait pu être remplacé par :
message = 'Le début est en blanc <font color="#B0BCF9">puis en bleu</font> et de nouveau en blanc !';
monTexteDynamique.text = message;


Pour me simplifier la vie, je me suis créé une variable "bleu" et une variable "blanc" qui me permettent de changer de couleur facilement ; la variable "bleu" correspond en fait à la balise d'ouverture <font color ="#B0BCF9"> tandis que la variable "blanc" correspond à la balise de fermeture </font> :

bleu = "<font color=" add chr(34) add "#B0BCF9" add chr(34) add ">";
blanc = "</font>";
message = "Le début est en blanc " add bleu add "puis en bleu" add blanc add " et de nouveau en blanc !";
monTexteDynamique.text = message;

Le problème, c'est que l'activation des balises HTML a pour conséquence la désactivation de l'option "typo pixelisée" : Mon texte est certes multicolore, mais devient par la même occasion tout lisse !

J'ai donc dû me mettre en quête d'une typo qui soit pixelisée d'origine afin de pouvoir utiliser les balises HTML sans risque de lissage. Et mon choix s'est porté sur Apple ][ (une typo gratuite créée par Ivan Rood), même si l'espacement entre les caractères est plus important ; j'apprécie le fait que chaque caractère occupe un espace de taille identique, ça fait encore plus "vieux jeu 8-bits".

J'ai donc ma fenêtre de texte et ma typo, il me suffit juste d'afficher tout ça au moment adéquat et de modifier mon texte dynamique en fonction du message que je souhaite afficher ; cela donne à peu près ceci :

Une simple pression sur "Entrée" ferme la fenêtre de texte et permet au jeu de reprendre son cours.

Évidemment ce n'est pas tout de dire qu'une Potion+30 a été trouvée : Il faut aussi que l'inventaire soit modifié en conséquence, et que l'affichage soit actualisé pour témoigner de cette modification (ce qui est le cas sur le screen ci-dessus).


Pour les objets que le Héros peut équiper, les choses sont légèrement différentes : Le joueur a le choix entre équiper l'objet trouvé et jeter l'ancien, ou garder l'ancien et jeter le nouveau.
Ce choix s'effectue via un curseur qui se déplace lorsqu'on appuie sur les touches "Gauche" ou "Droite" et qui sélectionne deux cadres ("Yes" et "No") ; par défaut le curseur pointe sur la réponse qui privilégie le bonus de stat le plus important : Si le Héros ramasse une Épée +10 alors qu'il a une Épée +30, c'est la réponse "No" qui sera sélectionnée dès le départ.

Dans l'exemple de la capture d'écran ci-dessus, si le joueur appuie sur "Entrée", la stat "Attaque" (représentée par l'icone Épée) passera de 8 à 9 et l'Épée +8 sera jetée : Le coût en pièces d'or de cette Épée jetée sera ajoutée au "Junk" (l'icone Poubelle en haut à droite du cadre ITEMS) ; le Héros pourra par la suite convertir son Junk en pièces d'or s'il rencontre un marchand qui lui rachète ses objets inutiles.

samedi 18 septembre 2010

11 - Contenu des Trésors

Le contenu des trésors est différent selon qu'il s'agit d'une bourse ou d'un coffre :
  • Bourse : 50% de chances d'obtenir un équipement de puissance faible, 20% de chances de trouver une petite somme d'argent, 30% de chances de trouver un objet de soin (viande ou potion) ;
  • Coffre en bois : 75% de chances d'obtenir un équipement de puissance moyenne, 25% de chances de trouver une grosse somme d'argent ;
  • Coffre en métal rouge et or : 100% de chances d'obtenir un équipement de puissance supérieure.
À partir de ces pourcentages j'ai créé plusieurs variables de type "tableau": À chaque fois que le Héros entre en contact avec un trésor, on tire au sort une case du tableau correspondant afin de déterminer le contenu du trésor.

Exemple : La variable _global.contenuBourse est un tableau à 100 cases : 50 cases ont pour contenu la chaîne de caractères "Equipment", 20 cases ont pour contenu "Gold" et les 30 dernières ont pour contenu "Item". Si le Héros ramasse une bourse, je tire un nombre entre 1 et 100 * et je regarde le contenu de la case portant ce numéro.
( * : En réalité la première case d'une variable de type Array (tableau) a pour indice 0 ; en toute rigueur ce n'est donc pas un nombre entre 1 et 100 qui est tiré, mais un nombre entre 0 et 99. )

Si le trésor est de type "Equipment", il faut effectuer un nouveau tirage au sort afin de déterminer la nature de l'équipement (Épée, Armure, Bottes... ).
Le taux d'obtention de chaque type d'équipement est lié à l'élément de départ du Héros ; ainsi le Fire Knight (Chevalier de Feu) aura 24% de chances de trouver une Épée, 16% de chances de trouver un Anneau, 10% de trouver un Bouclier, etc.
Ces pourcentages sont eux aussi mis sous forme de tableau à 100 cases.

Une fois la nature du trésor déterminée, un dernier tirage au sort permet d'en déterminer la puissance ; ce tirage au sort s'effectue entre 2 valeurs, liées à l'étage où se trouve le Héros : Une Épée trouvée à l'étage -18 donnera entre +12 et +22 Attaque, tandis qu'une Épée trouvée à l'étage -180910 donnera entre +63 et +79 Attaque.

vendredi 17 septembre 2010

10 - Génération des Trésors

Au moment de la génération du Donjon, un trésor peut être créé dans chacune des salles qui composent l'étage.
Il y a forcément un coffre dans la salle la plus à gauche de l'étage (probabilité de 100%) ; en revanche dans la salle suivante la probabilité d'apparition d'un trésor est nulle.
Pour les autres salles, la probabilité d'apparition d'un trésor est liée à la position de la salle : Plus on s'approche du bord droit de l'écran, plus cette probabilité est faible (50% au mieux, 25% au minimum).
En fonction de ces diverses probabilités, je détermine pour chacune des salles si oui ou non un trésor va être généré. Si c'est le cas, je fais la liste des positions potentielles en fonction du type de salle (exactement comme je l'ai fait pour les monstres, en pensant à exclure la position du Héros de la liste) et j'en tire une au sort.

L'apparence du trésor est liée à sa position : Le trésor situé à l'extrême gauche est représenté par un coffre en bois, les trésors des autres salles sont représentés par des bourses ; l'apparence du trésor détermine la qualité des objets que l'on peut trouver dedans (les objets trouvés dans des coffres en bois sont plus puissants que ceux trouvés dans des bourses). Il parait même qu'il existe des coffres en métal rouges renfermant des objets prodigieux...

De plus, à chaque fois qu'un étage est généré, il y a une chance sur 3 pour qu'une des bourses de l'étage soit remplacée par un marchand.

Dans le cercle rouge : Le coffre en bois situé à l'extrême gauche de l'étage.
Dans les cercles verts : Des bourses.
Dans le cercle jaune : Un marchand (qui a remplacé une des bourses après tirage au sort).

Normalement je m'occuperai du contenu des trésors demain ; en attendant, j'ai ajouté un mode "pause" à mon jeu : Une simple pression sur la touche "Entrée" interrompt le mouvement des personnages (Héros et monstres), et une seconde pression permet au jeu de reprendre son cours.

jeudi 16 septembre 2010

09 - Déplacement des Ennemis

Avant de m'occuper du déplacement des ennemis, j'ai dû régler un dernier détail ; j'ai en effet oublié de tenir compte de la possibilité que les ennemis soient générés sur la même tile que le Héros, une situation que je veux bien sûr éviter.

J'ai donc modifié mon code : Au moment de faire la liste des positions potentielles, je vérifie d'abord que chacune des positions est différente de celle du Héros ; si c'est le cas je l'ajoute à la liste, dans le cas contraire je l'ignore.

function comparePosTestHero(ar, test){
/*
cette fonction compare la position "test" avec la position du Héros ; si ces deux positions sont différentes, la position "test" est ajoutée à la liste des potentielles ("ar")
*/
if (_global.positionHeros[0] !== test[0] || _global.positionHeros[1] !== test[1]){
ar.push(test);
}
return (ar);
}

Cette fonction me servira aussi plus tard lorsque je m'occuperai de la génération des trésors...

Une fois ce détail réglé, j'ai donc pu programmer le déplacement des ennemis ; à chaque ennemi est associé une série de variables :
  • "ligne" et "colonne" permettent de déterminer la position de l'ennemi ;
  • "vitesse" représente la vitesse de déplacement de l'ennemi (16/12 pour "lent", 16/8 pour "normal", 16/6 pour "rapide") ;
  • "déplacement" représente le nombre de pixels à parcourir avant d'atteindre la position suivante (nombre entre 0 et 16) ;
  • "direction" représente la direction de déplacement (1 bas, 2 gauche, 3 haut, 4 droite) ; sa valeur initiale est fixée à -20.
À chaque frame, on vérifie la variable "déplacement" : Si elle est négative, elle augmente de 1 (cela permet donc d'immobiliser les ennemis pendant 20 frames, soit 0,8 seconde, afin que le Héros ne se fasse pas sauter dessus dès son arrivée dans l'étage) ; si elle est supérieure ou égale à 0, on vérifie les 4 tiles autour du monstre : chaque direction pointant vers une tile de type "dalle" est ajoutée à la liste des directions potentielles, sauf s'il s'agit de la direction d'où vient l'ennemi.

Exemple :
1 : Le monstre a le choix entre 3 directions : "gauche", "haut" et "droite". La direction "bas" est impossible à cause de la brique.
2 : Parmi les 3 directions possibles, c'est la direction "haut" qui a été tirée au sort ; le monstre parcourt donc 16 pixels vers le haut, puis on détermine le déplacement suivant. Seule la direction "haut" est possible (la direction "bas"est retirée de la liste des directions potentielles pour empêcher le monstre de revenir sur ses pas).
3 : Le monstre a le choix entre "gauche" et "haut" ("droite" est impossible à cause de la brique, "bas" est retiré de la liste pour empêcher le monstre de revenir sur ses pas).

Si le monstre se retrouve dans une impasse (la liste des directions potentielles est vide), alors seulement il a le droit de revenir sur ses pas.

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).