Dienstag, 28. Juni 2011

Actionscript 3: Kollisionserkennung

Nun kommen wir mal, nachdem ich doch ziemlich beschäftigt war, zu einem der interessantesten und doch sehr schwierigen Themen, Kollisionserkennung. Die Theorie ist ja doch recht simpel, trivial ausgedrückt: Wenn 2 Objekte kollidieren, dann sollen sie irgendwas tun. Etwas komplizierter wird es nun, wenn man sich fragt: Wann kollidieren 2 Objekte mit einander?
Wieder trivial: Wenn die x- und y-koordinaten übereinander liegen. Frage: Welche x- und y-koordinaten?

Langsam wird einem bewusst, dass Kollision doch schwieriger ist als man es zuerst vermutet. Gerade bei Actionscript gibt es ziemlich viele Tutorials für Kollision, die alle mehr oder weniger funktionieren aber auch früher oder später über die Kollision mit unregelmäßigen Objekten stolpern. Hier würde ich eine Methode, die ich mal in einem Spielprogrammierungsbuch gelesen habe, empfehlen: Man teilt den Körper in einzelne Kollisionsquadrate ein. Kümmern wir uns aber nun um eine Methode, die ich von den bestehenden ausgehend entwickelt habe, die bei einer Stage mit vielen Objekten sehr effektiv arbeitet.

Theorie:
  • Kollisionsobjekte sind in einem eigenen Array gespeichert
  • zu jedem Kollisionsobjekt gibt es ein extra Objekt, dass alle Positionsdaten enthält
  • wir prüfen erst grob etwas kollidiert oder nicht
  • wenn es eine Kollision gibt, dann wird die Kollision fein geprüft, ob die Kollision auch wirklich stattfindet

Praxis:

Array mit Kollisionsobjekten:

var _stage:Array
Ein simples Array, dass alle nötigen Kollisionsobjekte beinhaltet.


Kollisionsdaten Objekt:

class StageDatas
{
private var _x:int;
private var _y:int;
private var _index:int;

public function StageDatas(index:int, x:int, y:int)
{
_x = x;
_y = y;
_index = index;
}

public function get x():int
{
return _x;
}

public function get y():int
{
return _y;
}

public function get index():int
{
return _index;
}

public function set x(x:int):void
{
_x = x;
}

public function set y(y:int):void
{
_y = y;
}

public function set index(index:int):void
{
_index = index;
}
}
Dies ist eine einfache Speicherklasse, die x-Position, y-Position ausgehend vom linken oberen Bildschirmpunkt speichert. index speichert den Index des Objektes innerhalb des Arrays mit den Kollisionsobjekten (_stage), damit nicht immer wieder neu danach gesucht werden muss, bzw. damit man die Daten schnell wieder finden kann.

Grobe Kollisionskontrolle

var hitObject:Sprite;
var data:StageDatas;
In hitObject wird unser aktuelles Testsprite rein geladen, in data finden wir alle nötigen angaben zu dem Sprite, da die Stage keine Positionsdaten speichert

for(var i:int = 0; i < _stage.length; i++)
{
   hitObject = _stage[i];
   if(!_activeObject.LastHit.hitTestPoint(_activePic.x + wOff,
       _activePic.y + hOff, true) || 
      !_activeObject.LastHit.hitTestPoint(_activePic.x +
       _activePic.width + wOff, _activePic.y + hOff,
       true))
   {
       //hier euren Code, wenn keine Kollision stattfindet
   }
   else
   {
      //hier kommt noch mehr Code
   }
}
Das erscheint jetzt etwas kompliziert. Die Idee selbst ist einfach, ich überprüfe alle möglichen HitObjekte gegen ein aktives Objekt, dass selber nicht im HitArray (_stage) ist. Somit verhindere ich Fehler durch eine Kollision mit sich selbst. hitObject speichert unser zu testendes Objekt und data die dazugehörigen Daten. _activeObject ist das Hauptobjekt, dass Kollidieren soll und wurde von mir extra definiert, ich werde euch den Aufbau ein anderes mal erklären, wichtig ist derzeit, dass es das letzte Objekt kennt, mit dem es kollidiert hat, das ist _activeObject.LastHit. Damit will ich Fehler vorbeugen, die entstehen können, wenn er auf das Objekt, mit dem er schon interagiert hat wieder interagiert obwohl es nicht notwendig ist. Zuerst teste ich ob die Kollision mit diesem Objekt überhaupt noch stattfindet. Für das _activeObject werden auch noch höhen und weiten Offset mit berücksichtigt.


Feine Kollisionsprüfung

else
{

   for(var i:int = 0; i < _stage.length; i++)
   {
      hitObject = _stage[i];
      if(_activeObject.LastHit != hitObject)
      {
         data = _stageData[i];
         if((_activeObject.LeftCollision.x == data.x + hitObject.width ||
             _activeObject.LeftCollision.x + leftOff == data.x + hitObject.width) &&
             _activeObject.LeftCollision.y >= data.y && _player.dx < 0)
         {
            //hier der Code
         }
         else if((_activeObject.RightCollision.x == data.x ||
                  _activeObject.RightCollision.x + rightOff == data.x) &&
                  _activeObject.RightCollision.y >= data.y && _activeObject.dx > 0)
         {
            //hier der Code
         }        
      }
   }
}
Die Feinabtastung ist etwas einfacher, da letztendlich nur auf Kollision gegen einzel Zonen am Objekt getestet werden. In diesem einfachen Beispiel kennt mein Objekt nur die Rechte- und die Linkekollisionszone. Gegen diese wird getestet, dabei wird darauf geachtet, dass nur immer gegen eine der beiden getestet und nicht gegen beide gleichzeitig, ansonsten kann es zu fehlern kommen. Dieser Code muss natürlich erweitert werden, wenn es mehrere Zonen gibt, hier lohnt sich sogar schon ein Array, wenn die Menge zunimmt und einfach das Array durchtesten.

Damit haben wir uns eine einfache Kollisionserkennung gebaut. Wie immer ohne Funktionsgarantie und auf eigene Verantwortung. Beim nächsten mal werde ich für euch eine Playerklasse aufziehen.

Wie immer viel Spaß damit.

Keine Kommentare:

Kommentar veröffentlichen