Spielprogrammierung mit Java
HomeAufgabenDruckenJava-Online

Präzise Kollisionserkennung

Theoretisch kollidieren zwei Figuren dann, wenn sich mindestens zwei Pixel der beiden Figuren überlagern. Da Computerfiguren sich immer in einem rechteckigen Bereich befinden, müsste man alle nicht-transparenten Pixel der beiden Bilder auf Überlagerung prüfen. Selbst für kleine Bilder ist der Aufwand dafür enorm. Enthalten die Bilder beispielsweise 50 x 50 Pixel wären dazu 2500 x 2500 Vergleiche notwendig. Um dieses Problem zu überwinden, legt man einfache geometrische Formen als Kollisionsbereiche fest. In JGameGrid sind dies Rechtecke, Kreise, Linien oder Punkte, die bezüglich der Figur beliebige Position und Orientierung haben können. Standardmässig ist der Kollisionsbereich das umhüllende Rechteck (bounding rectangle).

Beispiel 1: Die beiden rechteckigen Figuren bewegen sich auf einer kreisförmigen Bahn und reflektieren dabei am Fensterrand. Wenn sie zusammenstossen, ertönt ein Ton und die beiden Stäbe ändern ihre Bewegungsrichtung um 180°.

In der Applikationsklasse wird ein GGActorCollisionListener implementiert. Dieser wird mit addActorCollisionListener(this) registriert, damit bei Kollisionsevents die Callbackmethode collide() aufgerufen wird.

Dem stick1 wird mit addCollisionActor(stick2) mitgeteilt, dass er auf Kollisionen mit stick2 reagieren soll.

In diesem Beispiel kann man gut beobachten, wie exakt der Kollision-Algorithmus von JGameGrid funktioniert. Selbst wenn sich die beiden Figuren mit Eckpunkten berühren, wird eine Kollision registriert.

 

// JGameEx26.java

import ch.aplu.jgamegrid.*;

public class JGameEx26 extends GameGrid implements GGActorCollisionListener
{
  public JGameEx26()
  {
    super(600, 600, 1, false);
    setSimulationPeriod(10);
    Stick stick1 = new Stick();
    addActor(stick1, new Location(200, 200), 30);
    Stick stick2 = new Stick();
    addActor(stick2, new Location(400, 400), 30);
    stick2.show(1);
    stick1.addCollisionActor(stick2);
    stick1.addActorCollisionListener(this);
    playSound(GGSound.DUMMY);
    show();
    doRun();
  }

  public int collide(Actor actor1, Actor actor2)
  {
    actor1.setDirection(actor1.getDirection() + 180);
    actor2.setDirection(actor2.getDirection() + 180);
    playSound(GGSound.PING);
    return 10;
  }

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

//
class Stick extends Actor
{
  private final double step = 1;

  public Stick()
  {
    super(true"sprites/stick.gif"2 );  // Rotatable
  }

  public void act()
  {
    Location loc = getLocation();
    double dir = (getDirection() + step% 360;

    if (loc.x < 50)
    {
      dir = 180 - dir;
      setLocation(new Location(55, loc.y));
    }
    if (loc.x > 550)
    {
      dir = 180 - dir;
      setLocation(new Location(545, loc.y));
    }
    if (loc.y < 50)
    {
      dir = 360 - dir;
      setLocation(new Location(loc.x, 55));
    }
    if (loc.y > 550)
    {
      dir = 360 - dir;
      setLocation(new Location(loc.x, 545));
    }
    setDirection(dir);
    move();
  }
}

Programmcode für lokale Bearbeitung downloaden: JGameEx26.zip

Erklärungen zum Programmcode:
setSimulationPeriod(10) Verkürzt die Simulationsperiode, damit sich die beiden Stäbe schneller bewegen
stick2.show(1) Die beiden Sticks sind Instanzen der gleichen Klasse Stick, wobei beim stick1 das Bild stick_0.gif (rot) und beim stick2 stick_1.gif (gelb) sichtbar ist. Standardmässig ist immer das Sprite mit der Id 0 sichtbar
collide(Actor actor1, Actor actor2) Callbackmethode des CollisionListener. Deklariert das Verhalten der beiden Actors nach der Kollision
return 10 Der Rückgabewert der Methode collide() legt die minimale Anzahl der Simulationszyklen fest, bis sie wieder aufgerufen werden kann

 

 

Beispiel 2: Der Fisch muss den schwimmenden Quallen ausweichen. Falls er eine berührt, wird er auf seine Ausgangsposition am linken Rand des Fensters zurück versetzt. Das Ziel ist es, den rechten Fensterrand zu erreichen.

 

 

 

// JGameEx27.java

import ch.aplu.jgamegrid.*;
import java.awt.*;
import java.awt.event.KeyEvent;


public class JGameEx27 extends GameGrid implements GGActorCollisionListener
{
  private final int countMax = 10;
  private int count = countMax;
  private Clownfish1 nemo = new Clownfish1();
  private final Location startLocation = new Location(50, 300);

  public JGameEx27()
  {
    super(600, 600, 1, nullfalse);
    setSimulationPeriod(100);
    addActor(nemo, startLocation);
    addKeyListener(nemo);
    nemo.addActorCollisionListener(this);
    nemo.setCollisionCircle(new Point(0, 0), 40);
    for (int = 0; i < 10; i++)
      createJellyfish();
    show();
    doRun();
  }

  public int collide(Actor actor1, Actor actor2)
  {
    nemo.setLocation(startLocation);
    return 0;
  }

  private void createJellyfish()
  {
    int = 500 + (int)(400 * Math.random());
    int = (int)(600 * Math.random());
    Jellyfish jelly  = new Jellyfish();
    addActor(jelly, new Location(x, y), -180);
    nemo.addCollisionActor(jelly);
  }

  public void act()
  {
    count--;
    if (count == 0)
    {
       createJellyfish();
       count = countMax;
    }
  }

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

// ---------------------   class Clowfish1 --------------------------------
class Clownfish1 extends Actor implements GGKeyListener
{
  private int id = 0;

  public Clownfish1()
  {
    super("sprites/nemo.gif"2);
  }

  public boolean keyPressed(KeyEvent evt)
  {
    switch (evt.getKeyCode())
    {
      case KeyEvent.VK_UP:
        setLocation(new Location(getLocation().x, getLocation().y - 5));
        break;
      case KeyEvent.VK_DOWN:
        setLocation(new Location(getLocation().x, getLocation().y + 5));
        break;
      case KeyEvent.VK_LEFT:
        setLocation(new Location(getLocation().x - 5, getLocation().y));
        break;
      case KeyEvent.VK_RIGHT:
        setLocation(new Location(getLocation().x + 5, getLocation().y));
        break;
    }
    return false;  // Don't consume
  }

  public void act()
  {
    if (getLocation().x > 500)
      showNextSprite();
  }

  public boolean keyReleased(KeyEvent evt)
  {
    return false;
  }
}

// ------------------------ class Jellyfish -----------------------------------

class Jellyfish extends Actor
{
  public Jellyfish()
  {
    super("sprites/jellyfish.gif");
  }

  public void act()
  {
    move();
    if (getLocation().x < - 10)
      removeSelf();
  }
}

Programmcode für lokale Bearbeitung downloaden: JGameEx27.zip

Erklärungen zum Programmcode:
nemo.setCollisionCircle(new Point(0, 0), 40) Die Kollisionsfläche wird als Kreis mit dem Mittelpunkt 0,0 und Radius 40 definiert
nemo.addCollisionActor(jelly) Jede neu erzeugte Qualle wird als CollisonActor registriert

count = countMax
count--

Die Quallen werden am rechten Rand des Fensters erzeugt und bewegen sich nach links. Damit es nicht zu viele Quallen gibt, werden sie gezählt
nemo.setLocation(startLocation) Der Nemo wird bei jeder Kollision an die Startposition zurückgesetzt. Ereicht er den rechten Rand, ändert er sein Erscheinungsbild

 

Beispiel 3: Ein mausgesteuerter Pfeil kann mit seiner vorderen Spitze (CollisionSpot) Ballone zerplatzen.

10 Ballone werden an zufällig gewählten Positionen erzeugt. Beim Pfeil wird der Endpunkt der Nadel mit dart.setCollisionSpot(new Point(30, 0)) als Collisionsspot festgelegt. Zur Bestimmung des Collisionspots wird ein Koordinatensystem mit Ursprung (0, 0) im Mittelpunkt des Spritebildes verwendet. Das Spritebild ist 60 x 17 pixel gross, d.h. (30, 0) sind die Koordinaten des Endpunktes der Nadel.

Der Pfeil dreht sich bei der Bewegung in die Richtung der Mausbewegung. Dies wird in der Klasse Dart mit folgendem Verfahren erreicht: Man folgt dem Mauscursor und erfasst fortlaufend die Cursorposition. Jedesmal, wenn die alte und die neue Position 5 Pixel voneinander entfernt sind, bestimmt man die Verbindungsgerade der beiden Punkte. Ihre Richtung, die man mit actan2() bestimmt, betrachtet man als die Bewegungsrichtung der Maus.

 

 

// JGameEx28.java

import ch.aplu.jgamegrid.*;
import ch.aplu.util.*;
import java.awt.*;

public class JGameEx28 extends GameGrid implements GGActorCollisionListener
{
  public JGameEx28()
  {
    super(6006001null"sprites/town.jpg"false);
    setTitle("Move dart with mouse to pick the balloon");
    setSimulationPeriod(50);
    playSound(this, GGSound.DUMMY);
    Dart dart = new Dart();
    addActor(dart, new Location(100300));
    addMouseListener(dart, GGMouse.lDrag);
    dart.setCollisionSpot(new Point(300))// Endpoint of dart needle
    
    for (int i = 0; i < 10; i++)
    {
      Actor balloon = new Actor("sprites/balloon.gif");
      addActor(balloon, new Location((int)(500*Math.random() + 50 )
                                     (int)(500*Math.random() + 50)));
      dart.addCollisionActor(balloon);
      dart.addActorCollisionListener(this);
    }
    show();
    doRun();
  }

  public int collide(Actor actor1, Actor actor2)
  {
    actor2.removeSelf();
    playSound(this, GGSound.PING);  
    return 0;
  }

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

// --------------------- class Dart -----------------------
class Dart extends Actor implements GGMouseListener
{
  private Location oldLocation = new Location();

  public Dart()
  {
    super(true"sprites/dart.gif");  // Rotatable
  }

  public boolean mouseEvent(GGMouse mouse)
  {
    Location location =
      gameGrid.toLocationInGrid(mouse.getX(), mouse.getY());
    setLocation(location);
    double dx = location.x - oldLocation.x;
    double dy = location.y - oldLocation.y;
    if (dx * dx + dy * dy < 25)
      return true;
    double phi = Math.atan2(dy, dx);
    setDirection(Math.toDegrees(phi));
    oldLocation.x = location.x;
    oldLocation.y = location.y;
    return true;
  }
}

Programmcode für lokale Bearbeitung downloaden: JGameEx28.zip

Erklärungen zum Programmcode:
dart.setCollisionSpot(new Point(30, 0)) Setzt den CollisionSpot auf die Koordinaten der Nadelspitze, relativ zum Mittelpunkt des Sprite-Bildes
dart.addCollisionActor(balloon) Jeder neu erzeugte balloon wird als CollisonActor registriert

actor2.removeSelf()

Lässt den mit der Pfeilspitze berührten Ballon verschwinden
return 0 Man gibt in der Methode collide() die Anzahl der Simulationszyklen zurück, bis die Kollisionsdetektion wieder aktiv wird. In diesem Fall kann sie sofort wieder aufgerufen werden
double dx = location.x - oldLocation.x
double dy = location.y - oldLocation.y
Berechnet die Differenz der neuen und alten Koordinaten
if (dx * dx + dy * dy < 25)
  return true
Wartet, bis der Abstand des alten und neuen Punktes mindestens 5 pixel ist