TigerJython xx
für Gymnasien

Präzise Kollisionserkennung mit Pixel-Kollisionen

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°.

Kollisionen werden als Events erfasst. Dabei wird die Methode collide() aufgerufen.

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

In diesem Beispiel kann man beobachten, wie exakt der Kollision-Algorithmus von JGameGrid funktioniert. Selbst wenn sich die beiden Figuren mit Eckpunkten berühren, wird ein Kollisionsevent ausgelöst.


 

# Gg9.py

from gamegrid import *
from soundsystem import *

# --------------------- class Stick ----------------------------------
class Stick(Actor):
    def __init__(self):
        Actor.__init__(self, True, "sprites/stick.gif", 2)  
   
    def act(self):
        step = 1
        loc = self.getLocation()
        dir = (self.getDirection() + step) % 360;
        if loc.x < 50:
            dir = 180 - dir
            self.setLocation(Location(55, loc.y))
        if loc.x > 450:
            dir = 180 - dir
            self.setLocation(Location(445, loc.y))
        if loc.y < 50:
            dir = 360 - dir
            self.setLocation(Location(loc.x, 55))
        if loc.y > 450:
            dir = 360 - dir
            self.setLocation(Location(loc.x, 445))
        self.setDirection(dir)
        self.move()

    def collide(self, actor1, actor2):
        actor1.setDirection(actor1.getDirection() + 180)
        actor2.setDirection(actor2.getDirection() + 180)
        play()
        return 10

# --------------------- main -----------------------------------------
makeGameGrid(500, 500, 1, False)
setSimulationPeriod(10)
stick1 = Stick()
addActor(stick1, Location(200, 200), 30)
stick2 = Stick()
addActor(stick2, Location(400, 400), 30)
stick2.show(1)
stick1.addCollisionActor(stick2)
show()
doRun()
openSoundPlayer("wav/ping.wav")    
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

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). Hier wird festgelegt, was bei einem Kollisionsevent geschehen soll. Die Bewegungsrichtungen der Actors wird gekehrt
return 10: Der Rückgabewert der Methode collide() legt die Anzahl der Simulationszyklen fest, bis die Kollisionsevents wieder aktiviert werden. Nach einem Kollisionsevent müssen wir warten, bis sich die Sticks nicht mehr berühren
from soundsystem import * : stellt die Soundlibrary zur Verfügung
openSoundPlayer("wav/ping.wav") : stellt die Sounddatei bereit.
Folgende kurze Sounds sind in der Disribution tigerjython,jar vorhanden: bird.wav, boing.wav, cat.wav, click.wav,explore.wav,frog.wav. notify.wav, boing.wav
play(): spielt den sound ab

 

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

 


# Gg9a.py

from gamegrid import *
from soundsystem import *
import random

# --------------------- class Ball ----------------------------------
class Ball(Actor):

   def __init__(self):
      Actor.__init__(self, True, "sprites/peg.png", 5)  
      
   def act(self):
      step = 1
      loc = self.getLocation()
      dir = (self.getDirection() + step) % 360;

      if loc.x < 20:
         dir = 180 - dir
         self.setLocation(Location(20, loc.y))
      if loc.x > 480:
         dir = 180 - dir
         self.setLocation(Location(478, loc.y))
      if loc.y < 20:
         dir = 360 - dir
         self.setLocation(Location(loc.x, 22))
      if loc.y > 480:
         dir = 360 - dir
         self.setLocation(Location(loc.x, 478))
      self.setDirection(dir)
      self.move()

# --------------------- class MyActorCollisionListener ---------------
class MyActorCollisionListener(GGActorCollisionListener):
   def collide(self, actor1, actor2):
      actor1.setDirection(actor1.getDirection() + 180)
      actor2.setDirection(actor2.getDirection() + 180)
      play()
      return 10

# --------------------- main -----------------------------------------
makeGameGrid(500, 500, 1, False)
setSimulationPeriod(10)
myActorCollisionListener = MyActorCollisionListener()
balls = []
for i in range(5):
   ball = Ball()
   ball.show(i)
   addActor(ball, Location(100 + 50 * i, 100 + 50 * i), random.randint(0, 360))
   ball.addActorCollisionListener(myActorCollisionListener)
   balls.append(ball)

for i in range(5):
   for k in range(i + 1, 5):
      balls[i].addCollisionActor(balls[k])

openSoundPlayer("wav/ping.wav")  
show()
doRun()   
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

Erklärungen zum Programmcode:

ball.addActorCollisionListener(): Zu jeder Kugel wird ein GGActorCollisionListener registriert
balls[i].addCollisionActor(balls[k]): Bei jeder Kugel werden Kollisionen mit allen übrigen Kugeln aktiviert

 

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 Collisionsspots 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 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.

 

# Gg9b.py

from gamegrid import *
from soundsystem import *
import random
import math

# --------------------- class Dart --------------------------
class Dart(Actor, GGMouseListener):
    def __init__(self):
        Actor.__init__(self, True, "sprites/dart.gif") 
        self.oldLocation = Location()
    
    def mouseEvent(self, mouse):
        location = toLocationInGrid(mouse.getX(), mouse.getY())
        self.setLocation(location)
        dx = location.x - self.oldLocation.x
        dy = location.y - self.oldLocation.y
        if (dx * dx + dy * dy < 25):
            return True
        phi = math.atan2(dy, dx)
        self.setDirection(math.degrees(phi))
        self.oldLocation.x = location.x
        self.oldLocation.y = location.y
        return True

    def collide(self, actor1, actor2):
        play()
        actor2.removeSelf()
        return 0


# --------------------- main -----------------------------------------
makeGameGrid(600, 600, 1, None, "sprites/town.jpg", False)
setTitle("Move dart with mouse to pick the balloon")
setSimulationPeriod(50)
dart = Dart()
addActor(dart, Location(100, 300))
addMouseListener(dart, GGMouse.lDrag)
dart.setCollisionSpot(Point(30, 0)) # End point of dart needle
for i in range(15):
    balloon = Actor("_sprites/balloon.gif")
    addActor(balloon, Location(int(500 * random.random() + 50 ), 
                                            int(500 * random.random() + 50)))
    dart.addCollisionActor(balloon)
show()
doRun()
openSoundPlayer("wav/explode.wav")
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

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 Ballon ist ein Collisonspartner des Pfeils
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