JavaFX ist ein plattformübergreifendes GUI-Toolkit für Java und ist der Nachfolger der Java-Swing-Bibliotheken. In diesem Lernprogramm werden wir die Funktionen von JavaFX kennenlernen, die es Ihnen erleichtern, die ersten Spiele in Java zu programmieren.
In diesem Lernprogramm wird vorausgesetzt, dass Sie bereits wissen, wie Sie in Java programmieren. Wenn nicht, lesen Sie Learn Java für Android, Einführung in die Computerprogrammierung mit Java: 101 und 201, Head First Java, Greenfoot oder Learn Java the Hard Way.
Wenn Sie bereits Anwendungen mit Java entwickeln, müssen Sie wahrscheinlich überhaupt nichts herunterladen: JavaFX ist seit JDK Version 7u6 (August 2012) im Standard-JDK-Paket (Java Development Kit) enthalten. Wenn Sie Ihre Java-Installation seit einiger Zeit nicht aktualisiert haben, besuchen Sie die Java-Download-Website, um die neueste Version zu erhalten.
Das Erstellen eines JavaFX-Programms beginnt mit der Application-Klasse, aus der alle JavaFX-Anwendungen erweitert werden. Ihre Hauptklasse sollte die starten()
Methode, die dann die drin()
Methode und dann die Start()
warten Sie, bis die Anwendung abgeschlossen ist, und rufen Sie dann die halt()
Methode. Von diesen Methoden nur die Start()
Methode ist abstrakt und muss überschrieben werden.
Die Stage-Klasse ist der JavaFX-Container der obersten Ebene. Beim Start einer Anwendung wird eine erste Stufe erstellt und an die Startmethode der Anwendung übergeben. Stufen steuern grundlegende Fenstereigenschaften wie Titel, Symbol, Sichtbarkeit, Größenänderung, Vollbildmodus und Dekorationen; Letzteres wird mit StageStyle konfiguriert. Zusätzliche Stufen können nach Bedarf gebaut werden. Nachdem eine Bühne konfiguriert und der Inhalt hinzugefügt wurde, wird die Show()
Methode wird aufgerufen.
Wenn wir all das wissen, können wir ein minimales Beispiel schreiben, das ein Fenster in JavaFX startet:
Import javafx.application.Application; Import javafx.stage.Stage; public class Example1 erweitert Application public static void main (String [] args) launch (args); public void start (Stage theStage) theStage.setTitle ("Hallo, Welt!"); theStage.show ();
Inhalt in JavaFX (z. B. Text, Bilder und Steuerelemente der Benutzeroberfläche) wird in einer baumartigen Datenstruktur organisiert, die als Szenendiagramm bezeichnet wird und die Elemente einer grafischen Szene gruppiert und anordnet.
Darstellung eines JavaFX-Szenengraphen.Ein allgemeines Element eines Szenendiagramms in JavaFX wird als Knoten bezeichnet. Jeder Knoten in einem Baum hat einen einzelnen "Elternknoten", mit Ausnahme eines speziellen Knotens, der als "Stamm" bezeichnet wird. Eine Gruppe ist ein Knoten, der viele "untergeordnete" Knotenelemente enthalten kann. Grafische Transformationen (Übersetzung, Rotation und Skalierung) und Effekte, die auf eine Gruppe angewendet werden, gelten auch für ihre untergeordneten Elemente. Knoten können mit JavaFX Cascading Style Sheets (CSS) gestaltet werden, ähnlich wie das zum Formatieren von HTML-Dokumenten verwendete CSS.
Die Scene-Klasse enthält den gesamten Inhalt für ein Szenendiagramm und erfordert das Festlegen eines Stammknotens (in der Praxis handelt es sich dabei häufig um eine Gruppe). Sie können die Größe einer Szene spezifisch einstellen. Andernfalls wird die Größe einer Szene automatisch basierend auf ihrem Inhalt berechnet. Ein Scene-Objekt muss an die Bühne übergeben werden setScene ()
Methode), um angezeigt zu werden.
Das Rendern von Grafiken ist besonders für Programmierer wichtig! In JavaFX ist das Canvas-Objekt ein Bild, auf dem Text, Formen und Bilder mithilfe des zugehörigen GraphicsContext-Objekts gezeichnet werden können. (Für Entwickler, die mit dem Java Swing-Toolkit vertraut sind, ähnelt dies dem Graphics-Objekt, das an das übergeben wird Farbe()
Methode in der JFrame-Klasse.)
Das GraphicsContext-Objekt enthält zahlreiche leistungsstarke Anpassungsfähigkeiten. Um Farben für das Zeichnen von Text und Formen zu wählen, können Sie die Füllungs- (Innen-) und Konturfarben (Rahmen) festlegen. Hierbei handelt es sich um Paint-Objekte: Hierbei kann es sich um eine einzelne Farbe, um einen benutzerdefinierten Verlauf (entweder LinearGradient oder RadialGradient) oder handeln sogar ein ImagePattern. Sie können auch ein oder mehrere Effektstilobjekte (z. B. Beleuchtung, Schatten oder GaussianBlur) anwenden und die Standardschriftarten mithilfe der Font-Klasse ändern.
Die Image-Klasse vereinfacht das Laden von Bildern aus verschiedenen Formaten und das Zeichnen über die GraphicsContext-Klasse. Es ist einfach, prozedural generierte Bilder zu erstellen, indem die WritableImage-Klasse zusammen mit den PixelReader- und PixelWriter-Klassen verwendet wird.
Mit diesen Klassen können wir ein viel würdigeres "Hello, World" -Stil wie folgt schreiben. Der Kürze halber nehmen wir nur die Start()
Methode hier (wir überspringen die Importanweisungen und Main()
Methode); Den vollständigen Arbeitsquellcode finden Sie jedoch im GitHub-Repo, das diesem Lernprogramm beiliegt.
public void start (Stage theStage) theStage.setTitle ("Canvas-Beispiel"); Gruppenwurzel = neue Gruppe (); Szene theScene = neue Szene (Wurzel); theStage.setScene (theScene); Leinwand Leinwand = neue Leinwand (400, 200); root.getChildren (). add (Zeichenfläche); 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 ("Hallo, Welt!", 60, 50); gc.strokeText ("Hallo, Welt!", 60, 50); Bild Erde = neues Bild ("earth.png"); gc.drawImage (Erde, 180, 100); theStage.show ();
Als nächstes müssen wir unsere Programme erstellen dynamisch, Dies bedeutet, dass sich der Spielzustand im Laufe der Zeit ändert. Wir implementieren eine Spielschleife: eine Endlosschleife, die die Spielobjekte aktualisiert und die Szene auf dem Bildschirm darstellt, idealerweise 60 Mal pro Sekunde.
Der einfachste Weg, dies in JavaFX zu erreichen, besteht darin, die AnimationTimer-Klasse zu verwenden, wobei eine Methode (benannt Griff()
) kann so geschrieben werden, dass sie 60 Mal pro Sekunde oder so nahe wie möglich angerufen wird. (Diese Klasse muss nicht nur für Animationszwecke verwendet werden; sie kann weit mehr.)
Die AnimationTimer-Klasse ist etwas kompliziert: Da es sich um eine abstrakte Klasse handelt, kann sie nicht direkt erstellt werden. Die Klasse muss erweitert werden, bevor eine Instanz erstellt werden kann. In unseren einfachen Beispielen erweitern wir die Klasse jedoch um eine anonyme innere Klasse. Diese innere Klasse muss die abstrakte Methode definieren Griff()
, Dem wird ein einziges Argument übergeben: die aktuelle Systemzeit in Nanosekunden. Nachdem wir die innere Klasse definiert haben, rufen wir sofort die Start()
Methode, die die Schleife beginnt. (Die Schleife kann durch Aufruf von beendet werden halt()
Methode.)
Mit diesen Klassen können wir unser Beispiel "Hallo, Welt" ändern und eine Animation erstellen, bei der die Erde vor einem Sternenhimmelbild um die Sonne kreist.
public void start (Stage theStage) theStage.setTitle ("Timeline-Beispiel"); Gruppenwurzel = neue Gruppe (); Szene theScene = neue Szene (Wurzel); theStage.setScene (theScene); Leinwand Leinwand = neue Leinwand (512, 512); root.getChildren (). add (Zeichenfläche); GraphicsContext gc = canvas.getGraphicsContext2D (); Bild Erde = neues Bild ("earth.png"); Bildsonne = neues Bild ("sun.png"); Bildraum = neues Bild ("space.png"); final long startNanoTime = System.nanoTime (); new AnimationTimer () öffentliches ungültiges Handle (long currentNanoTime) double t = (currentNanoTime - startNanoTime) / 1000000000.0; doppeltes x = 232 + 128 * Math.cos (t); doppeltes y = 232 + 128 * Math.sin (t); // Hintergrundbild löscht die Leinwand gc.drawImage (Leerzeichen, 0, 0); gc.drawImage (Erde, x, y); gc.drawImage (son, 196, 196); .Start(); theStage.show ();
Es gibt alternative Möglichkeiten, eine Spielschleife in JavaFX zu implementieren. Ein etwas längerer (aber flexiblerer) Ansatz umfasst die Timeline-Klasse, eine Animationssequenz, die aus einer Reihe von KeyFrame-Objekten besteht. Um eine Spieleschleife zu erstellen, sollte die Timeline auf unbegrenzte Zeit wiederholt werden. Es ist nur ein einzelner KeyFrame erforderlich. Die Dauer beträgt 0,016 Sekunden (um 60 Zyklen pro Sekunde zu erreichen). Diese Implementierung finden Sie im Beispiel3T.java
Datei im GitHub-Repo.
Eine weitere, häufig benötigte Spielprogrammierungskomponente ist die bildbasierte Animation: Anzeige einer Folge von Bildern in schneller Folge, um die Illusion von Bewegung zu erzeugen.
Unter der Annahme, dass alle Animationen eine Schleife und alle Frames für die gleiche Anzahl von Sekunden anzeigen, könnte eine grundlegende Implementierung so einfach sein:
öffentliche Klasse AnimatedImage public Image [] Frames; öffentliche Doppeldauer; public Image getFrame (doppelte Zeit) int index = (int) ((Zeit% (frames.length * duration)) / duration); Rückgaberahmen [Index];
Um diese Klasse in das vorherige Beispiel zu integrieren, können Sie ein animiertes UFO erstellen, das das Objekt mit dem Code initialisiert:
AnimatedImage ufo = new AnimatedImage (); Image [] imageArray = neues Image [6]; für (int i = 0; i < 6; i++) imageArray[i] = new Image( "ufo_" + i + ".png" ); ufo.frames = imageArray; ufo.duration = 0.100;
… Und innerhalb des AnimationTimers die einzelne Codezeile hinzufügen:
gc.drawImage (ufo.getFrame (t), 450, 25);
… An der richtigen Stelle. Ein vollständiges Arbeitscode-Beispiel finden Sie in der Datei Beispiel3AI.java
im GitHub-Repo.
Das Erkennen und Verarbeiten von Benutzereingaben in JavaFX ist unkompliziert. Benutzeraktionen, die vom System erkannt werden können, z. B. Tastendruck und Mausklick, werden aufgerufen Veranstaltungen. In JavaFX führen diese Aktionen automatisch zur Generierung von Objekten (z. B. KeyEvent und MouseEvent), in denen die zugehörigen Daten gespeichert sind (z. B. die tatsächlich gedrückte Taste oder die Position des Mauszeigers). Jede JavaFX-Klasse, die die EventTarget-Klasse implementiert, z. B. eine Szene, kann auf Ereignisse "hören" und diese verarbeiten. In den folgenden Beispielen zeigen wir Ihnen, wie Sie eine Szene einrichten, um verschiedene Ereignisse zu verarbeiten.
Beim Durchsehen der Dokumentation für die Scene-Klasse gibt es viele Methoden, die darauf achten, mit verschiedenen Arten von Eingaben aus verschiedenen Quellen umzugehen. Zum Beispiel die Methode setOnKeyPressed ()
kann einem EventHandler die Methode zuweisen, die beim Drücken einer Taste aktiviert wird setOnMouseClicked ()
kann einen EventHandler zuweisen, der beim Drücken einer Maustaste aktiviert wird, und so weiter. Die EventHandler-Klasse dient einem einzigen Zweck: Um eine Methode einzukapseln (aufgerufen Griff()
) das aufgerufen wird, wenn das entsprechende Ereignis eintritt.
Beim Erstellen eines EventHandler müssen Sie den angeben Art von Ereignis, das es behandelt: Sie können ein deklarieren EventHandler
oder ein EventHandler
, zum Beispiel. EventHandlers werden außerdem häufig als anonyme innere Klassen erstellt, da sie normalerweise nur einmal verwendet werden (wenn sie als Argument an eine der oben aufgeführten Methoden übergeben werden)..
Benutzereingaben werden häufig innerhalb der Hauptspielschleife verarbeitet, und es muss aufgezeichnet werden, welche Tasten gerade aktiv sind. Eine Möglichkeit, dies zu erreichen, ist das Erstellen einer ArrayList von String-Objekten. Wenn eine Taste anfänglich gedrückt wird, fügen wir der Liste die String-Darstellung des KeyEvent-Schlüsselcodes hinzu. Wenn der Schlüssel losgelassen wird, entfernen wir ihn aus der Liste.
Im folgenden Beispiel enthält die Leinwand zwei Bilder mit Pfeiltasten. Immer wenn eine Taste gedrückt wird, wird das entsprechende Bild grün.
Der Quellcode ist in der Datei enthalten Example4K.java
im GitHub-Repo.
public void start (Stage theStage) theStage.setTitle ("Keyboard Example"); Gruppenwurzel = neue Gruppe (); Szene theScene = neue Szene (Wurzel); theStage.setScene (theScene); Leinwand Leinwand = neue Leinwand (512 - 64, 256); root.getChildren (). add (Zeichenfläche); Anordnungslisteinput = neue ArrayList (); theScene.setOnKeyPressed (neuer EventHandler () public void handle (KeyEvent e) String code = e.getCode (). toString (); // Nur einmal hinzufügen… Duplikate verhindern, wenn (! input.contains (code)) input.add (code); ); theScene.setOnKeyReleased (neuer EventHandler () public void handle (KeyEvent e) String code = e.getCode (). toString (); input.remove (code); ); GraphicsContext gc = canvas.getGraphicsContext2D (); Bild links = neues Bild ("left.png"); Bild leftG = neues Bild ("leftG.png"); Bild rechts = neues Bild ("right.png"); Bild rightG = neues Bild ("rightG.png"); new AnimationTimer () öffentlicher void-Handle (long currentNanoTime) // Die Leinwand löschen gc.clearRect (0, 0, 512,512); if (input.contains ("LEFT")) gc.drawImage (leftG, 64, 64); else gc.drawImage (links, 64, 64); if (input.contains ("RIGHT")) gc.drawImage (rightG, 256, 64); else gc.drawImage (rechts, 256, 64); .Start(); theStage.show ();
Betrachten wir nun ein Beispiel, das sich auf die MouseEvent-Klasse anstatt auf die KeyEvent-Klasse konzentriert. In diesem Minispiel erhält der Spieler bei jedem Klick auf das Ziel einen Punkt.
Da es sich bei den EventHandlers um innere Klassen handelt, müssen alle von ihnen verwendeten Variablen final oder "effektiv final" sein, was bedeutet, dass die Variablen nicht erneut initialisiert werden können. Im vorherigen Beispiel wurden die Daten mittels einer ArrayList an den EventHandler übergeben, deren Werte ohne Neuinitialisierung geändert werden können (über die hinzufügen()
und Löschen()
Methoden).
Bei Basisdatentypen können die Werte jedoch nach der Initialisierung nicht mehr geändert werden. Wenn der EventHandler auf grundlegende Datentypen zugreifen soll, die an anderer Stelle im Programm geändert werden, können Sie eine Wrapper-Klasse erstellen, die entweder öffentliche Variablen oder Getter / Setter-Methoden enthält. (Im Beispiel unten, IntValue
ist eine Klasse, die a enthält Öffentlichkeit
int
Variable aufgerufen Wert
.)
public void start (Stage theStage) theStage.setTitle ("Klicken Sie auf das Ziel!"); Gruppenwurzel = neue Gruppe (); Szene theScene = neue Szene (Wurzel); theStage.setScene (theScene); Leinwand Leinwand = neue Leinwand (500, 500); root.getChildren (). add (Zeichenfläche); Kreis Zieldaten = neuer Kreis (100, 100, 32); IntValue-Punkte = neuer IntValue (0); theScene.setOnMouseClicked (neuer EventHandler() public void handle (MouseEvent e) if (targetData.containsPoint (e.getX (), e.getY ())) double x = 50 + 400 * Math.random (); doppelt y = 50 + 400 * Math.random (); targetData.setCenter (x, y); Punkte.Wert ++; 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); Bild bullseye = neues Bild ("bullseye.png"); new AnimationTimer () öffentlicher void-Handle (long currentNanoTime) // Die Leinwand löschen gc.setFill (neue Farbe (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); .Start(); theStage.show ();
Der vollständige Quellcode ist im GitHub-Repo enthalten. Die Hauptklasse ist Beispiel4M.java
.
Bei Videospielen a Sprite ist der Begriff für eine einzelne visuelle Entität. Im Folgenden finden Sie ein Beispiel für eine Sprite-Klasse, in der ein Bild und eine Position sowie Geschwindigkeitsinformationen (für mobile Entitäten) und Breiten- / Höheninformationen gespeichert werden, die zum Berechnen von Begrenzungsrahmen zum Zwecke der Kollisionserkennung verwendet werden. Wir haben auch die Standard-Getter- / Setter-Methoden für die meisten dieser Daten (zur Verkürzung weggelassen) und einige Standardmethoden, die bei der Spielentwicklung benötigt werden:
aktualisieren()
: berechnet die neue Position basierend auf der Geschwindigkeit des Sprites.machen()
: zeichnet das zugehörige Bild (über die GraphicsContext-Klasse) auf die Leinwand, wobei die Position als Koordinaten verwendet wird.getBoundary ()
: Gibt ein JavaFX Rectangle2D-Objekt zurück, das aufgrund seiner Methode der Kreuzung zur Kollisionserkennung nützlich ist.schneidet ()
: bestimmt, ob der Begrenzungsrahmen dieses Sprites den eines anderen Sprites schneidet.öffentliche Klasse Sprite private Image image; private DoppelpositionX; private DoppelstellungY; privates doppeltes VelocityX; private doppelte VelocityY; private doppelte Breite; private doppelte Höhe; //… // Methoden, die der Kürze halber ausgelassen wurden //… public void update (doppelte Zeit) positionX + = VelocityX * time; PositionY + = GeschwindigkeitY * Zeit; public void render (GraphicsContext gc) gc.drawImage (Bild, PositionX, PositionY); public Rectangle2D getBoundary () return new Rectangle2D (positionX, positionY, width, height); public boolean intersects (Sprite s) return s.getBoundary (). intersects (this.getBoundary ());
Der vollständige Quellcode ist in enthalten Sprite.java
im GitHub-Repo.
Mit Hilfe der Sprite-Klasse können Sie in JavaFX problemlos ein einfaches Sammelspiel erstellen. In diesem Spiel übernehmen Sie die Rolle eines empfindungsfrohen Aktenkoffers, dessen Ziel es ist, die vielen Geldsäcke zu sammeln, die ein unvorsichtiger Vorbesitzer herumliegen hat. Die Pfeiltasten bewegen den Spieler auf dem Bildschirm.
Dieser Code entstammt stark den vorherigen Beispielen: Einrichten von Schriftarten zur Anzeige der Partitur, Speichern der Tastatureingaben mit einer ArrayList, Implementieren der Spielschleife mit einem AnimationTimer und Erstellen von Wrapper-Klassen für einfache Werte, die während der Spielschleife geändert werden müssen.
Ein Codesegment von besonderem Interesse umfasst das Erstellen eines Sprite-Objekts für den Player (Aktenkoffer) und eine ArrayList von Sprite-Objekten für die Sammelobjekte (Geldbeutel):
Sprite Aktenkoffer = neues Sprite (); briefcase.setImage ("briefcase.png"); Aktenkoffer.setPosition (200, 0); AnordnungslistemoneybagList = neue ArrayList (); für (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 );
Ein weiteres interessantes Codesegment ist die Erstellung des AnimationTimer
, die mit folgenden Aufgaben betraut ist:
new AnimationTimer () öffentliches ungültiges Handle (long currentNanoTime) // Zeit seit der letzten Aktualisierung berechnen. double abgelaufene Zeit = (currentNanoTime - lastNanoTime.value) / 1000000000.0; lastNanoTime.value = currentNanoTime; // Spiellogik 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); Aktenkoffer.update (elapsedTime); // Kollisionserkennungs-IteratormoneybagIter = moneybagList.iterator (); while (moneybagIter.hasNext ()) Sprite moneybag = moneybagIter.next (); if (aktenkoffer.durchschnitte (moneybag)) moneybagIter.remove (); score.value ++; // render gc.clearRect (0, 0, 512,512); Aktenkoffer.Render (gc); für (Sprite moneybag: moneybagList) moneybag.render (gc); String pointsText = "Cash: $" + (100 * score.value); gc.fillText (pointsText, 360, 36); gc.strokeText (pointsText, 360, 36); .Start();
Den vollständigen Code finden Sie wie üblich in der beigefügten Codedatei (Beispiel5.java
) im GitHub-Repo.
In diesem Tutorial habe ich Ihnen JavaFX-Klassen vorgestellt, die bei der Programmierung von Spielen nützlich sind. Wir haben eine Reihe von Beispielen mit zunehmender Komplexität durchgearbeitet, die in einem auf Sprites basierenden Spiel im Sammlungsstil gipfelten. Jetzt können Sie entweder einige der oben aufgeführten Ressourcen untersuchen oder eintauchen und Ihr eigenes Spiel erstellen. Ihnen viel Glück bei Ihren Bemühungen!