Sound abspielen und dann nichts wie raus
So, nun sind wir fast am Ende des zweiten Levels angelangt. Im abschließenden Beitrag wollen wir jetzt noch das Programm etwas aufpolieren. Um es wirklich hübsch zu machen, fehlt uns zwar noch einiges an Wissen, aber mit zwei kleinen Änderungen möchte ich mal zeigen, wo der Weg hinführen kann. Wie in L.O.V.E. – Die Planung unter Punkt 7 erwähnt, sollten / müssen wir ganz zum Schluß das komplette Projekt nochmal überarbeiten, um ein professionelles Ergebnis zu erhalten.
Sound für die Schubdüsen
Auch wenn ich immer noch nicht auf die Soundprogrammierung eingegangen bin, laßt uns, wie beim Puzzle, zumindest für ein Geräusch sorgen. Es wäre doch schön, wenn die Düsen auch hörbar sind. Da Rauschen dafür nahezu perfekt geeignet ist, baut doch diese neue Funktion ins Programm ein, z. B. vor initFuel.
!zone soundThrust ;******************************************************************************* ;*** Ton für die Steuerdüsen ;******************************************************************************* ;*** Übergabe: - ;******************************************************************************* ;*** Rückgabe: - ;******************************************************************************* ;*** ändert : A, SR ;******************************************************************************* soundThrust lda #SID_WAVE_WHITENOISE ;Rauschen sta SID_VOICE1_WAVEFORM ;für die 1. Stimme lda #$05 ;niedrige Frequenz sta SID_VOICE1_FREQ_MSB ;für die erste Stimme lda #$FF sta SID_SUSTAIN_RELEASE ;Anschlag & Abschwellen sta SID_VOLUME ;Lautstärke rts ;zurück
Diese Routine entspricht eigentlich der, aus dem Puzzle. Wir setzen diesmal nur Sustain und Release, sowie die Lautstärke aufs Maximum. Der Rest bleibt unverändert. Jetzt müssen wir unseren Soundeffekt natürlich noch starten und beenden. Dies machen wir zunächst einfach in der Funktion checkInput.
Ändert das Programm dort ab .exit wie folgt:
.exit ;im X-Register stehen die sichtbaren stx VIC_SPRITEACTIVE ;Sprites, die wir jetzt wieder setzen txa ;aktive Sprites in den Akku and #%00001110 ;prüfen ob eine der Düsen aktiv ist beq .skip ;falls nicht Sound aus bei .skip jsr soundThrust ;sonst Sound aktivieren rts ;zurück .skip lda #$00 sta SID_VOLUME ;Lautstärke auf 0 setzen (Sound aus) rts ;zurück
Wir holen uns hier die aktiven Sprites in den Akku und prüfen, ob eine der Düsen „unter Feuer steht“. Ist das der Fall, dann springen wir nach soundThrust um den Soundeffekt zu starten. Falls keine Düse aktiv ist, setzen wir bei .skip die Lautstärke einfach auf 0. Da es sonst keine Effekte oder Musik gibt, können wir das einfach so machen.
Startet ihr das Spiel nun, dann vernehmt ihr hoffentlich ein wohliges Rauschen, sobald die Düsen sichtbar sind. Ein Problem fällt euch wahrscheinlich spätestens beim Ableben oder einer Landung auf. Der Sound bleibt bestehen! Daher sollten wir den, bei der Landung und beim „Gameover“ noch abschalten.
Fügt dazu direkt hinter ;sonst -> gelandet die Zeilen
lda #$00 sta SID_VOLUME ;Lautstärke auf 0 setzen
ein, um den Ton abzuschalten (wir setzen hier einfach wieder die Lautstärke auf 0).
Bei gameover werden bereits die Sprites abgeschaltet, fügt hinter sta VIC_SPRITEACTIVE ;Sprites abschalten noch die Zeile sta SID_VOLUME ;Lautstärke auf 0 setzen ein.
Jetzt endet unser Düsensound auch bei einer Landung oder falls wir scheitern.
Den Canyon verlassen
Da der Spieler in unserem nächsten Level – „Die Fahrt zur Basis“, eben genau dies machen muss, eben zur Basis zu fahren, schaffen wir noch eine passende Überleitung, indem wir nach der erfolgreichen Landung zeigen, wie das Fahrzeug den Canyon verläßt.
Also habe ich noch ein weiteres Spriteprojekt „Finished.spriteproject“ angelegt und eine Mini-Version des Space-Rovers als MultiColor-Sprite entworfen. Damit es nicht so aussieht, als sei der Space-Rover aus dem Landungsschiff gezaubert worden, habe ich noch ein Singlecolor-Sprite vorbereitet, dass die geöffnete Luke des Schiffes darstellen soll.
Um diese Sprites nun ins Programm einzubauen, müssen wir natürlich als erstes wieder den Assembler anweisen die neuen Sprites zu laden. Fügt dazu, ziemlich am Ende des Sourcecodes, hinter den bisherigen Sprite, die neuen Sprites ein.
;*** Die Sprites !align 63,0 spriteSpaceship !media "Ship.spriteproject",sprite,0,5 spriteFuel !media "Fuel.spriteproject",sprite,0,3 spriteFinished !media "Finished.spriteproject",sprite,0,2
Wir machen es uns nun auch wieder ganz einfach. Begebt euch zum gameloop und führt die folgenden Änderungen, direkt vor der Zeile lda #COLOR_LIGHTGREEN ;mit grün anzeigen, dass alles gut ist, durch.
lda #COLOR_BLACK ;da wir die Sprites überschreiben sta VIC_SPRITE1COLOR ;die neuen Farbe setzen lda #COLOR_GREY sta VIC_SPRITE0COLOR lda #COLOR_BLACK sta VIC_SPRITEMULTICOLOR0 lda #COLOR_WHITE sta VIC_SPRITEMULTICOLOR1
Außer dem Landungsschiff, sind alle Sprites deaktiviert. Für unsere Schlußsequenz benötigten wir noch neue Spritefarben. Daher setzen wir die Farben für den Space-Rover (dieser wird gleich Sprite-0) und für die offene Luke (wird Sprite-1). Solltet ihr euch jetzt fragen, warum wir die offene Luke nicht, wie die bisher geschlossene, als Sprite-0 und den Rover als 1 anlegen, dann schaut euch nochmal an wie die Sprites gezeichnet werden. Wir wollen es ja hübsch machen 😉 .
Jetzt müssen wir wieder die entsprechende Nummer des 64-Byte-Blocks für den VIC berechnen. Fügt also die folgenden Zeilen, hinter denen von eben ein.
ldx #spriteFinished/64 ;Adresse für den Rover stx SPRITEPOINTER0 inx ;die Luke befindet sich direkt dahinter stx SPRITEPOINTER0+1
Nun haben wir dem VIC gezeigt, wo die Spritedaten liegen, jetzt müssen wir die Sprites noch positionieren und einige Register setzen.
lda VIC_SPRITE4X ;den Rover positionieren clc adc #$0B sta VIC_SPRITE0X tax ;Position für Bewegung ins X-Reg lda #%00010011 sta VIC_SPRITEACTIVE ;Sprites aktivieren lda #%00000001 sta VIC_SPRITEMULTICOLOR ;der Rover ist Multicolor!
Da die Landeposition variieren kann, müssen wir hier die X-Position für den Rover zur Laufzeit berechnen. Der Rest ist soweit bekannt.
Nun lassen wir den Rover nach rechts aus dem Canyon fahren.
@loop: ldy #$30 ;etwas Zeit für die sty calc16bit: ;Bewegung des Rovers verschwenden dec calc16bit: bne *-3 dey bne *-9 inx ;X-Position erhöhen stx VICSPRITE0X ;und ins VIC-Register schreiben bne @loop: ;solange nicht 0 -> @loop: lda VICSPRITESMAXX ;sonst prüfen, ob für die X-Posi and #%00000001 ;bereits das höchste BIT gesetzt ist bne @skip1: ;falls ja sind wir fertig -> @skip: inc VICSPRITESMAXX ;sonst höchstes BIT setzen jmp @loop: ;und zum Beginn der Schleife springen @skip1: lda VICSPRITEACTIVE ;zur Sicherheit noch den Rover 'abschalten' and #%11111110 sta VICSPRITEACTIVE
Damit der Rover nicht zu schnell bewegt wird, bauen wir bei .loop eine Verzögerung ein (bei Verwendung einer Super-CPU werden solche Warteschleifen natürlich viel schneller durchlaufen). Wenn diese zwei verschachtelten Schleifen abgearbeitet sind, erhöhen wir das X-Register (dort steht immer noch die X-Position drin) und beachten dabei, dass wir auch das Bit für die maximale X-Position in $d010 setzen müssen. Dies machen wir, wenn das Bit noch nicht gesetzt wurde und die X-Position 0 ist. Wird die X-Position 0 und das höchste Bit ist bereits gesetzt, dann verlassen wir die Schleife durch einen Sprung nach .skip1. Dort schalten wir noch den Rover ab und das Programm läuft dann wie bisher (BS grün, auf Feuer warten) weiter.
Hinter FuelClear brauchen wir jetzt noch die Hilfsvariable für die obige Berechnung:
Calc16Bit !word 0
Führt ihr jetzt eine erfolgreiche Landung aus, dann könnt ihr beobachten, wie der Space-Rover den Canyon verläßt. Nachdem er den sichtbaren Bereich bereits verlassen hat, dauert es etwas, bis der Bildschirm endlich grün wird. Dass könnt ihr ja noch anpassen.
Ein letztes Problem wollen wir aber noch gemeinsam lösen. Da wir unseren IRQ gestoppt haben, verschwindet auch des Lasernetz. Dadurch sieht es während der Rover-Fahrt so aus, als sei der Canyon eine Höhle, weil der Himmel verschwindet.
Wir sollten nun also unseren Raster-IRQ bei einer Landung aktviert lassen, aber natürlich gleichzeitig dafür sorgen, dass man nicht wieder losfliegen kann.
Löscht erstmal bei gameloop die markierte Zeile mit dem sei-Befehl.
bmi gameloop ;falls ja, Hauptschleife sei ;IRQs sperren beq gameover ;falls 0 -> crash ;sonst -> gelandet
Damit bleibt der Interrupt weiterhin aktiv, egal ob das Schiff zerstört oder gelandet wurde.
Um nun zu verhindern, dass das Schiff weiterhin sinkt bzw. steuerbar ist, fügen wir noch zwei kleine Prüfungen in unseren Interrupt ein.
Bei landerIRQ begeben wir uns vor die Zeile jsr updateSprites ;Sprites positionieren und fügen die Prüfung ein.
lda Alive ;prüfen, ob das Schiff noch aktiv ist bpl *+5 ;falls nicht -> updatesprites: überspringen
Hier kontrollieren wir, ob Alive positiv ist. Denkt daran, wie wir die Variable verwenden: ;-1 = OK, 0 = zerstört, 1 = gelandet. Wir rufen updateSprites also nur auf, solange Alive den Wert -1 hat (nicht vergessen, 0 gilt auch als positiv!), anderenfalls wird der Funktionsaufruf übersprungen.
Der freie Fall ist somit gestoppt, nun müssen wir noch die Eingabe unterbinden. Sucht die Anweisung jsr checkInput ;Joystick verarbeiten und fügt davor nochmal die Prüfung für Alive von eben ein.
lda Alive ;prüfen, ob das Schiff noch aktiv ist bpl *+5 ;falls nicht -> updatesprites: überspringen
Um ein unschönes Flackern zu verhindern, löscht abschließend noch hinter waitForFire das setzen der Rahmenfarbe. Wir färben nun nur noch den Canyon-Hintergrund rot und grün ein.
Jetzt sieht es schon besser aus…
Falls ihr bis hierhin alles 1:1 übernommen habt, kommt es zu einem weiteren Problem:
Die Treibstoffanzeige sieht komisch aus.
Wir haben uns bisher überhaupt keine Gedanken darum gemacht, wo wir welche Daten im Speicher ablegen. Dies ist die Ursache für unser neues Problem und wird eins der ersten Themen beim nächsten Level. Verschiebt für den Moment, einfach die Daten (s. u.) ganz ans Programmende.
!zone Data ;******************************************************************************* ;*** Daten ;******************************************************************************* LaserFlash !byte $00 ;Rahmenfarbe fürs Laser-Raster Spaceship_PosX !byte $00 !byte SPACESHIP_STARTX ;X- und Spaceship_PosY !byte $00 !byte SPACESHIP_STARTY ;Y-Position des Landungsschiffs Spaceship_SpeedX !word $00 Spaceship_SpeedY !word $00 InputState !byte $00 ;letzte Joystickeingabe Alive ;Zustand des Landungsschiffes !byte $00 ;-1 = OK, 0 = zerstört, 1 = gelandet FuelCounter !byte $00 ;Zähler für Treibstoffverbrauch FuelClear !byte $00 ;nächstes Byte, das gelöscht werden soll Calc16Bit !word 0
Wie ihr seht, kann das Feintuning einiges an Zeit kosten und dies war nur ein kleiner Vorgeschmack, was man zur Verbesserung machen kann. Natürlich gibt es noch viel mehr: hübschere Grafiken (besonders der Canyon), beim Ableben eine Explosion anzeigen und einen passenden Sound-Effekt abspielen, evtl. etwas Musik, eine Punkteanzeige, usw.
Schritt 19 - Landung 009
Wir schließen die Landung nun aber ab und wenden uns dem 3. Level „Die Fahrt zur Basis“ zu. Dort wollen wir endlich etwas entwickeln, dass man auch mal vorzeigen kann.
Bis dahin, gutes Gelingen,
Jörn