FMIN205 Rapport TER : Bounding Balls FrameWork du Master 1

Transcription

FMIN205 Rapport TER : Bounding Balls FrameWork du Master 1
FMIN205
Rapport TER : Bounding Balls FrameWork
du Master 1 Informatique
Participants :
Galais Sami
Gendre Samuel
Giachetto Frédéric
Sauvajol Guillaume
Tuteur :
Dony Christophe
1
Table des matières
1 Introduction - Cahier des Charges
1.1 Rappel du sujet . . . . . . . . . . .
1.2 Analyse du code et tâches . . . . .
1.3 Répartition du travail . . . . . . .
1.4 Récapitulatif du travail effectué . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2 Le Framework
2.1 Le framework fourni . . . . . . . . . . . . . . . . . . .
2.1.1 Le fonctionnement de base . . . . . . . . . . .
2.1.2 Diagramme de classe simplifié du framework de
2.1.3 Schéma de classe d’un jeu . . . . . . . . . . . .
2.2 Les améliorations apportées . . . . . . . . . . . . . . .
2.2.1 Animations des Entity . . . . . . . . . . . . . .
2.2.2 L’ajout du son . . . . . . . . . . . . . . . . . .
2.2.3 La gestion de la souris . . . . . . . . . . . . . .
2.2.4 L’éditeur de niveau générique . . . . . . . . . .
2.2.5 Diagramme de classe final simplifié . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3
3
3
3
4
. . .
. . .
base
. . .
. . .
. . .
. . .
. . .
. . .
. . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5
5
5
6
7
7
7
9
10
10
13
.
.
.
.
.
.
.
.
3 La création d’un jeu
14
3.1 Les classes à créer . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.2 L’utilisation du pattern state . . . . . . . . . . . . . . . . . . . . 14
3.3 Schéma de classe du casse-brique . . . . . . . . . . . . . . . . . . 15
4 Conlusion
16
2
Chapitre 1
Introduction - Cahier des
Charges
1.1
Rappel du sujet
Un précédent groupe a réalisé un framework en Java permettant la création de jeux type ’objet mobiles-obstacles-collisions’. Le but du TER est donc
d’analyser le code, de reprendre le framework et de l’étendre. Par conséquent il
nous faudra développer un premier jeu à partir de ce framework de base. Après
cette première étape, il sera nécessaire d’étendre le framework en y intégrant
les fonctions qui nous apparaissent indispensables après l’implémentation du
premier jeu.
1.2
Analyse du code et tâches
Ayant déjà fait une première lecture du code et regardé les jeux fournis
avec le framework, nous avons décidé de diviser le travail en plusieurs étapes :
– tout d’abord nous allons commencer par implémenter un premier jeu
afin de bien comprendre l’utilisation du framework.
– ensuite voir les extensions possibles du framework. (graphisme/sons/souris
...)
– enfin développer de nouveaux jeux en utilisant les nouvelles fonctionnalités implémentées.
1.3
Répartition du travail
Afin d’optimiser notre travail, et donc de pouvoir finir à temps l’ensemble
de ce que nous souhaitons produire nous avons décidé de répartir le travail
comme suit. Dans un premier temps nous allons développer chacun de notre coté
un premier jeu simple « falldown », afin que tout le monde est pris connaissance
du framework et puisse ainsi travailler dessus sans soucis. Ensuite nous distribuerons les différents ajouts à effectuer sur le framework à chacun en fonction de
nos préférences. Ainsi un membre du groupe s’occupera de développer la partie
3
graphisme, un de la gestion de la souris, un autre implémentera le son et le dernier de l’ajout de l’éditeur générique. Après ce travail effectué nous travaillerons
ensemble sur le développement du premier jeu, afin que chacun puisse voir le
travail des autres. Pour faciliter la communication entre les différents membres
du groupe nous utiliserons bien sur les mails, nous avons aussi mis en place un
groupe de discussion à l’adresse http ://groups.google.fr/group/terbbfw afin de
faciliter les mises à jour du code. Enfin, en fonction de nos emplois du temps,
nous nous réunirons au moins une fois par semaine à la bibliothèque et si cela
n’est pas possible nous le ferons via IRC ou MSN.
1.4
Récapitulatif du travail effectué
Après la réalisation d’un premier jeu rapide, nous avons commencé à
modifier le framework en groupe. Le premier ajout sur lequel nous avons travaillé
fut assez simple, et permet au framework de charger une image de fond pour les
niveaux. Après cela nous avons travaillé sur le chargement et l’affichage d’images
animées (plus communément appelées sprites) à la place des formes simples de
java.
4
Chapitre 2
Le Framework
2.1
2.1.1
Le framework fourni
Le fonctionnement de base
Le framework qu’on nous a demandé d’améliorer est un framework de
jeu qui charge un jar contenant un jeu écrit pour l’utiliser. Sa réalisation suit
le schéma de conception MVC (Modèle Vue Contrôleur) qui permet de séparer
l’affichage graphique et la représentation des données.
Dans la partie « model » on trouve notamment la classe GameManager qui
contrôle la partie à chaque tour d’horloge, et appelle toute les autres classes. On
trouve aussi toutes les sous classes de Entity qui définissent les diverses entités
d’un jeu. (celles qui sont statiques : StaticEntity, celles qui peuvent se déplacer :
MovableEntity, celle qui sont contrôlables : ControllableEntity ...)
Dans la partie « view » on trouve la classe qui gère l’affichage des entités Gentity.
Cette classe possédant bien sûr un attribut model de type Entity !
Il va de soi que chaque jeu écrit pour utiliser ce framework devra surcharger
toutes les classes citées au dessus.
5
2.1.2
Diagramme de classe simplifié du framework de base
Fig. 2.1 – cf. http ://groups.google.fr/group/terbbfw pour les diagrammes complets
6
2.1.3
Schéma de classe d’un jeu
Fig. 2.2 – Diagramme d’un jeu
2.2
2.2.1
Les améliorations apportées
Animations des Entity
Nous avons intégré au framework la gestion d’images animées qui n’était
pas présente à l’origine. Pour cela nous avons dû modifier des classes existantes
et en créer de nouvelles.
Nous avons commencé par définir une classe Sprite qui permet simplement
de stocker une image, avec ses dimensions et ses coordonnées ; une méthode
draw(Graphics 2D) qui est appelée pour dessiner l’image en fonction des attributs de la classe.
C’est en fait la classe de base pour instancier et dessiner une image qui fait
partie d’une animation.
La hiérarchie du framework est faite de telle sorte qu’une classe abstraite Entity
représente les entités des jeux. Quatre autres classes abstraites en héritent :
– MovableEntity qui représente les entités mouvantes, mais non controllables par un joueur
– ControllableEntity pour les entités qui peuvent se mouvoir et également
controllables
– StaticEntity pour les entités immobiles, typiquement des briques
– enfin BonusEntity pour les bonus.
7
Le moteur de jeu possède une liste pour chaque type d’entité : une méthode qui
détermine les collisions entre elles et appelle une méthode collide() dans l’entité
pour déterminer les effets de la collision. Les entités possèdent donc chacune
quatre méthodes collide().
Pour gérer des animations, nous avions tout d’abord pensé créer une classe AnimatedEntity qui heriterait de Entity . L’héritage multiple étant inexistant en
java, il nous fallait une classe héritant de AnimatedEntity par type d’entité (AnimatedControllableEntity ...) en plus, bien sûr, des classes d’entités existantes.
Cela aurait posé quelques problèmes. Tout d’abord, dans les classes animées, il
y aurait eu systématiquement le même code que dans l’homologue non animé.
Les autres problèmes seraient venus du fonctionnement du framework lui même.
En effet, le GameEngine aurait comporté non pas quatre, mais huit listes d’entités. Par exemple, il n’y aurait plus eu d’héritage entre une ControllableEntity
et une AnimatedControllableEntity, ce qui veut dire deux listes différentes.
Un autre gros problème aurait été posé par les méthodes collide() des entités et
par la méthode de détection des collisions du GameEngine. De fait, trop de cas
aurait dû être déterminé, soixante-quatre au total, et tous avec un code similaire.
Nous avons donc opté pour une autre solution, c’est-à-dire, définir une
classe ” Animated ” par entité héritant directement de cette entité. Cela résout
le problème des listes du GameEngine et celui des méthodes de collision. Même
si le code de ces classes est semblable, cette solution est bien meilleure que la
précédente envisagée.
Le fonctionnement du système d’animation est quant à lui assez simple : il
repose sur un système de ” mode ”, facilement implémentable grâce à un pattern state. Les classes possèdent une table de hachage avec des clés String, qui
correspondent au ” mode ”, et une liste de Sprite comme valeur qui constitue
l’animation. Chaque ” mode ” a donc une animation particulière.
Un attribut currentImage correspond à l’image courante à afficher dans le mode
courant. Une méthode advance() appelée à chaque tick, ou rafraichissement de
la scène, fait passer par défaut currentImage au Sprite suivant dans la liste. Il
est possible de ralentir l’animation en changeant la valeur de l’attribut entier
step. Il correspond au nombre de ticks à laisser passer pour avancer au Sprite
suivant. Sa valeur par défaut est de zéro.
Toutes ces actions se font de manière transparente ; l’utilisateur du framework
n’aura pas besoin de les coder, mis à part s’il désire, par exemple, faire d’autres
choses dans la méthode advance(). Il lui suffira alors de la redéfinir, en pensant
à appeler super.advance().
Le chargement des images se fait grâce à la méthode createImageList()
qui est appelée directement par le constructeur de la classe. Elle commence par
charger le chemin absolu vers le dossier du jeu chargé, puis rentre dans le dossier
Sprites. Là, elle cherche un dossier qui porte le nom de la classe appelante et
le parcourt. Les dossiers qu’elle rencontre correspondent à des ”modes ”. Leur
nom sert de clé dans la HashMap et les images contenues dans le dossier sont
associés à cette clé .
Cela impose quelques contraintes pour que les images et a fortiori les animations
soient bien chargées. Tout d’abord, le .jar du jeu doit se trouver dans un dossier
qui contient également un dossier ” Sprites ”. A l’intérieur, on doit trouver un
dossier par classe animée contenant lui-même un dossier par ” mode ” de la
8
classe, avec les images correspondantes dedans.
Finalement, la contrainte reste bien maigre puisque le chargement des images
se fait réellement de manière automatique.L’utilisateur n’a rien besoin de coder
pour le faire. Il lui faut simplement déterminer les modes de son entité et les
images associées. Dans le code, il doit tout de même déterminer les changements
de modes qui interviennent souvent suite à une collision ou un déplacement, par
exemple.
La part de codage est minimisée et le résultat est garanti. Il est très facile de
changer complètement l’apparence d’un jeu juste en changeant les images dans
les dossiers.
2.2.2
L’ajout du son
Il s’agissait ici de parvenir à integrer une solution simple au framework
pour jouer des sons associés aux évenements du jeu ainsi qu’une musique de
fond. Cela c’est fait en deux temps :
Tout d’abord nous avons créer une classe Sound dans le package util.
Celle-ci possède cinq attributs :
– soundFile qui correspond au fichiers .wav à jouer
– soundStream qui est le stream associé au son
– soundFormat, le format du son
– soundInfo, le DataLine.info du son
– soundClip le clip associé au sons, c’est lui qui est joué par Java.
Cette classe implemente les méthodes play(), pause(), stop() et loop()
qui sont des redéfinitions de méthodes Java qui s’appliquent sur les AudioClip.
Ceci permet de simplifier grandement l’utilisation des sons lors de la création
des jeux, ainsi :
– play() remet le son au début puis appele la méthode start() de Java
qui lance le son
– pause() appele juste la méthode stop() de Java
– stop() coupe le son et le remet au début
– loop() permet de faire boucler un son indefinimment (utilisé pour la
musique de fond)
Ensuite nous avons implémenté une méthode de chargement des sons
identique à celle du chargement des images, chaque entity possédant une HashMap soundList, de mode et de son. Au jar du jeu doit etre associé un dossier
Sound qui respecte une certaine hiérarchie :
– à chaque classe devant jouer un son est associé un dossier portant le
nom de la classe
– un son à pour nom le mode qui lui est associé :
– ainsi pour une classe CbBrick qui joue un son d’explosion lors de sa
destruction on aura :
– pour hierarchie : Sound/CbBrick/explode.wav et
– la méthode CbBrick.getSoundList.get(’explode’).play() pour jouer
le son
9
2.2.3
La gestion de la souris
De part la nature des jeux créés ou modifiés, la gestion du seul controleur
clavier n’était pas satisfaisant pour plusieurs raisons interdépendantes :
– un manque de précision du déplacement
– une vitesse de déplacement fixe
– une utilisation fastidieuse
Suite à ce constat, un première implémentation de la souris a été réalisée dans
le framework en utilisant l’interface mouseMotionListener pour retranscrire les
mouvement de la souris sur l’objet controlé.
Premier bilan, l’implémentation a été rapide et le résultat acceptable cependant
le curseur de la souris restait par dessus l’objet controlé, ce qui est peu ergonomique. Afin de contourner ce léger soucis, nous avons remplacé le curseur de la
zone de jeu par une image vide.
Pour laisser la liberté d’utiliser ou non un curseur invisible aux futurs
utilisateurs de ce framework, deux modifications du framework ont été nécessaires :
– un attribut ’GameZonePanel displayPanel’ a été rajouté dans le gameEngine afin que le gameEngine connaisse la zone de jeu et puisse en
modifier le curseur.
– une méthode ’void setCursorVisible(boolean visible)’ dans le gameEngine afin que les futurs créateurs de jeu aient un accès facilité à cette
fonctionnalité.
2.2.4
L’éditeur de niveau générique
Nous avons développé une autre fonctionnalité intéressante et inédite du
framework : un éditeur de niveau. Il permet de construire facilement et intuitivement un niveau pour un jeu chargé avec les entités disponibles.
Un bouton ’Editor’ a été ajouté dans la fenêtre principale qui permet de lancer
l’éditeur après avoir charger un jeu. Si aucun un jeu n’est chargé, il se produit
une erreur, sinon l’éditeur se lance et la zone de jeu en cours est effacée.
L’éditeur apparait dans une nouvelle fenêtre à côté de la fenêtre principale. Il
contient une JList, dans laquelle apparait la liste des entités disponibles pour le
jeu chargé, ainsi qu’un JPanel au dessus de la liste qui permet d’avoir un aperçu
de l’entité choisie.
Quand un jeu est chargé le GameEngine appelle sa méthode loadGamePackage() qui remplit le gamePackage qu’elle a pour attribut. La classe GamePackage contient en fait des listes de toutes les classes d’entité propre au jeu
chargé. Le gamePackage est créée grâce à la classe JarLoader et à sa méthode
loadJar() qui retourne un GamePackage.
Cette méthode ouvre le .jar du jeu à partir du chemin du fichier. Elle parcours
ensuite les dossiers et lorsqu’elle rencontre une classe de type Entity (MovableEntity, ControllableEntity, StaticEntity ou BonusEntity) elle ajoute l’objet
de type Class dans la liste correspondante. A la fin de la méthode un GamePackage est créé à partir des listes remplies et toutes les classes rencontrées sont
chargées donc connues et instanciables par l’application principale. Les Class des
10
Entity ’Animated’ sont placées dans les listes de leur classe mère. Par rapport
à l’ancien framework, une nouvelle liste de Class est ajoutée, c’est une liste de
classe EntityState qui permet d’utiliser facilement le pattern state sur les Entity,
afin notamment de gérer les ’modes’ pour les animations. (pour l’intégration du
pattern state : cf 3.2 L’utilisation du Pattern State)
La liste des classes contenues dans l’éditeur est construite grâce au gamePackage
chargé dans le GameEngine. Toutes les classes ne sont pas ajoutées : les classes
héritant de EntityState abstraites (dont le nom fini par ” State ”), ainsi que les
classes parmi les Entity ’standard’ qui possèdent une implémentation state ne
le sont pas. Pour les reconnaı̂tre il suffit de parcourir la liste des Class de type
EntityState et si l’on trouve une classe dont le nom a pour préfixe le nom de la
classe que l’on cherche on ne l’ajoute pas. L’instanciation de ces classes se fera
à partir de celle de leur état.
La sélection d’un élément dans la liste met à jour un attribut de type
Class dans le gameEngine ainsi que dans le JPanel qui propose un aperçu de
la classe. Dans le gameZonePanel nous avons ajouté un MouseListener qui permet de détecter les évènements souris tels que les clicks. Ainsi quand le bouton
gauche de la souris est pressé sur la zone de jeu et si l’attribut instanceToInstanciate du GameEngine n’est pas null, c’est-à-dire si une classe est sélectionnée
dans la liste, la méthode instanciate du gameEnigne est appelée.
Les coordonnées du click sont passées en paramètre à cette méthode. Une instance du type de classToInstanciate est alors créée et placée dans la liste correspondant à son type dans le gameEngine. Les coordonnées de l’entité sont celles
du click, la zone du jeu est redessinée et l’entité est affichée directement.
L’instanciation de la classe se fait grâce à la méthode newInstance(). Une
instanciation typique s’écrit : classToInstanciate.getConstructor(param).newInstance(new
Object [] this). classToInstanciate est le type de l’objet à instancier. param est
un tableau de Class qui contient le type des paramètres du constructeur, ici
GameEngine. this correspond à l’instance passée en paramètre de type param,
ici on utilise this car c’estle gameEngine qui doit être passé en paramètre.
Le cas de l’instanciation d’un EntityState est différent puisque il doit aboutir
sur la création d’une Entity dans le bon état. On commence donc par créer une
instance de classToInstanciate. On récupère ensuite le nom de la classe Entity
correspondante au state, ce qui est possible grâce à la structure des noms de
classes d’état : <Nom classe Entity>+’State’+<Nom etat> Il faut impérativement respecter cette structure pour que le chargement fonctionne. Grâce à
elle l’extraction du nom de la classe est aisé. Une fois le nom récupéré, on récupère l’objet Class par l’appel de la méthode Class.forName (<Nom classe>).
Ensuite on créé une instance de cette classe sur laquelle on appelle la méthode
setState(EntityState), le paramètre étant l’objet précédemment créé. On arrive
ainsi au résultat recherché : un objet Entity dans l’état demandé.
Nous avons mis dans la classe d’aperçu un attribut de type Entity que
l’on instancie avec l’attribut Class que l’on a récupéré grâce à la liste. Pour cela
on utilise la même méthode que précédemment. On affiche ensuite tout simplement l’entité dans le JPanel en faisant attention d’adapter la taille de l’image.
Quand l’utilisateur du jeu estime avoir fini la création du niveau, il lui suffit
de cliquer sur ’Start Game’ pour commencer la partie instantanément puisque
11
toutes les instances sont chargées dans le moteur de jeu.
Nous avons réussi à créer un éditeur de niveau générique qui fonctionne avec
tous les jeux créés à partir du framework. Il suffit pour cela que les noms de
classes respectent la structure précisée, l’utilisateur du framework n’a pas besoin d’écrire une seule ligne de code pour utiliser cet éditeur. Nous n’avons
malheureusement pas pu intégré de système de sauvegarde et de chargement
des niveaux faute de temps.
12
2.2.5
Diagramme de classe final simplifié
Fig. 2.3 – cf. http ://groups.google.fr/group/terbbfw pour les diagrammes complets
13
Chapitre 3
La création d’un jeu
3.1
Les classes à créer
Afin de créer un jeu à partir du framework il est nécessaire de suivre une
structure définie ainsi que créer l’ensemble des classes nécessaires.
Il faut tout d’abord créer deux packages : view et model, dans lesquels s’inséreront nos diverses classes, ce sont ces dossiers qui seront inclus dans le jar.
Comme nous l’avons vu précédemment il faudra aussi créer les dossiers Sprites
et Sound pour integrer les sons et les animations.
En ce qui concerne les classes, il sera nécessaire de les nommer selon une certaine nomenclature : [Nom du package ][Nom de la classe], par exemple pour
une classe Brick dans le package cb.model, la classe aura pour nom CbBrick.
Les GEntity associés auront pour nom G[Nom de la classe associée], dans notre
exemple cela donnera la classe GCbBrick.
Enfin il sera nécessaire de créer les différentes classes du jeu :
– dans le model :
– le controller (extends Controllable)
– les diverses entités (extends Bonus, Movable, Static)
– le gestionnaire de jeu (extends GameManager)
– dans le view :
– les diverses GEntity associées aux Entity précedentes
3.2
L’utilisation du pattern state
Lors de la création des Entity il est fortement conseillé d’utiliser un pattern state. En effet ceci permet d’associer divers états à une Entity, ainsi si l’on
prend l’exemple du casse-brique, la classe CBBrick on a 7 états différents :
– chaque brick a déjà deux états pour son animation, BrickStandard et
BrickActive.
– des etats qui évolue lors des collisions :
– on commence à Red, la brique est rouge
– puis lors d’une collision on passe à Purple
– ensuite la brique devient bleu (StateBlue)
– enfin la brique explose et disparait (StateExplode)
14
Ce design pattern permet ainsi d’éviter de nombreux tests sur les modes
pour les animations des entités dans la classe. L’utilisation des sons est elle
aussi simplifiée, les sons sont joués lors d’un changement d’état par exemple.
Enfin ceci permet d’avoir un code plus propre et aussi de faciliter grandement
la création d’un jeu.
3.3
Schéma de classe du casse-brique
Fig. 3.1 – cf. http ://groups.google.fr/group/terbbfw pour les diagrammes complets
15
Chapitre 4
Conlusion
Bien que nous ne soyons pas parvenu à intégrer tout ce que nous avons
noté dans le planning prévisionnel, nous pensons être parvenu à rajouter les
fonctionnalités qui nous apparaissait les plus importantes pour rendre le framework le plus complet possible.
Il est vrai aussi que nous avons eu quelques difficultés à comprendre certaines
parties du code (jarLoader ou gamePackage par exemple), ce qui nous a fait
perdre un temps précieux. Cependant la compréhension de ces classes nous a
permis d’intégrer plus facilement l’éditeur générique, en réutilisant le systeme
de chargement et d’instantiation des classes du GamePackage.
En conclusion, on peut dire que bien que nous ayons rencontré certaines difficultés durant l’amélioration du framework, nous avons pu, grâce à ce projet,
pousser plus loin nos connaissances en Java, voir les principes fondamentaux de
la réutilisabilité ainsi que comprendre clairement le fonctionnement du pattern
state, qui s’avère très utile.
16