Imprimer
Catégorie : SAÉ - Développement IHM
Affichages : 1396

Préambule

L'objectif de ce premier cours est de voir comment créer les bases d'un jeu en javafx où un élément bouge en fonction des interactions de l'utilisateur. En l’occurrence, il s'agit d'une simple balle qui rebondit sur les murs délimitant la fenêtre de visualisation.

La conception des classes doit suivre le paradigme MVC, ce qui nécessite d'analyser correctement les besoins applicatifs et d'en déduire les attributs et méthodes de chaque classe. Cependant, compte tenu du fait que l'on utilise javafx, il y a une structure générale à suivre qui est commune à tout jeu et dont il faut tenir compte pour créer les classes des éléments du jeu. A noter que cette structure générale est celle qui est utilisée dans gamifier, mais de façon plus poussée afin de tenir compte des nombreuses caractéristiques possibles des jeux (niveaux, animations, ...).

 

Pour suivre correctement ce cours, il est conseillé d'ouvrir en parallèle les sources, dont l'archive est téléchargeable [ ici ] 

1°/ Les besoins

 

 

 

2°/ La structure générale.

Afin de suivre de bonnes pratiques de codage, et en supposant que l'application soit crée sous idea, le répertoire src des sources du projet contient :

 

3°/ Partie modèle

Cette partie doit être implémentée de façon indépendante du reste, à partir du moment où elle est bien conçue. On va donc commencer par coder ses classes.

3.1°/ Model.java

Cette classe contient les attributs nécessaires à la gestion générale du jeu. Il y a donc au moins :

Remarque 1 : les dimensions de l'espaces pourraient être placée dans la partie vue. Cependant, comme c'est la partie modèle qui gère la position des éléments dans l'espace, il est indispensable de connaître sa dimension. Par exemple, si on veut positionner la balle au centre de l'espace au lancement du jeu, il faut bien connaître les dimensions de l'espace. 

Remarque 2 : il est possible de gérer les positions des éléments dans la partie vue, ce qui invalide la remarque précédente. Cependant, ce n'est pas forcément pratique lorsque l'on veut régler des interactions entre éléments, selon leur position, ce qui n'est normalement pas dépendant de leur aspect visuel (sauf collision) et doit donc être fait dans le modèle.

3.2°/ BallModel.java

Comme la balle est amenée à se déplacer selon un angle entre 0 et 360°, avec une certaine vitesse, il est logique de définir 2 attributs représentant ces données. La valeur de la vitesse sera en nombre de pixels par frame. Cependant, on déplace la balle en modifiant ses coordonnées x,y. Il est donc souhaitable de calculer la vitesse en X et Y, à partir de l'angle et de la vitesse spatiale, ce qui nécessite 2 attributs supplémentaires.

Par ailleurs, le paradigme MVC impose que la vue accède au modèle pour se mettre à jour. Il doit donc y avoir toutes les informations nécessaires pour cette opération, par rapport aux besoins applicatifs. Pour trouver les attributs nécessaires, il suffit de réfléchir en terme d'événement ayant changé l'état de l'élément. Dans le cas présent :

En procédant ainsi, la partie vue peut facilement tester les attributs booléens pour en déduire comment modifier l'aspect/position visuelle de l'élément.

Pour les méthodes, il suffit de réfléchir à l'identique :

 

4°/ Partie vue

En javafx, le visuel d'une application est créé à partie des classes :

Si l'application ne repose que sur une seule fenêtre, il n'y a pas besoin de la créer explicitement. En effet, la classe principale contenant main() doit hériter de la classe Application, et se faisant, elle DOIT définir une méthode start(), qui prend en paramètre un objet Stage. C'est donc la machinerie javafx qui crée la fenêtre initiale et la passe en paramètre à start() (cf. section 6)

IMPORTANT : normalement, rien n'impose que l'espace des modèles d'élément soit le même que celui du visuel des éléments. Cependant, il est beaucoup plus facile de déboguer et gérer l'ensemble des éléments quand il y a effectivement correspondance. Cela veut donc dire qu'un modèle d'élément positionné en 50,75 sera effectivement placé dans la fenêtre au pixel 50,75.

4.1°/ View.java

Cette classe permet de regrouper tous les éléments du visuel de l'application, excepté le Stage et la Scene. Dans le cas présent, on a donc :

Fonctionnellement, cette classe ne fait pas grand chose, à part créer les instances mentionnées ci-dessus et d'ajouter la balle et les murs comme fils du Pane racine.

 

Remarque : contrairement à la balle, il n'y a pas spécialement besoin de créer une classe de modèle pour les murs. En effet, ces derniers n'ont rien de spécial et ils sont uniquement utilisés dans la détection de collision. Or, celle-ci se base sur le visuel et non sur un modèle physique de collision.

 

4.2°/ BallLook.java

Pour représenter des éléments visuels dans la Scene, il existe principalement 2 moyens :

L'avantage de la seconde solution est que ça facilite la détection de collision : il existe une méthode permettant de trouver l'intersection entre 2 objets Shape. Si la taille de cette intersection est non nulle, il y a collision.

Cependant, le visuel d'un élément du jeu peut être constitué grâce à plusieurs Shape. Si les coordonnées du modèle de l'élément changent, il faut alors déplacer toutes les Shape. Pour gérer ce problème, la solution la plus simple consiste à mettre toutes ces Shape dans une instance de Group. Si on déplace le Group, tous ses fils sont déplacés de même.

Pour la détection de collision, il existe deux approches :

La deuxième solution évite que des éléments puissent se chevaucher à l'écran. C'est donc celle-ci qui est retenu pour ce cours. Le problème, c'est qu'il faut tester la collision sans pour autant déplacer réellement l'élément. Pour cela, on définit pour chaque visuel d'élément des Shape transparentes, qui décrivent l'enveloppe de l'élément. C'est cette enveloppe que l'on déplace à la prochaine position de l'élément et que l'on utilise pour les collisions. Cette enveloppe étant elle-même éventuellement constituée de plusieurs Shape, on utilise de nouveau un Group pour les stocker.

Fonctionnellement, cette classe doit essentiellement :

 

5°/ Partie contrôle

 

5.1°/ ControllerAnimation.java

Cette classe est quasi identique pour tous les jeux. Elle se contente de créer un timer dont la méthode handle() va être appelée régulièrement par la machinerie javafx, avec en paramètre le temps passé en µS depuis le lancement de l'application. On peut donc ainsi mesurer des intervalles de temps (par exemple 10ms), et à la fin de chaque intervalle, on demande au contrôleur principal de mettre à jour le modèle puis la vue.

5.2°/ ControllerKeyboard.java

Généralement, cette classe se contente de détecter les appuis sur les touches et d'appeler des méthodes du contrôleur général.

5.3°/ Controller.java

Cette classe contient toute la logique de gestion du jeu, par exemple les méthodes pour réagir aux interactions de l'utilisateur. Elle contient également les méthodes permettant de tester les collisions. Dans les deux cas, cela amène généralement à modifier le modèle en appelant ses méthodes.

Enfin, elle contient une méthode, appelée régulièrement, qui détecte les collisions, met à jour le modèle, puis la vue.

 

6°/ Classe principale

Comme mentionné en section 4,  la classe principale contenant main() doit hériter de la classe Application, et se faisant, DOIT définir une méthode start()

Dans start(), on procède quasi toujours à l'identique :