mercredi 17 novembre 2010

Work in progress

"Sali salut !" comme dirait ce cher Ned Flanders. Alors aujourd'hui je reviens avec une petite avancée dans mon projet de FMR. Jusqu'ici, j'avais développé petit à petit une démonstration de jeu afin de voir comment tournait XNA et afin de tâter un peu la "bête".
Un problème me travaillait depuis un moment : comment faire pour créer une navigation entre les différents états d'un jeu (menus, niveaux, phases de carte, s'il y en a, et autres phases possibles) sans m'arracher les cheveux et sans avoir à reprendre le travail d'autres? (chose particulièrement douloureuse de manière générale...)
Pour résumer l'ensemble des contraintes, il faut savoir qu'avec XNA il y a véritablement trois temps dans un jeu : l'initialisation, la boucle de jeu et l'arrêt du jeu. Ce qui nous intéresse ici, c'est la boucle du jeu qui se divise elle-même en deux phases qui servent respectivement à la synchronisation (mise à jour de toutes les données) et à l'affichage (normalement aucun calcul, juste l'affichage du résultat).
Donc, pour réaliser un début de jeu, c'est extrêmement simple : on détecte les entrées de périphérique (clavier, souris, manette...), on met à jour les informations d'affichage puis on affiche. Mais dans le cas, plus complexe, où on veut commencer à faire un jeu plus complexe, on se heurte au problème des états. Pour répondre à cela, une solution toute simple existe, utiliser des enums. Les utiliser aide à gagner en clarté (ça évite les codes en entiers ou en chaines de caractères) mais ça n'améliore que peu la lisibilité (dans le cas de multiples if ou case). C'est donc pour à la fois améliorer cela et améliorer l'indépendance entre les différentes parties du jeu que j'ai opté pour une restructuration complète du jeu. La nouvelle structure est organisée en couches, voire en arbre.


Cette façon d'organiser le programme ne se suffit pas, il faut en plus que chaque couche mère transmette un socle d'informations communes à ses couches filles. Ce socle d'informations contient tout ce qui est fenêtre, espace d'affichage, périphériques d'entrée (clavier, souris, manette...) ou même le temps d'exécution du programme (pour permettre, par exemple, d'afficher des animations de façon fluide, en fonction du temps écoulé).
Le fonctionnement de cette structure est assez simple : quand on lance le jeu, une variable d'état de "jeu" est par défaut sur "intro". Une fois que l'intro a fini de s'exécuter, elle place elle-même l'état de "jeu" à "menus". Une autre variable qui permet de se repérer dans la couche Menus est elle par défaut sur "Principal". Selon les actions et choix de l'utilisateur, on changera en local la valeur de cette variable ou, si on veut jouer, la valeur de "jeu". On procède ainsi sur tous les niveaux, ce qui permet d'automatiser certaines actions. Par exemple, si dans Jeu-> Partie -> En-Jeu -> Menus on choisit "Quitter la partie", la partie "Menus" ne modifiera que la définition locale qui dira quelle action faire à la couche supérieure, qui elle-même modifiera sa définition locale et ainsi de suite jusqu'à ce qu'on en revienne à l'état Jeu -> Menus -> Principal.
Cette façon de concevoir permet de mettre en place des algorithmes plus courts, plus légers et automatise pas mal de traitements qui autrement pourraient entrainer des erreurs.

Bon bien sûr, cette façon de faire n'est certainement pas la meilleure, mais c'est en tout cas une manière efficace d'arriver au résultat voulu et (je peux en témoigner) d'une façon largement satisfaisante.

Cette avancée m'a permis de refaire à zéro l'ensemble de ce que j'avais déjà fait (afin de coller le plus possible au schéma donné précédemment) et surtout de n'avoir que des petites modifications à chaque ajout. Par exemple, j'ai mis dans mon menu principal une animation de fond plutôt qu'une image : je n'ai eu pour cela que la partie Menus à refaire. Ce principe de couches est réutilisé d'ailleurs dans la gestion des périphériques. En effet, comme mon développement prend en compte le PC et la XBox 360 et que dans le cas du PC on peut avoir soit une manette soit combo clavier / souris, il serait plus que lourd de faire tout mon programme en version double. La solution que j'ai choisie, c'est de faire un objet (une couche) Commande qui regarde quel périphérique utiliser et qui retourne donc la même chose quelque soit le support et quelques soient les périphériques. Du coup, pour reprendre l'idée du schéma donné plus haut, l'organisation serait la suivante :

En fait, on décrit dans Commandes des méthodes comme "Pause" ou "Action" qui sont en fait des informations "abstraites" que l'on veut récupérer et dans lesquelles on va d'abord analyser les informations spécifiques aux périphériques pris en compte pour retourner un état. Cet état a pour intérêt d'être toujours valable et de ne demander aucune information à la classe qui l'utilise.

Donc pour terminer cette petite partie, je vais vous donner quelques images de ce que j'ai réalisé puis j'ajouterai un petit bonus coup de cœur.





Et finalement, voici mon petit bonus coup de cœur, qui n'est pas si récent que cela et qui réveillera quelques souvenirs chez ceux qui connaissent l'original : Zelda 2 (NES).
Mais attention, ce n'est pas vraiment Zelda 2 tel qu'il est sorti il y a quelques décennies déjà, mais une version totalement revue en 3D par un passionné. Si en général 3D rime avec mauvaise nouvelle pour ce genre de classique, ici il faut avouer qu'en plus d'être bien réalisé, cet épisode est plutôt fidèle à l'original (hormis pour les villes).
Vous pourrez le trouver ici, après avoir installé un petit patch pour que votre navigateur puisse afficher le jeu.
Quelques images pour vous faire une idée :





Aucun commentaire:

Enregistrer un commentaire