JavaFX è un toolkit GUI multipiattaforma per Java ed è il successore delle librerie Java Swing. In questo tutorial, esploreremo le funzionalità di JavaFX che lo rendono facile da usare per iniziare a programmare i giochi in Java.
Questo tutorial presume che tu sappia già come codificare in Java. In caso contrario, consulta Learn Java per Android, Introduzione alla programmazione con il computer con Java: 101 e 201, Head First Java, Greenfoot o Learn Java the Hard Way per iniziare.
Se si sviluppano già applicazioni con Java, probabilmente non è necessario scaricare nulla: JavaFX è stato incluso nel pacchetto JDK (Java Development Kit) standard dalla versione 7J6 di JDK (agosto 2012). Se non hai aggiornato l'installazione Java per un po ', vai al sito Web di download Java per la versione più recente.
La creazione di un programma JavaFX inizia con la classe Application, dalla quale vengono estese tutte le applicazioni JavaFX. La tua classe principale dovrebbe chiamare il lanciare()
metodo, che chiamerà il dentro()
metodo e poi il inizio()
metodo, attendere il completamento dell'applicazione e quindi chiamare il Stop()
metodo. Di questi metodi, solo il inizio()
il metodo è astratto e deve essere ignorato.
La classe Stage è il contenitore JavaFX di livello superiore. Quando viene lanciata un'applicazione, viene creato uno stage iniziale che viene passato al metodo di avvio dell'applicazione. Le fasi controllano le proprietà della finestra di base come titolo, icona, visibilità, resizabilità, modalità a schermo intero e decorazioni; quest'ultimo è configurato usando StageStyle. Le fasi aggiuntive possono essere costruite secondo necessità. Dopo aver configurato uno stage e aggiunto il contenuto, il mostrare()
il metodo è chiamato.
Sapendo tutto questo, possiamo scrivere un esempio minimale che lancia una finestra in JavaFX:
importare javafx.application.Application; import javafx.stage.Stage; public class Example1 estende Application public static void main (String [] args) launch (args); public void start (Stage theStage) theStage.setTitle ("Hello, World!"); theStage.show ();
Il contenuto in JavaFX (come testo, immagini e controlli dell'interfaccia utente) è organizzato utilizzando una struttura di dati ad albero nota come un grafico di scena, che raggruppa e organizza gli elementi di una scena grafica.
Rappresentazione di un grafico di scena JavaFX.Un elemento generale di un grafico di scena in JavaFX è chiamato un nodo. Ogni nodo in un albero ha un singolo nodo "genitore", con l'eccezione di un nodo speciale designato come "root". Un gruppo è un nodo che può avere molti elementi del nodo "figlio". Le trasformazioni grafiche (traslazione, rotazione e scala) e gli effetti applicati a un gruppo si applicano anche ai suoi figli. I nodi possono essere disegnati usando i fogli di stile CSS (Cascading Style Sheets) di JavaFX, abbastanza simili ai CSS usati per formattare i documenti HTML.
La classe Scene contiene tutto il contenuto per un grafico di scena e richiede l'impostazione di un nodo radice (in pratica, questo è spesso un gruppo). È possibile impostare le dimensioni di una scena in modo specifico; in caso contrario, la dimensione di una scena verrà calcolata automaticamente in base al suo contenuto. Un oggetto Scene deve essere passato allo stage (dal setScene ()
metodo) per essere visualizzato.
La grafica di rendering è particolarmente importante per i programmatori di giochi! In JavaFX, l'oggetto Canvas è un'immagine su cui possiamo disegnare testo, forme e immagini, usando l'oggetto GraphicsContext associato. (Per quegli sviluppatori che hanno familiarità con il toolkit Java Swing, questo è simile all'oggetto Graphics passato al dipingere()
metodo nella classe JFrame.)
L'oggetto GraphicsContext contiene una vasta gamma di potenti capacità di personalizzazione. Per scegliere i colori per disegnare testo e forme, puoi impostare i colori di riempimento (interno) e di traccia (bordo), che sono oggetti di disegno: possono essere un singolo colore solido, un gradiente definito dall'utente (LinearGradient o RadialGradient) oppure anche un ImagePattern. Puoi anche applicare uno o più oggetti di stile Effect, come Illuminazione, Ombra o GaussianBlur, e cambiare i font dall'impostazione predefinita usando la classe Font.
La classe Image semplifica il caricamento di immagini da una varietà di formati da file e disegni tramite la classe GraphicsContext. È facile costruire immagini generate proceduralmente utilizzando la classe WritableImage insieme alle classi PixelReader e PixelWriter.
Usando queste classi, possiamo scrivere un esempio in stile "Hello, World" molto più degno di quanto segue. Per brevità, includeremo semplicemente il inizio()
metodo qui (salteremo le istruzioni di importazione e principale()
metodo); tuttavia, il codice sorgente completo funzionante può essere trovato nel repository GitHub che accompagna questo tutorial.
public void start (Stage theStage) theStage.setTitle ("Esempio di canvas"); Radice di gruppo = nuovo gruppo (); Scene theScene = new Scene (root); theStage.setScene (theScene); Canvas canvas = new Canvas (400, 200); root.getChildren (). add (canvas); GraphicsContext gc = canvas.getGraphicsContext2D (); gc.setFill (Color.RED); gc.setStroke (Color.BLACK); gc.setLineWidth (2); Carattere theFont = Font.font ("Times New Roman", FontWeight.BOLD, 48); gc.setFont (theFont); gc.fillText ("Hello, World!", 60, 50); gc.strokeText ("Hello, World!", 60, 50); Immagine terra = nuova immagine ("earth.png"); gc.drawImage (earth, 180, 100); theStage.show ();
Quindi, dobbiamo fare i nostri programmi dinamico, il che significa che lo stato del gioco cambia nel tempo. Implementeremo un ciclo di gioco: un ciclo infinito che aggiorna gli oggetti del gioco e rende la scena sullo schermo, idealmente ad una velocità di 60 volte al secondo.
Il modo più semplice per farlo in JavaFX è l'utilizzo della classe AnimationTimer, in cui un metodo (denominato maniglia()
) può essere scritto che verrà chiamato ad una velocità di 60 volte al secondo, o il più vicino possibile a quella percentuale. (Questa classe non deve essere utilizzata solo per scopi di animazione, ma è in grado di fare molto di più.)
L'uso della classe AnimationTimer è un po 'complicato: poiché è una classe astratta, non può essere creata direttamente: la classe deve essere estesa prima che un'istanza possa essere creata. Tuttavia, per i nostri semplici esempi, estenderemo la classe scrivendo una classe interiore anonima. Questa classe interiore deve definire il metodo astratto maniglia()
, che passerà un singolo argomento: il tempo corrente del sistema in nanosecondi. Dopo aver definito la classe interiore, invochiamo immediatamente il inizio()
metodo, che inizia il ciclo. (Il ciclo può essere fermato chiamando il Stop()
metodo.)
Con queste classi, possiamo modificare il nostro esempio "Hello, World", creando un'animazione composta dalla Terra che orbita intorno al Sole contro un'immagine di sfondo stellata.
public void start (Stage theStage) theStage.setTitle ("Esempio di timeline"); Radice di gruppo = nuovo gruppo (); Scene theScene = new Scene (root); theStage.setScene (theScene); Canvas canvas = new Canvas (512, 512); root.getChildren (). add (canvas); GraphicsContext gc = canvas.getGraphicsContext2D (); Immagine terra = nuova immagine ("earth.png"); Immagine sole = nuova immagine ("sun.png"); Spazio immagine = nuova immagine ("spazio.png"); final long startNanoTime = System.nanoTime (); nuovo AnimationTimer () handle void pubblico (long currentNanoTime) double t = (currentNanoTime - startNanoTime) / 1000000000.0; double x = 232 + 128 * Math.cos (t); doppio y = 232 + 128 * Math.sin (t); // l'immagine di sfondo cancella la tela gc.drawImage (spazio, 0, 0); gc.drawImage (earth, x, y); gc.drawImage (sun, 196, 196); .inizio(); theStage.show ();
Esistono modi alternativi per implementare un ciclo di gioco in JavaFX. Un approccio leggermente più lungo (ma più flessibile) coinvolge la classe Timeline, che è una sequenza di animazione costituita da un insieme di oggetti KeyFrame. Per creare un ciclo di gioco, la Timeline dovrebbe essere impostata per la ripetizione indefinita, e solo un singolo KeyFrame è richiesto, con la sua Durata impostata su 0.016 secondi (per raggiungere 60 cicli al secondo). Questa implementazione può essere trovata nel Example3T.java
file nel repository GitHub.
Un altro componente di programmazione di gioco comunemente richiesto è l'animazione basata su frame: visualizzazione di una sequenza di immagini in rapida successione per creare l'illusione del movimento.
Supponendo che tutto il ciclo di animazioni e tutti i frame vengano visualizzati per lo stesso numero di secondi, un'implementazione di base potrebbe essere la seguente:
public class AnimatedImage public Image [] frames; doppia durata pubblica; getFrame pubblica dell'immagine (doppia volta) int index = (int) ((time% (frames.length * duration)) / duration); return frames [indice];
Per integrare questa classe nell'esempio precedente, potremmo creare un UFO animato, inizializzando l'oggetto usando il codice:
AnimatedImage ufo = new AnimatedImage (); Immagine [] imageArray = new Image [6]; per (int i = 0; i < 6; i++) imageArray[i] = new Image( "ufo_" + i + ".png" ); ufo.frames = imageArray; ufo.duration = 0.100;
... e, all'interno di AnimationTimer, aggiungendo la singola riga di codice:
gc.drawImage (ufo.getFrame (t), 450, 25);
... nel punto appropriato. Per un esempio di codice di lavoro completo, vedere il file Example3AI.java
nel repository GitHub.
Rilevare e processare l'input dell'utente in JavaFX è semplice. Vengono chiamate le azioni dell'utente che possono essere rilevate dal sistema, come i tasti premuti e i clic del mouse eventi. In JavaFX, queste azioni provocano automaticamente la generazione di oggetti (come KeyEvent e MouseEvent) che memorizzano i dati associati (come il tasto premuto o la posizione del puntatore del mouse). Qualsiasi classe JavaFX che implementa la classe EventTarget, come una Scene, può "ascoltare" gli eventi e gestirli; negli esempi che seguono, mostreremo come impostare una scena per elaborare vari eventi.
Guardando la documentazione per la classe Scene, ci sono molti metodi che ascoltano per gestire diversi tipi di input da fonti diverse. Ad esempio, il metodo setOnKeyPressed ()
può assegnare un EventHandler che si attiverà quando viene premuto un tasto, il metodo setOnMouseClicked ()
può assegnare un EventHandler che si attiva quando viene premuto un pulsante del mouse e così via. La classe EventHandler ha uno scopo: incapsulare un metodo (chiamato maniglia()
) che viene chiamato quando si verifica l'evento corrispondente.
Quando si crea un EventHandler, è necessario specificare il genere dell'evento che gestisce: puoi dichiarare un Gestore di eventi
o un Gestore di eventi
, per esempio. Inoltre, EventHandlers vengono spesso creati come classi interne anonime, in quanto vengono in genere utilizzati una sola volta (quando vengono passati come argomento a uno dei metodi elencati sopra).
L'input dell'utente viene spesso elaborato all'interno del ciclo di gioco principale, pertanto è necessario tenere un record di quali chiavi sono attualmente attive. Un modo per ottenere ciò è creare un oggetto ArrayList di oggetti String. Quando viene inizialmente premuto un tasto, aggiungiamo la rappresentazione String del KeyCode di KeyEvent all'elenco; quando la chiave viene rilasciata, la rimuoviamo dalla lista.
Nell'esempio seguente, il canvas contiene due immagini di tasti freccia; ogni volta che viene premuto un tasto, l'immagine corrispondente diventa verde.
Il codice sorgente è contenuto nel file Example4K.java
nel repository GitHub.
public void start (Stage theStage) theStage.setTitle ("Esempio di tastiera"); Radice di gruppo = nuovo gruppo (); Scene theScene = new Scene (root); theStage.setScene (theScene); Canvas canvas = new Canvas (512 - 64, 256); root.getChildren (). add (canvas); Lista di arrayinput = new ArrayList (); theScene.setOnKeyPressed (new EventHandler () handle void pubblico (KeyEvent e) String code = e.getCode (). toString (); // aggiungi solo una volta ... previ i duplicati se (! input.contains (code)) input.add (code); ); theScene.setOnKeyReleased (new EventHandler () handle void pubblico (KeyEvent e) String code = e.getCode (). toString (); input.remove (codice); ); GraphicsContext gc = canvas.getGraphicsContext2D (); Immagine a sinistra = nuova immagine ("left.png"); Immagine leftG = new Image ("leftG.png"); Immagine a destra = nuova immagine ("right.png"); Immagine rightG = new Image ("rightG.png"); new AnimationTimer () public void handle (long currentNanoTime) // Cancella il canvas gc.clearRect (0, 0, 512,512); if (input.contains ("LEFT")) gc.drawImage (leftG, 64, 64); else gc.drawImage (left, 64, 64); if (input.contains ("RIGHT")) gc.drawImage (rightG, 256, 64); else gc.drawImage (right, 256, 64); .inizio(); theStage.show ();
Ora diamo un'occhiata a un esempio che si concentra sulla classe MouseEvent piuttosto che sulla classe KeyEvent. In questo mini-gioco, il giocatore guadagna un punto ogni volta che si fa clic sul bersaglio.
Dato che gli EventHandler sono classi interne, qualsiasi variabile che usano deve essere finale o "efficacemente finale", il che significa che le variabili non possono essere reinizializzate. Nell'esempio precedente, i dati sono stati passati all'EventHandler per mezzo di un ArrayList, i cui valori possono essere modificati senza reinizializzazione (tramite il Inserisci()
e rimuovere()
metodi).
Tuttavia, nel caso di tipi di dati di base, i valori non possono essere modificati una volta inizializzati. Se si desidera che EventHandler acceda ai tipi di dati di base modificati altrove nel programma, è possibile creare una classe wrapper che contenga variabili pubbliche o metodi getter / setter. (Nell'esempio sotto, intValue
è una classe che contiene a pubblico
int
variabile chiamata valore
.)
public void start (Stage theStage) theStage.setTitle ("Fai clic sul bersaglio!"); Radice di gruppo = nuovo gruppo (); Scene theScene = new Scene (root); theStage.setScene (theScene); Canvas canvas = new Canvas (500, 500); root.getChildren (). add (canvas); Circle targetData = new Circle (100,100,32); IntValue points = new IntValue (0); theScene.setOnMouseClicked (new EventHandler() public void handle (MouseEvent e) if (targetData.containsPoint (e.getX (), e.getY ())) double x = 50 + 400 * Math.random (); doppio y = 50 + 400 * Math.random (); targetData.setCenter (x, y); points.value ++; else points.value = 0; ); GraphicsContext gc = canvas.getGraphicsContext2D (); Carattere theFont = Font.font ("Helvetica", FontWeight.BOLD, 24); gc.setFont (theFont); gc.setStroke (Color.BLACK); gc.setLineWidth (1); Immagine bullseye = new Image ("bullseye.png"); nuovo AnimationTimer () public void handle (long currentNanoTime) // Cancella il canvas gc.setFill (new Color (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); .inizio(); theStage.show ();
Il codice sorgente completo è contenuto nel repository GitHub; la classe principale è Example4M.java
.
Nei videogiochi, a folletto è il termine per una singola entità visiva. Di seguito è riportato un esempio di una classe Sprite che memorizza un'immagine e una posizione, oltre a informazioni sulla velocità (per le entità mobili) e la larghezza / altezza da utilizzare quando si calcolano le caselle di delimitazione ai fini del rilevamento delle collisioni. Abbiamo anche i metodi standard getter / setter per la maggior parte di questi dati (omessi per brevità) e alcuni metodi standard necessari nello sviluppo del gioco:
aggiornare()
: calcola la nuova posizione in base alla velocità dello Sprite.render ()
: disegna l'immagine associata alla tela (tramite la classe GraphicsContext) usando la posizione come coordinate.getBoundary ()
: restituisce un oggetto Rectangle2D JavaFX, utile nel rilevamento delle collisioni grazie al metodo intersects.interseca ()
: determina se il riquadro di delimitazione di questo Sprite si interseca con quello di un altro Sprite.Sprite di classe pubblica immagine immagine privata; doppia posizione privata X; doppia posizione privataY; doppia velocityX privata; doppia velocità privataY; doppia larghezza privata; doppia altezza privata; // ... // metodi omessi per brevità // ... public void update (double time) positionX + = velocityX * time; posizioneY + = velocityY * tempo; public void render (GraphicsContext gc) gc.drawImage (image, positionX, positionY); public Rectangle2D getBoundary () return new Rectangle2D (positionX, positionY, width, height); intersechi booleani pubblici (Sprite s) return s.getBoundary (). intersects (this.getBoundary ());
Il codice sorgente completo è incluso in Sprite.java
nel repository GitHub.
Con l'assistenza della classe Sprite, possiamo facilmente creare un semplice gioco di raccolta in JavaFX. In questo gioco, si assume il ruolo di una valigetta senziente il cui obiettivo è quello di raccogliere le molte borse di denaro che sono state lasciate in giro da un proprietario precedente incurante. I tasti freccia spostano il giocatore sullo schermo.
Questo codice prende molto in prestito dagli esempi precedenti: impostazione dei caratteri per visualizzare lo spartito, memorizzazione degli input da tastiera con ArrayList, implementazione del ciclo di gioco con un AnimationTimer e creazione di classi wrapper per valori semplici che devono essere modificati durante il ciclo di gioco.
Un segmento di codice di particolare interesse comporta la creazione di un oggetto Sprite per il giocatore (valigetta) e un oggetto ArrayList di oggetti Sprite per gli oggetti da collezione (sacchi di denaro):
Sprite briefcase = new Sprite (); briefcase.setImage ( "briefcase.png"); briefcase.setPosition (200, 0); Lista di arraymoneybagList = new ArrayList (); per (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 altro segmento di codice di interesse è la creazione del AnimationTimer
, che ha il compito di:
nuovo AnimationTimer () handle void pubblico (long currentNanoTime) // calcola il tempo dall'ultimo aggiornamento. double elapsedTime = (currentNanoTime - lastNanoTime.value) / 1000000000.0; lastNanoTime.value = currentNanoTime; // game logic 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); // Iterator di rilevamento collisionimoneybagIter = moneybagList.iterator (); while (moneybagIter.hasNext ()) Sprite moneybag = moneybagIter.next (); if (briefcase.intersects (moneybag)) moneybagIter.remove (); score.value ++; // render gc.clearRect (0, 0, 512,512); briefcase.render (gc); per (Sprite moneybag: moneybagList) moneybag.render (gc); String pointsText = "Cash: $" + (100 * score.value); gc.fillText (pointsText, 360, 36); gc.strokeText (pointsText, 360, 36); .inizio();
Come al solito, il codice completo può essere trovato nel file di codice allegato (Example5.java
) nel repository GitHub.
In questo tutorial, ho introdotto le classi JavaFX utili nella programmazione di giochi. Abbiamo lavorato attraverso una serie di esempi di crescente complessità, culminando in un gioco in stile collezione basato su sprite. Ora sei pronto per indagare su alcune delle risorse sopra elencate, o per tuffarti e iniziare a creare il tuo gioco. La migliore fortuna per te nei tuoi sforzi!