Affichage des articles dont le libellé est code. Afficher tous les articles
Affichage des articles dont le libellé est code. Afficher tous les articles

mardi 22 mars 2011

50 - Version 1.04

Voici les modifications apportées par la nouvelle version du jeu :
  • La typo utilisée lors des divers messages à l'écran (ouverture de coffre, dialogue avec le marchand, victoire à la fin d'un combat...) est correctement affichée.
  • Un message rappelle à la fin du Tutoriel qu'il suffit d'appuyer sur la touche "Entrée" pour quitter ce dernier.


Ça y est, le problème de typo est enfin réglé ; pour résumer, voici en quoi il consistait :
  • Lorsque j'affiche un texte dynamique (c'est-à-dire, un texte que je peux modifier en fonction du message que je souhaite afficher), la police utilisée est intégrée au jeu Flash et peut être vue correctement par n'importe quel utilisateur, même si elle n'est pas installée sur son ordinateur ; seule contrainte : les effets appliqués au texte (taille, couleur, gras, italique, etc.) affectent TOUT le texte.
  • Il existe une option "html" qui, si elle est cochée, permet d'utiliser des balises html au sein d'un texte dynamique : Cela permet de modifier ponctuellement certaines parties du texte, comme par exemple afficher des mots-clés dans une couleur différente pour les mettre en avant. C'est cette méthode que j'avais utilisée jusqu'à présent, mais elle a une conséquence indésirable : Dans ce cas la police n'est pas affichée convenablement si elle n'a pas été installée sur l'ordinateur de l'utilisateur !
On se retrouvait donc avec des textes qui ressemblaient à ça :


...alors qu'ils auraient dû ressembler à ça :


J'étais donc confronté à 2 choix : Soit je conserve les effets de couleur, et dans ce cas je renonce à me typo pixélisée ; soit je garde ma typo pixélisée, mais je dois alors renoncer aux effets de couleur.
J'ai donc opté pour la troisième solution :D :
Après avoir décoché l'option "html", j'ai créé une copie de l'objet texte que j'ai placée par dessus ce dernier ; alors que le texte original était de couleur blanche, j'ai donné à la copie la couleur bleue ; je me suis donc retrouvé avec deux textes à afficher : le blanc (texte d'origine) et le bleu (qui ne comprend que les mots-clés, les autres ayant été remplacés par des sauts de ligne et des espaces), avec le texte bleu qui se superpose au texte blanc et recouvre les mots-clés (communs aux deux textes).

Le code ressemble à ça :

_global.texte = "You open the treasure chest:\n\nYou have found the Key of Time!" ; // le texte d'origine
_global.texto = "\n\n\n\nKey of Time"; // le mot-clé ("\n" permet de sauter une ligne)
cadreTexte.textePNJ.text = _global.texte; // l'objet texte dynamique blanc affiche le texte d'origine
cadreTexte.textePNJb.text = _global.texto; // l'objet texte dynamique bleu, placé par dessus le blanc, n'affiche que le mot-clé

J'ai donc dû revoir un à un tous les textes afin de définir la valeur de la variable _global.texto, une tâche fastidieuse mais qui a porté ses fruits : Vous devriez désormais être en mesure de voir les textes affichés correctement, et ce même si la police Apple ][ n'est pas installée sur votre ordi... Et tout ça sans que j'aie eu à faire de compromis !

Il se peut néanmoins que, malgré ma vigilance, certains textes en bleu soient décalés par rapport à la version blanche : Si c'est le cas, n'hésitez pas à prendre une capture d'écran et à me le signaler ! ;)

jeudi 10 février 2011

41 - Intégration des Musiques

Une fois les musiques composées et converties au format mp3, il n'y a plus qu'à les intégrer au jeu pour qu'elles soient jouées au bon moment. Mais les choses ne sont pas si simples...

Durant le jeu, les phases d'exploration et de combat s'alternent, ce qui se traduit entre autres par un changement de musique.
Quand le combat est terminé, si la musique d'exploration recommence depuis le début, le joueur risque de se lasser facilement de cette dernière ; en effet, pour peu que les combats s'enchainent rapidement, il est fort probable que le joueur n'entende guère plus que l'introduction du morceau !

Afin que le joueur ait l'occasion d'entendre autre chose que l'introduction, on peut par exemple faire en sorte que la musique d'exploration continue pendant le combat, mais en sourdine.
En ActionScript 2, cela peut se faire facilement en attachant la musique d'exploration et la musique de combat à des variables de type "Son" différentes ; cela revient à dire que chaque musique est sur une piste différente, et il suffit de mettre le son de la piste "exploration" à zéro au début du combat et à 100 à la fin pour obtenir l'effet désiré :

var musique_exploration: Sound = new Sound();
musique_exploration.attachSound("bgm1");
var musique_combat: Sound = new Sound();
musique_combat.attachSound("combat");

Au début du combat :
musique_exploration.setVolume(0) // la musique ne s'arrête pas, mais son volume est à 0.

À la fin du combat :
musique_exploration.setVolume(100) // la musique est de nouveau audible.

Le problème, c'est que le langage utilisé par SWiSH Max 3 (le logiciel que j'utilise pour programmer mon jeu) n'est pas l'ActionScript 2 mais le SWISHscript ; bien que similaires dans plus de 90% des cas, ces deux langages possèdent quelques différences subtiles, et le son en fait partie : Avec SWiSH Max 3, l'utilisation de "setVolume" affecte TOUTES les variables "Son" définies ! Si je décide de mettre le volume de la musique d'exploration à zéro pendant le combat, alors la musique de combat sera elle aussi à zéro !

Heureusement, j'ai une autre possibilité pour empêcher la musique d'exploration de recommencer depuis le début après un combat : La mettre en "pause" au début du combat et la faire reprendre là où elle en était quand ce dernier est terminé.


Il existe deux façon de faire jouer un son avec SWiSH Max 3 :
  • Le son est associé à un Clip ou une Scène ; dans ce cas, il est possible de contrôler la musique avec "play()", "stop()", "GotoAndPlay()"... Cela permet entre autres de sauter l'intro d'un morceau, de répéter en boucle un passage précis, etc.
  • Le son est appelé depuis un évènement "OnFrame()" d'un clip ou d'une Scène avec l'instruction "playSound()" ; dans ce cas la musique continue même si la lecture du Clip ou de la Scène est stoppée, et on a accès à de nombreuses options : Choix du volume et du nombre de répétitions du morceau, effets de fade in et de fade out... Un inconvénient majeur cependant : Impossible d'utiliser "playSound()" depuis une fonction, une boucle ou une condition...

J'ai donc créé un Clip "musique" contenant lui-même les Clips "bgm1", "bgm2", "bgm3"...
Chacun de ces Clips "bgm" est placé sur une frame différente du Clip "musique" (bgm1 est sur la frame 2, bgm2 sur la frame 3, etc.).
Le choix de la musique étant lié à la variable "_global.rang", je me retrouve avec le code suivant :

if (_global.rang == _global.bgmActuelle){ // la musique est inchangée
}
else { //on change de musique
_global.bgmActuelle = _global.rang;
musique.gotoAndStop(_global.rang + 1);
}
if (_global.soundSwitch == false){
bgm.setVolume(0)
}

Au début du combat le Clip "bgm" en cours de lecture est interrompu par un "stop()" et je joue la musique de combat depuis un autre Clip appelé "musique2" (qui permet de jouer la musique de combat, mais aussi la musique du Boss final). Une fois le combat terminé, la lecture du Clip "bgm" est relancée avec un "play()" et le morceau reprend là où il en était.

J'ai ensuite intégré les musiques des différentes cinématiques de fin et légèrement retravaillé leur timing.

J'ai aussi ajouté une commande "Toggle Sound ON/OFF" au Menu Clic Droit qui permet de passer du mode "volume normal" (setVolume(100)) au mode "muet" (setVolume(0)) et inversement ; un bouton "Son" ayant le même effet a aussi été ajouté à l'écran titre.
Si le jeu est en mode "muet" au moment de la dernière sauvegarde, il sera aussi en mode "muet" lors du prochain chargement.

dimanche 19 décembre 2010

37 - Menu Clic Droit

Les cinématiques de fin sont enfin timées convenablement ; j'ai donc pu enchainer sur le menu clic droit.

Quand on effectue un clic droit sur un fichier Flash standard, on a accès à de nombreuses options : lire, zoomer, en avant, rembobiner...
Le problème, c'est que si un joueur clique sur "en avant", il accède à la scène suivante, ce qui peut engendrer de nombreux bugs et/ou tricheries :

Dans mon jeu, les scènes "playerSelect", "donjon" et "ending" se suivent dans cet ordre.
  • si on clique sur "en avant" alors qu'on est à l'écran de sélection du personnage, on se retrouve dans le donjon sans avoir chargé les stats du Héros ;
  • si on clique sur "en avant" alors qu'on est dans le donjon, on accède directement à la cinématique de fin.
Pour éviter ces désagréments, il faut modifier le menu clic droit en supprimant les commandes de base ; on peut aussi ajouter ses propres commandes personnalisées.

var MENUPERSO = new ContextMenu();// création de la variable "menu contextuel personnalisé"
MENUPERSO.hideBuiltInItems();// suppression des commandes usuelles (lire, rembobiner, zoomer... )
var commande1 = new ContextMenuItem("Reset", reset); // la commande "Reset" appelle la fonction "reset" définie plus bas
var commande2 = new ContextMenuItem("Version " add _global.version, version);
MENUPERSO.customItems.push(commande1); // on ajoute la commande1 au menu
MENUPERSO.customItems.push(commande2);
function reset() { // cette fonction permet de retourner à l'écran titre
maxdepth = 2000;
for (i=0; i<=maxdepth; i++) { removeMovieClip(_root.getInstanceAtDepth(i));
// on efface tous les clips issus des scènes précédentes pour avoir un écran propre
}
gotoSceneAndPlay("preloader", 1);
// on retourne à la première scène du jeu
}
function version() {
// si la variable _global.version a pour valeur "1.00", alors la commande du menu aura pour nom "Version 1.00"
// le numéro de la version est indiqué à titre informatif : cliquer dessus n'a aucun effet
}
_root.menu = MENUPERSO;

Ce qui nous donne ceci :

Prochaine étape : Création du Preloader !

dimanche 10 octobre 2010

19 - Contretemps

Hier, après avoir mis au point l'affichage des différentes icones d'actions utilisables en combat, j'ai eu droit à ce charmant message d'erreur.
Il semblerait que mon code soit trop long et que je sois obligé de le découper en sous-fonctions, car il existe visiblement une taille limite 32k que doit respecter chaque section de code.

En fait mon jeu peut très bien tourner dans le lecteur Flash malgré ce souci, c'est juste que tant que j'ai ce message d'erreur je n'ai pas accès à la fenêtre de débogage (qui est quand même bien pratique pour vérifier l'évolution de mes variables et traquer les bugs).

Me voilà donc parti pour créer des tas de sous-fonctions à coups de copier-coller (avec au passage le risque de générer d'autres erreurs, en particulier à cause des variables locales que je vais sûrement devoir transformer en variables globales).


[edit] : Après quelques tests, il s'avère que le terme "section de code" ne désigne pas uniquement les instructions entre deux accolades (au sein d'une function ou d'un onFrame event), mais aussi l'ensemble des instructions au sein de ma scène "donjon" ; donc même si je découpe mes fonctions en sous-fonctions, du moment qu'elle sont toujours définies dans la scène "donjon", elles sont prises en compte et continuent à me faire dépasser la limite de 32k !
Mais j'ai trouvé la parade : Définir mes fonctions dans des Movie Clips ! Même si ces Clips sont présents dans la scène "donjon", leur code n'est pas considéré comme faisant partie du code de la scène.
Bref, j'en suis au même point : Copier-coller des portions de code ailleurs... -_-

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.

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

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 31 août 2010

03 - Inventaire (suite) - Génération du Donjon

Hier je n'avais pas tout à fait fini l'Inventaire ; je m'étais occupé des objets consommables, mais il me restait encore une catégorie d'objets : ceux à usage multiples.
Contrairement aux consommables, ces objets n'existent qu'en un seul exemplaire. Par conséquent il n'y a pas de compteur de quantité dans le coin supérieur gauche de l'icone.

L'ajout de ces 4 derniers objets a été très rapide, et je me retrouve donc avec un Inventaire opérationnel, à un détail près : Les objets sont affichés, certes, mais pour l'instant inutilisables ! Néanmoins je n'ai pas l'intention de m'occuper de ça pour l'instant. Non, aujourd'hui j'ai décidé de m'attaquer à un gros morceau : La génération des donjons !

Chaque étage du donjon est composé d'un assemblage de tiles (des blocs carrés de 16 pixels de côté qui représentent soit les dalles sur lesquelles les personnages peuvent marcher, soit les murs qui les empêchent de passer) formant un grand rectangle de 34 tiles de long et 7 tiles de haut.

L'étage est représenté par une variable de type tableau à 2 dimensions (un indice pour indiquer la colonne, et un pour indiquer la ligne) : chaque case du tableau représente une tile, et possède soit la valeur 0 (mur) soit la valeur 1 (dalle).

Chaque étage peut être vu comme une succession de salles ayant une forme prédéfinie qui sont reliées entre elles par des couloir.
Dans un premier temps j'ai rempli toutes les cases du tableau avec des 0. Puis, en fonction du type de salle que je souhaite avoir, je modifie les cases pour faire apparaitre les dalles qui donneront à la salle la forme souhaitée.

map[c]=[0,1,0,0,0,1,0];
/*
la première colonne est remplie comme suit, de haut en bas:
brique, dalle, brique, brique, brique, dalle, brique.
*/
map[c+1]=[0,1,1,1,1,1,0];
// on fait de même pour la colonne suivante...
map[c+2]=[0,0,0,0,0,1,0];
//... et pour celle d'après.
/*
on obtient donc une salle ayant cette forme :
XXX
OOX
XOX
XOX
XOX
OOO
XXX
*/

Une fois mon tableau rempli, je n'ai plus qu'à dupliquer mes Clips en les plaçant au bon endroit et en affichant la frame adéquate (brique ou dalle). J'ai ainsi obtenu le résultat suivant :

Comme vous pouvez le constater, les différentes salles sont bien affichées, mais il y a un léger souci : Elles sont toutes séparées par des colonnes de brique !
La prochaine étape consistera donc à relier les salles entre elles...

Le jeu propose 7 environnements graphiques différents, c'est à dire 7 paires "brique-dalle" de couleurs et d'aspects différents.
Lors de la phase de recherches graphiques j'avais réalisé le design de ces 14 éléments. Cependant, une fois assemblés sous forme de donjon, j'ai pu constater certains défauts qui m'ont poussé à modifier légèrement l'apparence et les couleurs de mes sprites afin d'avoir un rendu visuel plus agréable à l'oeil.

Ancienne version (trop terne) :

Nouvelle version (plus colorée) :

lundi 30 août 2010

02 - Inventaire

Outre le Score en haut de l'écran et les stats du personnage en bas à gauche, il reste encore un élément important de l'interface à ajouter avant de s'attaquer à la partie "jouable" de l'affichage : l'Inventaire.

Chaque objet consommable (potions de regain de PM, viandes rendant des PV) est accompagné d'un chiffre en haut à gauche indiquant sa quantité ; comme je n'ai pas trouvé de typo adéquate, j'ai dû dessiner moi-même les 10 chiffres de notre système numérique.
Si la quantité d'un objet consommable est égale à zéro, l'icone de l'objet apparait en niveaux de gris.

Les différentes icones d'objets et les différentes valeurs des quantités ont été rassemblées au sein d'un seul et même Movie Clip qui est ensuite dupliqué aux coordonnées adéquates. À chaque duplication j'indique quelle frame de l'icone et quelle frame de la quantité sont affichées.

// affiche potion 15 :
name = "potion15";
/* la variable "_global.potion15" a pour valeur
le nombre de potions rendant 15 PM présentes
dans l'inventaire ; comme "potion15" est aussi le
nom du Movie Clip qui affiche l'icone de cette
potion, je crée une variable "name" qui me permet
d'alléger mon code et de le réutiliser facilement
pour les autres objets. */
item_icon.duplicateMovieClip(name, 1);
_root[name]._x = 339;
_root[name]._y = 347;
if (_global[name]==0){
// quantité nulle : icone en niveaux de gris
_root[name].icone.gotoAndStop(2);
}
else{
// au moins 1 objet de ce type : icone colorée
_root[name].icone.gotoAndStop(1);
}
_root[name].qte.gotoAndStop(_global[name] + 1);