15 Kollisionstest
Zwei MovieClips treffen sich – die Grundlage für viele Animationen und SpieleSie werden lernen:
- Wie führen Sie einen Kollisionstest durch?
- Wie lässt sich die Kollision exakt feststellen?
- Welche Nachteile haben handgeschriebene Lösungen?
Fahre wie der Teufel und du wirst ihn bald treffen. – Robert Lembke
Interaktiv wird ein Flash-Film, wenn der Nutzer mit Objekten interagiert. Aber auch Objekte können zueinander in Beziehung stehen. Damit aus vormals gesitteten Beziehungen nicht totales Chaos wird, müssen Sie Kollisionen zwischen den Objekten abfangen. Kollision heißt, dass sich zwei Objekte treffen. Und wie meist bei ActionScript handelt es sich bei diesen Objekten um MovieClips.
15.1 Kollisionen abfangen
Für das Abfangen von Kollisionen gibt es zwei Möglichkeiten:
- Die Methode hitTest() des MovieClip-Objekts liefert einen Wahrheitswert, ob eine Kollision vorliegt (true) oder nicht (false).
- Eine handgeschriebene Lösung, die die Koordinaten von Objekten vergleicht.
Koordinatensystem |
Die Koordinaten für hitTest() entstammen automatisch dem Koordinatensystem des Hauptfilms; sie heißen globale Koordinaten. Mit den Methoden localToGlobal() und globalToLocal() des MovieClip-Objekts können Sie normale Koordinaten eines MovieClips in globale Koordinaten umwandeln und umgekehrt. Dazu benötigen Sie ein Objekt (Object), das die beiden Koordinaten speichert. Mehr hierzu finden Sie in der Hilfe zu den beiden Methoden. Ein Beispiel sehen Sie im Abschnitt »Handgeschriebene Lösungen«. |
Um die zweite Alternative drückt sich der Programmierer gerne, außer es geht wirklich nicht anders. Insofern ist von Interesse, was Sie mit hitTest() bewerkstelligen können und was nicht.
hitTest() ist in zwei Varianten einsetzbar:
1. | mit einem beliebigen Punkt auf der Bühne des Hauptfilms: |
movieclip_mc.hitTest(x, y, Form)
-
- Als Parameter geben Sie die x- und y-Koordinate des Punktes an. Form enthält einen Boolean. Beim Wert true wird nur die Form des MovieClips (der gefüllte Bereich) zum Testen herangezogen. Das heißt, nur wenn der Punkt innerhalb der Form des MovieClips liegt, liefert hitTest() den Wert true zurück. Ist der Wert false, zählt die Begrenzung um den Inhalt des MovieClips. Die Begrenzung ist der kleinstmögliche rechteckige Bereich, der sich um alle Inhalte des MovieClips legen lässt. In diesem Fall zählt also auch der transparente Bereich zwischen dem Inhalt und der Begrenzung dazu.
Abbildung 15.1 Form und Begrenzung eines MovieClips.
2. | Die zweite Variante für den Einsatz von hitTest() wird mit einem anderen MovieClip realisiert: |
movieclip_mc.hitTest(Ziel);
-
- In diesem Fall ist das Ziel ein beliebiger anderer MovieClip, dessen Instanznamen Sie angeben. Relevant sind allerdings die Begrenzungen der beiden MovieClips, nicht der Inhalt selbst.
hitTest() im Test
Das Kernproblem bei hitTest() ist, dass beim Kollisionstest zweier MovieClips nur die Begrenzungen, nicht aber der Inhalt relevant sind.
Dies können Sie ganz einfach ausprobieren: Erzeugen Sie einen Kreis und wandeln Sie ihn in einen MovieClip um. Duplizieren Sie den MovieClip und geben Sie beiden einen Instanznamen (bei uns test1_mc und test2_mc). Einer der beiden Kreise wird nun per Drag & Drop beweglich, wenn Sie folgenden Code in die Aktion einfügen:
on (press) {
startDrag(this);
}
on (release, releaseOutside) {
stopDrag();
}
Ab nächstem Schritt müssen Sie im ersten Schlüsselbild des Hauptfilms mit einer Ereignisprozedur testen, ob sich die zwei MovieClips berühren:
_root.onEnterFrame = function() { if (this.test1_mc.hitTest(test2_mc)) { trace("Hit"); } };
Abbildung 15.2 Wie Sie sehen, treffen sich die beiden MovieClips schon am Rand der Begrenzungen.
Das Beispiel zeigt: Wenn absolute Genauigkeit zählt und die MovieClips ungewöhnlich geformt sind, hilft nur eine handgeschriebene Lösung. Überlegen Sie sich aber von Fall zu Fall, ob die hitTest()-Variante nicht doch ausreicht. Zum einen ist sie schneller, zum anderen wird sie auch mit Größenänderungen fertig.
Sie finden unser kleines Beispiel unter dem Namen hittest.fla für Flash MX 8 bzw. 2004 (Ordner Flash8 bzw. FlashMX2004) und Flash MX (Ordner FlashMX) auf der CD-ROM.
Handgeschriebene Lösungen
Väter der Gedanken |
Für jede der Varianten gibt es Paradebeispiele, die zwar nicht nachweislich die ersten waren, aber am weitesten verbreitet sind. Für eine rein mathematische Berechnung der Kollision zweier Kreise ist der ActionScript-Experte Colin Moock zuständig. Die kleinen, unsichtbaren MovieClips sind hauptsächlich durch das Bounce- und Collide-Beispiel von Gary Fixler bekannt. |
Bei den handgeschriebenen Lösungen schwirrt eine Unzahl an Varianten durch das Netz. Fischt man etwas intensiver, wird die Ähnlichkeit vieler Varianten offensichtlich. Im Kern gibt es drei Ideen, die Sie verfolgen könne:
- Sie überprüfen jeden einzelnen Punkt eines Objekts. Diese Lösung kommt am seltensten zum Einsatz, da sie viel zu aufwendig ist.
- Sie verwenden eine geometrische Formel, um den Abstand zwischen zwei Elementen festzustellen. Wird der Abstand zu gering, meldet das Skript eine Kollision.
- Sie erzeugen leere, unsichtbare MovieClips, um die Form des MovieClips, der geprüft werden soll, näherungsweise darzustellen. Die kleinen MovieClips sind dabei dem eigentlich zu testenden MovieClip untergeordnet.
Sehen Sie sich die letzten zwei Ideen für handgeschriebene Lösungen einmal an Beispielen an. Wir verwenden dazu die beiden Kreise aus dem vorigen Abschnitt. An Drag & Drop ändert sich nichts, dieses Mal soll die Kollisionsabfrage aber exakt erfolgen.
Werfen Sie zuerst einen Blick auf die geometrische Lösung (kollision_handgeschrieben.fla), die aus mehreren Teilen besteht:
1. | Am Anfang erstellen Sie eine ZählervariableZählervariable und starten dann die Ereignisprozedur onEnterFrame (Ereignisprozedur)onEnterFrame, um den Kollisionstest in regelmäßigen Abständen durchzuführen: |
var i:Number = 0; _root.onEnterFrame = function() {
2. | Im nächsten Schritt berechnen Sie die Entfernung der zwei zu testenden Kreise auf der x- und auf der y-Achse: |
var entX_num:Number = this.test1_mc._x – this.test2_mc._x; var entY_num:Number = this.test1_mc._y – this.test2_mc._y;
3. | Die Entfernungen auf den beiden Achsen bilden zusammen zwei Schenkel eines rechtwinkligen Dreiecks. Hier kommt Ihnen der Satz des Pythagoras zu Hilfe:Math.pow() (Methode) |
var entfernung_num:Number = Math.pow(entX_num, 2) + Math.pow(entY_num, 2);
4. | Jetzt berechnen Sie den Radius der beiden Kreise: |
var radius1_num:Number = this.test1_mc._height / 2; var radius2_num:Number = this.test2_mc._height / 2;
5. | Aus dem Radius entsteht die minimale Entfernung: |
var minEntfernung_num:Number = Math.pow((radius1_num + radius2_num), 2);
6. | Ist die minimale Entfernung größer als die gemessene, berühren sich die beiden Kreise: |
if (entfernung_num < minEntfernung_num) { trace("Hit" + ++i); } };
Abbildung 15.3 Dank geometrischer Berechnung funktioniert der Kollisionstest nun exakt.
Als Nächstes widmen wir uns der Lösung mit den leeren, unsichtbaren MovieClips (kollision_handgeschrieben2.fla). Hier müssen Sie einiges an Vorbereitungsarbeit erledigen:
1. | Sie erstellen ein neues MovieClip-Symbol, das ungefüllt in der Bibliothek landet. |
2. | Anschließend wechseln Sie in den MovieClip, den Sie mit dem Kollisionstest versehen möchten. |
3. | Ziehen Sie den leeren MovieClip von der Bibliothek in den zu testenden MovieClip. |
4. | Duplizieren Sie den leeren MovieClip und ordnen Sie die Duplikate an den Grenzen des zu testenden MovieClips an. Das Ergebnis dieser etwas stupiden Arbeit sehen Sie in der nächsten Abbildung und im Beispiel auf der CD-ROM, wenn Sie in den MovieClip des rechten Kreises wechseln. |
Abbildung 15.4 Der Kreis wird von leeren MovieClips umrahmt. Ein leerer MovieClip besteht nur aus einem weißen Kreis. Wird er angeklickt, erhält der weiße Kreis ein schwarzes Kreuz.
Nach diesen Vorbereitungen kommen wir zu ActionScript. Der Code landet im ersten Schlüsselbild des Hauptfilms auf der Ebene ActionScript. Nun erledigen Sie die weiteren Schritte:
5. | Wieder benötigen Sie zuerst die Zählervariable i und die Ereignisprozedur onEnterFrame: |
var i:Number = 0; _root.onEnterFrame = function() {
6. | Nun folgt eine Variable, die die Namen von einzelnen MovieClips aufnehmen soll: |
var name_str:String;
7. | Der nächste Schritt ist der große Trick: |
for (name_str in this.test2_mc) {
-
- Eine for-in-Schleife geht nach und nach alle MovieClips durch, die test2_mc direkt untergeordnet sind. Es handelt sich hier um die leeren MovieClips. Ihre Namen werden in die Variable name_str gespeichert. Das funktioniert, obwohl die MovieClips keine Instanznamen besitzen. Nun haben Sie vollen Zugriff auf die leeren, unsichtbaren MovieClips.
8. | Den Zugriff nutzen Sie gleich, um die Koordinaten des jeweiligen MovieClips in das Objekt punkt als Eigenschaft zu speichern. |
var punkt = new Object(); punkt.x = this.test2_mc[name_str]._x; punkt.y = this.test2_mc[name_str]._y;
9. | Der folgende Schritt verrät, warum Sie dieses Objekt benötigen: Mit der Funktion localToGlobal(Punkt) können Sie einen beliebigen Punkt aus dem lokalen (MovieClip-) Koordinatensystem in das globale Koordinatensystem des Hauptfilms umwandeln. Der Punkt muss allerdings als Objekt angegeben sein. |
this.test2_mc.localToGlobal(punkt);
10. | Nun folgt die Überprüfung, ob der Punkt, den Sie aus dem leeren MovieClip gewonnen haben, den zweiten Kreis berührt. |
if (this.test1_mc.hitTest(punkt.x, punkt.y, true)) { trace("Hit" + ++i); } } };
-
- Da ein Punkt mit der exakten Form getestet werden kann, benötigen Sie die leeren MovieClips immer nur für einen der beiden MovieClips, deren Kollision Sie testen möchten.
Abbildung 15.5 Die Lösung mit leeren MovieClips ergibt ebenfalls eine exakte Kollisionsabfrage.
Prinzipiell gilt: Wenn es eine geometrische Lösung gibt, sollten Sie diese in der Regel vorziehen, da sie einfacher ist und eine bessere Performance besitzt.
Die geometrische Lösung scheitert allerdings dann, wenn es sich nicht um eine überprüfbare Form handelt. Deswegen ist in der Praxis die Lösung mit leeren, unsichtbaren MovieClips wesentlich häufiger.
hitArea – Kollision für Schaltflächen-MovieClips
Ab Flash MX können Sie MovieClips wie Schaltflächen behandeln. Eine neue Eigenschaft in der MovieClip-Klasse ist Ihnen dabei behilflich: hitArea. Diese Eigenschaft gibt für einen MovieClip an, welcher MovieClip seine Hitarea ist, also der Bereich, an dem er gedrückt wird:
movieclip_mc.hitArea = andererMovieclip_mc;
Was heißt das? Sie können Funktionalität mit einem MovieClip verbinden und sie wird nur aufgerufen, wenn der Nutzer einen anderen MovieClip drückt.
Auf der CD-ROM finden Sie unter dem Namen hitarea.fla ein Beispiel, bei dem die linke Schaltfläche (schalt1_mc) die rechte Schaltfläche (schalt2_mc) als Hitarea besitzt. Der linken Schaltfläche ist eine Ereignisprozedur zugeordnet, die etwas ausgibt, wenn der Nutzer auf die rechte Schaltfläche drückt (onPress):
this.schalt1_mc.hitArea = this.schalt2_mc; this.schalt1_mc.onPress = function() { trace("Hit"); };
Der Effekt: Die linke Schaltfläche ist für den Nutzer funktionslos, wenn er aber die rechte drückt, wird die mit der linken verknüpfte Meldung ausgegeben.
Abbildung 15.6 Die linke Schaltfläche besitzt die Funktionalität, aber aufgerufen wird sie, wenn der Nutzer auf die rechte Schaltfläche klickt.
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.