Skip to content

CollisionListener (Kollisionen-Erkennung)

Methoden

Die Klasse Actor bietet drei überladene Methoden an, um CollisionListener zu registrieren:

Tutorial: FroggyJump1

Zu diesem Tutorial gibt es ein Repository in der Engine-Pi-Github-Organsiation. Die einzelnen Entwicklungsstadien sind in folgenden Git-Branches hinterlegt:

  1. blank: Die Projektstruktur, die leere Hauptklasse FroggyJump ist angelegt, sowie die zwei benötigten Grafikdateien sind dem Repository hinzugefügt.
  2. basic: Der FroggyJump-Szene wurden zehn vertikal übereinander positionierte Plattformen hinzugefügt. Der Frosch kann horizontal bewegt werden und springt automatisch von den Plattformen ab.
  3. jump-through: Der Frosch kann durch die Plattformen hindurchspringen.
  4. platforms-deluxe Die Szene wurde um zufällig angeordnete Plattformen erweitert, die nach oben hin immer mehr Abstand zueinander aufweisen.
  5. spike-balls: Eisenkugeln mit Stacheln fallen auf den Frosch, wenn er ihnen zu nahe kommt.
  6. death-scene bzw. final: Wird der Frosch von einer Stachelkugel getroffen, erscheint eine Szene, die seinen Tod verkündet.

Spielkonzept und grundlegender Aufbau

Ein Frosch soll fröhlich durch das Spielfeld hüpfen und sich immer dann vom Boden abstoßen, wenn sich die Gelegenheit dazu bietet.

Dieser Frosch soll durch das Spiel springen

In der Szene FroggyJump kann der Spieler eine Figur der Klasse Frog steuern. Zusätzlich bieten Figuren der Klasse Platform Halt.

Damit ergibt sich das Codegerüst für das Spiel:

Klasse FroggyJump

import pi.Camera;
import pi.Controller;
import pi.Scene;
import pi.graphics.geom.Vector;

public class FroggyJump extends Scene
{
    private Frog frog;

    private static final double PLATFORM_HEIGHT = 0.5;

    public FroggyJump()
    {
        frog = new Frog();
        add(frog);
        gravityOfEarth();
        Camera camera = camera();
        camera.focus(frog);
        camera.offset(new Vector(0, 4));
        makePlatforms(10);
    }

    private void makePlatforms(int count)
    {
        for (int i = 0; i < count; i++)
        {
            Platform platform = new Platform(5, PLATFORM_HEIGHT);
            platform.anchor(0, (double) i * 4);
            add(platform);
        }
    }

    public static void main(String[] args)
    {
        Controller.instantMode(false);
        Controller.start(new FroggyJump(), 400, 600);
    }
}

Klasse Frog

import java.awt.event.KeyEvent;

import pi.Controller;
import pi.actor.Image;
import pi.event.FrameListener;

class Frog extends Image implements FrameListener
{
    private boolean jumpEnabled = true;

    private static final double MAX_SPEED = 4;

    public Frog()
    {
        super("images/Frog.png");
        pixelPerMeter(25);
        makeDynamic();
        rotationLocked(true);
    }

    public void jumpEnabled(boolean jumpEnabled)
    {
        this.jumpEnabled = jumpEnabled;
    }

    @Override
    public void onFrame(double pastTime)
    {
        // Die Blickrichtung des Frosches steuern
        flippedHorizontally(velocityX() < 0);

        // Die horizontale Bewegung steuern
        if (Controller.isKeyPressed(KeyEvent.VK_A))
        {
            if (velocityX() > 0)
            {
                velocityX(0);
            }
            applyForce(-600, 0);
        }
        else if (Controller.isKeyPressed(KeyEvent.VK_D))
        {
            if (velocityX() < 0)
            {
                velocityX(0);
            }
            applyForce(600, 0);
        }

        // Die horizontale Geschwindigkeit begrenzen
        if (Math.abs(velocityX()) > MAX_SPEED)
        {
            velocityX(MAX_SPEED * Math.signum(velocityX()));
        }

        // Wenn möglich den Frosch springen lassen
        if (isGrounded() && velocityY() <= 0 && jumpEnabled)
        {
            velocityY(0);
            applyImpulse(0, 180);
        }
    }
}

Klasse Platform

import pi.actor.Rectangle;

class Platform extends Rectangle
{
    public Platform(double width, double height)
    {
        super(width, height);
        makeStatic();
        color("brown");
    }
}

Der Frosch kann sich bewegen, knallt aber unangenehmerweise noch gegen die Decke

Ein paar Erklärungen zum Codegerüst für FroggyJump:

Physikalische Eigenschaften

Wie im Physics-Tutorial beschrieben, werden die physikalischen Eigenschaften der Spielobjekte und ihrer Umgebung bestimmt:

  • Plattformen sind statische Objekte: Sie ignorieren die Schwerkraft und können nicht durch andere Objekte verschoben werden – egal, mit wie viel Kraft der Frosch auf sie fällt.
  • Der Frosch ist ein dynamisches Objekt: Er wird von der Schwerkraft beeinflusst und von den statischen Plattformen aufgehalten.
  • In der Szene FroggyJump wird die Schwerkraft der Erde simuliert. Sie wird mit der Methode gravityOfEarth() gesetzt.

Bewegung des Frosches

Die Bewegung des Frosches wird in jedem Frame kontrolliert. Wie im Game Loop Tutorial beschrieben, wird hierzu das Interface Listener genutzt.

In jedem Einzelbild wird die Bewegung des Frosches kontrolliert:

Blickrichtung des Frosches

Das Bild des Frosches wird gespiegelt, falls er sich nach links bewegt.

flippedHorizontally(velocityX() < 0);

Horizontale Bewegung des Frosches

Jedes Einzelbild, in dem der Spieler den Frosch (per Tastendruck) nach links oder rechts steuern möchte, wird eine Bewegungskraft auf den Frosch angewendet. Wird der Frosch in die Gegenrichtung seiner aktuellen Bewegung gesteuert, wird seine horizontale Geschwindigkeit zuvor auf 0 gesetzt, um ein langsames Abbremsen zu verhindern. Das ermöglicht schnelle Reaktion auf Nutzereingabe und ein besseres Spielgefühl.

if (Controller.isKeyPressed(KeyEvent.VK_A))
{
    if (velocityX() > 0)
    {
        velocityX(0);
    }
    applyForce(-600, 0);
}
else if (Controller.isKeyPressed(KeyEvent.VK_D))
{
    if (velocityX() < 0)
    {
        velocityX(0);
    }
    applyForce(600, 0);
}

Zusätzlich wird seine Geschwindigkeit auf die Konstante MAX_SPEED begrenzt.

if (Math.abs(velocityX()) > MAX_SPEED)
{
    velocityX(MAX_SPEED * Math.signum(velocityX()));
}

Springe, wenn möglich

Mit der Methode isGrounded() bietet die Engine einen einfachen Test, um sicherzustellen, dass der Frosch Boden unter den Füßen hat. Wenn dies gegeben ist, wird ein Sprungimpuls auf den Frosch angewandt. Zuvor wird die vertikale Komponente seiner Geschwindigkeit auf 0 festgesetzt - das garantiert, dass der Frosch jedes mal die selbe Sprunghöhe erreicht.

if (isGrounded() && velocityY() <= 0 && jumpEnabled)
{
    velocityY(0);
    applyImpulse(0, 180);
}

Die Kamera folgt dem Frosch

Der Frosch soll stets sichtbar bleiben. Hierzu werden zwei Funktionen der Engine-Kamera genutzt:

  1. Der Frosch wird mit Camera#focus(pi.actor.Actor) in den Mittelpunkt der Kamera gesetzt. Sie folgt dem Frosch.
  2. Gleichzeitig soll der Frosch nicht exakt im Mittelpunkt des Bildschirms sein: Weil das Spielziel ist, sich nach oben zu bewegen, braucht der Spieler mehr Blick nach oben als nach unten. Mit Camera#offsetY(double) wird die Kamera nach oben verschoben.
Camera camera = camera();
camera.focus(frog);
camera.offsetY(4);

Durch Platformen Springen: Kollisionen kontrollieren

Das Interface CollisionListener wurde bereits in seiner grundlegenden Form im Nutzereingabe-Tutorial benutzt.

CollisionListener kann mehr als nur melden, wenn zwei Actor-Objekte sich überschneiden. Um das FroggyJump-Spiel zu implementieren, nutzen wir weitere Features.

Unser Frosch soll fähig sein, von unten „durch die Platform hindurch“ zu springen. Von oben fallend soll er natürlich auf der Platform stehen bleiben.

Um diesen Effekt zu erzeugen, müssen Kollisionen zwischen Frosch und Platform unterschiedlich behandelt werden:

  1. Kollidiert der Frosch von unten, so soll die Kollision ignoriert werden. Er prallt so nicht von der Decke ab und kann weiter nach oben Springen.
  2. Kollidiert der Frosch von oben, so soll die Kollision normal aufgelöst werden, sodass er nicht durch den Boden fällt.

Hierzu stellt das CollisionEvent-Objekt in der onCollision-Methode Funktionen bereit.

class Platform extends Rectangle implements CollisionListener<Frog>
{
    public Platform(double width, double height)
    {
        super(width, height);
        makeStatic();
        color("brown");
        addCollisionListener(Frog.class, this);
    }

    @Override
    public void onCollision(CollisionEvent<Frog> event)
    {
        Frog frog = event.colliding();
        if (frog.y() < y())
        {
            event.ignoreCollision();
            frog.jumpEnabled(false);
        }
    }

    @Override
    public void onCollisionEnd(CollisionEvent<Frog> event)
    {
        event.colliding().jumpEnabled(true);
    }
}
Zum Java-Code: demos//home/runner/work/engine-pi/engine-pi/subprojects/demos/src/main/java/demos/docs/events/collision/froggy_jump/Platform.java

Kompletter Code

import pi.Camera;
import pi.Controller;
import pi.Random;
import pi.Scene;

public class FroggyJump extends Scene
{
    private Frog frog;

    private static final double PLATFORM_HEIGHT = 0.5;

    public FroggyJump()
    {
        info().title("Spiel „Froggy Jump“")
            .help(
                "Tastenkürzel: a bewegt den Frosch nach links, d bewegt den Frosch nach rechts.");
        frog = new Frog();
        add(frog);
        gravityOfEarth();
        Camera camera = camera();
        camera.focus(frog);
        camera.offsetY(4);
        makePlatforms(10);
        makePlatformsDeluxe(40);
    }

    /**
     * Erstellt Plattformen, die in vertikal übereinander liegen.
     *
     * @param countLevels Auf wie vielen Ebenen neue Plattformen erstellt werden
     *     sollen.
     */
    private void makePlatforms(int countLevels)
    {
        for (int i = 0; i < countLevels; i++)
        {
            Platform platform = new Platform(5, PLATFORM_HEIGHT);
            platform.anchor(0, (double) i * 4);
            add(platform);
        }
    }

    /**
     * Erstellt neue Plattformen in zufälliger Art und Weise.
     *
     * <p>
     * Pro Ebene werden 1, 2 oder 3 Platformen erstellt. Je weiter oben die
     * Plattformen liegen, desto größer ist der Abstand zwischen ihnen.
     * </p>
     *
     * @param countLevels Auf wie vielen Ebenen neue Plattformen erstellt werden
     *     sollen. Eine neue Ebene wird oben im Spielfeld eingebaut.
     */
    private void makePlatformsDeluxe(int countLevels)
    {
        for (int i = 0; i < countLevels; i++)
        {
            int numPlatforms = Random.range(2) + 1;
            for (int j = 0; j < numPlatforms; j++)
            {
                Platform platform = new Platform((double) 6 / numPlatforms,
                        PLATFORM_HEIGHT);
                platform.anchor(numPlatforms * (j + 1) * i * Random.range(),
                    (double) i * 4);
                // Wir färben die Plattformen dieser Methode anders, damit wir
                // sie von Plattformen der Methode makePlatforms() unterscheiden
                // können.
                platform.color("grey");
                add(platform);
            }
            if (i > 3)
            {
                for (int j = 0; j < Random.range(3); j++)
                {
                    SpikeBall.create(Random.range() * (4 + j) * i,
                        Random.range() * 4 + 0.5 + 5 * i,
                        layer());
                }
            }
        }
    }

    public static void main(String[] args)
    {
        Controller.instantMode(false);
        Controller.start(new FroggyJump(), 400, 600);
    }
}
Zum Java-Code: demos//home/runner/work/engine-pi/engine-pi/subprojects/demos/src/main/java/demos/docs/events/collision/froggy_jump/FroggyJump.java

import java.awt.event.KeyEvent;

import pi.Controller;
import pi.Scene;
import pi.actor.Text;
import pi.event.KeyStrokeListener;

class DeathScene extends Scene implements KeyStrokeListener
{
    public DeathScene()
    {
        Text message = new Text("You Died. Press any button to try again");
        message.height(.6);
        message.center(camera().focus());
        add(message);
    }

    @Override
    public void onKeyDown(KeyEvent e)
    {
        Controller.transitionToScene(new FroggyJump());
    }
}
Zum Java-Code: demos//home/runner/work/engine-pi/engine-pi/subprojects/demos/src/main/java/demos/docs/events/collision/froggy_jump/DeathScene.java

import java.awt.event.KeyEvent;

import pi.Controller;
import pi.actor.Image;
import pi.event.FrameListener;

class Frog extends Image implements FrameListener
{
    private boolean jumpEnabled = true;

    private static final double MAX_SPEED = 4;

    public Frog()
    {
        super("Pixel-Adventure-1/Main Characters/Ninja Frog/Jump (32x32).png");
        pixelPerMeter(25);
        makeDynamic();
        rotationLocked(true);
    }

    public void jumpEnabled(boolean jumpEnabled)
    {
        this.jumpEnabled = jumpEnabled;
    }

    public void kill()
    {
        Controller.transitionToScene(new DeathScene());
    }

    @Override
    public void onFrame(double pastTime)
    {
        // Die Blickrichtung des Frosches steuern
        flippedHorizontally(velocityX() < 0);

        // Die horizontale Bewegung steuern
        if (Controller.isKeyPressed(KeyEvent.VK_A))
        {
            if (velocityX() > 0)
            {
                velocityX(0);
            }
            applyForce(-600, 0);
        }
        else if (Controller.isKeyPressed(KeyEvent.VK_D))
        {
            if (velocityX() < 0)
            {
                velocityX(0);
            }
            applyForce(600, 0);
        }

        // Die horizontale Geschwindigkeit begrenzen
        if (Math.abs(velocityX()) > MAX_SPEED)
        {
            velocityX(MAX_SPEED * Math.signum(velocityX()));
        }

        // Wenn möglich den Frosch springen lassen
        if (isGrounded() && velocityY() <= 0 && jumpEnabled)
        {
            velocityY(0);
            applyImpulse(0, 180);
        }
    }
}
Zum Java-Code: demos//home/runner/work/engine-pi/engine-pi/subprojects/demos/src/main/java/demos/docs/events/collision/froggy_jump/Frog.java

import pi.actor.Rectangle;
import pi.event.CollisionEvent;
import pi.event.CollisionListener;

class Platform extends Rectangle implements CollisionListener<Frog>
{
    public Platform(double width, double height)
    {
        super(width, height);
        makeStatic();
        color("brown");
        addCollisionListener(Frog.class, this);
    }

    @Override
    public void onCollision(CollisionEvent<Frog> event)
    {
        Frog frog = event.colliding();
        if (frog.y() < y())
        {
            event.ignoreCollision();
            frog.jumpEnabled(false);
        }
    }

    @Override
    public void onCollisionEnd(CollisionEvent<Frog> event)
    {
        event.colliding().jumpEnabled(true);
    }
}
Zum Java-Code: demos//home/runner/work/engine-pi/engine-pi/subprojects/demos/src/main/java/demos/docs/events/collision/froggy_jump/Platform.java

import pi.actor.Image;
import pi.Layer;
import pi.event.CollisionEvent;
import pi.event.CollisionListener;

class SpikeBall extends Image implements CollisionListener<Frog>
{
    public SpikeBall()
    {
        super("Pixel-Adventure-1/Traps/Spiked Ball/Spiked Ball.png");
        pixelPerMeter(40);
        gravityScale(0);
        addCollisionListener(Frog.class, this);
    }

    public static SpikeBall create(double x, double y, Layer layer)
    {
        SpikeBall ball = new SpikeBall();
        ball.center(x, y);
        SpikeSensor sensor = new SpikeSensor(ball);
        sensor.anchor(x - 1, y - 8);
        layer.add(ball, sensor);
        return ball;
    }

    @Override
    public void onCollision(CollisionEvent<Frog> event)
    {
        event.colliding().kill();
    }
}
Zum Java-Code: demos//home/runner/work/engine-pi/engine-pi/subprojects/demos/src/main/java/demos/docs/events/collision/froggy_jump/SpikeBall.java

import pi.Rectangle;
import pi.event.CollisionEvent;
import pi.event.CollisionListener;

class SpikeSensor extends Rectangle implements CollisionListener<Frog>
{
    private SpikeBall ball;

    public SpikeSensor(SpikeBall ball)
    {
        super(2, 8);
        this.ball = ball;
        visible(false);
        makeSensor();
        addCollisionListener(Frog.class, this);
        gravityScale(0);
    }

    @Override
    public void onCollision(CollisionEvent<Frog> event)
    {
        ball.gravityScale(1);
    }
}
Zum Java-Code: demos//home/runner/work/engine-pi/engine-pi/subprojects/demos/src/main/java/demos/docs/events/collision/froggy_jump/SpikeSensor.java


  1. Der Abschnitt stammt aus dem Engine-Alpha-Wiki: https://engine-alpha.org/wiki/v4.x/Collision