Game-Apps für Smartphones und Tablets

Bern University oh Teacher Education  
HomeOnline-Editor startenDruckenAndroid-TurtlegrafikJava-Online

Kollisionen in Pixelgames

Theoretisch kollidieren zwei Actors dann, wenn sich ihre Sprite-Bilder in mindestens einem Pixel überlagern. Theoretisch müsste man alle nicht-transparenten Pixel der beiden Sprites 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 bei Actors einfache geometrische Formen als Kollisionsbereiche fest. In JDroidLib 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: . Ein Ball bewegt sich in einem Spielfeld mit Pixelauflösung und reflektieren dabei am Fensterrand. Wenn er mit einem rechteckiger Stab, der sich vertikal hin und her bewegt, zusammenstösst, wird ein Ton abgespielt und der Ball ändert seine Bewegungsrichtung. Dieses Beispiel kann zu einem Pingpong-Game weiter entwickelt werden.

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

Stick wird mit addCollisionActor(ball) mitgeteilt, dass er auf Kollisionen mit ball reagieren soll. Beim Ball wird als Kollisionsbereich ein Kreis gewählt.

In diesem Beispiel kann man gut beobachten, wie exakt der Kollisionsalgorithmus von JDroidLib funktioniert. Selbst wenn sich die beiden Figuren nur am Rand berühren, wird eine Kollision registriert.

Beispiel im Online-Editor bearbeiten

App installieren auf Smartphone oder Tablet

QR-Code

Sources downloaden (AndroidEx20.zip). Auf Tablet mit Longpress.

 

// AndroidEx20.java

package app.ex20;

import ch.aplu.android.*;
import android.graphics.Point;

public class AndroidEx20 extends GameGrid implements GGActorCollisionListener
{
  public AndroidEx20()
  {
    super(400, 400, 1);
  }
  
  public void main()
  {
    setSimulationPeriod(10);
    getBg().clear(DKGRAY);
    Ball ball = new Ball();
    addActor(ball, new Location(300, 30));
    Stick stick = new Stick();
    addActor(stick, new Location(200, 200));
    stick.setDirection(90);
    stick.addCollisionActor(ball);
    stick.addActorCollisionListener(this);
    doRun();
  }

  public int collide(Actor actor1, Actor actor2)
  {
    actor2.setDirection(actor2.getDirection() + 180);
    playTone(1200, 20);
    return 10;
  }
}

// --------------class Stick ---------
class Stick extends Actor
{
  public Stick()
  {
    super("stick");
  }

  public void act()
  {  
    move();
    if (getY() < 40 || getY() > 360)
      turn(180);
  }
}

// ---------class Ball ---------------
class Ball extends Actor
{
  private final int radius = 20;

  public Ball()
  {
    super("ball");
    setCollisionCircle(new Point(0, 0), radius + 1);
  }

  public void act()
  {
    Location loc = getLocation();
    double dir = getDirection();

    if (loc.x < radius)
    {
      dir = 180 - dir;
      setLocation(new Location(radius, loc.y));
    }
    if (loc.x > 400 - radius)
    {
      dir = 180 - dir;
      setLocation(new Location(400 -radius, loc.y));
    }
    if (loc.y < radius)
    {
      dir = 360 - dir;
      setLocation(new Location(loc.x, radius));
    }
    if (loc.y > 400 -radius)
    {
      dir = 360 - dir;
      setLocation(new Location(loc.x, 400 -radius));
    }
    setDirection(dir);
    move();
  }
}

Erklärungen zum Programmcode:
setSimulationPeriod(10) Verkürzt die Simulationsperiode, damit sich die beiden Figuren schneller bewegen
collide(Actor actor1, Actor actor2)

Callbackmethode des CollisionListener. Deklariert das Verhalten der beiden Actors nach einer Kollision. (Actor1 ist Stick, Actor2 ist Ball)

setCollisionCircle(new Point(0, 0), radius) Die Kollisionsfläche wird als Kreis mit dem Mittelpunkt 0,0 und Radius 21 definiert
return 10 Der Rückgabewert der Methode collide() legt die minimale Anzahl der Simulationszyklen fest, bis sie wieder aufgerufen werden kann

 

Beispiel 2: Fünf farbige Kugeln bewegen sich frei im Spielfeld und reflektieren am Rand. So bald ene Kugel eine andere berührt wird eine Kollision registriert. Dabei prallt jede Kugel unter dem gleichen Winkel zurück, wie sie angerollt ist, und es wird ein Ton abgespielt.

Beispiel im Online-Editor bearbeiten

App installieren auf Smartphone oder Tablet

QR-Code

Sources downloaden (AndroidEx21.zip). Auf Tablet mit Longpress.

 

 

// AndroidEx21.java

package app.ex21;

import ch.aplu.android.*;
import android.graphics.Point;

public class AndroidEx21 extends GameGrid implements GGActorCollisionListener
{
  protected static int nbBalls = 5;
  
  public AndroidEx21()
  {
    super(400, 400, 1);
  }
  
  public void main()
  {
    setSimulationPeriod(10);
    getBg().clear(DKGRAY);
    Ball[] balls = new Ball[nbBalls];
    for (int = 0; i < nbBalls; i++)
    {
      balls[i] = new Ball();
      balls[i].show(i);
      addActor(balls[i], new Location(100 + 50 * i, 100 + 50 * i), Math.random()*360);
      balls[i].addActorCollisionListener(this);
    }

    for (int = 0; i < nbBalls; i++)
    {
      for (int = + 1; k < nbBalls; k++)
        balls[i].addCollisionActor(balls[k]);
    }
    doRun();
  }

  public int collide(Actor actor1, Actor actor2)
  {
    actor1.setDirection(actor1.getDirection() + 180);
    actor2.setDirection(actor2.getDirection() + 180);
    playTone(1200, 20);
    return 10;
  }
}

// ------- class Ball ------------------
class Ball extends Actor
{
  private final int radius = 20;
  
  public Ball()
  {
    super("peg"5);
   setCollisionCircle(new Point(0, 0), radius + 1);
  }

  public void act()
  {
    Location loc = getLocation();
    double dir = getDirection();

    if (loc.x < radius)
    {
      dir = 180 - dir;
      setLocation(new Location(20, loc.y));
    }
    if (loc.x > getNbHorzCells() - radius)
    {
      dir = 180 - dir;
      setLocation(new Location(getNbHorzCells()-radius, loc.y));
    }
    if (loc.y < radius)
    {
      dir = 360 - dir;
      setLocation(new Location(loc.x, radius));
    }
    if (loc.y > getNbVertCells()-radius)
    {
      dir = 360 - dir;
      setLocation(new Location(loc.x, getNbVertCells()-radius));
    }
    setDirection(dir);
    move();
  }
}

Erklärungen zum Programmcode:
setCollisionCircle(new Point(0, 0), 21) Die Kollisionsfläche wird als Kreis mit dem Mittelpunkt 0,0 und Radius 21 definiert
balls[i].addActorCollisionListener(this) Zu jeder Kugel wird ein CollisionListener hinzugefügt
balls[i].addCollisionActor(balls[k]); Bei jeder Kugel werden Kollisionen mit allen übrigen Kugeln registriert

 

 

Beispiel 3: Ein durch Berührung gesteuerter Pfeil kann mit seiner vorderen Spitze (CollisionSpot) Ballons zerplatzen.

15 Ballons werden an zufällig gewählten Positionen erzeugt. Beim Pfeil wird der Endpunkt der Nadel mit dart.setCollisionSpot(new Point(45, 0)) als Kollisionspunkt festgelegt. Zur Bestimmung des Collisionspots wird ein Koordinatensystem mit Ursprung (0, 0) im Mittelpunkt des Spritebildes verwendet. Das Spritebild ist 90 x 17 Pixel gross, d.h. (45, 0) sind die Koordinaten des Endpunktes der Nadel. Der Pfeil kann mit dem Finger verschoben werden. Damit der Pfeil nicht vom Finger verdeckt wird, wird die Position des Griffs nach links verschoben. Für die Anzeige wird WindowZoom(), verwendet, d.h. die Bilder und die Positionen der Touchs werden automatisch auf die Display-Grösse angepasst.

Beispiel im Online-Editor bearbeiten

App installieren auf Smartphone oder Tablet

QR-Code

Sources downloaden (AndroidEx22.zip). Auf Tablet mit Longpress.

 

 

// AndroidEx22.java

package app.ex22;

import ch.aplu.android.*;
import android.graphics.Color;
import android.graphics.Point;

public class AndroidEx22 extends GameGrid implements GGActorCollisionListener
{
  public AndroidEx22()
  {
    super("town"windowZoom(500));
    setScreenOrientation(LANDSCAPE);
  }

  public void main()
  {
    setSimulationPeriod(50);
    Dart dart = new Dart();
    addActor(dart, new Location(50, 300));
    addTouchListener(dart, GGTouch.drag);
    dart.setCollisionSpot(new Point(45, 0));
    
    for (int = 0; i < 15; i++)
    {
      Actor balloon = new Actor("balloon");
      Location loc = new Location((int)(420*Math.random() + 40 ), 
                                  (int)(420*Math.random() + 40));       
      addActor(balloon, loc.toReal());
      dart.addCollisionActor(balloon);
      dart.addActorCollisionListener(this);
    }
    doRun();    
  }
   
  public int collide(Actor actor1, Actor actor2)
  {
    actor2.removeSelf();
    playTone(1200, 20);
    return 0;
  }  
}

// ------class Dart ------------------
class Dart extends Actor implements GGTouchListener
{
  public Dart()
  {
    super("dart");  
  }

  public boolean touchEvent(GGTouch touch)
  {
    Location location =
      gameGrid.toLocationInGrid(touch.getX() + 90, touch.getY());
    setLocation(location);
    return true;
  }
}

Erklärungen zum Programmcode:
super("town", windowZoom(500)); Die Fenstergrösse soll automatisch angepasst werden, wobei 500 die ursprüngliche Hintergrundbildgrösse ist
setScreenOrientation(LANDSCAPE) Die Anzeige erfolgt im Breiformat und bleibt beim Drehen des Smartphones/Tablets fix
dart.setCollisionSpot(new Point(45, 0)) Setzt den Kollisionspunkt des Pfeils auf die Spitze des Pfeils
addActor(balloon, loc.toReal()) Rechnet die Koordinaten des Touchevents auf die durch Zoom angepassten Koordinaten