Teil 1: Grundlagen

FXGL Tutorial: Einfache Spiele mit JavaFX erstellen

Almas Baimagambetov

© Shutterstock.com/chuckchee

Im ersten Teil dieses Tutorials werden wir ein sehr einfaches Spiel erstellen. Ich gehe davon aus, dass FXGL bereits vorhanden ist und sich ein Java-Projekt mit Zugriff auf die neueste Version von FXGL in der IDE der Wahl befindet. Es ist zu beachten, dass dieses Tutorial für FXGL 11.0+ und Java 11+ gedacht ist.

Phase 1: Die Definition des Ziels

Zuerst definieren wir ein paar Eckdaten für unser einfaches Spiel:

  1. Es soll in einem Fenster mit der Auflösung von 600×600 Pixeln laufen.
  2. Ein Spieler soll sich auf dem Bildschirm befinden, dargestellt durch ein blaues Rechteck.
  3. Die Spielfigur soll mit den W-, A-, S- und D-Tasten des Keyboards bewegt werden.
  4. Das UI wird durch eine einzelne Textzeile dargestellt.
  5. Der angezeigte Text wird aktualisiert, sobald sich der Spieler bewegt. Angezeigt wird in Textform, um wie viele Pixel sich der Spieler während seiner Lebensdauer bewegt hat.

Am Ende dieses Tutorials sollten Sie so etwas herausbekommen:

Fig. 1: Grundlagenspielbeispiel. Quelle: GitHub

Quelle: GitHub

Obwohl das Ganze vielleicht nicht wie ein Spiel aussieht, wird es Ihnen helfen, die grundlegenden Funktionen von FXGL zu verstehen. Nachdem Sie dieses Tutorial beendet haben, können Sie basierend auf diesen Grundlagen eine Vielzahl von einfachen Spielen erstellen.

Phase 2: Die Vorbereitung

Jetzt, da wir eine grobe Vorstellung davon haben, wie unser Spiel aussehen soll, können wir zurück in die IDE gehen und ein Paket für unser Spiel erstellen.

Achtung: Die Verzeichnisstruktur ist ähnlich wie die Maven-Verzeichnisstruktur, aber wenn Sie nicht wissen, was das ist, machen Sie sich keine Sorgen. Wir werden die Struktur zu einem späteren Zeitpunkt behandeln. An dieser Stelle ist es ausreichend, src als Hauptquellverzeichnis zu haben.

Ich werde tutorial als Namen für das Paket verwenden.

  1. Erstellen Sie das Paket tutorial in Ihrer IDE.
  2. Erstellen Sie eine Java-Klasse mit dem Namen BasicGameApp innerhalb des tutorial-Pakets.

Es ist üblich, dass man app an die Klasse anhängt, in der sich ihre main()-Methode befindet. Auf diese Weise können andere Entwickler leicht erkennen, wo sich der wichtigste Einstiegspunkt Ihres Spiels befindet. Ich schlage vor, dass Sie ihre BasicGameApp-Klasse öffnen und diese Importe hinzufügen. Das erleichtert die nächsten Schritte maßgeblich:

import com.almasb.fxgl.app.GameApplication;
import com.almasb.fxgl.app.GameSettings;
import com.almasb.fxgl.dsl.FXGL;
import com.almasb.fxgl.entity.Entity;
import com.almasb.fxgl.input.Input;
import com.almasb.fxgl.input.UserAction;
import javafx.scene.input.KeyCode;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import java.util.Map;

Jetzt sind wir zum Programmieren bereit.

Phase 3: Die Programmierung

Um FXGL verwenden zu können, muss Ihre App-Klasse die GameApplication erweitern und die initSettings()-Methode überschreiben:

public class BasicGameApp extends GameApplication {

    @Override
    protected void initSettings(GameSettings settings) {}
}

Die meisten IDEs werden die überschriebene Methode automatisch generieren, sobald man GameApplication erweitert hat. Nun wollen wir in der Lage sein, das Spiel zu starten. Um das zu bewerkstelligen fügen wir einfach Folgendes hinzu:

public static void main(String[] args) {
    launch(args);
}

Wer bereits in JavaFX programmiert hat, dem wird auffallen, dass es sich um die exakt gleiche Signatur handelt, die man auch benutzt, um eine JavaFX-Applikation zu starten. Kurz und knapp gesagt handelt es sich bei FXGL um nichts anderes, als um eine JavaFX-Applikation, die über Funktionen zur Spieleentwicklung verfügt.

Herausforderung 1: Das Fenster

Zu diesem Zeitpunkt sollte es schon möglich sein, das Spiel auszuführen, aber vorher optimieren wir ein paar Einstellungen.

@Override
protected void initSettings(GameSettings settings) {
    settings.setWidth(600);
    settings.setHeight(600);
    settings.setTitle("Basic Game App");
    settings.setVersion("0.1");
}

Wie man sehen kann, werden alle Einstellungen innerhalb von initSettings() geändert. Sobald die Einstellungen festgelegt worden sind, können diese während der Laufzeit nicht geändert werden. Jetzt kann man in seiner IDE auf „Ausführen“ klicken, was das Spiel in einem 600×600 Pixel großen Fenster mit dem Titel „Basic Game App“ starten sollte.

Wir haben damit die erste Herausforderung gemeistert. Ganz leicht, nicht wahr?

Herausforderung 2: Der Spieler

Der nächste Schritt besteht darin, den Spieler hinzuzufügen und ihn auf dem Bildschirm darzustellen. Das werden wir in initGame() machen. Kurz gesagt, hier richtet man alles ein, was vor Spielbeginn fertig sein muss.

private Entity player;

@Override
protected void initGame() {
    player = FXGL.entityBuilder()
            .at(300, 300)
            .view(new Rectangle(25, 25, Color.BLUE))
            .buildAndAttach();
}

(Achtung: Für das Speichern bzw. Laden von Systemen ist es wichtig, dass man Instanzebenenfelder nicht bei der Deklaration initialisiert, sondern in initGame().)

Den Spieler hinzuzufügen ist eine ganze Menge Arbeit auf einmal, wenn man nicht mit Fluent API vertraut ist. Deswegen wollen wir es langsam angehen. Es gibt ein Instanzebenenfeld, dass player heißt und vom Typ Entity ist. Eine Entity ist im Grunde genommen ein Spielobjekt. Das ist alles, was man im Moment darüber wissen muss. FXGL.entityBuilder() ist die bevorzugte Methode, um Entitys zu erstellen. Durch den Aufruf von .at() positionieren wir die Entity dort, wo wir sie haben wollen. In diesem Fall ist das bei x = 300 und y = 300.

(Achtung: Die Position einer Entity in FXGL ist ihr oberer linker Punkt, wie in JavaFX.)

Dann sagen wir dem Builder, dass er eine Ansicht der Entity erstellen soll, wofür er den UI-Knoten verwendet, den wir als Parameter übergeben. In diesem Fall ist dies ein Standard-JavaFX-Rectangle mit width = 25, height = 25 und der Farbe Blau.

(Achtung: Man kann jedes Objekt, das auf JavaFX-Knoten basiert, verwenden, was ziemlich cool ist 😄).

Schließlich rufen wir .buildAndAndAttach() auf. Durch den Aufruf von „build“ können wir die Referenz auf die Entity erhalten, die wir erstellt haben. Der „attach“-Teil ermöglicht es uns praktischerweise, die erstellte Entity direkt in die Spielwelt zu integrieren. Wenn man das Spiel ausführt, sollte man jetzt ein blaues Rechteck in der Mitte des Bildschirms sehen.

Großartig, wir haben gerade die zweite Herausforderung gemeistert!

Herausforderung 3: Der Input

Wir werden nun mit der Herausforderung „Benutzereingabe“ fortfahren. Den Code für das Handling des User Inputs fügen wir in initInput() ein.

@Override
protected void initInput() {
    Input input = FXGL.getInput();

    input.addAction(new UserAction("Move Right") {
        @Override
        protected void onAction() {
            player.translateX(5); // move right 5 pixels
        }
    }, KeyCode.D);
}

Gehen wir diesen Ausschnitt Zeile für Zeile durch. Zuerst holen wir uns das Input-Objekt. Um die meisten FXGL-Funktionen zu verwenden, muss man nur FXGL.*** aufrufen. Die IDE zeigt einem dann alle Funktionen an, die man aufrufen kann.

Als nächstes fügen wir eine Aktion hinzu, gefolgt von einem Schlüssel-Code. Auch hier gilt: Wenn man JavaFX schon einmal verwendet hat, weiß man, dass es sich hierbei um genau die gleichen Schlüssel-Codes handelt, die auch in Event Handlern verwendet werden. Wir sagen im Grunde genommen: Wenn ‚D‘ gedrückt wird, mach die Aktion, die wir erstellt haben.

Schauen wir uns nun die Aktion selbst an. Wenn wir eine Aktion erstellen, geben wir ihr auch einen Namen – Move Right. Das ist wichtig, da diese direkt an die Steuerungen und Menüsysteme weitergeleitet wird, wo der Benutzer sie jederzeit ändern kann. Der Name muss also für den Benutzer aussagekräftig und auch einzigartig sein.

Sobald wir die Aktion erstellt haben, überschreiben wir eine ihrer Methoden (diesmal onAction()) und stellen etwas Code bereit. Dieser Code wird aufgerufen, wenn die Aktion ausgeführt wird, d.h. wenn ‚D‘ gedrückt wird. Erinnern Sie sich an die Voraussetzung, dass wir den Spieler bewegen wollen. Wenn also ‚D‘ gedrückt wird, wollen wir den Spieler nach rechts bewegen. Wir rufen also player.translateX(5) auf, was seine X-Koordinate um 5 Pixel verschoben.

Dies führt dazu, dass sich die Spieler-Entität um 5 Pixel nach rechts bewegt. Sie können sich wahrscheinlich denken, wie der Rest des Eingabe-Codes aussehen wird, aber für den Fall der Fälle sind hier die Angaben für ‚W‘, ‚S‘ ‚D‘ und ‚A‘.

@Override
protected void initInput() {
    Input input = FXGL.getInput();

    input.addAction(new UserAction("Move Right") {
        @Override
        protected void onAction() {
            player.translateX(5); // move right 5 pixels
        }
    }, KeyCode.D);

    input.addAction(new UserAction("Move Left") {
        @Override
        protected void onAction() {
            player.translateX(-5); // move left 5 pixels
        }
    }, KeyCode.A);

    input.addAction(new UserAction("Move Up") {
        @Override
        protected void onAction() {
            player.translateY(-5); // move up 5 pixels
        }
    }, KeyCode.W);

    input.addAction(new UserAction("Move Down") {
        @Override
        protected void onAction() {
            player.translateY(5); // move down 5 pixels
        }
    }, KeyCode.S);
}

Die dritte Herausfoderung ist damit erledigt und abgehakt. Wir haben schon mehr als die Hälfte erledigt, gut gemacht!

Herausforderung 4: Das UI

Jetzt gehen wir zum nächsten Teil über – dem UI, dessen Handhabung wir in, richtig gedacht, initUI() definieren.

@Override
protected void initUI() {
    Text textPixels = new Text();
    textPixels.setTranslateX(50); // x = 50
    textPixels.setTranslateY(100); // y = 100

    FXGL.getGameScene().addUINode(textPixels); // add to the scene graph
}

Für die meisten UI-Objekte verwenden wir einfach JavaFX-Objekte, da es nicht notwendig ist, das Rad neu zu erfinden. Als wir eine Entity zur Welt hinzugefügt haben, hat das Spiel die Tatsache aufgegriffen, dass mit der Entity eine bestimmte Ansicht assoziiert ist. Daher hat das Spiel die Entity auf „magische“ Weise der Spielanzeige hinzugefügt. Bei UI-Objekten sind wir für ihre Einbettung in die Spielanzeige verantwortlich und können dies durch den Aufruf von getGameScene().addUINode() tun.

Das ist alles, was für das Meistern der vierten Herausforderung gemacht werden muss. Weiter so!

Herausforderung 5: Das Gameplay

Um die letzte Herausforderung zu meistern, werden wir eine Spielvariable verwenden. In FXGL kann von jedem Teil des Spiels aus auf eine Spielvariable zugegriffen und diese geändert werden. In gewisser Weise ist es eine globale Variable, deren Umfang an die FXGL-Spielinstanz gebunden ist. Darüber hinaus können solche Variablen auch an etwas gebunden werden (ähnlich wie bei JavaFX-Eigenschaften). Wir beginnen damit, eine solche Variable zu erstellen:

@Override
protected void initGameVars(Map<String, Object> vars) {
    vars.put("pixelsMoved", 0);
}

Dann müssen wir die Variable aktualisieren, sobald sich der Spieler bewegt. Das können wir in der Sektion des Input Handlings machen.

input.addAction(new UserAction("Move Right") {
    @Override
    protected void onAction() {
        player.translateX(5);
        FXGL.getGameState().increment("pixelsMoved", +5);    
    }
}, KeyCode.D);

Das Gleiche machen wir mit dem Rest der Bewegungsabläufe (links, oben und unten). Die letzte Herausforderung (und auch der letzte Abschnitt dieses ersten Teils unseres Tutorials) besteht darin, unser UI-Textobjekt an die Variable pixelsMoved zu binden. In initUI() können wir folgendes tun, sobald wir das textPixels-Objekt erstellt haben:

textPixels.textProperty().bind(FXGL.getGameState().intProperty("pixelsMoved").asString());

Danach wird der Text im User Interface anzeigen, um wie viele Pixel sich der Spieler automatisch bewegt hat.

Jetzt haben wir ein einfaches FXGL-Spiel. Hoffentlich hat es Ihnen Spaß gemacht. Hier ist der vollständige Quellcode von diesem Tutorial:

 package tutorial;

import com.almasb.fxgl.app.GameApplication;
import com.almasb.fxgl.app.GameSettings;
import com.almasb.fxgl.dsl.FXGL;
import com.almasb.fxgl.entity.Entity;
import com.almasb.fxgl.input.Input;
import com.almasb.fxgl.input.UserAction;
import javafx.scene.input.KeyCode;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import java.util.Map;

public class BasicGameApp extends GameApplication {
    @Override
    protected void initSettings(GameSettings settings) {
        settings.setWidth(600);
        settings.setHeight(600);
        settings.setTitle("Basic Game App");
        settings.setVersion("0.1");
    }

    @Override
    protected void initInput() {
        Input input = FXGL.getInput();

        input.addAction(new UserAction("Move Right") {
            @Override
            protected void onAction() {
                player.translateX(5); // move right 5 pixels
                FXGL.getGameState().increment("pixelsMoved", +5);
            }
        }, KeyCode.D);

        input.addAction(new UserAction("Move Left") {
            @Override
            protected void onAction() {
                player.translateX(-5); // move left 5 pixels
                FXGL.getGameState().increment("pixelsMoved", +5);
            }
        }, KeyCode.A);

        input.addAction(new UserAction("Move Up") {
            @Override
            protected void onAction() {
                player.translateY(-5); // move up 5 pixels
                FXGL.getGameState().increment("pixelsMoved", +5);
            }
        }, KeyCode.W);

        input.addAction(new UserAction("Move Down") {
            @Override
            protected void onAction() {
                player.translateY(5); // move down 5 pixels
                FXGL.getGameState().increment("pixelsMoved", +5);
            }
        }, KeyCode.S);
    }

    @Override
    protected void initGameVars(Map<String, Object> vars) {
        vars.put("pixelsMoved", 0);
    }

    private Entity player;

    @Override
    protected void initGame() {
        player = FXGL.entityBuilder()
                .at(300, 300)
                .view(new Rectangle(25, 25, Color.BLUE))
                .buildAndAttach();
    }

    @Override
    protected void initUI() {
        Text textPixels = new Text();
        textPixels.setTranslateX(50); // x = 50
        textPixels.setTranslateY(100); // y = 100

        textPixels.textProperty().bind(FXGL.getGameState().intProperty("pixelsMoved").asString());

        FXGL.getGameScene().addUINode(textPixels); // add to the scene graph
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Im zweiten Teil des Tutorials geht es um das Hinzufügen von Bildern und Sound. Stay tuned!

Verwandte Themen:

Geschrieben von
Almas Baimagambetov
Almas Baimagambetov
Almas is engaged with research in the field of Automated Diagram Generation. He also teaches Computer Science. In his spare time, he enjoys playing video games as well as designing and developing them.
Kommentare

Hinterlasse einen Kommentar

2 Kommentare auf "FXGL Tutorial: Einfache Spiele mit JavaFX erstellen"

avatar
4000
  Subscribe  
Benachrichtige mich zu:
Ifosil
Gast

Error:(3, 27) java: cannot access com.almasb.fxgl.app.GameApplication
bad class file: C:\Users\Name\.m2\repository\com\github\almasb\fxgl\11.0-alpha\fxgl-11.0-alpha.jar(com/almasb/fxgl/app/GameApplication.class)
class file has wrong version 55.0, should be 52.0
Please remove or make sure it appears in the correct subdirectory of the classpath.

Ifosil
Gast

Hat sich erledigt, hatte einfach Java 11 nicht aktiv 🙂 *peinlich*