JavaFX est une boîte à outils d'interface graphique multi-plateformes pour Java, qui succède aux bibliothèques Java Swing. Dans ce didacticiel, nous allons explorer les fonctionnalités de JavaFX qui facilitent l’utilisation de la programmation de jeux en Java..
Ce tutoriel suppose que vous savez déjà comment coder en Java. Sinon, consultez le guide Apprendre Java pour Android, Introduction à la programmation informatique avec Java: 101 et 201, Head First Java, Greenfoot ou Apprendre Java à la dure pour commencer..
Si vous développez déjà des applications avec Java, vous n'avez probablement pas besoin de télécharger quoi que ce soit: JavaFX est inclus dans le kit standard JDK (Java Development Kit) depuis la version 7 J6 du JDK (août 2012). Si vous n'avez pas mis à jour votre installation Java depuis longtemps, rendez-vous sur le site de téléchargement Java pour obtenir la dernière version..
La création d'un programme JavaFX commence par la classe Application, à partir de laquelle toutes les applications JavaFX sont étendues. Votre classe principale devrait appeler le lancement()
méthode, qui appellera alors le init ()
méthode et la début()
méthode, attendez que l'application se termine, puis appelez le Arrêtez()
méthode. Parmi ces méthodes, seules les début()
méthode est abstraite et doit être remplacée.
La classe Stage est le conteneur JavaFX de niveau supérieur. Lorsqu'une application est lancée, une étape initiale est créée et transmise à la méthode de démarrage de l'application. Les étapes contrôlent les propriétés de base de la fenêtre, telles que le titre, l'icône, la visibilité, la redimensionnement, le mode plein écran et les décorations; ce dernier est configuré à l'aide de StageStyle. Des étapes supplémentaires peuvent être construites si nécessaire. Une fois la scène configurée et le contenu ajouté, le spectacle()
la méthode s'appelle.
Sachant tout cela, nous pouvons écrire un exemple minimal qui lance une fenêtre dans JavaFX:
importer javafx.application.Application; importer javafx.stage.Stage; public class Exemple1 étend Application public static void main (String [] args) launch (args); public void start (Stage theStage) theStage.setTitle ("Hello, World!"); theStage.show ();
Le contenu en JavaFX (texte, images et contrôles d'interface utilisateur, par exemple) est organisé à l'aide d'une structure de données en forme d'arborescence appelée graphe de scène, qui regroupe et organise les éléments d'une scène graphique..
Représentation d'un graphe de scène JavaFX.Un élément général d'un graphe de scène en JavaFX est appelé un nœud. Chaque nœud dans une arborescence a un seul nœud "parent", à l'exception d'un nœud spécial désigné comme "racine". Un groupe est un nœud qui peut avoir plusieurs éléments de nœud "enfants". Les transformations graphiques (translation, rotation et échelle) et les effets appliqués à un groupe s'appliquent également à ses enfants. Les nœuds peuvent être stylés à l'aide de feuilles de style en cascade (CSS) JavaFX, assez similaires au CSS utilisé pour formater les documents HTML..
La classe Scene contient tout le contenu d'un graphe de scène et nécessite la définition d'un nœud racine (en pratique, il s'agit souvent d'un groupe). Vous pouvez définir la taille d'une scène de manière spécifique. sinon, la taille d'une scène sera automatiquement calculée en fonction de son contenu. Un objet Scene doit être passé sur la scène (par le setScene ()
méthode) pour être affiché.
Le rendu des graphiques est particulièrement important pour les programmeurs de jeux! En JavaFX, l’objet Canvas est une image sur laquelle nous pouvons dessiner du texte, des formes et des images, à l’aide de son objet GraphicsContext associé. (Pour les développeurs habitués à la boîte à outils Java Swing, cet objet est similaire à l’objet Graphics transmis à la peindre()
méthode dans la classe JFrame.)
L'objet GraphicsContext contient une multitude de puissantes capacités de personnalisation. Pour choisir les couleurs de dessin du texte et des formes, vous pouvez définir les couleurs de remplissage (intérieur) et de contour (bord), qui sont des objets Paint: il peut s’agir d’une couleur unie, d’un dégradé défini par l'utilisateur (LinearGradient ou RadialGradient) ou même un ImagePattern. Vous pouvez également appliquer un ou plusieurs objets de style d’effet, tels que Lighting, Shadow ou GaussianBlur, et modifier les polices par défaut à l’aide de la classe Font..
La classe Image permet de charger facilement des images à partir de divers formats à partir de fichiers et de les dessiner via la classe GraphicsContext. Il est facile de construire des images générées procéduralement en utilisant la classe WritableImage avec les classes PixelReader et PixelWriter..
En utilisant ces classes, nous pouvons écrire un exemple beaucoup plus digne du style "Hello, World" comme suit. Par souci de brièveté, nous inclurons simplement le début()
méthode ici (nous ignorerons les instructions d'importation et principale()
méthode); Cependant, le code source de travail complet se trouve dans le dépôt GitHub qui accompagne ce tutoriel..
public void start (Stage theStage) theStage.setTitle ("Exemple de canevas"); Group root = new Group (); Scene theScene = nouvelle scène (racine); theStage.setScene (theScene); Toile toile = nouvelle toile (400, 200); root.getChildren (). add (canvas); GraphicsContext gc = canvas.getGraphicsContext2D (); gc.setFill (Color.RED); gc.setStroke (Color.BLACK); gc.setLineWidth (2); Font theFont = Font.font ("Times New Roman", FontWeight.BOLD, 48); gc.setFont (theFont); gc.fillText ("Hello, World!", 60, 50); gc.strokeText ("Hello, World!", 60, 50); Image Earth = new Image ("earth.png"); gc.drawImage (terre, 180, 100); theStage.show ();
Ensuite, nous devons faire nos programmes dynamique, ce qui signifie que l'état du jeu change avec le temps. Nous allons implémenter une boucle de jeu: une boucle infinie qui met à jour les objets du jeu et restitue la scène à l'écran, idéalement à une vitesse de 60 fois par seconde..
Le moyen le plus simple de réaliser cela en JavaFX consiste à utiliser la classe AnimationTimer, où une méthode (nommée manipuler()
) peuvent être écrites et seront appelées à un taux de 60 fois par seconde, ou aussi proche que possible de ce taux. (Cette classe ne doit pas nécessairement être utilisée uniquement à des fins d'animation; elle est capable de beaucoup plus.)
Utiliser la classe AnimationTimer est un peu délicat: étant donné qu’il s’agit d’une classe abstraite, elle ne peut pas être créée directement. La classe doit être étendue avant de pouvoir créer une instance. Cependant, pour nos exemples simples, nous allons étendre la classe en écrivant une classe interne anonyme. Cette classe interne doit définir la méthode abstraite manipuler()
, qui sera passé un seul argument: le temps système actuel en nanosecondes. Après avoir défini la classe interne, nous invoquons immédiatement le début()
méthode, qui commence la boucle. (La boucle peut être arrêtée en appelant le Arrêtez()
méthode.)
Avec ces classes, nous pouvons modifier notre exemple "Hello, World", en créant une animation consistant à placer la Terre en orbite autour du Soleil sur une image de fond étoilée..
public void start (Stage theStage) theStage.setTitle ("Exemple de scénario"); Group root = new Group (); Scene theScene = nouvelle scène (racine); theStage.setScene (theScene); Canvas canvas = nouveau Canvas (512, 512); root.getChildren (). add (canvas); GraphicsContext gc = canvas.getGraphicsContext2D (); Image Earth = new Image ("earth.png"); Image sun = new Image ("sun.png"); Image space = new Image ("space.png"); final long startNanoTime = System.nanoTime (); new AnimationTimer () handle de vide public (long currentNanoTime) double t = (currentNanoTime - startNanoTime) / 1000000000.0; double x = 232 + 128 * Math.cos (t); double y = 232 + 128 * Math.sin (t); // l'image d'arrière-plan efface la toile gc.drawImage (space, 0, 0); gc.drawImage (terre, x, y); gc.drawImage (sun, 196, 196); .début(); theStage.show ();
Il existe d'autres moyens d'implémenter une boucle de jeu dans JavaFX. Une approche légèrement plus longue (mais plus flexible) implique la classe Timeline, qui est une séquence d'animation composée d'un ensemble d'objets KeyFrame. Pour créer une boucle de jeu, la timeline doit être définie pour se répéter indéfiniment et un seul KeyFrame est requis, avec sa durée définie sur 0,016 seconde (pour atteindre 60 cycles par seconde). Cette implémentation peut être trouvée dans le Example3T.java
fichier dans le repo GitHub.
Une autre composante de programmation de jeu fréquemment utilisée est l’animation par images: afficher une séquence d’images en succession rapide pour créer l’illusion de mouvement..
En supposant que toutes les animations en boucle et toutes les images s'affichent pendant le même nombre de secondes, une implémentation de base pourrait être aussi simple que suit:
classe publique AnimatedImage images publiques [] cadres; double durée publique; public Image getFrame (heure double) int index = (int) ((time% (frames.length * duration)) / duration); cadres de retour [index];
Pour intégrer cette classe à l'exemple précédent, nous pourrions créer un OVNI animé, initialisant l'objet à l'aide du code:
AnimatedImage ufo = new AnimatedImage (); Image [] imageArray = new Image [6]; pour (int i = 0; i < 6; i++) imageArray[i] = new Image( "ufo_" + i + ".png" ); ufo.frames = imageArray; ufo.duration = 0.100;
… Et dans AnimationTimer, l'ajout d'une seule ligne de code:
gc.drawImage (ufo.getFrame (t), 450, 25);
… À l'endroit approprié. Pour un exemple complet de code de travail, voir le fichier Example3AI.java
dans le repo GitHub.
La détection et le traitement des entrées utilisateur dans JavaFX sont simples. Les actions utilisateur pouvant être détectées par le système, telles que les appuis sur les touches et les clics de souris, sont appelées événements. En JavaFX, ces actions entraînent automatiquement la génération d'objets (tels que KeyEvent et MouseEvent) qui stockent les données associées (telles que la clé réelle enfoncée ou l'emplacement du pointeur de la souris). Toute classe JavaFX qui implémente la classe EventTarget, telle qu'une scène, peut "écouter" les événements et les gérer. dans les exemples suivants, nous montrerons comment configurer une scène pour traiter divers événements..
En parcourant la documentation de la classe Scene, de nombreuses méthodes permettent de gérer différents types d’entrées provenant de différentes sources. Par exemple, la méthode setOnKeyPressed ()
pouvez affecter un gestionnaire d’événements qui sera activé lorsqu’une touche est enfoncée, la méthode setOnMouseClicked ()
peut affecter un gestionnaire d'événements qui s'active lorsqu'un bouton de la souris est enfoncé, etc. La classe EventHandler sert un objectif: encapsuler une méthode (appelée manipuler()
) appelé lorsque l'événement correspondant se produit.
Lors de la création d’un gestionnaire d’événements, vous devez spécifier le type d'événement qu'il gère: vous pouvez déclarer un Gestionnaire d'événements
ou un Gestionnaire d'événements
, par exemple. De plus, les gestionnaires d'événements sont souvent créés en tant que classes internes anonymes, car ils ne sont généralement utilisés qu'une seule fois (lorsqu'ils sont transmis en tant qu'argument à l'une des méthodes répertoriées ci-dessus)..
Les entrées utilisateur sont souvent traitées dans la boucle principale du jeu et il est donc nécessaire de conserver un enregistrement des clés actuellement actives. Pour ce faire, vous pouvez créer un ArrayList d'objets String. Lorsqu'une touche est enfoncée pour la première fois, nous ajoutons la représentation sous forme de chaîne du KeyCode du KeyEvent à la liste. lorsque la clé est relâchée, nous la retirons de la liste.
Dans l'exemple ci-dessous, le canevas contient deux images de touches de direction; chaque fois qu'une touche est enfoncée, l'image correspondante devient verte.
Le code source est contenu dans le fichier Example4K.java
dans le repo GitHub.
public void start (Stage theStage) theStage.setTitle ("Exemple de clavier"); Group root = new Group (); Scene theScene = nouvelle scène (racine); theStage.setScene (theScene); Canvas canvas = new Canvas (512 - 64, 256); root.getChildren (). add (canvas); ArrayListinput = new ArrayList (); theScene.setOnKeyPressed (nouveau gestionnaire d’événements () public void handle (KeyEvent e) Code de chaîne = e.getCode (). toString (); // ajoute seulement une fois… empêche les doublons if (! input.contains (code)) input.add (code); ); theScene.setOnKeyReleased (nouveau gestionnaire d’événements () public void handle (KeyEvent e) Code de chaîne = e.getCode (). toString (); input.remove (code); ); GraphicsContext gc = canvas.getGraphicsContext2D (); Image left = new Image ("left.png"); Image leftG = new Image ("leftG.png"); Image right = new Image ("right.png"); Image rightG = new Image ("rightG.png"); new AnimationTimer () public void handle (long currentNanoTime) // Efface le canevas gc.clearRect (0, 0, 512,512); if (input.contains ("LEFT")) gc.drawImage (leftG, 64, 64); sinon gc.drawImage (gauche, 64, 64); if (input.contains ("RIGHT")) gc.drawImage (rightG, 256, 64); sinon gc.drawImage (à droite, 256, 64); .début(); theStage.show ();
Voyons maintenant un exemple qui se concentre sur la classe MouseEvent plutôt que sur la classe KeyEvent. Dans ce mini-jeu, le joueur gagne un point à chaque fois que la cible est cliquée.
Etant donné que les gestionnaires d'événements sont des classes internes, toutes les variables qu'ils utilisent doivent être finales ou "effectivement définitives", ce qui signifie que les variables ne peuvent pas être réinitialisées. Dans l'exemple précédent, les données ont été transmises à EventHandler au moyen d'une ArrayList, dont les valeurs peuvent être modifiées sans être réinitialisées (via la commande ajouter()
et retirer()
méthodes).
Toutefois, dans le cas des types de données de base, les valeurs ne peuvent pas être modifiées une fois initialisées. Si vous souhaitez que le gestionnaire d'événements accède aux types de données de base modifiés ailleurs dans le programme, vous pouvez créer une classe wrapper contenant des variables publiques ou des méthodes getter / setter. (Dans l'exemple ci-dessous, IntValue
est une classe qui contient un Publique
int
variable appelée valeur
.)
public void start (Stage theStage) theStage.setTitle ("Cliquez sur la cible!"); Group root = new Group (); Scene theScene = nouvelle scène (racine); theStage.setScene (theScene); Toile toile = nouvelle toile (500, 500); root.getChildren (). add (canvas); Circle targetData = nouveau cercle (100, 100, 32); IntValue points = new IntValue (0); theScene.setOnMouseClicked (nouveau gestionnaire d’événements() public void handle (MouseEvent e) if (targetData.containsPoint (e.getX (), e.getY ())) double x = 50 + 400 * Math.random (); double y = 50 + 400 * Math.random (); targetData.setCenter (x, y); points.value ++; else points.value = 0; ); GraphicsContext gc = canvas.getGraphicsContext2D (); Font theFont = Font.font ("Helvetica", FontWeight.BOLD, 24); gc.setFont (theFont); gc.setStroke (Color.BLACK); gc.setLineWidth (1); Image bullseye = new Image ("bullseye.png"); new AnimationTimer () public void handle (long currentNanoTime) // Efface le canevas gc.setFill (nouvelle couleur (0.85, 0.85, 1.0, 1.0)); gc.fillRect (0,0, 512, 512); gc.drawImage (bullseye, targetData.getX () - targetData.getRadius (), targetData.getY () - targetData.getRadius ()); gc.setFill (Color.BLUE); String pointsText = "Points:" + points.value; gc.fillText (pointsText, 360, 36); gc.strokeText (pointsText, 360, 36); .début(); theStage.show ();
Le code source complet est contenu dans le dépôt GitHub; la classe principale est Example4M.java
.
Dans les jeux vidéo, un lutin est le terme désignant une seule entité visuelle. Vous trouverez ci-dessous un exemple de classe Sprite qui stocke une image et une position, ainsi que des informations de vitesse (pour les entités mobiles) et des informations de largeur / hauteur à utiliser lors du calcul des cadres de sélection pour la détection de collision. Nous avons également les méthodes standard de lecture / définition pour la plupart de ces données (omises pour des raisons de brièveté), ainsi que certaines méthodes standard nécessaires au développement de jeux:
mettre à jour()
: calcule la nouvelle position en fonction de la vitesse du sprite.rendre()
: dessine l'image associée sur le canevas (via la classe GraphicsContext) en utilisant la position comme coordonnées.getBoundary ()
: retourne un objet JavaFX Rectangle2D, utile dans la détection de collision en raison de sa méthode intersects.intersections ()
: détermine si la boîte englobante de ce Sprite intersecte celle d'un autre Sprite.public class Sprite image privée image; double position privée X; double position privéeY; privé double vélocitéX; privé double vélocitéY; privé double largeur; double hauteur privée; //… // méthodes omises par souci de brièveté //… mise à jour publique de vide (temps double) positionX + = velocityX * time; positionY + = vélocitéY * temps; public void render (GraphicsContext gc) gc.drawImage (image, positionX, positionY); public Rectangle2D getBoundary () retourne un nouveau Rectangle2D (positionX, positionY, largeur, hauteur); public boolean intersects (Sprite s) return s.getBoundary (). intersects (this.getBoundary ());
Le code source complet est inclus dans Sprite.java
dans le repo GitHub.
Avec l’aide de la classe Sprite, nous pouvons facilement créer un jeu de collecte simple en JavaFX. Dans ce jeu, vous assumez le rôle d'une mallette sensible dont le but est de collecter les nombreux sacs d'argent laissés par un ancien propriétaire insouciant. Les touches fléchées déplacent le joueur sur l'écran.
Ce code emprunte beaucoup aux exemples précédents: configuration des polices pour afficher la partition, stockage des entrées au clavier avec ArrayList, implémentation de la boucle de jeu avec AnimationTimer et création de classes wrapper pour les valeurs simples devant être modifiées pendant la boucle..
Un segment de code présentant un intérêt particulier consiste à créer un objet Sprite pour le lecteur (porte-documents) et une ArrayList d'objets Sprite pour les objets de collection (sacs d'argent):
Porte-documents Sprite = nouveau Sprite (); briefcase.setImage ("briefcase.png"); briefcase.setPosition (200, 0); ArrayListmoneybagList = new ArrayList (); pour (int i = 0; i < 15; i++) Sprite moneybag = new Sprite(); moneybag.setImage("moneybag.png"); double px = 350 * Math.random() + 50; double py = 350 * Math.random() + 50; moneybag.setPosition(px,py); moneybagList.add( moneybag );
Un autre segment de code intéressant est la création de la AnimationTimer
, qui est chargé de:
new AnimationTimer () public void handle (long currentNanoTime) // calcule le temps écoulé depuis la dernière mise à jour. double elapsedTime = (currentNanoTime - lastNanoTime.value) / 1000000000.0; lastNanoTime.value = currentNanoTime; // logique de jeu briefcase.setVelocity (0,0); if (input.contains ("LEFT")) briefcase.addVelocity (-50,0); if (input.contains ("RIGHT")) briefcase.addVelocity (50,0); if (input.contains ("UP")) briefcase.addVelocity (0, -50); if (input.contains ("DOWN")) briefcase.addVelocity (0,50); briefcase.update (elapsedTime); // détection de collision IteratormoneybagIter = moneybagList.iterator (); while (moneybagIter.hasNext ()) Sprite moneybag = moneybagIter.next (); if (briefcase.intersects (moneybag)) moneybagIter.remove (); score.valeur ++; // render gc.clearRect (0, 0, 512,512); briefcase.render (gc); for (Sprite moneybag: moneybagList) moneybag.render (gc); String pointsText = "Cash: $" + (100 * score.value); gc.fillText (pointsText, 360, 36); gc.strokeText (pointsText, 360, 36); .début();
Comme d’habitude, le code complet se trouve dans le fichier de code ci-joint (Example5.java
) dans le repo GitHub.
Dans ce tutoriel, je vous ai présenté les classes JavaFX utiles à la programmation de jeux. Nous avons travaillé sur une série d'exemples de complexité croissante, aboutissant à un jeu de style collection basé sur des sprites. Vous êtes maintenant prêt à explorer certaines des ressources répertoriées ci-dessus ou à vous lancer et à créer votre propre jeu. Bonne chance à vous dans vos efforts!