Teil 4: Kobolde in Aktion

FXGL Tutorial: Animierte Spielfiguren via Sprite Sheet implementieren

Almas Baimagambetov

© Shutterstock / chuckchee

Willkommen zum vierten Teil unseres FXGL-11-Tutorials! Wir haben unser Basisspiel inwzischen mit Grafik- und Soundeffekten ausgestattet und Kollisionen mit Hilfe von JavaFX hinzugefügt. Heute werden wir uns auf das Hinzufügen von Sprite-Animationen konzentrieren. Wie immer gilt, dass dieses Tutorial auf FXGL 11.0+ und Java 11+ ausgelegt ist.

Dieser Teil des Tutorials ist als eigenständige Einheit gedacht, bereits Gelerntes wird daher nicht erneut angesprochen. Man sollte jedoch die vorherigen Einheiten (siehe Kasten) bereits abgeschlossen haben. Am Ende gibt es wie immer den kompletten Quellcode.

FXGL Tutorial: JavaFX-Spiele selbst gestalten

Fangen wir an – dieses Mal haben wir lediglich eine Aufgabe zu erledigen: Eine Spielfigur (in Form eines animierten Kobolds) hinzufügen, die von einem Sprite Sheet aus geladen wird.

Vorbereitung

Ich werde dieses einfache Sprite Sheet benutzen, das von der Phaser Spiele-Engine übernommen wurde. Die Datei legen wir einfach unter assets/textures ab.

Grundlagen

Bringen wir nun unsere Game-Anwendung zum laufen:

import com.almasb.fxgl.app.GameApplication;
import com.almasb.fxgl.app.GameSettings;

public class SpriteSheetAnimationApp extends GameApplication {

    @Override
    protected void initSettings(GameSettings settings) { }

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

Nach Ausführen der Anwendung sollten wir ein einfaches Fenster mit den Maßen 800×600 Pixel erhalten.

Spieler hinzufügen

Jetzt fügen wir einen Spieler bei den Koordinaten 200, 200 hinzu – allerdings ohne View, dafür aber mit einer AnimateComponent, die wir später erstellen werden.

private Entity player;

@Override
protected void initGame() {
    player = FXGL.entityBuilder()
            .at(200, 200)
            .with(new AnimationComponent())
            .buildAndAttach();
}

Im FXGL-Wiki kann man mehr über die Component-Klasse erfahren, aber kurz gesagt beinhaltet eine Komponente Daten und fügt einer Entität ein bestimmtes Verhalten hinzu.

AnimationComponent hinzufügen

Zunächst schauen wir uns den vollständigen Quellcode der AnimationComponent an, den wir gleich Zeile für Zeile durchgehen werden:

public class AnimationComponent extends Component {

    private int speed = 0;

    private AnimatedTexture texture;
    private AnimationChannel animIdle, animWalk;

    public AnimationComponent() {
        animIdle = new AnimationChannel(FXGL.image("newdude.png"), 4, 32, 42, Duration.seconds(1), 1, 1);
        animWalk = new AnimationChannel(FXGL.image("newdude.png"), 4, 32, 42, Duration.seconds(1), 0, 3);

        texture = new AnimatedTexture(animIdle);
    }

    @Override
    public void onAdded() {
        entity.getTransformComponent().setScaleOrigin(new Point2D(16, 21));
        entity.setView(texture);
    }

    @Override
    public void onUpdate(double tpf) {
        entity.translateX(speed * tpf);

        if (speed != 0) {

            if (texture.getAnimationChannel() == animIdle) {
                texture.loopAnimationChannel(animWalk);
            }

            speed = (int) (speed * 0.9);

            if (FXGLMath.abs(speed) < 1) {
                speed = 0;
                texture.loopAnimationChannel(animIdle);
            }
        }
    }

    public void moveRight() {
        speed = 150;

        getEntity().setScaleX(1);
    }

    public void moveLeft() {
        speed = -150;

        getEntity().setScaleX(-1);
    }
}

Zuerst das Folgende: class AnimationComponent extends Component – jede Komponente erweitert Component.

private int speed = 0;

Mit dieser Variable werden wir prüfen, ob sich die Spielfigur bewegt. Hier geht es nun um das Kernstück des Tutorials: Zuerst deklarieren wir eine animierte Textur. Diese ist wie eine normale Textur, aber sie kann sogenannte Animationskanäle enthalten. Ein Animationskanal definiert eine einzelne Animation, etwa „laufen“, „stehen“, „angreifen“, „springen“, usw.

In unserem Beispiel gibt es zwei Kanäle: inaktiv (animIdle) und laufen (animWalk), wie der untere Codeausschnitt zeigt. Im Konstruktor AnimationComponent legen wir die Channels mit den folgenden Parametern an: sprite sheet image, number of frames per row, single frame width, single frame height, duration of the animation channel, start frame und end frame.

private AnimatedTexture texture;
private AnimationChannel animIdle, animWalk;

public AnimationComponent() {
    animIdle = new AnimationChannel(FXGL.image("newdude.png"), 4, 32, 42, Duration.seconds(1), 1, 1);
    animWalk = new AnimationChannel(FXGL.image("newdude.png"), 4, 32, 42, Duration.seconds(1), 0, 3);

    texture = new AnimatedTexture(animIdle);
}

@Override
public void onAdded() {
    entity.getTransformComponent().setScaleOrigin(new Point2D(16, 21));
    entity.setView(texture);
}

Wir möchten, dass die Spielfigur und das Bild perfekt aufeinander abgestimmt sind. Dazu müssen wir den Ursprung entsprechend einstellen. Es ist zu beachten, dass entity.setView(texture); in onAdded() aufgerufen wird und nicht im Konstruktor. Wir wissen nicht, an welche Entität die Komponente im Konstruktor angehängt wird, bei onAdded() wissen wir es.

Betrachten wir die Update-Methode:

@Override
public void onUpdate(double tpf) {
    entity.translateX(speed * tpf);

    if (speed != 0) {

        if (texture.getAnimationChannel() == animIdle) {
            texture.loopAnimationChannel(animWalk);
        }

        speed = (int) (speed * 0.9);

        if (FXGLMath.abs(speed) < 1) {
            speed = 0;
            texture.loopAnimationChannel(animIdle);
        }
    }
}

Wir übersetzen (bewegen) die Entität mit der angegebenen Geschwindigkeit. Dann überprüfen wir, was für eine Animation benutzt werden soll. Bewegen wir uns (speed !=0), überprüfen wir, ob die aktuelle Animation inaktiv ist. Ist das der Fall, dann wechseln wir zu der walk-Animation. Wir verringern die Geschwindigkeit um 10% und überprüfen, ob die Geschwindigkeit gering genug für uns ist, um anzuhalten. Um anhalten zu können, setzen wir die Geschwindigkeit auf 0 und führen die idle-Animation in einem Loop aus.

Mit den Methoden moveLeft() und moveRight() können wir die Entität (in dem Fall die Spielfigur) so einstellen, dass sie in die richtige Richtung schaut, wenn sie sich bewegt.

public void moveRight() {
    speed = 150;

    getEntity().setScaleX(1);
}

public void moveLeft() {
    speed = -150;

    getEntity().setScaleX(-1);
}

Input hinzufügen

Was jetzt kommt, ist sehr einfach: Wir binden die Tasten A und D, um die linke und rechte Bewegungsmethode von AnimationComponent aufzurufen.

@Override
protected void initInput() {
    FXGL.getInput().addAction(new UserAction("Right") {
        @Override
        protected void onAction() {
            player.getComponent(AnimationComponent.class).moveRight();
        }
    }, KeyCode.D);

    FXGL.getInput().addAction(new UserAction("Left") {
        @Override
        protected void onAction() {
            player.getComponent(AnimationComponent.class).moveLeft();
        }
    }, KeyCode.A);
}

Jetzt sollten wir dazu in der Lage sein, eine einfache Sprite-Sheet-Animation in unserem Spiel einzusetzen.

Der vollständige Quellcode ist, wie immer, unten aufgeführt:

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.UserAction;
import javafx.scene.input.KeyCode;

public class SpriteSheetAnimationApp extends GameApplication {

    @Override
    protected void initSettings(GameSettings settings) { }

    private Entity player;

    @Override
    protected void initInput() {
        FXGL.getInput().addAction(new UserAction("Right") {
            @Override
            protected void onAction() {
                player.getComponent(AnimationComponent.class).moveRight();
            }
        }, KeyCode.D);

        FXGL.getInput().addAction(new UserAction("Left") {
            @Override
            protected void onAction() {
                player.getComponent(AnimationComponent.class).moveLeft();
            }
        }, KeyCode.A);
    }

    @Override
    protected void initGame() {
        player = FXGL.entityBuilder()
                .at(200, 200)
                .with(new AnimationComponent())
                .buildAndAttach();
    }

    public static void main(String[] args) {
        launch(args);
    }
}
import com.almasb.fxgl.core.math.FXGLMath;
import com.almasb.fxgl.dsl.FXGL;
import com.almasb.fxgl.entity.component.Component;
import com.almasb.fxgl.texture.AnimatedTexture;
import com.almasb.fxgl.texture.AnimationChannel;
import javafx.geometry.Point2D;
import javafx.util.Duration;

public class AnimationComponent extends Component {

    private int speed = 0;

    private AnimatedTexture texture;
    private AnimationChannel animIdle, animWalk;

    public AnimationComponent() {
        animIdle = new AnimationChannel(FXGL.image("newdude.png"), 4, 32, 42, Duration.seconds(1), 1, 1);
        animWalk = new AnimationChannel(FXGL.image("newdude.png"), 4, 32, 42, Duration.seconds(1), 0, 3);

        texture = new AnimatedTexture(animIdle);
    }

    @Override
    public void onAdded() {
        entity.getTransformComponent().setScaleOrigin(new Point2D(16, 21));
        entity.setView(texture);
    }

    @Override
    public void onUpdate(double tpf) {
        entity.translateX(speed * tpf);

        if (speed != 0) {

            if (texture.getAnimationChannel() == animIdle) {
                texture.loopAnimationChannel(animWalk);
            }

            speed = (int) (speed * 0.9);

            if (FXGLMath.abs(speed) < 1) {
                speed = 0;
                texture.loopAnimationChannel(animIdle);
            }
        }
    }

    public void moveRight() {
        speed = 150;

        getEntity().setScaleX(1);
    }

    public void moveLeft() {
        speed = -150;

        getEntity().setScaleX(-1);
    }
}

Stay tuned! Wer mehr über die Kernfunktionen wissen oder mit vorgefertigten Spielen experimentieren will, findet dazu alles auf GitHub.

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

Hinterlasse den ersten Kommentar!

avatar
4000
  Subscribe  
Benachrichtige mich zu: