The Eagle has landed
Nun ist es an der Zeit, dass wir auf eine Kollision reagieren und natürlich ebenfalls prüfen, ob das Landungsschiff sicher aufgesetzt hat. Basis ist (wie jedesmal) der Download aus dem letzten Beitrag Landung 006.
Eine Kollision erkennen
Um festzustellen, ob es zu einer Kollision mit dem Hintergrund gekommen ist, prüfen wir einfach das VIC-II Register 31 $d01f / VIC_SPRITEBACKGROUNDCOLL. Auf einen extra Interrupt (der ja durchaus möglich ist) verzichten wir. Schreiben wir also erstmal eine kleine Routine, die das Register prüft. Fügt diese am besten direkt hinter dem jmp gameloop ein.
!zone checkCollision ;******************************************************************************* ;*** Sprites-Hintergrundkollision (inkl. perfekter Landung) prüfen ;******************************************************************************* ;*** Übergabe: - ;******************************************************************************* ;*** Rückgabe: Bei Kollision Alive auf 0, bei perfekter Landung auf 1 setzen ;******************************************************************************* ;*** ändert : A, SR ;******************************************************************************* checkCollision lda VIC_SPRITEBACKGROUNDCOLL ;Kollision mit dem Hintergrund? and #%00010001 ;nur das Schiff kontrollieren beq .exit ;falls nicht, weiter -> .exit lda #$00 ;crash mit #$00 sta Alive ;in Alive anzeigen .exit rts ;zurück zum Aufrufer
Wie ihr an der Einleitung erkennen könnt, soll die Routine Kollisionen und die korrekte Landung (folgt weiter unten) erkennen. Die neue Variable Alive wird nun verwendet, um den Status des Landungsschiffes anzuzeigen. Alive erhält den Wert -1 / $ff, solange das Raumschiff unbeschädigt ist. Bei einer Kollision wechselt Alive auf 0, um die Zerstörung anzuzeigen. Ein perfekte Landung wird mit einer 1 gekennzeichnet.
Die Funktion holt sich einfach den Inhalt des VIC-Registers 31 in den Akku. Da die Düsen nicht geprüft werden müssen, maskieren wir einfach alles aus, sodass die beiden Hauptsprites (0 & 4) des Landungsschiffes übrig bleiben. Anschließend prüfen wir, ob eine Kollision stattgefunden hat (dann ist der Akku ungleich 0). Gab es keine Kollision, wird die Routine einfach verlassen, anderenfalls erhält Alive, wie eben erwähnt, den Wert 0, bevor die Funktion endet.
Jetzt brauchen wir natürlich noch die neue Variable. Fügt diese am besten hinter der Variablen InputState ein.
Alive ;Zustand des Landungsschiffes !byte $00 ;-1 = OK, 0 = zerstört, 1 = gelandet
Außerdem sollten wir nicht vergessen den Wert korrekt zu initialisieren. Sucht dazu die Routine initLander und fügt direkt an den Anfang diese zwei Zeilen hinzu:
lda #$ff ;Alive auf -1 / $ff setzen sta Alive
Wir müssen natürlich noch auf eine Kollision reagieren, dazu erweitern wir einfach unsere Hauptschleife gameloop. Ersetzt nun die Anweisung jmp gameloop mit diesem Block.
gameloop jsr checkCollision ;zur Kollisionserkennung lda Alive ;Landungsschiff noch 'am Leben'? (-1) bmi gameloop ;falls ja, Hauptschleife sei ;IRQs sperren beq gameover ;falls 0 -> crash ;sonst -> gelandet lda #COLOR_LIGHTGREEN ;mit grün anzeigen, dass alles gut ist jmp waitForFire ;auf Feuer warten gameover lda #$00 sta VIC_SPRITEACTIVE ;Sprites abschalten lda #COLOR_RED ;crash = rot waitForFire sta VIC_BACKGROUNDCOLOR sta VIC_BORDERCOLOR jsr joystickInput ;ggf. warten and #JOY_FIRE beq *-5 ;bis der Feuerknopf losgelassen wurde jsr joystickInput ;warten and #JOY_FIRE bne *-5 ;bis Feuer gedrückt wird jsr joystickInput ;und nochmal warten and #JOY_FIRE beq *-5 ;bis der Feuerknopf losgelassen wird jmp main ;dann -> Neustart
Hinter gameloop springen wir zu unserer neuen Routione checkCollision und holen danach Alive in den Akku. Ist Alive negativ, dann springen wir einfach wieder zu gameloop. Dies geschieht solange, bis sich der Wert von Alive ändert. Sobald dieser nicht mehr negativ ist, werden erstmal die Interrupts gesperrt, anderenfalls könnte sich das Schiff noch bewegen. Es wird dann geprüft, ob der Wert 0 ist (also eine Kollision stattgefunden hat). Bei einem Zusammenstoß geht es beim gameover weiter. Dort deaktivieren wir alle Sprites, laden den Akku mit der Farbe rot und warten darauf, dass der Feuerknopf gedrückt wird.
Bei einer Landung wird nur grün in den Akku geholt und dann zu waitForFire gesprungen.
Bei waitForFire setzen wir Rahmen- und Hintergrundfarbe auf den Wert aus dem Akku. Danach benutzen wir drei Prüfungen für den Feuerknopf. Die erste wartet, bis der evtl. noch gedrückte Knopf losgelassen wird. Wir könnten ja mit aktivem Schub irgendwo reingedonnert sein. Ist der Knopf nicht mehr gedrückt, dann warten wir, bis er gedrückt wird. Würden wir jetzt sofort unser Spiel neustarten käme es gleich wieder zum Unglück, das Landungsschiff würde sofort in den Laser fliegen. Daher warten wir (bei der dritten Abfrage) mit dem Neustart, bis der Feuerknopf wieder losgelassen wird. Dann geht es weiter zu Label main.
Startet ihr das Programm, dann kann es sein, dass sofort eine Kollision angezeigt wird. Spätestens bei einem Neustart, wird es euch passieren, dass ihr zweimal Feuer drücken müsst, um neuzustarten.
Wir haben hier vergessen, das Kollisionsregister zu löschen. Durch das Initialisieren der Sprites wurde versehentlich eine Kollision ausgelöst. Also noch mal die Funktion initLander aufsuchen und am Ende (vor dem rts) das Register einmal lesen, damit es gelöscht wird:
lda VIC_SPRITEBACKGROUNDCOLL ;Kollisionsregister zur Sicherheit löschen
Jetzt sollte das Programm korrekt starten bzw. neustarten.
Vom Laser verbrutzelt
Wer direk mal Vollgas gibt, wird feststellen, dass der Laser uns immer noch nicht aufhält. Der Grund sollte jedem klar sein, er ist kein Teil des Hintergrundes und wird somit von unserer Prüfung bisher nicht erkannt. Lasst uns in der Routine updateSprites prüfen, ob das Schiff den Laser berührt. Dazu begeben wir uns ans Ende und fügen vor die Zeile sta VIC_SPRITE0Y ;für alle fünf Sprites die folgende kleine Prüfung ein.
cmp #RASTERIRQ_LASER+4 ;prüfen, ob der Laser berührt wird bcs .skip ;falls nicht weiter -> .skip ldx #$00 ;sonst Alive auf 0 stx Alive ;setzen .skip
Durch die vorherigen Anweisungen befindet sich im Akku die aktuelle Y-Position der Sprites. Diese vergleichen wir mit der letzen Laser-Zeile. Findet keine Berührung statt, springt das Programm einfach zu .skip, sonst wird Alive wieder auf 0 gesetzt, um die Zerstörung zu kennzeichnen, bevor es bei automatisch bei .skip weitergeht.
Schon werdet ihr feststellen, dass das Programm jetzt auch endet, falls ihr den Laserstrahl berührt.
Aufgesetzt
Ich hoffe ihr habt nicht versucht das Schiff wirklich zu landen. Denn bisher ist das unmöglich. Egal wie sanft ihr in der grünen Landezone aufsetzt, das Schiff wird zerstört. Auch hierfür fehlt natürlich noch unsere Prüfung!
Um die Landung zu erkennen müssen wir nur die neue Funktion checkCollision noch etwas erweitern. Auch bei einer Landung lassen wir zunächst eine Kollision auftreten, allerdings prüfen wir dann über absolute Werte, ob der Landebereich getroffen wurde. Dazu müssen wir ermitteln welche X- & Y-Werte für den Landebereich zulässig sind. Befindet sich das Schiff innerhalb dieses Fensters, dann ist eine perfekte Landung wahrscheinlich, sonst haben wir das Ziel verfehlt und das Landungsschiff wird wiedermal zerstört. Dieses Vorgehen ist natürlich nicht sehr flexibel, aber da wir nur einen festen Bereich haben, finde ich das hier durchaus OK.
Um dies mal zu verdeutlichen, werft einen Blick auf die obige Grafik. Dort seht ihr zur Veranschaulichung ausnahmsweise zwei Sprites. Diese zeigen die äußerst linke und rechte Landeposition an. Die weißen Punkte markieren die obere / linke Ecke des Sprites, dies ist unsere X- & Y-Position. Bilden wir nun ein Rechteck nach unten mit diesen Eckpunkten, dann erhalten wir die sichere Landezone. Außerdem bemerkt ihr bestimmt, dass das Sprite sich bereits im grünen Landebereich befindet. Wir erhalten also eine Kollision. Nur dann müssen wir prüfen, ob sich das Sprite (Y-Position) unter der roten Linie befindet. Ist dies der Fall, dann prüfen wir ob sich die X-Position innerhalb des gedachten Rechteckes befindet. Dabei muss natürlich beachtet werden, dass links an der Landezone eine weitere Kollisionsmöglichkeit besteht. Treffen alle Prüfungen zu, dann wurde die Landezone getroffen, sonst zerschellt das Schiff.
Übernehmt die nächsten Zeilen in checkCollision, hinter der Anweisung beq .exit ;falls nicht, weiter -> .exit.
;*** perfekte Landung? lda VIC_SPRITE0Y cmp #$dd ;Y-Position größer 220? bcc .hit lda VIC_SPRITE0X cmp #$bc ;X-Position größer/gleich 188? bcc .hit cmp #$dd ;X-Position kleiner 220? bcs .hit lda Spaceship_SpeedY+1 ;passt die Sinkgeschwindigkeit bne .hit lda Spaceship_SpeedX+1 ;passt die links/rechts Geschwindigkeit bne .hit lda #$01 ;Landung hat geklappt, $01 sta Alive ;nach Alive schreiben jmp .exit ;und raus .hit
Wir vergleichen hier die X- & Y-Position des Schiffes, ob diese innerhalb der Landezone liegen. Dabei beachten wir natürlich, dass die Position immer die linke obere Ecke des Sprites angibt. Die Werte wurden also entsprechend der Spritegröße angepasst. Sind wir in der Landezone, dann wird noch geschaut, ob die Sink- und links/rechts-Geschwindigkeit niedrig genug war. Wir wollen schließlich, dass das Schiff sanft gelandet wird. Um dies zu prüfen, schauen wir uns die aktuelle X- & Y-Geschwindigkeit an. Ist der „Vorkommateil“ der beiden Werten 0, dann gehen wir von einer korrekten Landung aus.
Stimmen schließlich alle Vorgaben, wird eine 1 nach Alive geschrieben, um die perfekte Landung zu kennzeichnen. Weicht auch nur ein Wert ab, wird das Schiff, wie bei jeder Kollision, zerstört.
Jetzt könnt ihr euch an einen Testlauf wagen und versuchen, dass Schiff im Canyon sicher ans Ziel zubringen.
Wir können uns noch um zwei Kleinigkeiten kümmern. Einmal steht das Schiff bei der Landung etwas zu niedrig (wir haben ja erst bei einer Kollision reagiert). Außerdem könnte es passieren, dass trotz Landung eine der Düsen sichtbar bleibt. Korrigieren wir also die Spriteposition und schalten zur Sicherheit die Düsen ab. Das machen wir im gameloop direkt hinter dem Kommentar ;sonst -> gelandet mit diesen Anweisungen:
lda #$dd ;das Landungsschiff passend platzieren sta VIC_SPRITE0Y sta VIC_SPRITE4Y lda #%00010001 ;Düsen abschalten sta VIC_SPRITEACTIVE
Langsam nähert sich der Level seinem Ende. Wir wollen aber noch dafür sorgen, dass der Spieler sich nicht unnötig lange mit der Landung aufhält und werden als nächstes noch den Spritverbrauch einbauen.
Schritt 17 - Landung 007