Mit den beiden hitTest-Methoden kann man abfragen, ob ein DisplayObject ein anderes DisplayObject oder einen Punkt berührt. Hat eine Kollision stattgefunden liefert die hitTest Funktion den Wert true, andernfalls false. (siehe auch den Tipp alle Objekte unter Punkt):
ersterMc.hitTestObject(zweiterMc);
Im folgenden Beispiel wird überprüft, ob die Mausposition nicht innerhalb des Hintergrunds der Movieclipinstanz "meinMc" liegt . Der dritte Parameter ist ein boolscher Wert. Bei true, wird nur der gefüllte Bereich des MC's herangezogen. Bei false gilt das kleinstmöglichste Rechteck, welches sich um den Inhalt des MC's legt als Überprüfungsbereich.
public function kollision(evt:Event) {
if (! meinMc.hitTestPoint(root.mouseX,root.mouseY,false)) {
meinMc.removeEventListener(Event.ENTER_FRAME, kollision);
hideTextfields();
}
}
Event Listener hinzufügen:
meinMc.addEventListener(Event.ENTER_FRAME, kollision);
Verschiebe die Elemente mit gedrückter Maustaste und drehe sie durch Doppelklick.
in den MCs m1_mc und m2_mc befinden sich jeweils die Instanzen k1_mc und k2_mc.
nur wenn beide Kreise die beiden Kreise des anderen MCs berühren, wird m1_mc auf die Position von m2_mc gebracht.
Nehmen wir mal an wir hätte mehrere Instanzen von "Feinden" die man abschießen kann. Nun gibt es 2 Möglichkeiten, wie man mit hitTest abfragt, ob die Kugel einen der Feinde getroffen hat.
1. Möglichkeit
Man beachte, dass der Feind bei Treffer aus dem Array gelöscht, aus der Displayliste entfernt und dass sein EventListener entfernt wird. Würde man das Objekt nicht aus dem Array entfernen, gäbe es eine Fehlermeldung, denn man hätte einen Verweis im Array auf Objekt, welches aus der Displayliste entfernt wurde. Das Entfernen des EventListeners erhöht die Performance.
import flash.events.Event; import flash.events.MouseEvent; var anzahl:uint = 10; var ship:Ship = new Ship(); ship.y = 390; addChild(ship); ship.addEventListener(Event.ENTER_FRAME, moveShip); var bulletSpeed:Number = 9; var bullet:Bullet = new Bullet(); addChild(bullet); var feinde:Array = new Array(); for (var i:uint=0; i < anzahl; i++) { var feind:Feind = new Feind(); feind.x = i * 30; feind.y = Math.random() * 400 + 100; feind.addEventListener(Event.ENTER_FRAME, fallDown); addChild(feind); feinde.push(feind); } stage.addEventListener(MouseEvent.CLICK, shoot); function moveShip(evt:Event):void { ship.x += ship.mouseX * 0.1; } function shoot(evt:MouseEvent):void { bullet.visible = true; bullet.x = ship.x; bullet.y = ship.y; bullet.addEventListener(Event.ENTER_FRAME, fly); } function fly(evt:Event):void { bullet.y -= bulletSpeed; for (var e:uint=0; e < feinde.length; e++) { if (bullet.hitTestObject(feinde[e])) { feinde[e].removeEventListener(Event.ENTER_FRAME, fallDown); removeChild(feinde[e]); feinde.splice(e,1); bullet.removeEventListener(Event.ENTER_FRAME, fly); bullet.visible = false; bullet.y = -1000; } } } function fallDown(evt:Event):void { evt.currentTarget.y += 2; if (evt.currentTarget.y > 450) { evt.currentTarget.y = -150 + Math.random() * 100; evt.currentTarget.x = Math.random() * 550; } }
2. Möglichkeit
Man beachte, dass hier kein Array mehr benötigt wird, in dem die "Feinde" referenziert werden. Bei einem Treffer, mit der Instanz bullet, löscht sich das Objekt selbst und entfernt seinen EventListener.
import flash.events.Event; import flash.events.MouseEvent; var anzahl:uint = 10; var ship:Ship = new Ship(); ship.y = 390; addChild(ship); ship.addEventListener(Event.ENTER_FRAME, moveShip); var bulletSpeed:Number = 9; var bullet:Bullet = new Bullet(); addChild(bullet); for (var i:uint=0; i < anzahl; i++) { var feind:Feind = new Feind(); feind.x = i * 30; feind.y = Math.random() * 400 + 100; feind.addEventListener(Event.ENTER_FRAME, fallDown); addChild(feind); } stage.addEventListener(MouseEvent.CLICK, shoot); function moveShip(evt:Event):void { ship.x += ship.mouseX * 0.1; } function shoot(evt:MouseEvent):void { bullet.visible = true; bullet.x = ship.x; bullet.y = ship.y; bullet.addEventListener(Event.ENTER_FRAME, fly); } function fly(evt:Event):void { bullet.y -= bulletSpeed; } function fallDown(evt:Event):void { evt.currentTarget.y += 2; if (evt.currentTarget.y > 450) { evt.currentTarget.y = -150 + Math.random() * 100; evt.currentTarget.x = Math.random() * 550; } if (evt.currentTarget.hitTestObject(bullet)) { evt.currentTarget.removeEventListener(Event.ENTER_FRAME, fallDown); removeChild(DisplayObject(evt.currentTarget)); bullet.removeEventListener(Event.ENTER_FRAME, fly); bullet.visible = false; bullet.y = -1000; } }
Im folgenden Spiel wird es noch etwas komplexer. Man kann nun mit mehreren bullets schießen. Jeder bullet wird in einem Array munition hinterlegt. Bei einem Treffer, wird es aus dem Array gelöscht, von der Displayliste entfernt und der EventListener entfernt.
Klicke einmal auf das Spiel, dann steuere das Shiff mit der Maus und schieße mit der Pfeiltaste nach oben.
import flash.events.Event; import flash.events.MouseEvent; var anzahl:uint = 10; var munition:Array = new Array(); var bulletSpeed:Number = 9; var ship:Ship = new Ship(); ship.y = 390; addChild(ship); ship.addEventListener(Event.ENTER_FRAME, moveShip); for (var i:uint=0; i < anzahl; i++) { var feind:Feind = new Feind(); feind.x = i * 30; feind.y = Math.random() * 400 + 100; feind.addEventListener(Event.ENTER_FRAME, fallDown); addChild(feind); } this.stage.addEventListener(KeyboardEvent.KEY_DOWN, shoot); function moveShip(evt:Event):void { ship.x += ship.mouseX * 0.1; } function shoot(evt:KeyboardEvent):void { switch (evt.keyCode) { case Keyboard.UP : var bullet:Bullet = new Bullet(); addChild(bullet); munition.push(bullet); bullet.x = ship.x; bullet.y = ship.y - 50; bullet.addEventListener(Event.ENTER_FRAME, fly); break; } } function fly(evt:Event):void { evt.currentTarget.y -= bulletSpeed; if (evt.currentTarget.y < -200) { evt.currentTarget.addEventListener(Event.ENTER_FRAME, fly); } } function fallDown(evt:Event):void { evt.currentTarget.y += 2; if (evt.currentTarget.y > 450) { evt.currentTarget.y = -150 + Math.random() * 100; evt.currentTarget.x = Math.random() * 550; } for (var e:uint=0; e < munition.length; e++) { if (evt.currentTarget.hitTestObject(munition[e])) { evt.currentTarget.removeEventListener(Event.ENTER_FRAME, fallDown); removeChild(DisplayObject(evt.currentTarget)); munition[e].removeEventListener(Event.ENTER_FRAME, fly); removeChild(DisplayObject(munition[e])); munition.splice(e,1); } } }
Dieses Spiel ist ein wenig erweitert worden. Allerdings ist hier der Punkt erreicht, wo es langsam unübersichtlich wird. Die Lösung wäre, hier mit Klassen zu arbeiten.
Um zu Überprüfen, ob alle Feinde zerstört wurden, ist eine Variable "anzahl" hinzugefügt worden, in dem die Anzahl der "Feinde" hinterlegt ist. Sofern die anzahl den Wert 0 hat, ist das Spiel beendet.
import flash.events.Event; import flash.events.MouseEvent; var anzahl:uint = 10; var munition:Array = new Array(); var bulletSpeed:Number = 9; var ship:Ship = new Ship(); ship.y = 390; ship.x = 250; ship.visible = false; addChild(ship); ship.addEventListener(Event.ENTER_FRAME, moveShip); start_btn.addEventListener(MouseEvent.CLICK, startGame); function startGame(evt:MouseEvent):void { ship.visible = true; start_btn.visible = false; start_btn.removeEventListener(MouseEvent.CLICK, startGame); for (var i:uint=0; i < anzahl; i++) { var feind:Feind = new Feind(); feind.x = Math.random() * 550; feind.punkte = 10; feind.speed = Math.ceil(Math.random() * 5) + 1; feind.y = Math.random() * 300; feind.addEventListener(Event.ENTER_FRAME, fallDown); addChild(feind); } this.stage.addEventListener(KeyboardEvent.KEY_UP, shoot); } function moveShip(evt:Event):void { ship.x += ship.mouseX * 0.1; } function shoot(evt:KeyboardEvent):void { switch (evt.keyCode) { case Keyboard.UP : var bullet:Bullet = new Bullet(); addChild(bullet); munition.push(bullet); bullet.x = ship.x; bullet.y = ship.y - 60; bullet.addEventListener(Event.ENTER_FRAME, fly); break; } } function fly(evt:Event):void { evt.currentTarget.y -= bulletSpeed; if (evt.currentTarget.y < -200) { evt.currentTarget.addEventListener(Event.ENTER_FRAME, fly); } } function fallDown(evt:Event):void { evt.currentTarget.y += evt.currentTarget.speed; if (evt.currentTarget.y > 450) { evt.currentTarget.y = -150 + Math.random() * 100; evt.currentTarget.x = Math.random() * 550; } for (var e:uint=0; e < munition.length; e++) { if (evt.currentTarget.hitTestObject(munition[e])) { evt.currentTarget.punkte--; munition[e].removeEventListener(Event.ENTER_FRAME, fly); removeChild(DisplayObject(munition[e])); munition.splice(e,1); if (evt.currentTarget.punkte == 0) { evt.currentTarget.removeEventListener(Event.ENTER_FRAME, fallDown); removeChild(DisplayObject(evt.currentTarget)); anzahl--; if (anzahl<=0) { anzahl = 10; start_btn.visible = true; ship.visible = false; start_btn.addEventListener(MouseEvent.CLICK, startGame); this.stage.removeEventListener(KeyboardEvent.KEY_UP, shoot); } } } } }
Dieses Spiel ist nun weiter optimiert.
Zur Info:
Man beachte auch den Event Dispatcher, den ich hier eingesetzt habe, um außerhalb der Klasse auf ein hitTest Ereignis zu reagieren.
Actionscript Klasse Feind
Die Klasse Feind hat ein dazugehöriges Bibliothekselement. Im ersten Bild befindet sich die Raumschiffgrafik. Der Feind bewegt sich ständig von oben nach unten. Die horizontale Bewegung habe ich in dem Trigonometrie Tipp AS2 erklärt. Hierbei definiert die Eigenschaft amplitude den Auschlag rechts und links der Eigenschaft xAchse. Wenn das Objekt am unteren Bühnenrand ankommt wird es wieder oben plaziert und die xAchse wird neu gesetzt und zwar zufällig innerhalb der Bühnenbreite. Auch die Geschwindigkeit wird neu gesetzt. Jeder Feind hat Punkte welche mit get set gesetzt und abgefragt werden können. Der Sinn ist, dass bei Treffern mit einer Bullet Instanz die Punktzahl um einen herabgesetzt wird. Wenn die Instanz keine Punkte mehr hat, löscht sie sich selbst. Außerdem wird die maximale Punktzahl im Konstruktor gesetzt. Wenn eine Instanz wieder am oberen Rand plaziert wird, werden auch die Punkte wieder auf die Höchstpunktzahl zurückgesetzt. Wenn ein Feind sich selbst löscht wird zuvor das Ereignis mit EventDispatcher FEIND_DESTROY versandt.
Im Konstruktor bekommt der Feind eine Referenz auf das Ship. Die Feindklasse fragt ab, ob sie mittels hitTest das Ship berührt. Auch dieses Ereignis wird mit EventDispatcher weitergereicht. Das Ship hat eine public Variable namens fehler. Diese setzt die Feindinstanz bei Berührung um einen höher. Die Zeitleiste des Ship wird außerdem abgespielt, man sieht ein kurzes Flackern oder Leuchten.
package { import flash.events.Event; import flash.events.MouseEvent; import flash.display.MovieClip; import flash.display.DisplayObject; public class Feind extends MovieClip { private var speed:Number; private var punkte:int; private var fullPunkte:int; private var ship:MovieClip; public static var minusPunkt:Number; public var test:String; private var xAchse:Number; private var xSin:Number = 0.03; private var amplitude:Number; //Konstante public static const FEIND_HITSHIP:String = "FeindEvent_FEIND_HITSHIP"; public static const FEIND_DESTROY:String = "FeindEvent_FEIND_DESTROY"; public function Feind(_amplitude:Number, _punkte:Number, _ship:MovieClip) { if( _amplitude > 50 || _amplitude < -50) { amplitude = 50; } else { amplitude = _amplitude; } speed = Math.ceil(Math.random()*5) + 1; fullPunkte = _punkte; punkte = fullPunkte; ship = _ship; if (stage) { init(); } else { addEventListener(Event.ADDED_TO_STAGE,init); } } private function init(evt:Event=null):void { removeEventListener(Event.ADDED_TO_STAGE, init); addEventListener(Event.REMOVED_FROM_STAGE, destroy); this.addEventListener(Event.ENTER_FRAME, movement); xAchse = Math.random() * (stage.stageWidth-50)+25; } private function newPosition():void { speed = Math.ceil(Math.random() * 5) + 1; this.y = -150 + Math.random() * 100; xAchse = Math.random() * (stage.stageWidth-50)+25; punkte=fullPunkte; } private function movement(evt:Event):void { this.y += this.speed; this.x = Math.sin(xSin += 0.09) * amplitude + xAchse; if (this.y > stage.stageHeight + 100) { newPosition(); } if (this.hitTestObject(ship)) { ship.fehler ++; ship.play(); newPosition(); dispatchEvent(new Event(FEIND_HITSHIP)); } } public function get punkt():int { return punkte; } public function set punkt(nr:int):void { if(nr < 0) { punkte=0; dispatchEvent(new Event(FEIND_DESTROY)); this.parent.removeChild(DisplayObject(this)); } else { punkte = nr; } } private function destroy(evt:Event):void { removeEventListener(Event.REMOVED_FROM_STAGE, destroy); this.removeEventListener(Event.ENTER_FRAME, movement); } } }
Actionscript Klasse Bullet
Die Klasse Bullet bekommt im Konstruktor einen Sprite, in dem sich alle Instanzen der Klasse Feind befinden. Der Sprite hat hier den Namen "feinde" Dadurch ist es einfacher abzufragen, ob eine Bullet Instanz eine Feindinstanz per hitTest getroffen hat, denn die Displayfunktionen bieten alles, was man braucht.
Jeder Bullet fliegt nach oben, und löscht sich selbst, wenn er oben angkommen ist oder wenn er eine Feind- Instanz per hitTest getroffen hat und die Punkte der Feind- Instanz werden um 1 erhöht.
Ich hatte die Abfrage des Sprites "feinde" mit einer for-Schleife durchlaufen. Das führte zu Fehlern, wenn getroffene Feind- Instanzen nah zusammen liegen. Nachdem ich die for-Schleife durch eine while Schleife ersetzt habe, wo bei einem Treffer, die Schleife mittels break abgebrochen wird, war das Problem gelöst.
package { import flash.display.*; import flash.events.*; public class Bullet extends MovieClip { public var feinde:Sprite; public var bulletSpeed:Number = 9; public function Bullet(_feinde:Sprite) { feinde = _feinde; if (stage) { init(); } else { addEventListener(Event.ADDED_TO_STAGE,init); } } private function init(e:Event=null):void { removeEventListener(Event.ADDED_TO_STAGE, init); addEventListener(Event.REMOVED_FROM_STAGE, destroy); addEventListener(Event.ENTER_FRAME, fly); } private function fly(evt:Event):void { if (evt.currentTarget.y < -10) { evt.currentTarget.removeEventListener(Event.ENTER_FRAME, fly); this.parent.removeChild(DisplayObject(evt.currentTarget)); } else { y -= bulletSpeed; var i:uint = 0; while (i < feinde.numChildren) { if (feinde.getChildAt(i).hitTestPoint(evt.currentTarget.x, evt.currentTarget.y, true)) { Feind(feinde.getChildAt(i)).punkt -= 1; removeEventListener(Event.ENTER_FRAME, fly); this.parent.removeChild(DisplayObject(evt.currentTarget)); break; } i++; } } } //----------------------------------------------- private function destroy(e:Event):void { removeEventListener(Event.REMOVED_FROM_STAGE, destroy); } }
Actionscript Klasse Ship
Der größte Teil des Actionscripts besteht aus der Tastatursteuerung, die man in ähnlicher Form in meinem Tipp "programmierte Bewegung" findet. Das Ship hat die public Eigenschaft fehler, welche bei einem Treffert mit einer Feind Instanz von der Feind- Instanz um 1 erhöht wird.
package { import flash.display.MovieClip; import flash.events.Event; import flash.events.KeyboardEvent; import flash.ui.Keyboard; public class Ship extends MovieClip { //hier wird gezählt, wie oft ein Feind, das Shiff berührt hat, siehe Klasse Feind public var fehler:int = 0; private var horMove:int = 0; private var horDir:int; private var speed:int = 10; public var upkeydown:Boolean = false; public var leftkeydown:Boolean = false; public var rightkeydown:Boolean = false; public var downkeydown:Boolean = false; public function Ship() { if (stage) { init(); } else { addEventListener(Event.ADDED_TO_STAGE,init); } } private function init(e:Event=null):void { removeEventListener(Event.ADDED_TO_STAGE, init); addEventListener(Event.REMOVED_FROM_STAGE, destroy); this.stage.addEventListener(KeyboardEvent.KEY_UP,tasteUp); this.stage.addEventListener(KeyboardEvent.KEY_DOWN, tasteDown); addEventListener(Event.ENTER_FRAME,bewegung); } public function tasteDown(event:KeyboardEvent):void { switch ( event.keyCode ) { case Keyboard.LEFT : leftkeydown = true; break; case Keyboard.RIGHT : rightkeydown = true; break; } } protected function tasteUp(event:KeyboardEvent):void { switch ( event.keyCode ) { case Keyboard.UP : upkeydown = false; break; case Keyboard.LEFT : leftkeydown = false; break; case Keyboard.RIGHT : rightkeydown = false; break; } } public function bewegung(evt:Event):void { if (leftkeydown) { horMove = speed; horDir = -1; } if (rightkeydown) { horMove = speed; horDir = 1; } if (horMove>0) { horMove -= .5; } this.x = this.x + horMove * horDir; } private function destroy(e:Event):void { removeEventListener(Event.REMOVED_FROM_STAGE, destroy); this.stage.removeEventListener(KeyboardEvent.KEY_UP,tasteUp); this.stage.removeEventListener(KeyboardEvent.KEY_DOWN, tasteDown); removeEventListener(Event.ENTER_FRAME,bewegung); } } }
Actionscript erstes Bild fla Datei
Auf der Bühne wurden händisch eingefügt:
dynamisches Textfeld: fehlerAnzeige
dynamisches Textfeld: zeitAnzeige
simpleButton: start_btn mit der gesamten Anzeige des Startbildschirms
In der Bibliothek befinden sich MCs mit dazugehörigen Klassendateien:
Bullet, Ship, Feind
getTimer() gibt die Zeit in Millisekunden zurück, die seit dem Start des Flashfilms verstrichen ist. Mehr dazu in meinen AS2 Tipps.
Interessant sind hier die beiden EventDispatcher, welche beide von der Klasse Feind stammen. Bei einem Treffer eines Feindes mit dem Ship wird das Textfeld fehlerAnzeige aktualisiert.
Wenn ein Feind sich selbst löscht, weil seine Punkte anzeige auf 0 ist, wird direkt vor dem Lösche der Event dispatched. Deswegen wird dort auch nicht abgefragt, ob die displayObjects von "feinde" = 0 sind, sondern ob sie =1 sind.
import flash.events.Event; import flash.events.MouseEvent; //Anzahl der feindlichen Schiffe var feindCount:uint = 10; //Feind- Instanzen kommen in Feinde var feinde:Sprite = new Sprite(); addChild(feinde); var ship:Ship = new Ship(); ship.y = stage.stageHeight - ship.height; ship.visible = false; addChild(ship); //spielzeit var gameTime:Number; //startzeit millisek var startTime:Number; //start_btn wurde händisch auf die Bühne gezogen start_btn.addEventListener(MouseEvent.CLICK, startGame); //-------------------------------; function startGame(evt:MouseEvent):void { startTime = getTimer(); trace(startTime); ship.visible = true; ship.x = stage.stageWidth / 2; start_btn.visible = false; ship.fehler = 0; fehlerAnzeige.text = ""; zeitAnzeige.text = ""; start_btn.removeEventListener(MouseEvent.CLICK, startGame); for (var i:uint=0; i < feindCount; i++) { var feind:Feind = new Feind( Math.random()*-50,20,ship); feind.x = Math.random() * 550; feind.y = Math.random() * 400 - 400; feinde.addChild(feind); feind.addEventListener(Feind.FEIND_HITSHIP, countFehler); feind.addEventListener(Feind.FEIND_DESTROY, feindKill); } this.stage.addEventListener(KeyboardEvent.KEY_DOWN, shoot); } //-------------------------------; function shoot(evt:KeyboardEvent):void { switch (evt.keyCode) { case Keyboard.SPACE : var bullet:Bullet = new Bullet(feinde); addChild(bullet); bullet.x = ship.x; bullet.y = ship.y - bullet.height; break; } } //------------------------------- function countFehler(evt:Event):void { //Textfeld fehlerAnzeige, händisch auf Bühne gezogen fehlerAnzeige.text = "Damage: " + String(ship.fehler); } //------------------------------- function feindKill(evt:Event):void { //wird abgefragt, kurz bevor feind entfernt wird killerAnzeige.text="Kills: "+String(feindCount - feinde.numChildren+1); if (feinde.numChildren == 1) { start_btn.visible = true; ship.visible = false; start_btn.addEventListener(MouseEvent.CLICK, startGame); this.stage.removeEventListener(KeyboardEvent.KEY_DOWN, shoot); gameTime = getTimer() - startTime; zeitAnzeige.text = "Spielzeit: "+ String(Math.ceil(gameTime/1000))+" Sek"; } }