Dieses Tutorial ist die Übersetzung von Ice Guys Tutorial von smwc, mit ein zwei zusätzlichen Anmerkungen, welche ich noch hinzugefügt habe.
Vorweg, meine Kenntnisse in ASM sind um es mal noch schön auszudrücken nicht gut. Ich lerne das programmieren von Sprites auch mit diesem Tutorial, während ich es übersetze.
Wieso schreibe ich dann ein Sprite ASM Tutorial?
Ich hab fast keine Ahnung davon, dass stimmt, aber mir hätte so ein Tutorial sehr geholfen und deshalb, mache ich das jetzt einfach. Ich übersetzte ja auch nur und füge Anmerkungen von anderen hinzu. Da kann ich hoffentlich nicht so viel falsch machen
Solltet ihr allerdings Fragen haben, würde ich mich nicht an mich wenden, sondern an jemanden der ASM kann und wenn ihr hier drin Fehler entdeckt sagt es, oder behebt sie. Wahrscheinlich darf ich dann selbst irgendwann hierzu Fragen stellen
DIESE ÜBERSETZUNG KANN GELÖSCHT WERDEN, SOBALD EIN BESSERES ASM TUTORIAL EXISTIERT.
Was brauche ich für dieses Tutorial:
1. Kenntnisse:
Du hast RPG-Hackers Tutorial über ASM gelesen und auch verstanden
Du kannst Sprites mit Sprite Tool einfügen
2. Tools:
Spritetool
YY-Chr
Texteditor, mit welchem du ASM Dateien erstellen kannst
3. Hilfreiche Dokumente:
Die smwc RAM Map
4. Besondere Eigenschaften:
Den Wille zu lernen
Du brauchst Zeit
Kapitel 1: Ein Generator
Bevor wir anfangen einen „richtigen“ Sprite zu coden wäre es das Beste, wenn wir mit einem Generator anfangen würden. Als erstes sollten wir wissen, wie ein Generator aufgebaut ist und wie einer funktioniert. Im Grunde ist ein Generator ein Spritetype, welcher durch Spritetool in den Werten D0 bis DF eingesetzt wird. Was immer wir für einen Generator coden, es wird IMMER dann aktiviert, wenn Mario in den Screen kommt, in welchem der Generator platziert wurde. Generatoren haben keinerlei Grafiken und können sich weder bewegen, noch Mario angreifen, wie ein „richtiger“ Sprite.
Generatoren und „richtige“ Sprites arbeiten von Grund auf anders.
Der Code eines Generators wird außerdem jeden Frame ausgeführt.
Am Ende dieses Kapitels wirst du einen Generator erstellen können, welcher das Spiel zum abstürzen bringt. Nein Spaß, du wirst einen Generator erstellen können, welcher Mario eine Feuerblume gibt, wenn Mario 50 Münzen und mehr hat und außerdem mindestens 3 Leben. Sollte eine dieser Bedingungen nicht erfüllt sein, wird Mario verletzt. (verliert sein PowerUp, oder stirbt)
Alle Typen von Sprites, egal ob Sprite, Generator, oder Shooter, haben 2 Hauptbestandteile in ihrem Code
The Initialisation Routine ( INIT ):
Die INIT Routine in einem Sprite ist der Teil des Codes, welcher nur einmal ausgeführt wird. Außerdem steht die INIT Routine immer vor dem eigentlichen Hauptteil (MAIN), des Codes.
Das „initial“ bedeutet, dass der Code einmal ausgeführt wird, sobald der Sprite auf den Bildschirm zu sehen ist, bzw. sobald der Sprite existiert. Danach wird der Code nie wieder ausgeführt.
Jetzt könnten wir uns fragen, für was dass den nützlich ist. Es gibt auf jeden Fall viele Möglichkeiten. Wir könnten sagen, dass der Sprite immer in die Richtung von Mario laufen soll, ein Boss 10 HP haben soll, oder wir könnten auch alle RAM Adressen clearen, welche wir später benutzen wollen.
The Main Routine ( MAIN ):
In die MAIN Routine schreiben wir unseren Hauptteil (main Teil) des Spritecodes. Ist ja eigentlich vom Name her schon klar. Die MAIN Routine wird jeden frame ausgeführt, solange der Sprite im Bildschirm ist. In der MAIN Routine stehen z.B: die Grafikinformationen, die Bewegungsabläufe, etc.
INIT und MAIN sind die Grundlagen jedes Sprites.
Um die INIT Routine einzuleiten schreiben wir folgendes: (Dies ist die Information für Spritetool die INIT Routine zu starten.)
dcb “INIT“
Um die MAIN Routine einzuleiten schreiben wir folgendes: (Dies ist die Information für Spritetool die MAIN Routine zu starten.)
dcb “MAIN“
Machen wir also einen Basis Sprite, indem wir dafür sorgen, dass die beiden Routine nichts machen. Wir gehen einfach mit RTL wieder zurück.
dcb “INIT“
RTL
dcb “MAIN“
RTL
Was haben wir gemacht?
Wir haben die INIT und die MAIN Routine gestartet und beide mit RTL beendet/zurückgeschickt. Technisch gesehen ist das ganze hier schon ein richtiger Sprite. Nur bisher einfach ohne eine Funktion.
Das einzig wichtige was wir hier beachten sollten ist, dass wir RTL verwenden und nicht RTS. RTS würde unsere Spiel zerstören.
Kommen wir also zurück zu unserem Generator. Ein Generator ist ja wie oben bereits erklärt, eine Ausnahme bei den Sprites. Deswegen hat ein Generator auch keine INIT Routine. Er fängt aber trotzdem, wie oben gezeigt auch mit dcb “INIT“ an, nur wird diese, auch wie oben, sofort mit einem RTL beendet.
Schreiben wir nun also doch mal etwas Code für die MAIN Routine des Generators:
Fangen wir damit an, einen Code zu schreiben, der Marios Münzen auf 0 setzt.
dcb “INIT“
RTL
dcb “MAIN“
STZ $0DBF ; setzt Mario Münzen auf 0 (siehe RAM Map)
RTL
Natürlich können wir auch wesentlich kompliziertere Codes verwenden und erstellen. Uns sind quasi keine Grenzen gesetzt.
Eine wichtige Sache sollte man bei Generatoren aber beachten!
Ein Generator wird so lange ausgeführt, bis wir ihn mit einem anderen Generator beenden, oder das Level verlassen.
Würden wir unseren Generator also z.B. in Screen 05 platzieren, so würde der Münzcounter immer bei 00 stehen bleiben, wenn wir in den Screens danach Münzen sammeln.
Hier noch mal ein etwas komplexerer Generator:
dcb “INIT“
RTL
dcb “MAIN“
LDA $0DBF ; Lade Marios Münzen
CMP #10 ; Hat Mario mehr als 10 Münzen?
BCC Return ; Nein? Springe zum Label Return
LDA #$02 ; Ja? Lade eine Feder
STA $19 ; Speicher die Feder als Marios PowerUp
Return: ; das Label Return
RTL
Hiermit sollten wir auch schon am Ende sein. Zumindest am Ende von dem Teil, bei welchem es um Generatoren geht. Natürlich kannst du deine Codes so lange machen wie du willst. Die Hauptsache ist, dass sie am Ende funktionieren.
Kapitel 2: die .CFG Datei
Wir haben jetzt unseren Code für den Generator in eine .asm Datei verwandelt. Wir können den Generator im Spiel aber nicht testen. Dafür brauchen wir noch die .cfg Datei.
Bei einem Generator beinhaltet die .cfg Datei wesentlich weniger und auch andere Informationen als bei einem „richtigen“ Sprite wie du nachher feststellen wirst.
Um eine .cfg Datei für einen Generator zu erhalten empfiehlt es sich einfach in den Ordner generators von Sprite Tool zu gehen und dort einfach die .cfg Datei von einem Generator zu kopieren. Der .cfg Datei bennen wir noch in den gleichen Namen wie unsere .asm Datei um.
Wir haben nun diese 2 Dateien:
Beispiel.asm ← die Datei mit der MAIN Routine
Beispiel.cfg ← die Datei, welche mit Sprite Tool eingefügt wird
Natürlich können wir die Dateien auch anders benennen. Es muss nicht Beispiel sein.
Da wir die .cfg Datei nur kopiert haben stimmt diese natürlich noch nicht mit der .asm Datei überein. Dafür öffnen wir die .cfg Datei nun mit einem normalen Texteditor. Bei mir sieht das ganze dann so aus:
03
FF
FF FF FF FF FF FF
FF FF
generic.asm
Es sollte bei egal welcher .cfg Datei aus dem Bereich Generatoren von Sprite Tool so aussehen. Nur die letzte Zeile ist anders. Die letzte Zeile ist immer der Name der .asm Datei! Diese müssen wir also umbenennen. Da wir in unserem Beispiel ja mit der Beispiel.asm Datei gearbeitet haben müssen wir die kopierte .cfg Datei in folgendes umändern:
03
FF
FF FF FF FF FF FF
FF FF
Beispiel.asm
Nun speichern wir die .cfg Datei und können unseren Generator wie gewohnt mit Sprite Tool einfügen. Glückwunsch!
Es können allgemein bei langen ASM Dateien Fehler entstehen. Da dies früher oder später immer passiert, schauen wir uns ein System vom Finden von Fehlern noch am Ende dieses Kapitels an.
Wir werden es hier noch nicht brauchen, da unsere Codes alle noch sehr kurz und damit auch sehr übersichtlich sind, aber je länger sie werden, desto leichter passieren Fehler und desto schwieriger wird es sie zu finden. Deswegen hier jetzt einmal ein kurze Erklärung anhand unseres Generators von oben.
Hier noch mal der Code zur Erinnerung:
dcb “INIT“
RTL
dcb “MAIN“
LDA $0DBF
CMP #10
BCC Return
LDA #$02
STA $19
Return:
RTL
Tauschen wir doch einmal das BCC Return mit einem BCC Return2 aus. Wie wir wissen ist dies natürlich dann später ein Fehler und der Code funktioniert nicht mehr. Würden wir den Generator mit Spritetool einfügen, so würden wir folgende Fehlermeldung erhalten:
Error detecting in assembling .\generators\Beispiel.asm...
Wüssten wir jetzt nicht, wo wir den Fehler suchen müssten ist die einfachste Möglichkeit der Fehlerbehebung das Programm TRASM.exe. (Wird bei Spritetool mitgeliefert)
Dieses gibt uns die Zeilennummer an, in welcher wir den Fehler suchen müssen. Dies macht es uns wesentlich leichter den Fehler zu finden und zu beheben. (bei so kurzen Codes noch nicht)
Um das Programm auszuführen (muss an gleicher Stelle wie der Hack und Spritetool sein) müssen wir einfach unseren Code in das Programm ziehen.
Es sind nun 3 neue Dateien entstanden. Eine .bin Datei, eine .smc Datei und eine .err Datei.
Die .smc und die .bin Datei interessieren uns einfach nicht, wichtig ist für uns zum Fehler finden nur die .brr Datei. Wenn wir diese Datei nun öffnen (mit einem simplen Texteditor), so erhalten wir ungefähr folgende Meldung (in unserem Beispiel):
Error in 8.1/7: Symbol does not exist
Die Zahl hinter dem / gibt die Zeilennummer an, in welcher sich der Fehler befindet. Hier also in Zeile 7. Gehen wir nun zurück zu unserem Generator und öffnen die ASM Datei, können wir ganz einfach zu Zeile 7 springen (entweder mit der Funktion gehe zu oder strg + g) und dort dann den Fehler beheben. Hier wissen wir natürlich sofort um was für einen Fehler es sich handelt, schließlich haben wir ihn vor kurzem selbst und das auch noch bewusst eingebaut, aber sollte dies mal nicht der Fall sein so hilft uns wieder die Fehlermeldung weiter.
In unserem Fall wurde uns noch gesagt, dass das „Symbol“ nicht existiert. Genauer gesagt bedeutet das, dass wir nach einem Branch-Befehl ein Name angegeben haben, zu welchem gesprungen werden soll, wobei der Name aber überhaupt nicht in der Datei existiert.
Mehr möchte ich hier auch nicht weiter auf die Fehlermeldungen eingehen, da sie eigentlich selbst erklärend sind.
Alles was wir gerade bei der Fehlerbehebung für Generatoren gemacht haben funktioniert auch für jeden anderen Sprite-Type!
Es gibt noch 2 Dinge, welche man sich immer bewusst sein sollte wenn man mit TRASM.exe arbeitet.
1. TRASM.exe unterscheidet nicht zwischen Groß- und Kleinschreibung, Xkas tu dies.
2. Bevor man eine ASM Datei über TRASM.exe zieht, sollte man die davor von TRASM.exe entstandenen Dateien löschen. (alle)
Mehr gibt es zu Generatoren an sich nicht zu wissen. Hier hilft nun nur noch experimentieren weiter. Also beenden wir den ersten Teil des Tutorials und widmen wir uns dem zweiten. Einem einfachen statischen Sprite.
Kapitel 3: Grafiken eines einfachen statischen 16*16 Sprite
Bevor du jetzt denkst wir beginnen mit dem epischsten custom Boss den es gibt, lass mich deine Erwartungen weit zurück schrauben. Am Ende dieses Kapitels werden wir einen Sprite haben, welcher einen blauen Pilz darstellt, aber bisher noch nichts machen kann. Du siehst, wir beginnen ganz unten, beim Anzeigen von Grafiken.
Wie die Generatoren beinhalten „normale“ Sprites auch die INIT und die MAIN Routine. Im Unterschied zu Generatoren haben wir nun aber die Möglichkeit auch Code in die INIT Routine zu schreiben. Hier in diesem Teil werden wir es noch nicht machen, aber im nächsten.
Bevor wir nun anfangen noch eine Wichtige Sache!
Wenn wir Code für die INIT oder die MAIN Routine eines Sprites schreiben wird immer das x-Register benutzt. Dieses ist für verschiedene Spritetabellen zuständig. (z.B. x- und y-Speed, die Richtung, etc.) Es können maximal 12 Sprites auf dem Screen ausgeführt werden und wir benutzen das x-Register um zu bestimmen welcher Sprite welcher ist.
Das x-Register beinhaltet die Adresse $15E9 – Der Sprite, der gerade ausgeführt wird.
Was ist das x-Register:
In RPG-Hackers Tutorial wurde bisher nur mit dem Accumulator (A) gearbeitet. Es gibt aber noch 2 weitere Register, nämlich das x- und das y-Register. Mit Registern kann man vorallem Tabellen anlegen. Klären wir hier doch einmal 2 Fragen, welche ich mir zu diesem Thema gestellt habe.
Frage:
Ich brauche das x- und das y-Register wenn ich A nicht benutzen kann, aber für was genau und wann kann ich A nicht benutzen?
Antwort, die mir WYE gegeben hat:
Mit A funktioniert dies nicht.
Für unseren Sprite bedeutet dass jetzt also,dass immer wenn wir das x-Register benutzen zuerst PHX öffnen müssen und das ganze dann in PLX wieder speichern müssen, nachdem wir die Routine beendet haben. Sobald wir eine Tabelle für unsere Sprites benutzen wollen muss davor also das x-Register „geöffnet“ (PHX) worden sein. Ansonsten kann es zu unschönen Dingen kommen.
PHX, PLX, was, ich verstehe nur Bahnhof!
An dieser Stelle stand ich genau an diesem Punkt und WYE konnte mir mal wieder weiterhelfen.
Frage:
Was ist und was macht PHA, PHX, PHY, PLA, PLX, PLY, TAX, TAY, TXY, TYX, TYA, TXA?
Antwort, die mir WYE gegeben hat:
Was erstere angeht, da musst du über den Stack Bescheid wissen. Das ist ein Bereich im RAM, in dem Werte zwischengespeichert werden. Stell ihn dir als Bücherstapel vor: Wenn du etwas auf den Stack legst, platzierst du den neuen Wert immer an oberster Stelle, und wenn du einen Wert herausholst, nimmst du immer nur den, der ganz oben liegt.
PHA ("Push A") speichert einen Wert auf den Stack, PLA ("Pull A") holt ihn wieder zurück. Beispiel:
LDA #$02 ; A ist 02
PHA ; Wert von A auf den Stack speichern
LDA #$FF ; A ist FF
PLA ; Wert vom Stack zurückholen und in A speichern, A ist wieder 02
RTS
Natürlich können auch mehrere Werte zur selben Zeit auf dem Stack abgelegt sein:
LDA #$02 ; A ist 02
PHA ; Wert von A auf den Stack speichern
LDA #$03 ; A ist 03
PHA ; Wert von A auf den Stack speichern
LDA #$04 ; A ist 04
PLA ; Obersten Wert vom Stack in A speichern, A ist 03
PLA ; Obersten Wert vom Stack in A speichern, A ist 02
RTS
Nett ist auch, dass du von einem Register in ein anderes pushen und pullen kannst:
(hier in diesem Beispiel wäre TAX einfacher)
LDA #$05 ; A ist 05
LDX #$09 ; X ist 09
PHA ; Wert von A auf den Stack speichern
PLX ; Wert vom Stack zurückholen und in X speichern, X ist 05
RTS
Wichtig ist nur bei der ganzen Sache nur, dass am Ende genauso oft gepullt wie gepusht wurde!
Ok, nachdem wir nun unsere Köpfe mit vielen neuen Befehlen erschlagen haben die gute Nachricht, wir brauchen dass ganze jetzt am Anfang noch nicht wirklich, wir sollten sie nur im Hinterkopf behalten. Beginnen wir einfach wieder mit ein bisschen Basis Zeugs:
dcb “INIT“
RTL
dcb “MAIN“
RTL
In der Main Routine brauchen wir eine Routine, welche die Grafiken für unseren Sprite bereitstellt. Um das ganze einfach zu vereinfachen benutzten wir einen Sprungbefehl, JSR (Jump to SubRoutine), und verlassen so die Main Routine und springen zu einer anderen. Das ganze sieht dann so aus:
dcb “INIT“
RTL
dcb “MAIN“
JSR Graphics ; Wir springen zum Label Graphics (auf Englisch da Standart)
RTL
Graphics: ; Hier ist unser Graphics Label, noch ohne Grafiken
Das ganze ist jetzt nicht unbedingt kompliziert zu verstehen, oder?
Die Grafiken zeichnen:
Das erste was wir hierfür tun müssen, ist eine Routine zu starten die den so genanten Spriteindex ($15E9) in die OAM schiebt. Die OAM ist einfach ausgedrückt der Ort, an welchen viel Platzt für Spritetiles ist. Um dies zu bewerkstelligen brauchen wir einen weiteren Sprungbefehl zu einer anderen Routine. Bekannt unter dem Namen: GET_DRAW_INFO.
Das ganze sollte nun so aussehen:
dcb “INIT“
RTL
dcb “MAIN“
JSR Graphics ; Wir springen zum Label Graphics (auf Englisch da Standart)
RTL
Graphics: ; Hier ist unser Graphics Label, noch ohne Grafiken
JSR GET_DRAW_INFO
Es lohnt sich nun wirklich nicht die GET_DRAW_INFO Routine genau zu erklären, deswegen lasse ich sie auch Englisch. Ich bin zwar nicht der größte Englischfreund, aber ein bisschen Englisch sollte man drauf haben, sonst kriegt man früher oder später Probleme. Wichtig ist nur, dass die folgende Routine in fast jedem Sprite vorkommt (also einfach schön copy paste) und elementar ist. Mann muss sie aber meiner Meinung nach nicht unbedingt verstehen. Zumindest noch nicht. Das ganze sollte dann jetzt so sein: (das neu hinzugekommene ist die GET_DRA_INFO Routine)
dcb “INIT“
RTL
dcb “MAIN“
JSR Graphics ; Wir springen zum Label Graphics (auf Englisch da Standart)
RTL
Graphics: ; Hier ist unser Graphics Label
JSR GET_DRAW_INFO
RTS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; GET_DRAW_INFO
; This is a helper for the graphics routine. It sets off screen flags, and sets up
; variables. It will return with the following:
;
; Y = index to sprite OAM ($300)
; $00 = sprite x position relative to screen boarder
; $01 = sprite y position relative to screen boarder
;
; It is adapted from the subroutine at $03B760
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
SPR_T1 dcb $0C,$1C
SPR_T2 dcb $01,$02
GET_DRAW_INFO STZ $186C,x ; reset sprite offscreen flag, vertical
STZ $15A0,x ; reset sprite offscreen flag, horizontal
LDA $E4,x ; \
CMP $1A ; | set horizontal offscreen if necessary
LDA $14E0,x ; |
SBC $1B ; |
BEQ ON_SCREEN_X ; |
INC $15A0,x ; /
ON_SCREEN_X LDA $14E0,x ; \
XBA ; |
LDA $E4,x ; |
REP #$20 ; |
SEC ; |
SBC $1A ; | mark sprite invalid if far enough off screen
CLC ; |
ADC.W #$0040 ; |
CMP.W #$0180 ; |
SEP #$20 ; |
ROL A ; |
AND #$01 ; |
STA $15C4,x ; /
BNE INVALID ;
LDY #$00 ; \ set up loop:
LDA $1662,x ; |
AND #$20 ; | if not smushed (1662 & 0x20), go through loop twice
BEQ ON_SCREEN_LOOP ; | else, go through loop once
INY ; /
ON_SCREEN_LOOP LDA $D8,x ; \
CLC ; | set vertical offscreen if necessary
ADC SPR_T1,y ; |
PHP ; |
CMP $1C ; | (vert screen boundry)
ROL $00 ; |
PLP ; |
LDA $14D4,x ; |
ADC #$00 ; |
LSR $00 ; |
SBC $1D ; |
BEQ ON_SCREEN_Y ; |
LDA $186C,x ; | (vert offscreen)
ORA SPR_T2,y ; |
STA $186C,x ; |
ON_SCREEN_Y DEY ; |
BPL ON_SCREEN_LOOP ; /
LDY $15EA,x ; get offset to sprite OAM
LDA $E4,x ; \
SEC ; |
SBC $1A ; | $00 = sprite x position relative to screen boarder
STA $00 ; /
LDA $D8,x ; \
SEC ; |
SBC $1C ; | $01 = sprite y position relative to screen boarder
STA $01 ; /
RTS ; return
INVALID PLA ; \ return from *main gfx routine* subroutine...
PLA ; | ...(not just this subroutine)
RTS ; /
Ein ganz schöner Batzen, oder? Jetzt sind wir aber endlich soweit und können mit den Grafiken anfangen. Vielleicht hast du es schon bei manchen Sprites gesehen, das folgende dient einfach nur der Übersichtlichkeit, ich empfehle es aber, zu verwenden. Wir werden es auf jeden Fall tun:
; =========================
; GRAPHICS ROUTINE HERE
; =========================
Das schreiben des GFX Codes (die eigentlichen Grafiken) besteht aus 4 Stufen
- die x Position
- die y Position
- das Teil, welches benutzt wird
- die Eigenschaften des Teils
Die x und die y Position
Nachdem wir nun also unsere GET_DRAW_INFO aufgerufen haben beinhaltet das y-Register nun den Index der OAM. Man muss jetzt noch nicht verstehen was das genau bedeutet, da jeder Sprite das folgende beinhaltet und man sich das daher auch einfach auswendig merken kann.
Nachdem wir also nun unsere GET_DRAW_INFO haben gilt folgendes:
- die x Position des Sprites befindet sich in $00
- die y Position des Sprites befindet sich in $01
Die OAM beginnt bei $0300. Die x und die y Position eines Sprites wird mit y eingefügt. Alles was wir tun müssen ist also das wir $00 und $01 in $0300 und $0301 zu speichern mit der Hilfe von y.
LDA $00
STA $0300,y ; die OAM beginnt bei $0300
LDA $01
STA $0301,y ; erster Wert der OAM voll, eins weiter
Alles was wir für einen 16*16 Sprite immer tun müssen ist diese 4 Zeilen Code an den Beginn unserer Graphics routine zu schreiben.
Wir haben es nun geschafft die x und die y Position unseres Sprites zu speichern, nun können wir einen Schritt weiter gehen.
Unser Teil, welches wir benutzen (The tile to store)
Natürlich kann man das folgende auch mit selbstgemachten Grafiken machen, um das ganze hier aber etwas abzukürzen verwende ich ein original Teil aus LunarMagic.
1. Öffne den 8*8 Editor von LunarMagic
2. Von dem 16*16 Teil deiner Wahl gehst du auf das linke obere 8*8 Teil
3. Unten im 8*8 Editor steht nun folgendes für unseren Pilz: Tile 0*324 is selected for editing
4. Die letzten 2 Ziffern sind hier für uns interessant, das ist die tile number (auf Englisch da es sich auf Deutsch scheiße anhört)
5. Nun haben wir also unsere tile number (24) als kleine Übung such doch einfach mal die tile number für die Feuerblume, den Schlüssel, oder den P-Switch. (Lösung: 26, EC, 42)
6. Geh wir jetzt auf die nächste Seite des 8*8 Editors, also SP3 und SP4 stellen wir fest, dass hier noch mehr Teile sind (was für eine Überraschung) nur das stellt uns vor ein kleines Problem. Es gibt also mehrere Teile mit der tile number 24.
7. Das Problem lässt sich mit Hilfe des .cfg Editors beheben, nur da wir uns damit noch nicht beschäftigt haben machen wir es anders mit Hilfe des property byte.
Bevor wir uns mit diesem Property byte beschäftigen müssen wir erst unser Teil überhaupt mal der OAM hinzufügen. Einfach laden und wieder speichern, ich hoffe das kriegt jeder hin.
Code bisher:
LDA $00
STA $0300,y ; die OAM beginnt bei $0300
LDA $01
STA $0301,y ; erster Wert der OAM voll, eins weiter
LDA #$24 ; Wir laden den Wert (#!) unseres Teils
STA $0302,y ; zweiter Wert der OAM voll
War doch gar nicht so schwer, kommen wir nun also zum property byte.
Das property byte
Nun machen wir uns ans schreiben des property byte, welches, wer hätte es anders erwartet zu $0303,y gehört. Das property byte ist verantwortlich für die Spiegelung des Teils an der X- bzw. der Y-Achse, die priority des Teils, die Palette und wie oben erwähnt die Seitennummer von der das Teil stammt.
Da das property byte für all diese Dinge verwendet wird wird es auch gerne so abgekürtzt:
YXPPCCCT
Wichtig ist, dass beim property byte in dieser Form alles im binären Zahlensystem abläuft, es kann also nur die Ziffern 0 und 1 verwenden.
Y → Zuständig für die Spiegelung an der Y-Achse. (0 = nicht gespiegelt, 1 = gespiegelt)
X → Genau das gleiche wie Y nur für die X-Achse
PP → Zuständig für die Priority. (00 = erscheint hinter Sprites und Objekten, 11 = davor)
Bei PP gibt es 4 Möglichkeiten, experimentier einfach mal mit den mittleren.
00 - niedrigstes (Lowest)
01 - niedrig (Low)
10 - hoch (High)
11 - höchstes (Highest)
CCC → Zuständig für die Paletten (in Binärzahelen angeben)
000 - Palette 8
001 - Palette 9
010 - Palette A
011 - Palette B
100 - Palette C
101 - Palette D
110 - Palette E
111 - Palette F
T → Zuständig für die Seite von der das Teil kommt (0 = SP1 + SP2, 1 = SP3 + SP4)
Schauen wir uns doch also mal an, was wir mit unserem blauen Pilz machen wollen.
Wir brauchen keine Spiegelung an der X- und Y-Achse, keine Priority, benutzen Palette B und der Pilz ist SP1. Das ganze sieht dann also so aus:
; yxppccct
;00000110
Also noch mal kurz zusammengefasst:
Erste und zweite Stelle von links sind 0, also weder an der X-. noch an der Y-Achse gespiegelt
Die dritte und vierte Stelle von links sind 00. Es gibt also auch keine Priority.
Bei ccc steht hier 011, also Palette B und der Pilz ist SP1, da die letzte Stelle von links eine 0 ist.
Wir haben nun mehrere Möglichkeiten diese Binärzahl in unseren Code zu integrieren.
Entweder so:
LDA #%00000110
ORA $64
STA $0303,y
LDA und STA hier zu suchen haben sollte eigentlich klar sein. Die Frage ist nur, was bedeutet ORA $64. Um das zu verstehen schauen wir uns zu erst einmal AND an.
AND wandelt einen HEX-Wert aus A um eine Binärzahl. Das gleiche macht es noch mit einer zweiten hexadezimalen Zahl. Zum Schluss gibt es noch eine 3. Zahl (auch wieder binär) aus, nach dem die ersten beiden Zahlen (nun binär) verglichen wurden. Dabei gibt es eine bestimmte Regel, nach welcher die dritte Zahl generiert wird. Sind beide Ziffern an erster Stelle der ersten beiden binären Zahlen eine 0, so ist die erste Ziffer der dritten Zahl auch eine 0. Sind beide eine 1, ist es in der dritten Zahl auch eine 1. Gibt es eine 0 und eine 1, so wird es in der dritten zahl auch zu einer 0. Nun wird das ganze nur noch in A gespeichert.
Zu kompliziert? Ein Beispiel:
LDA #$88 ; in binär 10001000
AND #$C6 ; in binär 11000100
Das obere sehen wir als Code, was hier passiert ist folgendes:
10001000 (erste Zahl)
11000100 (zweite Zahl)
10000000 (Ergebnis)
Wenn man sich jetzt frag zu was AND nützlich ist, ist es sinnvoll zu Blöcken zurück zu gehen.
Stellen wir uns vor, wir wollen einen Block schreiben, der uns ZUFÄLLIG klein, groß, zu Feuermario, oder Capemario macht. SMW hat einen integrierten Zufallsgenerator, aber es nicht so einfach zu benutzen. Arbeiten wir hier doch mal mit den animation frame. Das beste, dabei benützen wir AND.
LDA $13 ; Laden der animation frame Nummer
AND #$03 ; Vergleichen von A mit dem binär Wert 00000011. Hier haben wir nun die
; Möglichkeit von 4 Ergebnissen (4 PowerUpstatuse) Nämlich 00000000, 00000001,
; 00000010, 00000011. Ich hoffe es ist klar warum diese 4. So erhalten wir nun
; zufällig eine Hex-Zahl zwischen 00 und 03 (die PowerUp Zahlen)
STA $19 ; Speichern in Marios PowerUp
RTS ; Codeende
Nachdem wir nun also wissen was AND macht kommen wir zu ORA. ORA macht das genaue Gegenteil von AND. Als Ergebnis für die dritte Zahl gibt es immer nur die 1, außer beide Ziffern in der ersten und zweiten Zahl sind 0. Ein kurzes Rechenbeispiel:
10001000 (erste Zahl)
11000100 (zweite Zahl)
11001100 (Ergebnis)
Ich hoffe ihr hab jetzt ansatzweise ein Vermutung, was ORA macht. Schauen wir uns also nochmal unseren Code von oben an.
LDA #%00000110
ORA $64
STA $0303,y
Bevor wir also das property byte in $0303,y speichern können, müssen wir es in das sprite priorites byte ($64) hinzufügen. Dies kann man wie gerade gezeigt über ORA machen.
Oben sprach ich noch von einer zweiten Möglichkeit, welche so aussieht:
LDA #$06 ;hier wurde die binär Zahl einfach in eine Hexadezimalzahl umgewandelt.
ORA $64
STA $0303,y
Ich denke es ist selbsterklärend, welche Methode man vorziehen sollte
Eine andere Möglichkeit die Palette und die Grafikteile (also CCCT) an zugeben ist mit Hilfe des .cfg-Editors. (siehe etwas tiefer) Dann muss man folgenden Code verwenden.
PHX ; nur nötig, wenn man X noch für etwas anderes in der Grafikroutine benutzt
LDX $15E9
LDA $15F6,x
PLX ; wieder nur nötig, wenn X für etwas anderes in der Grafikroutine benutzt wird
ORA $64
STA $0303,y
Ich hoffe der Grundaufbau ist klar. Auf eine genauere Erklärung des Codes möchte ich verzichten, da dieser teil eh fakultativ ist und ansonsten ein Blick auf die RAM-Map hilft.
Jetzt haben wir also die X- und Y-Position festgelegt, die Grafikinformationen und auch die properties bytes festgelegt. Jetzt sollten wir eigentlich anfangen können mit der eigentlichen Arbeit.
Eigentlich. Es fehlt noch ein ganz kleines bisschen von der Grafikenroutine. Nun müssen wir uns um folgende Punkte kümmern:
Aufstockung der OAM (4 mal)
Y (das Y-Register) mit der richtigen Teilgröße laden (Für 16*16 02 und für 8*8 00)
A mit der Anzahl der benutzten Grafikteile laden (bei uns 1, da wir ein 16*16 gemalt haben, da man hier aber die 0 mitzählt 00.)
Die Routine aufrufen, damit unsere Grafiken in die OAM geschrieben werden.
Ein 8*8 Teil benutzt 4 OAM Bytes. Um das nächste 8*8 Teil zu zeichnen braucht man 8 OAM Bytes. Man kann INY benutzen um zum nächsten OAM Slot zu gelangen. Ein 16*16 Teil (unser Pilz) besteht aus 4 8*8 Teilen. Also brauchen wir 4 INY
INY
INY
INY
INY
Wir müssen Y (also die OAM) immer 4 mal „erhöhen“, damit alle 8*8 Teile unseres * Sprites gezeichnet werden. Dies ist sehr wichtig und muss wie gesagt immer nach dem Schre*iben der Teilinformationen gemacht werden! Es gibt nur eine Ausnahme: Wir zeichnen nur ein 8*8 Teil, dann müssen wir das selbstverständlich nicht tun.
Als nächstes müssen wir A mit der Anzahl der gezeichneten Teile laden (-1), dann noch Y mit der Teilgröße und als letztes die Routine an der Stelle $01B7B3 mit Hilfe eines Sprungbefehles (JSL) aufrufen. Schematisch sieht das ganze dann also wie folgt aus:
- Laden von Y mit der Teilgröße (00=8*8 ; 02=16*16)
- Laden von A mit der Anzahl der (16*16) gemalten Teile -1
- JSL zu $01B7B3
Für der ersten Teil bewegen wir uns im Y-Register und das ganze ist natürlich ein 16*16 Sprite. Wir schreiben
LDY #$02
Für den zweiten Teil sind wir nun wieder in A. Wir haben ein 16*16 Teil gezeichnet und wollen dieses Laden. Da noch -1 erhalten wir nun
LDA #$00
Und nun wird noch zur OAM gesprungen:
JSL $01B7B3
Und das beste an dem ganzen ist, dass wir jetzt fertig sind! Vergesst bitte nicht das RTS am Ende des Codes um diesen zu beenden Wir haben nun also eine vollständige Grafikroutine für einen 16*16 Sprite! Das ganze sollte jetzt so ausschauen:
dcb "INIT"
RTL
dcb "MAIN"
JSR Graphics ; This routine controls graphics.
RTL ; After the graphics routine is over, return.
Graphics:
JSR GET_DRAW_INFO ; Before actually coding the graphics, we need a routine that will get the current sprite's value
; into the OAM.
; The OAM being a place where the tile data for the current sprite will be stored.
LDA $00 ;\
STA $0300,y ;/ Draw the X position of the sprite
LDA $01 ;\
STA $0301,y ;/ Draw the Y position of the sprite
;Those 2 above are always needed to figure out the X/Y position of the sprite
LDA #$24 ;\
STA $0302,y ;/ Tile to draw. This is currently tile 24.
; NOTE: The tile is the upper-left tile of the 16x16 tile you want to draw
LDA #$06
ORA $64
STA $0303,y ; Write the YXPPCCCT property byte of the sprite
; See this part of the tutorial again for more info
INY ;\
INY ; | The OAM is 8x8, but our sprite is 16x16 ..
INY ; | So increment it 4 times.
INY ;/
LDY #$02 ; Y ends with the tile size .. 02 means it's 16x16
LDA #$00 ; A -> number of tiles drawn - 1.
; I drew only 1 tile so I put in 1-1 = 00.
JSL $01B7B3 ; Call the routine that draws the sprite.
RTS ; Never forget this!
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; GET_DRAW_INFO
; This is a helper for the graphics routine. It sets off screen flags, and sets up
; variables. It will return with the following:
;
; Y = index to sprite OAM ($300)
; $00 = sprite x position relative to screen boarder
; $01 = sprite y position relative to screen boarder
;
; It is adapted from the subroutine at $03B760
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
SPR_T1 dcb $0C,$1C
SPR_T2 dcb $01,$02
GET_DRAW_INFO STZ $186C,x ; reset sprite offscreen flag, vertical
STZ $15A0,x ; reset sprite offscreen flag, horizontal
LDA $E4,x ; \
CMP $1A ; | set horizontal offscreen if necessary
LDA $14E0,x ; |
SBC $1B ; |
BEQ ON_SCREEN_X ; |
INC $15A0,x ; /
ON_SCREEN_X LDA $14E0,x ; \
XBA ; |
LDA $E4,x ; |
REP #$20 ; |
SEC ; |
SBC $1A ; | mark sprite invalid if far enough off screen
CLC ; |
ADC.W #$0040 ; |
CMP.W #$0180 ; |
SEP #$20 ; |
ROL A ; |
AND #$01 ; |
STA $15C4,x ; /
BNE INVALID ;
LDY #$00 ; \ set up loop:
LDA $1662,x ; |
AND #$20 ; | if not smushed (1662 & 0x20), go through loop twice
BEQ ON_SCREEN_LOOP ; | else, go through loop once
INY ; /
ON_SCREEN_LOOP LDA $D8,x ; \
CLC ; | set vertical offscreen if necessary
ADC SPR_T1,y ; |
PHP ; |
CMP $1C ; | (vert screen boundry)
ROL $00 ; |
PLP ; |
LDA $14D4,x ; |
ADC #$00 ; |
LSR $00 ; |
SBC $1D ; |
BEQ ON_SCREEN_Y ; |
LDA $186C,x ; | (vert offscreen)
ORA SPR_T2,y ; |
STA $186C,x ; |
ON_SCREEN_Y DEY ; |
BPL ON_SCREEN_LOOP ; /
LDY $15EA,x ; get offset to sprite OAM
LDA $E4,x ; \
SEC ; |
SBC $1A ; | $00 = sprite x position relative to screen boarder
STA $00 ; /
LDA $D8,x ; \
SEC ; |
SBC $1C ; | $01 = sprite y position relative to screen boarder
STA $01 ; /
RTS ; return
INVALID PLA ; \ return from *main gfx routine* subroutine...
PLA ; | ...(not just this subroutine)
RTS ; /
Damit wäre dieses Kapitel dann auch abgeschlossen. Beachtet bitte, das es sehr viel schwere ist einen 32*32 Sprite zu schreiben. Um so einen werden wir uns wesentlich später kümmern. Als nächstest werden wir uns um die CFG-Datei kümmern, damit wir unseren Sprite endlich auch mal testen können!
Kapitel 4: Der .CFG Editor
Wir haben nun die ASM Datei für unseren blauen Pilz vollständige. Wir können ihn aber noch nicht einfügen, da uns die CFG-Datei fehlt. Hier gehen wir zunächst genauso vor wie bei unserem Generator am Anfang dieses Tutorials. Als CFG-Datei von Sprite Tool kopieren, etc.
Anders als beim Generator werden wir nun aber Änderungen bei ein paar Funktionen der CFG-Datei durchführen. Dazu benutzen wir den CFG-Editor. (wird bei Sprite Tool mitgeliefert)
Öffnen wir den CFG-Editor sehen wir folgendes Fenster:
Wir werden uns jeden einzelnen Kasten nun separat anschauen:
Tweak und Acts Like
Diese Information ist NICHT für den Sprit, sondern für Sprite Tool. Für alle Custom Sprites legen wir Tweak ODER Acts Like fest. Tweak bedeutet, dass wir einen original Sprite von smw nur modifizieren. Ist Tweak ausgewählt haben wir keine Möglichkeit dem CFG-Editor zu sagen, wo sich unsere ASM Datei befindet. Acts Like legt natürlich fest, wie sich unser Sprite verhält. (einfach sich mal die Sprites in smw im Spritefenster anschauen, da steht auch immer die Nummer des jeweiligen Sprites)
Unser blauer Pilz ist natürlich keine Abwandlung eines Originalsprites. Also legen wir Acts Like fest. Wir wollen hier natürlich nicht, dass sich unser Pilz wie ein Goomba oder etwas ähnliches Verhält. Wir wollen dass unser Sprite bisher überhaupt nichts macht! Wir legen hier dann den sogenanten NULLSPRITE fest. Der Vorteil von Nullsprites ist, dass man sich bei ihnen keinerlei Gedanken über ihre vorherigen Funktionen machen muss. Der Nullsprite, welcher normalerweise verwendet wird ist Sprite 36. Ein Sprite, welcher in smw nie genutzt wurde.
Unten im Kasten befindet sich dann noch die Stelle wo wir einfach den Namen unsere ASM Datei eintragen müssen.
Die RAM Adresse 1656
Sprites benutzen einige Spritetabellen, für öfters verwendete Verwendungszwecke. Die RAM Adresse 1656 verwaltet einige von ihnen. z.B. ob man auf den Sprite draufspringen kann, etc.
Doch bevor diese Auswahlmöglichkeiten angeboten werden ist da noch das Fenster für das sogenante OBJECT CLIPPING (kenne kein gutes deutsches Wort)
Diese Einstellung legt fest, wie unser Sprite mit Objekten interagiert. Die häufigste Einstellung hier ist 00. Damit funktioniert das ganze einfach am besten. Es sind aber Werte von 00 bis 0F möglich. Hier einfach mal selbst etwas experimentieren.
Die RAM Adresse 1662
Diese RAM Adresse ist für das SPRITE CLIPPING zuständig. Also die Interaktion mit anderen Sprites. Das Interaktionsfeld eines Sprites kann wie folgt aussehen: 16*16, 16*32, 32*32, etc. Möglich sind hier Werte von 00 bis 3F. Ich sag mal so viel, dass 00 hier wieder ein sehr guter Wert ist für einen 16*16 Sprite. Für alle anderen Größen empfehle ich diesen Download: http://www.smwcentral.net/download.php?id=84&type=documents
Optionen
Hier gibt es nur ein kleines Feld
Hier wird die Auswahl getroffen, ob der Sprite mit XKAS (Haken) oder TRASM.exe ausgeführt werden soll. Wir schreiben Sprites für TRASM.exe. Also lassen wir das Feld frei.
Extra Informationen
Für uns erst mal uninteressant. Hier aber eine Möglichkeit, wenn man einen benutzerfreundlichen Sprite schreiben will. Sagen wir wir haben einen Custom Sprite, der nach X Frames explodiert und diese Zeit in Extra Property 1 definiert. Nun kann ein andere hier ganz einfach die Zeit bis der Sprite explodiert ändern.
Noch ist dies uninteressant für uns, aber sehr viel später werden wir hierauf zurück kommen.
Die RAM Adresse 166E
Diese Adresse beinhaltet das Grundgerüst für Grafik und Palette des Sprites.
Zuerst gibt es die Möglichkeit der Unterscheidung zwischen der ersten Grafikseite (SP0 und SP1) und der zweiten Grafikseite. Der Rest sollte denke ich selbsterklärend sein. Man sollte hier nur beachten, dass wie die Palette in der ASM Datei schon festgelegt haben.
Die RAM Adressen 167A, 1686 und 190F
Diese sollten ohne weitere Erklärung verstanden werden.
Hier noch ein bisschen Zusatzinfo, für die, die sich fragen wie diese ganzen RAM Adressen überhaupt arbeiten. Auf Englisch, da ich zu Faul zum Übersetzen bin:
The same goes for all other bits. The box where you can enter a value beside the RAM Addresses is the final value that address gets set to. For example, if I only check the "can't be kicked by a shell" option in $167A, $167A will become #$10 in the sprite's code.
For each bit 01234567, the first one is 0, and the last one is 7. 8 bits are only used in addresses from $167A, 1686 and 190F. The rest use 4 bits or so.
Nach dem wir dies nun geklärt haben können wir endlich wieder zu unserem Pilz zurück kommen.
Hier mal alle Einstellungen für diesen im Überblick:
Sprite Clipping ist 00
Dont interact with objects ist NICHT angewählt! (wenn angewählt fällt der Sprite einfach nur runter)
Dont use default interaction with Mario ist angewählt. Dies erlaubt es uns noch andere Interaktionsmöglichkeiten als die des original Spieles fest zu legen.
Hier mit haben wir es nun geschafft. Wir können den Sprite endlich einfügen! Natürlich beim Einfügen in LunarMagic das Extra Bit auf 02 stellen. Wir sind noch lange nicht so weit, dass wir hier die 3 benutzen könnten.
Und nun Glückwunsch, wir haben einen Sprite mit Grafiken eingefügt, der bisher absolut überhaupt nichts macht. Für alle die die jetzt denken: Toll dafür der ganze Aufwand, denen sei gesagt, dass wir es geschafft haben eigene Spritegrafiken einzufügen!
Kapitel 5: Die Interaktion des Sprites
Am Ende dieser Einheit wird unser Sprite in der Lage sein, bei Berührung von Mario einen Soundeffekt abzuspielen und Mario 10 Münzen zu geben. Dafür müssen wir folgende Punkte in die MAIN Routine des Sprites integrieren:
- Es muss kontrolliert werden ob Mario den Sprite berührt.
- Wenn NEIN zurückgeschickt
- Wenn ja 10 zum Münzcounter addieren und einen Sound abspielen
- Nach Berührung den Sprite entfernen (der Münzcounter soll ja nicht unendlich steigen)
Um die Berührung von Mario oder eines Sprites zu prüfen benutzen wir JSL um eine Routine aufzurufen. Die richtige Routine hier für ist $01A7DC. (der smwc RAM Map zu entnehmen)
Den Kontakt können wir nun mit Hilfe der Carry-Flag prüfen. (erinnert euch an RPG Hackers Tutorial) Wir schreiben also:
JSL $01A7DC
BBC Kein Kontakt ; im Original anders benannt
KeinKontakt:
RTL
Dieser Codeteil wird eingefügt, direkt nach der Grafikroutine im kompletten Sprite. Zwischen das BBC KeinKontakt und KeinKontakt: schreiben wir nun, was bei Kontakt geschehen soll. Wir wollten ja 10 Münzen une einen Soundeffekt, also erhalten wir: (Werte sind der RAM Map zu entnehme)
LDA #10 ; 10 Münzen
STA $13CC
LDA #$01 ; Irgendein Soundeffekt
STA $1DFC
Natürlich ist es hier möglich alles zu machen was man will. Oft wird hier z.B. überprüft ob Mario den Sprite von oben berührt und ihn so tötet, oder anders und Mario stirbt. Da wird dann z.B. folgende Adresse aufgerufen: $00F5B7 (verletzt Mario)
Das letzte was jetzt noch fehlt, ist das der Sprite verschwindet. Dafür benutzen wir eine Spritestatustabelle.
$14C8 ist diese. Für Spritetabellen wird wie ganz viel früher erwähnt das X Register verwendet. Daher benutzen wir $14C8,x
Dies sind die Werte, welche Möglich sind:
00 = macht den Sprite nicht existent
01 = Anfang (keine Ahnung was das bedeutet)
02 = Töten, aus dem Screen fallen
03 = zerdrücken
04 = Spinjumped
05 = in Lava versinken
06 = beim Levelende in eine Münze verwandeln
07 = in Yoshis Mund bleiben
08 = „normal“
09 = ortsfest, oder mitnehmbar
0A = gekickt
0B = getragen
0C = beim tragen durchs Ziel zu einem PowerUp werden
Wir haben also mehr als genug Auswahl. Wir wollen das der Pilz verschwindet. Wir setzen $14C8,x also zu 00.
LDA #$00
STZ $14C8,x ; hier ginge auch STA, dies wäre aber langsamer
Nun ist es denke ich wieder mal an der Zeit den kompletten Spritecode zu vergleichen:
dcb "INIT"
RTL
dcb "MAIN"
JSR Graphics ; This routine controls graphics.
JSL $01A7DC ; Check for Mario/sprite contact ..
BCC NoContact ; Return if there isn't.
;===================================
;Mario Contact
;===================================
LDA #10 ;\
STA $13CC ; | 10 coins.
LDA #$01 ; |
STA $1DF9 ;/ Play sound.
STZ $14C8,x ; Sprite status = 00 = non-existant. This makes the sprite erase itself.
; No need for an RTL because we wrote it below.
;===================================
;No Contact
;===================================
NoContact:
RTL ; After the graphics routine is over, return.
;===================================
;Graphics Code
;===================================
Graphics:
JSR GET_DRAW_INFO ; Before actually coding the graphics, we need a routine that will get the current sprite's value
; into the OAM.
; The OAM being a place where the tile data for the current sprite will be stored.
LDA $00 ;\
STA $0300,y ;/ Draw the X position of the sprite
LDA $01 ;\
STA $0301,y ;/ Draw the Y position of the sprite
;Those 2 above are always needed to figure out the X/Y position of the sprite
LDA #$24 ;\
ORA $64
STA $0302,y ;/ Tile to draw. This is currently tile 24.
; NOTE: The tile is the upper-left tile of the 16x16 tile you want to draw
LDA #$06
STA $0303,y ; Write the YXPPCCCT property byte of the sprite
; See this part of the tutorial again for more info
INY ;\
INY ; | The OAM is 8x8, but our sprite is 16x16 ..
INY ; | So increment it 4 times.
INY ;/
LDY #$02 ; Y ends with the tile size .. 02 means it's 16x16
LDA #$00 ; A -> number of tiles drawn - 1.
; I drew only 1 tile so I put in 1-1 = 00.
JSL $01B7B3 ; Call the routine that draws the sprite.
RTS ; Never forget this!
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; GET_DRAW_INFO
; This is a helper for the graphics routine. It sets off screen flags, and sets up
; variables. It will return with the following:
;
; Y = index to sprite OAM ($300)
; $00 = sprite x position relative to screen boarder
; $01 = sprite y position relative to screen boarder
;
; It is adapted from the subroutine at $03B760
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
SPR_T1 dcb $0C,$1C
SPR_T2 dcb $01,$02
GET_DRAW_INFO STZ $186C,x ; reset sprite offscreen flag, vertical
STZ $15A0,x ; reset sprite offscreen flag, horizontal
LDA $E4,x ; \
CMP $1A ; | set horizontal offscreen if necessary
LDA $14E0,x ; |
SBC $1B ; |
BEQ ON_SCREEN_X ; |
INC $15A0,x ; /
ON_SCREEN_X LDA $14E0,x ; \
XBA ; |
LDA $E4,x ; |
REP #$20 ; |
SEC ; |
SBC $1A ; | mark sprite invalid if far enough off screen
CLC ; |
ADC.W #$0040 ; |
CMP.W #$0180 ; |
SEP #$20 ; |
ROL A ; |
AND #$01 ; |
STA $15C4,x ; /
BNE INVALID ;
LDY #$00 ; \ set up loop:
LDA $1662,x ; |
AND #$20 ; | if not smushed (1662 & 0x20), go through loop twice
BEQ ON_SCREEN_LOOP ; | else, go through loop once
INY ; /
ON_SCREEN_LOOP LDA $D8,x ; \
CLC ; | set vertical offscreen if necessary
ADC SPR_T1,y ; |
PHP ; |
CMP $1C ; | (vert screen boundry)
ROL $00 ; |
PLP ; |
LDA $14D4,x ; |
ADC #$00 ; |
LSR $00 ; |
SBC $1D ; |
BEQ ON_SCREEN_Y ; |
LDA $186C,x ; | (vert offscreen)
ORA SPR_T2,y ; |
STA $186C,x ; |
ON_SCREEN_Y DEY ; |
BPL ON_SCREEN_LOOP ; /
LDY $15EA,x ; get offset to sprite OAM
LDA $E4,x ; \
SEC ; |
SBC $1A ; | $00 = sprite x position relative to screen boarder
STA $00 ; /
LDA $D8,x ; \
SEC ; |
SBC $1C ; | $01 = sprite y position relative to screen boarder
STA $01 ; /
RTS ; return
INVALID PLA ; \ return from *main gfx routine* subroutine...
PLA ; | ...(not just this subroutine)
RTS ; /
Es ist nicht schlimm, sollte euer Code nicht solche Dinge wie
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;No Contact
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
enthalten. Dies gilt nur der Übersichtlichkeit.
Nun kann man den Sprite wieder normal einfügen und ihr werdet feststellen, dass alles funktionert.
Damit sind wir auch schon am Ende dieses Kapitels.