Wie programmiere ich einen Sprite?

geschrieben am 24.02.2013 17:36:57
( Link )
Wie programmiere ich einen Sprite?



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.)
Code
dcb “INIT“


Um die MAIN Routine einzuleiten schreiben wir folgendes: (Dies ist die Information für Spritetool die MAIN Routine zu starten.)
Code
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.
Code
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.
Code
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:
Code
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:
Code
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:
Code
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:
Code
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:
Zitat von WYE (leicht geändert):
Wenn du indizieren willst. Genauer gesagt: LDA $1337 lädt den Wert aus Adresse $1337, das ist klar. Wenn du aber LDA $1337,x schreibst, spielt das X-Register eine Rolle darin, aus welcher Adresse gelesen wird - der Wert in X wird zu der Zahl hinzugezählt, und aus dieser Adresse wird dann gelesen. Ist der Wert in X gleich 0, wird weiterhin aus Adresse $1337 gelesen ($1337+0). Ist der Wert in X 1, lesen wir aber aus Adresse $1338 ($1337+1). Ist X $10, lesen wir aus $1347, und so weiter. Dasselbe gilt für Y, nur schreibt man da eben LDA $1337,y.
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:
Zitat von WYE (leicht abgeändert):
Das letzte ist relativ einfach zu erklären, TAX kopiert einen Wert von A nach X, TAY von A nach Y, und so weiter.
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:
Code
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:
Code
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)
Code
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:
Code
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:
Code
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:
Code
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)
Code
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:
Code
; =========================
; 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.
Code
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:
Code
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:
Code
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:
Code
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.
Code
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.
Code
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:
Code
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.
Code
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
Code
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
Code
LDA #$00

Und nun wird noch zur OAM gesprungen:
Code
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:
Code
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:
Zitat von Ice Guy:
You might be wondering how a .cfg editor can edit some sprite settings. In the 12 byte Sprite Tables starting from$1656, $1662, $166E, $1662, $167A, $1686 and $190F, certain bits are used for a sprite's behaviour. For example, the "can't be kicked by a shell" is a bit 4 in the sprite table $167A. If it's checked, that bit will be set.
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:
Code
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)
Code
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.
Code
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:
Code
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.
geschrieben am 24.02.2013 17:37:43
( Link )
Kapitel 6: Ein paar Dinge, die der Sprite noch beinhalten sollte

Es gibt noch ein paar Dinge, welche wir bei unsere ASM Datei hinzufügen sollten. Öfnnet die ASM Datei! Das ganze was nun folgt ist eigentlich in fast jedem custom Sprite vorhanden. Als erstens sollte man Main Routine vom eigentlichem Spritecode trennen. Dies geschieht mit einem simplen Sprungbefehl.
Code
dcb  “MAIN“
JSR SpriteCode
RTL

SpriteCode:
JSR Graphics
; Rest des Codes

Dies hat keinerlei Einfluss auf die Funktionsweise des Sprites. Später werdet ihr noch sehen, wieso das ganz nützlich für uns ist.
Wichtig ist hier, da wir JSR benutzt haben das RTL nach dem KeinKontakt Label zu einem RTS umwandeln müssen. Da wir um zum Spritecode zu kommen JSR benutzt haben müssen wir im Spriteteil von nun an immer RTS anstatt RTL verwenden.
Das folgende wir auch fast immer in Sprites verwendet:
Code
PHB
PHK
PLB
JSR SpriteCode

PLB
RTL

PHB? PLB? PHK? Keine Ahnung! Fragegen wir nach!
Frage:
Was machen PHB, PLB und PHK?
Antwort, die mir WYE gegeben hat:
Zitat von WYE (leicht abgeändert):
PHB und PHK pushen/pullen den Wert des Data Bank Registers vom Stack. Das Data Bank Register (DBR) wird immer dann gebraucht, wenn man mit 16-Bit-Adressierung aus ROM-Speicher liest: LDA $89AB würde aus der ROM-Adresse $0089AB laden, wenn das DBR $00 ist, und aus $1289AB, wenn es $12 ist.
PHK pusht das Program Bank Register, das ist die Bank, in der der Code gerade läuft.
Besonders häufig wird die Kombination aus PHB, PHK und PLB in Sprites eingesetzt, wo man eigentlich immer am Anfang so etwas findet wie PHB : PHK : PLB : JSR SpriteCode : PLB : RTL. PHK : PLB überträgt den Wert des Program Bank Register in das Data Bank Register - heißt also, der Code soll die Daten (Tilemaps zum Beispiel) aus derselben Bank lesen, in der er gerade läuft. Die PHB und PLB ganz am Anfang und Ende diesen dazu, den ursprünglichen Wert des DBR beizubehalten und wiederherzustellen.
Ohne diese Befehle kann es passieren, dass man aus der falschen Stelle liest. Wenn man zum Beispiel in Bank $23 LDA Tilemap,x schreibt und sich Tilemap an der Stelle $CDEF befindet, aber das Data Bank Register $00 ist, liest der Code LDA Tilemap,x nicht aus $23CDEF, sondern aus $00CDEF, und das will man meistens nicht.

Eine bessere Erklärung kann es dazu glaube ich fast nicht geben. Hie wurde sogar expliziet auf Sprites eingegangen. Für alle die es nun aber immer noch nicht verstehen merkt euch einfach, dass wir das brauchen, da wir sonst manche Befehle auch nicht ausführen könnten. (im Bezug auf das X Register)
Wir haben es nun geschafft, das Spritecode und Mainroutine getrennt voneinander betrachtet werden können. So sollte die komplette ASM Datei bis jetzt aussehen:
Code
dcb "INIT"
RTL

dcb "MAIN"

PHB ;\
PHK ; | Change the data bank to the one our code is running from.
PLB ; | This is a good practice.
JSR SpriteCode ; | Jump to the sprite's function.
PLB ; | Restore old data bank.
RTL ;/ And return.

;===================================
;Sprite Function
;===================================

SpriteCode:
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:
RTS ; 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 ;\
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 ; /

Nehmen wir nun also noch zum Ende dieses Kapitels leichte Veränderungen am Sprite vor, die ihn verbessern. Beginnen wir mit dieser:
Code
SpriteCode:
JSR Graphics
LDA $14C8,x ; \ die Striche dienen nur dazu zu Kennzeichnen, was zusammen gehört.
CMP #$08 ; | Sollte der Sprite tot sein, oder nicht „normal“ zurück
BNE RETURN ; /
LDA $9D ; \
BNE RETURN ; / Sollte der Sprite „verschlossen sein, zurück

JSL $01A7DC
REST VOM CODE HIER

RETURN:
RTS ; Ich hoffe ihr wisst noch warum RTS und nicht RTL

Schauen wir uns doch nun mal genauer an was genau neu dazu gekommen ist. Der Teil, den wir eingefügt haben steht direkt nach der Graphics Routine. Die nächsten 3 Zeilen, nach dem JSR Graphics überprüfen ob der Sprite überhaupt noch lebt und ob er normal funktioniert. Erst danach wir der Code weiter ausgeführt. Hierbei ist wie die Spritestaustabelle zeigt immer CMP #$08 als normal zu verwenden.
Der zweite Teil des Codes (die zwei Zeilen nach dem ersten BNE RETURN) kontrollieren, dass der Sprite nichts macht, wenn er „eingefroren“ ist. Sammelt Mario einen Pilz oder wird verletzt sind Sprites für einen kurzen Moment „eingefroren“. Der Sprite wird somit also nichts machen, wenn wir z.B. gerade ein PowerUp einsammeln.
Das letzte um was wir uns jetzt noch kümmern sollten ist was passiert, wenn der Sprite Offscreen ist. Wir brauchen also eine Routine, die dafür sorgt, dass nichts passiert, wenn der Sprite OffScreen ist. Dies erledigt die folgende Routine: SUB_OFF_SCREEN_X0
Sie gibt es auch in fast jedem Spritecode. Sie wird einfach ganz ans Ende unserer ASM Datei angehängt und das ist die SUB_OFF_SCREEN_X0 Routine:
Code
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; SUB_OFF_SCREEN
; This subroutine deals with sprites that have moved off screen
; It is adapted from the subroutine at $01AC0D
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

SPR_T12 dcb $40,$B0
SPR_T13 dcb $01,$FF
SPR_T14 dcb $30,$C0,$A0,$C0,$A0,$F0,$60,$90 ;bank 1 sizes
dcb $30,$C0,$A0,$80,$A0,$40,$60,$B0 ;bank 3 sizes
SPR_T15 dcb $01,$FF,$01,$FF,$01,$FF,$01,$FF ;bank 1 sizes
dcb $01,$FF,$01,$FF,$01,$00,$01,$FF ;bank 3 sizes

SUB_OFF_SCREEN_X1 LDA #$02 ; \ entry point of routine determines value of $03
BRA STORE_03 ; | (table entry to use on horizontal levels)
SUB_OFF_SCREEN_X2 LDA #$04 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X3 LDA #$06 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X4 LDA #$08 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X5 LDA #$0A ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X6 LDA #$0C ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X7 LDA #$0E ; |
STORE_03 STA $03 ; |
BRA START_SUB ; |
SUB_OFF_SCREEN_X0 STZ $03 ; /

START_SUB JSR SUB_IS_OFF_SCREEN ; \ if sprite is not off screen, return
BEQ RETURN_35 ; /
LDA $5B ; \ goto VERTICAL_LEVEL if vertical level
AND #$01 ; |
BNE VERTICAL_LEVEL ; /
LDA $D8,x ; \
CLC ; |
ADC #$50 ; | if the sprite has gone off the bottom of the level...
LDA $14D4,x ; | (if adding 0x50 to the sprite y position would make the high byte >= 2)
ADC #$00 ; |
CMP #$02 ; |
BPL ERASE_SPRITE ; / ...erase the sprite
LDA $167A,x ; \ if "process offscreen" flag is set, return
AND #$04 ; |
BNE RETURN_35 ; /
LDA $13 ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0756 VC:176 00 FL:205
AND #$01 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0780 VC:176 00 FL:205
ORA $03 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0796 VC:176 00 FL:205
STA $01 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0820 VC:176 00 FL:205
TAY ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0844 VC:176 00 FL:205
LDA $1A ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0858 VC:176 00 FL:205
CLC ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0882 VC:176 00 FL:205
ADC SPR_T14,y ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0896 VC:176 00 FL:205
ROL $00 ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizcHC:0928 VC:176 00 FL:205
CMP $E4,x ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:0966 VC:176 00 FL:205
PHP ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:0996 VC:176 00 FL:205
LDA $1B ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdizCHC:1018 VC:176 00 FL:205
LSR $00 ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdiZCHC:1042 VC:176 00 FL:205
ADC SPR_T15,y ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdizcHC:1080 VC:176 00 FL:205
PLP ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F0 P:eNvMXdizcHC:1112 VC:176 00 FL:205
SBC $14E0,x ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1140 VC:176 00 FL:205
STA $00 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:1172 VC:176 00 FL:205
LSR $01 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:1196 VC:176 00 FL:205
BCC SPR_L31 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZCHC:1234 VC:176 00 FL:205
EOR #$80 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZCHC:1250 VC:176 00 FL:205
STA $00 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1266 VC:176 00 FL:205
SPR_L31 LDA $00 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1290 VC:176 00 FL:205
BPL RETURN_35 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1314 VC:176 00 FL:205
ERASE_SPRITE LDA $14C8,x ; \ if sprite status < 8, permanently erase sprite
CMP #$08 ; |
BCC KILL_SPRITE ; /
LDY $161A,x ;A:FF08 X:0007 Y:0001 D:0000 DB:01 S:01F3 P:envMXdiZCHC:1108 VC:059 00 FL:2878
CPY #$FF ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdiZCHC:1140 VC:059 00 FL:2878
BEQ KILL_SPRITE ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdizcHC:1156 VC:059 00 FL:2878
LDA #$00 ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdizcHC:1172 VC:059 00 FL:2878
STA $1938,y ;A:FF00 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdiZcHC:1188 VC:059 00 FL:2878
KILL_SPRITE STZ $14C8,x ; erase sprite
RETURN_35 RTS ; return

VERTICAL_LEVEL LDA $167A,x ; \ if "process offscreen" flag is set, return
AND #$04 ; |
BNE RETURN_35 ; /
LDA $13 ; \
LSR A ; |
BCS RETURN_35 ; /
LDA $E4,x ; \
CMP #$00 ; | if the sprite has gone off the side of the level...
LDA $14E0,x ; |
SBC #$00 ; |
CMP #$02 ; |
BCS ERASE_SPRITE ; / ...erase the sprite
LDA $13 ;A:0000 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:1218 VC:250 00 FL:5379
LSR A ;A:0016 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1242 VC:250 00 FL:5379
AND #$01 ;A:000B X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1256 VC:250 00 FL:5379
STA $01 ;A:0001 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1272 VC:250 00 FL:5379
TAY ;A:0001 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1296 VC:250 00 FL:5379
LDA $1C ;A:001A X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0052 VC:251 00 FL:5379
CLC ;A:00BD X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0076 VC:251 00 FL:5379
ADC SPR_T12,y ;A:00BD X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0090 VC:251 00 FL:5379
ROL $00 ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:enVMXdizCHC:0122 VC:251 00 FL:5379
CMP $D8,x ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0160 VC:251 00 FL:5379
PHP ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0190 VC:251 00 FL:5379
LDA.W $001D ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F2 P:eNVMXdizcHC:0212 VC:251 00 FL:5379
LSR $00 ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:enVMXdiZcHC:0244 VC:251 00 FL:5379
ADC SPR_T13,y ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:enVMXdizCHC:0282 VC:251 00 FL:5379
PLP ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:envMXdiZCHC:0314 VC:251 00 FL:5379
SBC $14D4,x ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0342 VC:251 00 FL:5379
STA $00 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0374 VC:251 00 FL:5379
LDY $01 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0398 VC:251 00 FL:5379
BEQ SPR_L38 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0422 VC:251 00 FL:5379
EOR #$80 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0438 VC:251 00 FL:5379
STA $00 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0454 VC:251 00 FL:5379
SPR_L38 LDA $00 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0478 VC:251 00 FL:5379
BPL RETURN_35 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0502 VC:251 00 FL:5379
BMI ERASE_SPRITE ;A:8AFF X:0002 Y:0000 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0704 VC:184 00 FL:5490

SUB_IS_OFF_SCREEN LDA $15A0,x ; \ if sprite is on screen, accumulator = 0
ORA $186C,x ; |
RTS ; / return

Was sie genau macht, hat uns wie bei der Graphicsroutine nicht zu interessieren
Wir haben die SUB_OFF_SCREEN_X0 Routine nur hinzugefügt. Wir müssen sie aber noch aufrufen. Dafür fügen wir einfach die folgende Codezeile direkt nach der Überprüfung ob der Sprite verschlossen ist hinzu:
Code
JSR SUB_OFF_SCREEN_X0

JSR wird verwendet, da die SUB_OFF_SCREEN_X0 Routine mit einem RTS endet.

Damit sind wir mit diesem Kapitel auch schon am Ende. Hier ist es wieder mal an der Zeit unseren Sprite auszuprobieren. Vielleicht fällt eine Sache aber noch auf. Wird ein sich bewegender Sprite in die nähe zu unserem Pilz platziert, so stellt man fest, dass sich dieser Sprite einfach durch den Pilz hindurch bewegt. Um dies zu verhindern muss man unseren Sprite noch mit anderen Sprite interagieren lassen.

$018032 ist eine Routine, welche mit JSL aufgerufen werden kann und dafür sorgt, dass unser Sprites mit anderen interagiert. Sie kann direkt vor der Routine welche für Marios interaktion zuständig ist ($01A7DC) aufgerufen werden. Testet man nun dann den Sprite, muss man in der cfg Datei natürlich auch bei dont interact with other sprites den Haken weg machen! Nun sind wir komplett fertig mit unserem ersten Sprite, der kein Generator ist. Er sollte jetzt richtig funktionieren wenn wir ihn einfügen. Glückwunsch! Hier noch einmal zum Abschluss die komplette ASM Datei, wie sie jetzt ungefähr auszusehen hat:
Code
dcb "INIT"
RTL

dcb "MAIN"

PHB ;\
PHK ; | Change the data bank to the one our code is running from.
PLB ; | This is a good practice.
JSR SpriteCode ; | Jump to the sprite's function.
PLB ; | Restore old data bank.
RTL ;/ And return.

;===================================
;Sprite Function
;===================================

RETURN: ;\ The return label that gets called when the sprite is dead or locked is here.
RTS ;/ I added it here to ensure that the branch is in range.

SpriteCode:
JSR Graphics ; This routine controls graphics.

LDA $14C8,x ;\
CMP #$08 ; | If sprite is dead or not in it's normal routine..
BNE RETURN ;/ RETURN.

LDA $9D ; If the sprite is locked ..
BNE RETURN ; RETURN.

JSR SUB_OFF_SCREEN_X0

JSL $018032 ; Interact with other sprites. Make sure the "dont interact with other sprites" option in the .cfg editor is unchecked!

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:
RTS ; 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 ;\
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 ; /
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; SUB_OFF_SCREEN
; This subroutine deals with sprites that have moved off screen
; It is adapted from the subroutine at $01AC0D
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

SPR_T12: db $40,$B0
SPR_T13: db $01,$FF
SPR_T14: db $30,$C0,$A0,$C0,$A0,$F0,$60,$90 ;bank 1 sizes
db $30,$C0,$A0,$80,$A0,$40,$60,$B0 ;bank 3 sizes
SPR_T15: db $01,$FF,$01,$FF,$01,$FF,$01,$FF ;bank 1 sizes
db $01,$FF,$01,$FF,$01,$00,$01,$FF ;bank 3 sizes

SUB_OFF_SCREEN_X1: LDA #$02 ; \ entry point of routine determines value of $03
BRA STORE_03 ; | (table entry to use on horizontal levels)
SUB_OFF_SCREEN_X2: LDA #$04 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X3: LDA #$06 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X4: LDA #$08 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X5: LDA #$0A ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X6: LDA #$0C ; |
BRA STORE_03 ; | OMG YOU FOUND THIS HIDDEN z0mg place!111 you win a cookie!
SUB_OFF_SCREEN_X7: LDA #$0E ; |
STORE_03: STA $03 ; |
BRA START_SUB ; |
SUB_OFF_SCREEN_X0: STZ $03 ; /

START_SUB: JSR SUB_IS_OFF_SCREEN ; \ if sprite is not off screen, return
BEQ RETURN_35 ; /
LDA $5B ; \ goto VERTICAL_LEVEL if vertical level
AND #$01 ; |
BNE VERTICAL_LEVEL ; /
LDA $D8,x ; \
CLC ; |
ADC #$50 ; | if the sprite has gone off the bottom of the level...
LDA $14D4,x ; | (if adding 0x50 to the sprite y position would make the high byte >= 2)
ADC #$00 ; |
CMP #$02 ; |
BPL ERASE_SPRITE ; / ...erase the sprite
LDA $167A,x ; \ if "process offscreen" flag is set, return
AND #$04 ; |
BNE RETURN_35 ; /
LDA $13 ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0756 VC:176 00 FL:205
AND #$01 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0780 VC:176 00 FL:205
ORA $03 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0796 VC:176 00 FL:205
STA $01 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0820 VC:176 00 FL:205
TAY ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0844 VC:176 00 FL:205
LDA $1A ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0858 VC:176 00 FL:205
CLC ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0882 VC:176 00 FL:205
ADC SPR_T14,y ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0896 VC:176 00 FL:205
ROL $00 ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizcHC:0928 VC:176 00 FL:205
CMP $E4,x ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:0966 VC:176 00 FL:205
PHP ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:0996 VC:176 00 FL:205
LDA $1B ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdizCHC:1018 VC:176 00 FL:205
LSR $00 ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdiZCHC:1042 VC:176 00 FL:205
ADC SPR_T15,y ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdizcHC:1080 VC:176 00 FL:205
PLP ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F0 P:eNvMXdizcHC:1112 VC:176 00 FL:205
SBC $14E0,x ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1140 VC:176 00 FL:205
STA $00 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:1172 VC:176 00 FL:205
LSR $01 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:1196 VC:176 00 FL:205
BCC SPR_L31 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZCHC:1234 VC:176 00 FL:205
EOR #$80 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZCHC:1250 VC:176 00 FL:205
STA $00 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1266 VC:176 00 FL:205
SPR_L31: LDA $00 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1290 VC:176 00 FL:205
BPL RETURN_35 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1314 VC:176 00 FL:205
ERASE_SPRITE: LDA $14C8,x ; \ if sprite status < 8, permanently erase sprite
CMP #$08 ; |
BCC KILL_SPRITE ; /
LDY $161A,x ;A:FF08 X:0007 Y:0001 D:0000 DB:01 S:01F3 P:envMXdiZCHC:1108 VC:059 00 FL:2878
CPY #$FF ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdiZCHC:1140 VC:059 00 FL:2878
BEQ KILL_SPRITE ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdizcHC:1156 VC:059 00 FL:2878
LDA #$00 ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdizcHC:1172 VC:059 00 FL:2878
STA $1938,y ;A:FF00 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdiZcHC:1188 VC:059 00 FL:2878
KILL_SPRITE: STZ $14C8,x ; erase sprite
RETURN_35: RTS ; return

VERTICAL_LEVEL: LDA $167A,x ; \ if "process offscreen" flag is set, return
AND #$04 ; |
BNE RETURN_35 ; /
LDA $13 ; \
LSR A ; |
BCS RETURN_35 ; /
LDA $E4,x ; \
CMP #$00 ; | if the sprite has gone off the side of the level...
LDA $14E0,x ; |
SBC #$00 ; |
CMP #$02 ; |
BCS ERASE_SPRITE ; / ...erase the sprite
LDA $13 ;A:0000 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:1218 VC:250 00 FL:5379
LSR A ;A:0016 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1242 VC:250 00 FL:5379
AND #$01 ;A:000B X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1256 VC:250 00 FL:5379
STA $01 ;A:0001 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1272 VC:250 00 FL:5379
TAY ;A:0001 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1296 VC:250 00 FL:5379
LDA $1C ;A:001A X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0052 VC:251 00 FL:5379
CLC ;A:00BD X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0076 VC:251 00 FL:5379
ADC SPR_T12,y ;A:00BD X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0090 VC:251 00 FL:5379
ROL $00 ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:enVMXdizCHC:0122 VC:251 00 FL:5379
CMP $D8,x ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0160 VC:251 00 FL:5379
PHP ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0190 VC:251 00 FL:5379
LDA $001D ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F2 P:eNVMXdizcHC:0212 VC:251 00 FL:5379
LSR $00 ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:enVMXdiZcHC:0244 VC:251 00 FL:5379
ADC SPR_T13,y ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:enVMXdizCHC:0282 VC:251 00 FL:5379
PLP ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:envMXdiZCHC:0314 VC:251 00 FL:5379
SBC $14D4,x ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0342 VC:251 00 FL:5379
STA $00 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0374 VC:251 00 FL:5379
LDY $01 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0398 VC:251 00 FL:5379
BEQ SPR_L38 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0422 VC:251 00 FL:5379
EOR #$80 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0438 VC:251 00 FL:5379
STA $00 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0454 VC:251 00 FL:5379
SPR_L38: LDA $00 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0478 VC:251 00 FL:5379
BPL RETURN_35 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0502 VC:251 00 FL:5379
BMI ERASE_SPRITE ;A:8AFF X:0002 Y:0000 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0704 VC:184 00 FL:5490

SUB_IS_OFF_SCREEN: LDA $15A0,x ; \ if sprite is on screen, accumulator = 0
ORA $186C,x ; |
RTS ; / return




Kapitel 7: Ein animierter Sprite

Für alle die noch nicht aufgegeben haben geht es nun weiter mit einem animierten Sprite. Dafür werden wir einen kompletten neuen Sprite schreiben. Wir wollen einen Sprite schreiben, der zwischen 2 Frames animiert ist. Dafür nehmen wir einen Bob-Omb. Ein Bob-Omb wechselt zwischen seinem „Walking Frame“ und seinem „Moving Frame“.
Da wir uns nicht noch mal um alle Grundlagen kümmern wollen nehmen wir dieses vorgefertigte Gerüst von Ice Guy als ASM Datei:
Code
dcb "INIT"
RTL

dcb "MAIN"

PHB ;\
PHK ; | Change the data bank to the one our code is running from.
PLB ; | This is a good practice.
JSR SpriteCode ; | Jump to the sprite's function.
PLB ; | Restore old data bank.
RTL ;/ And return.

;===================================
;Sprite Function
;===================================

RETURN:
RTS

SpriteCode:
JSR Graphics

LDA $14C8,x
CMP #$08
BNE RETURN
LDA $9D
BNE RETURN

JSR SUB_OFF_SCREEN_X0
;===================================
;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 #$CA ;\
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 #$07
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 ; | IF YOU READ THIS, YOU WIN A COOKIE! PM ME AND I'LL GIVE YOU A FREE ASM HACK WOOHOO!
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 ; /

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; SUB_OFF_SCREEN
; This subroutine deals with sprites that have moved off screen
; It is adapted from the subroutine at $01AC0D
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

SPR_T12 dcb $40,$B0
SPR_T13 dcb $01,$FF
SPR_T14 dcb $30,$C0,$A0,$C0,$A0,$F0,$60,$90 ;bank 1 sizes
dcb $30,$C0,$A0,$80,$A0,$40,$60,$B0 ;bank 3 sizes
SPR_T15 dcb $01,$FF,$01,$FF,$01,$FF,$01,$FF ;bank 1 sizes
dcb $01,$FF,$01,$FF,$01,$00,$01,$FF ;bank 3 sizes

SUB_OFF_SCREEN_X1 LDA #$02 ; \ entry point of routine determines value of $03
BRA STORE_03 ; | (table entry to use on horizontal levels)
SUB_OFF_SCREEN_X2 LDA #$04 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X3 LDA #$06 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X4 LDA #$08 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X5 LDA #$0A ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X6 LDA #$0C ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X7 LDA #$0E ; |
STORE_03 STA $03 ; |
BRA START_SUB ; |
SUB_OFF_SCREEN_X0 STZ $03 ; /

START_SUB JSR SUB_IS_OFF_SCREEN ; \ if sprite is not off screen, return
BEQ RETURN_35 ; /
LDA $5B ; \ goto VERTICAL_LEVEL if vertical level
AND #$01 ; |
BNE VERTICAL_LEVEL ; /
LDA $D8,x ; \
CLC ; |
ADC #$50 ; | if the sprite has gone off the bottom of the level...
LDA $14D4,x ; | (if adding 0x50 to the sprite y position would make the high byte >= 2)
ADC #$00 ; |
CMP #$02 ; |
BPL ERASE_SPRITE ; / ...erase the sprite
LDA $167A,x ; \ if "process offscreen" flag is set, return
AND #$04 ; |
BNE RETURN_35 ; /
LDA $13 ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0756 VC:176 00 FL:205
AND #$01 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0780 VC:176 00 FL:205
ORA $03 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0796 VC:176 00 FL:205
STA $01 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0820 VC:176 00 FL:205
TAY ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0844 VC:176 00 FL:205
LDA $1A ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0858 VC:176 00 FL:205
CLC ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0882 VC:176 00 FL:205
ADC SPR_T14,y ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0896 VC:176 00 FL:205
ROL $00 ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizcHC:0928 VC:176 00 FL:205
CMP $E4,x ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:0966 VC:176 00 FL:205
PHP ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:0996 VC:176 00 FL:205
LDA $1B ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdizCHC:1018 VC:176 00 FL:205
LSR $00 ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdiZCHC:1042 VC:176 00 FL:205
ADC SPR_T15,y ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdizcHC:1080 VC:176 00 FL:205
PLP ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F0 P:eNvMXdizcHC:1112 VC:176 00 FL:205
SBC $14E0,x ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1140 VC:176 00 FL:205
STA $00 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:1172 VC:176 00 FL:205
LSR $01 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:1196 VC:176 00 FL:205
BCC SPR_L31 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZCHC:1234 VC:176 00 FL:205
EOR #$80 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZCHC:1250 VC:176 00 FL:205
STA $00 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1266 VC:176 00 FL:205
SPR_L31 LDA $00 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1290 VC:176 00 FL:205
BPL RETURN_35 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1314 VC:176 00 FL:205
ERASE_SPRITE LDA $14C8,x ; \ if sprite status < 8, permanently erase sprite
CMP #$08 ; |
BCC KILL_SPRITE ; /
LDY $161A,x ;A:FF08 X:0007 Y:0001 D:0000 DB:01 S:01F3 P:envMXdiZCHC:1108 VC:059 00 FL:2878
CPY #$FF ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdiZCHC:1140 VC:059 00 FL:2878
BEQ KILL_SPRITE ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdizcHC:1156 VC:059 00 FL:2878
LDA #$00 ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdizcHC:1172 VC:059 00 FL:2878
STA $1938,y ;A:FF00 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdiZcHC:1188 VC:059 00 FL:2878
KILL_SPRITE STZ $14C8,x ; erase sprite
RETURN_35 RTS ; return

VERTICAL_LEVEL LDA $167A,x ; \ if "process offscreen" flag is set, return
AND #$04 ; |
BNE RETURN_35 ; /
LDA $13 ; \
LSR A ; |
BCS RETURN_35 ; /
LDA $E4,x ; \
CMP #$00 ; | if the sprite has gone off the side of the level...
LDA $14E0,x ; |
SBC #$00 ; |
CMP #$02 ; |
BCS ERASE_SPRITE ; / ...erase the sprite
LDA $13 ;A:0000 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:1218 VC:250 00 FL:5379
LSR A ;A:0016 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1242 VC:250 00 FL:5379
AND #$01 ;A:000B X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1256 VC:250 00 FL:5379
STA $01 ;A:0001 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1272 VC:250 00 FL:5379
TAY ;A:0001 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1296 VC:250 00 FL:5379
LDA $1C ;A:001A X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0052 VC:251 00 FL:5379
CLC ;A:00BD X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0076 VC:251 00 FL:5379
ADC SPR_T12,y ;A:00BD X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0090 VC:251 00 FL:5379
ROL $00 ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:enVMXdizCHC:0122 VC:251 00 FL:5379
CMP $D8,x ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0160 VC:251 00 FL:5379
PHP ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0190 VC:251 00 FL:5379
LDA.W $001D ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F2 P:eNVMXdizcHC:0212 VC:251 00 FL:5379
LSR $00 ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:enVMXdiZcHC:0244 VC:251 00 FL:5379
ADC SPR_T13,y ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:enVMXdizCHC:0282 VC:251 00 FL:5379
PLP ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:envMXdiZCHC:0314 VC:251 00 FL:5379
SBC $14D4,x ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0342 VC:251 00 FL:5379
STA $00 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0374 VC:251 00 FL:5379
LDY $01 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0398 VC:251 00 FL:5379
BEQ SPR_L38 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0422 VC:251 00 FL:5379
EOR #$80 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0438 VC:251 00 FL:5379
STA $00 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0454 VC:251 00 FL:5379
SPR_L38 LDA $00 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0478 VC:251 00 FL:5379
BPL RETURN_35 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0502 VC:251 00 FL:5379
BMI ERASE_SPRITE ;A:8AFF X:0002 Y:0000 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0704 VC:184 00 FL:5490

SUB_IS_OFF_SCREEN LDA $15A0,x ; \ if sprite is on screen, accumulator = 0
ORA $186C,x ; |
RTS ; / return

Wenn dieser Sprite mit Sprite Tool eingefügt wird wird im Spiel nur das Bild eines Bob-Ombs angezeigt. Alles was Ice Guy gemacht hat, war das Property Byte zu ändern ($0303,y) und das Teil welches benutzt werden soll ($CA [Das Bild das sich nicht bewegenden Bob-Omb]).
Schaut man sich im 8*8 Editor GFX02.bin an, so stellt man fest, dass das Teil CA, das Bild ist, welches auch im Spiel angezeigt wird und somit auch in den Grafikinformationen der ASM Datei festgelegt wurde. Der „Walking Frame“ ist Teil CC. Wir wollen also für den Sprite nicht nur Teil CA geladen wird, sondern dass ständig zwischen Teil CA und CC gewechselt wird.
Eine gute und einfach Möglichkeit dies umzusetzen bietet der Frame Counter. ($13)
Der Frane Counter wechselt von alleine jeden Frame. Ausgehend von $13 werden wir verschiedene Teile in die Spritetiledata $0302,y laden.
Das erste was wir nun tun müssen, ist eine Tabelle erstellen, mit den Frames welche wir benutzen wollen.
Code
TILEMAP: dcb $CA,$CC		;TILEMAP ist einfach der Name der Tabelle

Hier werden nun zwei Werte (CA und CC, die wollen wir ja schließlich benutzen) in die Tabelle mit den Namen Tilemap geschrieben. Normalerweise kommt die Tabelle vor das Graphics Label.
Als nächstes müssen wir den Codeteil verändern, welcher für das Grafikteil zuständig ist. Wir haben bereits: (Steht schon in der ASM Datei)
Code
LDA #$CA
STA $030,y

Nun müssen wir als nächstes das X und das Y Register „hinzufügen“.
- Das X Register wird für die Spritetabelle benutzt
- Das Y Register für die OAM
Wir haben das X Register noch nicht im Grafikcode benutzt. Das heißt vor unserem Code müssen wir es erst aufrufen (PHX) und später wieder zurück tun wo es herkam
Dafür schreiben wir folgendes:
Code
PHX
LDA #$CA ; \ Haben wir bisher noch nicht besprochen
STA $0302,y ; /
PLX

Zwischen, bzw. für LDA #$CA und STA $0302,y werden wir den Code einfügen, der unseren Sprite animiert. Wie oben gesagt benutzen wir dafür $13.
Code
LDA $01
STA $0301,y ; Der Code für die Y ist eins höher als unserer

PHX
LDA $13
AND #$01 ; die Zahl hier ist muss eins geringer sein, als die Wert in der Tabelle
TAX ; kopiert wie viel früher gesagt einen Wert von A nach X

Was wir hier gemacht haben war Bit 0 zunehmen und es in X zu verschieben. Wechselt $13 jeden Frame, ist $13 immer entweder 1 oder 0 (also 00000001 oder 00000000). Wir laden also Teil CA oder Teil CC in Abhängigkeit davon, ob $13 ist 00000001 oder 00000000.
Code
LDA $01
STA $0301,y

PHX
LDA $13
AND #$01
TAX
LDA TILEMAP,x
STA $0302,y

Wenn $13 nun 0 ist laden wir mit Hilfe unserer Tabelle Teil CA und wenn $13 1 ist laden wir Teil CC. Da $13 ja immer wechslet, wird auch so immer zwischen den Teilen gewechselt. Wir müssen nun nur noch X Register „zurück schicken“ da wir den Stack ja nicht crashen wollen. Dafür schreiben wir einfach noch ein:
Code
PLX

Dieser Codeteil (von PHX bis PLX) ersetzt nun folgendes:
Code
LDA #$CA
STA $030,y

Denkt dran dass die Tabelle auch in die ASM Datei muss! Wie oben schon erwähnt, das ganze am besten direkt vor dem Graphics Label einfügen.
Fügt man den Sprite nun mit Sprite Tool ein, so wird man einen animierten Sprite haben, nur wird er sehr schnell zwischen den beiden Teilen wechseln. Wir müssen als die Geschwindigkeit, mit welcher die Animation abläuft verlangsamen.
Dafür benutzen wir LSR. (direkt nach LDA $13)
Code
PHX
LDA $13
LSR
AND #$01
TAX
LDA TILEMAP,x
STA $0302,y
PLX

Je mehr LSR hinzugefügt werden, desto langsamer wird die Animation.
Code
PHX
LDA $13
LSR
LSR
LSR
AND #$01
TAX
LDA TILEMAP,x
STA $0302,y
PLX

Ist natürlich langsamer, als das obere. Bei Sprites muss man einfach ausprobieren, wie viele LSR man braucht. Für den Bob-Omb sind 3 genau richtig.
Wie LSR funktioniert, werde ich hier nicht erklären, denn es ist nicht elementar und noch dazu nicht leicht zu verstehen.
Dies wäre unser fertiger Bob-Omb. Für Leute die langweilig ist nun kurz eine Erklärung, wie man mehr Frames animieren kann. AND kann alle Werte von #$01 bis #$FF annehmen und so viele Animationsschritte sind auch möglich. Wobei der Sinn von FF Animationsschritten mir nicht klar ist. Denkt natürlich daran, dass wenn AND #$03 ist in der Tabelle 4 Werte stehen müssen (also immer +1).
Wir haben nun endlich also einen Bob-Omb, der an der Stelle steht und animiert ist. Sehr schön, aber das ist ja noch ein bisschen langweilig. Wie wäre es, wenn er sich auch bewegen würde? Genau darum werden w
geschrieben am 24.02.2013 17:38:23
( Link )
Kapitel 8: Den Sprite bewegen

Also eines der wichtigsten Kapitel bricht an. Auf geht’s! Am Ende dieses Kapitels wird unsere Bob-Omb sich bewegen können.
Man kann zu Marios X Geschwindigkeit (horizontale Bewegung; Y Geschwindigkeit = vertikal) kann man entweder eine positive oder eine negative Zahl speichern. Dies ist abhängig von der Richtung in welche sich der Sprite bewegen soll. Positiv und negativ stimmt hier nicht ganz, denn mit positiv sind die Werte von 01-7F gemeint und mit negativ die Werte von FF-80.
Die X Geschwindigkeit wird in $B6,x gespeichert die Y Geschwindigkeit in $AA,x.
Das X muss hinten wieder hin, da es auch für die Geschwindigkeit von Nöten ist, Tabellen zu benutzen. Abhängig von der Richtung speichert man dann die Geschwindigkeit fürs nach Rechts laufen und die fürs nach Links laufen.
Die Richtung der Geschwindigkeit wird oft in $157C,x gespeichert. Dabei gilt: 00 = Rechts und 01 = Links
Als erstes erstellen wir für unsere ASM Datei eine kleine Tabelle. Sie kommt vor das Returnlabel.
Code
XSPEED: dcb $08,$F8		; Geschwindigkeit für Bewegung nach links und rechts

Die Tabelle heißt XSPEED und die Werte sind die für normale Geschwindigkeit. (Die anderen kann man einfach ausprobieren)
Die Richtung auf der X-Achse müssen wir wie oben schon erwähnt nun in $B6,x speichern. Dieser Codeteil kann direkt hinter JSR SUB_OFF_SCREEN_X0.
Code
LDY $157C,x				; Laden der Richtung mit LDY
LDA XSPEED,y ; Die Geschwindigkeit ist abhängig von der Richtung
STA $B6,x ; Speichern der Geschwindigkeit als X Geschwindigkeit

Sprites die auf dem Boden laufen haben für gewöhnlich auch einen Y Geschwindigkeit, nämlich die Erdanziehungskraft. Hätten sie diese nicht, könnten sie z.B. keine Schrägen laufen, etc. Die normale Y Geschwindigkeit als Gravitation ist #$10.
Wir haben mit dem oberen Code nun die Geschwindigkeit des Sprites gespeichert. Immer nach dem die Geschwindigkeit eines Sprite festgelegt wurde muss JSL zu einer andern Routine gesprungen werden, welche die Position des Sprites aktualisiert. Diese Routine ist unter $01802A zu finden. Für unseren Code bedeutet das:
Code
LDY $157C,x				
LDA XSPEED,y
STA $B6,x
LDA #$10 ; Hier habe ich jetzt auch die Gravitation hinzugefügt
STA $AA,x
JSL $01802A ; Unser Sprungbefehl
RTS
XSPEED: dcb $08, $F8

Hier eine kleine Anmerkung die die Tabelle betrifft:
DIE TABELLE SOLLTE IN EINEM SEPERATEN BEREICH SEIN, DAMIT SIE DEN EIGENTLICHEN CODE NICHT BEEINFLUSST. DAS BESTE IST SOMIT SIE NACH EINEM RTS EINER UNTERROUTINE ZU PLATZIEREN.

Nun kann der Sprite schon das erste mal getestet werden. Bisher war das ganze ja nicht so schwer. Wir sind aber auch noch nicht ganz fertig, was klar wird, wenn man den Sprite mal in Aktion gesehen hat. Das erste was auffällt ist dass der Sprite keinen richtigen Bezug zu Mario hat. Er läuft nicht automatisch auf ihn zu. Wenn der Sprite auf eine Wand oder ein Objekt tirfft gibt es noch ein zweites Problem. Er läuft einfach da hinter. Das wollen wir jetzt zu erst ändern. Der Sprite soll seine Richtung ändern, wenn er auf eine Wand trifft. ($157C,x)
Eigentlich ist es ganz leicht, man braucht an sich nur den Bewegeungsstatus von Mario $77.
xxxxUDLR (UDLR steht für Up, Down, Left, Right)
Nomalerweise schaut man ob Mario eine Wand berührt in dem man schaut ob Links und Rechts 1 sind. Wir schauen einfach mit Hilfe von AND #$03 ob das erste und das zweite Bit 1 sind. (Erinnerung man zählt Bits von hinten) Für Mario würde das ganze so aussehen:
Code
LDA $77
AND #$03
BEQ NotTouchingWall
; hier weiter wenn Wand berührt wird.

Für den Sprite funktioniert das quasi genau gleich:
Code
LDA $1588,x
AND #$03
BEQ NotTouchingWall
; hier weiter wenn Wand berührt wird.

Wenn der Sprite eine Wand berührt machen wir das mit Hilfe folgenden Codes:
Code
LDA $157C,x
EOR #$01
STA $157C,x

Mit diesem Codeteil sollte der komplette Code nun so oder so ähnlich aussehen:
Code
dcb "INIT"
RTL

dcb "MAIN"

PHB ;\
PHK ; | Change the data bank to the one our code is running from.
PLB ; | This is a good practice.
JSR SpriteCode ; | Jump to the sprite's function.
PLB ; | Restore old data bank.
RTL ;/ And return.

;===================================
;Sprite Function
;===================================

RETURN: RTS

SpriteCode:
JSR Graphics

LDA $14C8,x ;\
CMP #$08 ; | If sprite dead,
BNE RETURN ;/ Return.
LDA $9D ;\
BNE RETURN ;/ If locked, return.

JSR SUB_OFF_SCREEN_X0 ; Handle offscreen.

LDA $1588,x ; ... if sprite is touching a wall.
AND #$03 ; (bit 0 and 1)..
BEQ DontFlip ; If bits aren't set, don't flip the sprite.

LDA $157C,x ;\
EOR #$01 ; | Flip sprite's direction when touching wall.
STA $157C,x ;/

DontFlip:
LDY $157C,x ;\ Get direction into Y.
LDA XSPEED,y ; | Load X speed indexed by Y.
STA $B6,x ;/ Store to X speed table.
LDA #$10 ;\ Y Speed for sprite on ground
STA $AA,x ;/ = 10.
JSL $01802A ; Apply speed.
RTS

XSPEED: dcb $08,$F8

;===================================
;Graphics Code
;===================================

TILEMAP: dcb $CA,$CC

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

PHX
LDA $14
LSR
LSR
LSR
AND #$01
TAX
LDA TILEMAP,x
STA $0302,y
PLX

LDA #$07
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 ; /

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; SUB_OFF_SCREEN
; This subroutine deals with sprites that have moved off screen
; It is adapted from the subroutine at $01AC0D
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

SPR_T12: db $40,$B0
SPR_T13: db $01,$FF
SPR_T14: db $30,$C0,$A0,$C0,$A0,$F0,$60,$90 ;bank 1 sizes
db $30,$C0,$A0,$80,$A0,$40,$60,$B0 ;bank 3 sizes
SPR_T15: db $01,$FF,$01,$FF,$01,$FF,$01,$FF ;bank 1 sizes
db $01,$FF,$01,$FF,$01,$00,$01,$FF ;bank 3 sizes

SUB_OFF_SCREEN_X1: LDA #$02 ; \ entry point of routine determines value of $03
BRA STORE_03 ; | (table entry to use on horizontal levels)
SUB_OFF_SCREEN_X2: LDA #$04 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X3: LDA #$06 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X4: LDA #$08 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X5: LDA #$0A ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X6: LDA #$0C ; |
BRA STORE_03 ; | OMG YOU FOUND THIS HIDDEN z0mg place!111 you win a cookie!
SUB_OFF_SCREEN_X7: LDA #$0E ; |
STORE_03: STA $03 ; |
BRA START_SUB ; |
SUB_OFF_SCREEN_X0: STZ $03 ; /

START_SUB: JSR SUB_IS_OFF_SCREEN ; \ if sprite is not off screen, return
BEQ RETURN_35 ; /
LDA $5B ; \ goto VERTICAL_LEVEL if vertical level
AND #$01 ; |
BNE VERTICAL_LEVEL ; /
LDA $D8,x ; \
CLC ; |
ADC #$50 ; | if the sprite has gone off the bottom of the level...
LDA $14D4,x ; | (if adding 0x50 to the sprite y position would make the high byte >= 2)
ADC #$00 ; |
CMP #$02 ; |
BPL ERASE_SPRITE ; / ...erase the sprite
LDA $167A,x ; \ if "process offscreen" flag is set, return
AND #$04 ; |
BNE RETURN_35 ; /
LDA $13 ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0756 VC:176 00 FL:205
AND #$01 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0780 VC:176 00 FL:205
ORA $03 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0796 VC:176 00 FL:205
STA $01 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0820 VC:176 00 FL:205
TAY ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0844 VC:176 00 FL:205
LDA $1A ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0858 VC:176 00 FL:205
CLC ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0882 VC:176 00 FL:205
ADC SPR_T14,y ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0896 VC:176 00 FL:205
ROL $00 ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizcHC:0928 VC:176 00 FL:205
CMP $E4,x ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:0966 VC:176 00 FL:205
PHP ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:0996 VC:176 00 FL:205
LDA $1B ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdizCHC:1018 VC:176 00 FL:205
LSR $00 ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdiZCHC:1042 VC:176 00 FL:205
ADC SPR_T15,y ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdizcHC:1080 VC:176 00 FL:205
PLP ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F0 P:eNvMXdizcHC:1112 VC:176 00 FL:205
SBC $14E0,x ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1140 VC:176 00 FL:205
STA $00 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:1172 VC:176 00 FL:205
LSR $01 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:1196 VC:176 00 FL:205
BCC SPR_L31 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZCHC:1234 VC:176 00 FL:205
EOR #$80 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZCHC:1250 VC:176 00 FL:205
STA $00 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1266 VC:176 00 FL:205
SPR_L31: LDA $00 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1290 VC:176 00 FL:205
BPL RETURN_35 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1314 VC:176 00 FL:205
ERASE_SPRITE: LDA $14C8,x ; \ if sprite status < 8, permanently erase sprite
CMP #$08 ; |
BCC KILL_SPRITE ; /
LDY $161A,x ;A:FF08 X:0007 Y:0001 D:0000 DB:01 S:01F3 P:envMXdiZCHC:1108 VC:059 00 FL:2878
CPY #$FF ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdiZCHC:1140 VC:059 00 FL:2878
BEQ KILL_SPRITE ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdizcHC:1156 VC:059 00 FL:2878
LDA #$00 ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdizcHC:1172 VC:059 00 FL:2878
STA $1938,y ;A:FF00 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdiZcHC:1188 VC:059 00 FL:2878
KILL_SPRITE: STZ $14C8,x ; erase sprite
RETURN_35: RTS ; return

VERTICAL_LEVEL: LDA $167A,x ; \ if "process offscreen" flag is set, return
AND #$04 ; |
BNE RETURN_35 ; /
LDA $13 ; \
LSR A ; |
BCS RETURN_35 ; /
LDA $E4,x ; \
CMP #$00 ; | if the sprite has gone off the side of the level...
LDA $14E0,x ; |
SBC #$00 ; |
CMP #$02 ; |
BCS ERASE_SPRITE ; / ...erase the sprite
LDA $13 ;A:0000 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:1218 VC:250 00 FL:5379
LSR A ;A:0016 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1242 VC:250 00 FL:5379
AND #$01 ;A:000B X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1256 VC:250 00 FL:5379
STA $01 ;A:0001 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1272 VC:250 00 FL:5379
TAY ;A:0001 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1296 VC:250 00 FL:5379
LDA $1C ;A:001A X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0052 VC:251 00 FL:5379
CLC ;A:00BD X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0076 VC:251 00 FL:5379
ADC SPR_T12,y ;A:00BD X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0090 VC:251 00 FL:5379
ROL $00 ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:enVMXdizCHC:0122 VC:251 00 FL:5379
CMP $D8,x ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0160 VC:251 00 FL:5379
PHP ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0190 VC:251 00 FL:5379
LDA $001D ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F2 P:eNVMXdizcHC:0212 VC:251 00 FL:5379
LSR $00 ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:enVMXdiZcHC:0244 VC:251 00 FL:5379
ADC SPR_T13,y ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:enVMXdizCHC:0282 VC:251 00 FL:5379
PLP ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:envMXdiZCHC:0314 VC:251 00 FL:5379
SBC $14D4,x ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0342 VC:251 00 FL:5379
STA $00 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0374 VC:251 00 FL:5379
LDY $01 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0398 VC:251 00 FL:5379
BEQ SPR_L38 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0422 VC:251 00 FL:5379
EOR #$80 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0438 VC:251 00 FL:5379
STA $00 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0454 VC:251 00 FL:5379
SPR_L38: LDA $00 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0478 VC:251 00 FL:5379
BPL RETURN_35 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0502 VC:251 00 FL:5379
BMI ERASE_SPRITE ;A:8AFF X:0002 Y:0000 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0704 VC:184 00 FL:5490

SUB_IS_OFF_SCREEN: LDA $15A0,x ; \ if sprite is on screen, accumulator = 0
ORA $186C,x ; |
RTS ; / return

Wenn wir den Sprite jetzt testen wird er die Richtung ändern, sobald er auf ein Objekt trifft. Dafür haben wir jetzt ein neues Problem. Der Sprite schaut die ganze Zeit in eine Richtung. Das wollen wir natürlich nicht. Das bedeutet also, dass wenn der Sprite nach rechts läuft er einmal an der X-Achse gespiegelt werden soll. Dafür gehen wir noch ein mal ganz weit zurück.
YXPPCCT

Das X-Bit ist für die Spiegelung an der X-Achse zuständig.
Sucht im Code nun mal die Stelle mit folgendem Inhalt:
Code
LDA #$07
ORA $64
STA $0303,y

Wir wollen ja, dass das X-Bit gesetzt ist. Dafür können wir theoretisch einfach #$40 addieren. Also dann 47. Dann müssen wir aber auch noch 47 oder 7 (47 für rechts und 7 für links) in $0303,y laden.

Es gibt zwei Möglichkeiten das ganze zu machen. Hierbei muss aber beachtet werden, das wir NICHT das X-Register verwenden können da LDX $157C,x keine Gültigkeit besitzt.

Möglichkeit 1:
Wir benutzen das Y-Register aber erst müssen wir das Y-Register irgendwo speichern. Dies müssen wir tun, da es schon in der OAM benutzt wird!
Möglichkeit 2:
Wir speichern $157C an einer freien Stelle der RAM und benutzten so dann das X-Register.

Die erste Methode mag einfacher wirken, die zweite biete im Nachhinein aber ein paar Vorteile.
Für die erste Methode schreiben wir folgenden Code:
Code
PHY
LDY $157C,x
LDA PROPERTIES,y
PLY
ORA $64
STA $0303,y
PROPERTIES: dcb $47,$07

Das PHY ist hier wie schon gesagt nötig, da das Y-Register immer noch den Wert der OAM enthält. Danach wird die Tabelle geladen, welche hier PROPERTIES heißt. Die Tabelle hat entweder den Wert 47 oder 07. Sie hat den Wert 47 wenn der Sprite nach rechts schaut, woraufhin die Grafiken an der X-Achse gespiegelt werden und 07 wenn der Sprite nach links läuft und dabei werden die Grafiken dann nicht an der X-Achse gespiegelt. Vor dem speichern MUSS ein PLY stehen, da wir für die OAM ja auch das Y-Register brauchen, bzw. gebraucht haben. Das war dann schon die erste Möglichkeit.

Für die zweite Möglichkeit ist es von die Richtung in $02 zu speichern. Dann können wir $02 im X-Register benutzen, eine Tabelle machen und das in $0303,y speichern. Das ist dann ungefähr das gleiche was wir bei der ersten Methode gemacht haben. Der ganze Code steht direkt nach dem Sprungbefehl für GET_DRAW_IFNO. Also so:
Code
JSR GET_DRAW_INFO
LDA $157C,x
STA $02
; Rest des Codes dann einfach immer hier dran

Nun können wir das X-Register benutzten. Erfolg!!! Wir müssen hierbei bedenken, dass wir aber das X-Register auch schon benutzen nämlich als Spritetabelle. Deswegen brauchen wir auch zu Anfang wieder ein PHX.
Code
PHX
LDX $02
LDA PROPERTIES,x
PLx
ORA $64
STA $0303,y
PROPERTIES: dcb $47,$07

Nun kommen wir zu den oben erwähnten kleinen Vorteilen der 2. Methode. Ich werde sie nicht einzeln erläutern. Schaut euch dazu bitte das original Tutorial an.
Die kurze Erklärung wäre:
Braucht man das X-Register mehrmals und dabei wird keine Spritetabelle benutzt so kann man sich ein paar Zeilen Code sparen, da ein PLX und PHX wegfallen. Kann man einfach verbiden.
Aus PHX CODE PLX PHX CODE PLX wird dann ein PHX CODE CODE PLX. Dies macht den Code einfach effizienter.

Der Code sollte nun ungefähr so aussehen: (die größten Veränderungen gibt es in der Graphics Routine)
Code
dcb "INIT"
RTL

dcb "MAIN"

PHB ;\
PHK ; | Change the data bank to the one our code is running from.
PLB ; | This is a good practice.
JSR SpriteCode ; | Jump to the sprite's function.
PLB ; | Restore old data bank.
RTL ;/ And return.

;===================================
;Sprite Function
;===================================

RETURN: RTS

SpriteCode:
JSR Graphics

LDA $14C8,x ;\
CMP #$08 ; | If sprite dead,
BNE RETURN ;/ Return.
LDA $9D ;\
BNE RETURN ;/ If locked, return.

JSR SUB_OFF_SCREEN_X0 ; Handle offscreen.

LDA $1588,x ; ... if sprite is touching a wall.
AND #$03 ; (bit 0 and 1)..
BEQ DontFlip ; If bits aren't set, don't flip the sprite.

LDA $157C,x ;\
EOR #$01 ; | Flip sprite's direction when touching wall.
STA $157C,x ;/

DontFlip:
LDY $157C,x ;\ Get direction into Y.
LDA XSPEED,y ; | Load X speed indexed by Y.
STA $B6,x ;/ Store to X speed table.
LDA #$10 ;\ Y Speed for sprite on ground
STA $AA,x ;/ = 10.
JSL $01802A ; Apply speed.

JSL $018032 ; Make the sprite interact with others. Make sure the .cfg setting for this
; is off!
RTS

XSPEED: dcb $08,$F8

;===================================
;Graphics Code
;===================================

TILEMAP: dcb $CA,$CC

PROPERTIES: dcb $47,$07 ; NOTE: Flip the X direction when going right.

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 $157C,x
STA $02 ; Store direction to $02 for use with property routine later.

LDA $00 ;\
STA $0300,y ;/ Draw the X position of the sprite

LDA $01 ;\
STA $0301,y ;/ Draw the Y position of the sprite

PHX
LDA $14
LSR
LSR
LSR
AND #$01
TAX
LDA TILEMAP,x
STA $0302,y

LDX $02 ; We already pushed X, so we don't need to push it again.
LDA PROPERTIES,x ; Get properties indexed by direction ..
ORA $64
STA $0303,y ; Store to properties byte.
PLX ; Pull back X.

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 ; /

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; SUB_OFF_SCREEN
; This subroutine deals with sprites that have moved off screen
; It is adapted from the subroutine at $01AC0D
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

SPR_T12: db $40,$B0
SPR_T13: db $01,$FF
SPR_T14: db $30,$C0,$A0,$C0,$A0,$F0,$60,$90 ;bank 1 sizes
db $30,$C0,$A0,$80,$A0,$40,$60,$B0 ;bank 3 sizes
SPR_T15: db $01,$FF,$01,$FF,$01,$FF,$01,$FF ;bank 1 sizes
db $01,$FF,$01,$FF,$01,$00,$01,$FF ;bank 3 sizes

SUB_OFF_SCREEN_X1: LDA #$02 ; \ entry point of routine determines value of $03
BRA STORE_03 ; | (table entry to use on horizontal levels)
SUB_OFF_SCREEN_X2: LDA #$04 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X3: LDA #$06 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X4: LDA #$08 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X5: LDA #$0A ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X6: LDA #$0C ; |
BRA STORE_03 ; | OMG YOU FOUND THIS HIDDEN z0mg place!111 you win a cookie!
SUB_OFF_SCREEN_X7: LDA #$0E ; |
STORE_03: STA $03 ; |
BRA START_SUB ; |
SUB_OFF_SCREEN_X0: STZ $03 ; /

START_SUB: JSR SUB_IS_OFF_SCREEN ; \ if sprite is not off screen, return
BEQ RETURN_35 ; /
LDA $5B ; \ goto VERTICAL_LEVEL if vertical level
AND #$01 ; |
BNE VERTICAL_LEVEL ; /
LDA $D8,x ; \
CLC ; |
ADC #$50 ; | if the sprite has gone off the bottom of the level...
LDA $14D4,x ; | (if adding 0x50 to the sprite y position would make the high byte >= 2)
ADC #$00 ; |
CMP #$02 ; |
BPL ERASE_SPRITE ; / ...erase the sprite
LDA $167A,x ; \ if "process offscreen" flag is set, return
AND #$04 ; |
BNE RETURN_35 ; /
LDA $13 ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0756 VC:176 00 FL:205
AND #$01 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0780 VC:176 00 FL:205
ORA $03 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0796 VC:176 00 FL:205
STA $01 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0820 VC:176 00 FL:205
TAY ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0844 VC:176 00 FL:205
LDA $1A ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0858 VC:176 00 FL:205
CLC ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0882 VC:176 00 FL:205
ADC SPR_T14,y ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0896 VC:176 00 FL:205
ROL $00 ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizcHC:0928 VC:176 00 FL:205
CMP $E4,x ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:0966 VC:176 00 FL:205
PHP ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:0996 VC:176 00 FL:205
LDA $1B ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdizCHC:1018 VC:176 00 FL:205
LSR $00 ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdiZCHC:1042 VC:176 00 FL:205
ADC SPR_T15,y ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdizcHC:1080 VC:176 00 FL:205
PLP ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F0 P:eNvMXdizcHC:1112 VC:176 00 FL:205
SBC $14E0,x ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1140 VC:176 00 FL:205
STA $00 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:1172 VC:176 00 FL:205
LSR $01 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:1196 VC:176 00 FL:205
BCC SPR_L31 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZCHC:1234 VC:176 00 FL:205
EOR #$80 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZCHC:1250 VC:176 00 FL:205
STA $00 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1266 VC:176 00 FL:205
SPR_L31: LDA $00 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1290 VC:176 00 FL:205
BPL RETURN_35 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1314 VC:176 00 FL:205
ERASE_SPRITE: LDA $14C8,x ; \ if sprite status < 8, permanently erase sprite
CMP #$08 ; |
BCC KILL_SPRITE ; /
LDY $161A,x ;A:FF08 X:0007 Y:0001 D:0000 DB:01 S:01F3 P:envMXdiZCHC:1108 VC:059 00 FL:2878
CPY #$FF ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdiZCHC:1140 VC:059 00 FL:2878
BEQ KILL_SPRITE ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdizcHC:1156 VC:059 00 FL:2878
LDA #$00 ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdizcHC:1172 VC:059 00 FL:2878
STA $1938,y ;A:FF00 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdiZcHC:1188 VC:059 00 FL:2878
KILL_SPRITE: STZ $14C8,x ; erase sprite
RETURN_35: RTS ; return

VERTICAL_LEVEL: LDA $167A,x ; \ if "process offscreen" flag is set, return
AND #$04 ; |
BNE RETURN_35 ; /
LDA $13 ; \
LSR A ; |
BCS RETURN_35 ; /
LDA $E4,x ; \
CMP #$00 ; | if the sprite has gone off the side of the level...
LDA $14E0,x ; |
SBC #$00 ; |
CMP #$02 ; |
BCS ERASE_SPRITE ; / ...erase the sprite
LDA $13 ;A:0000 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:1218 VC:250 00 FL:5379
LSR A ;A:0016 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1242 VC:250 00 FL:5379
AND #$01 ;A:000B X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1256 VC:250 00 FL:5379
STA $01 ;A:0001 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1272 VC:250 00 FL:5379
TAY ;A:0001 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1296 VC:250 00 FL:5379
LDA $1C ;A:001A X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0052 VC:251 00 FL:5379
CLC ;A:00BD X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0076 VC:251 00 FL:5379
ADC SPR_T12,y ;A:00BD X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0090 VC:251 00 FL:5379
ROL $00 ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:enVMXdizCHC:0122 VC:251 00 FL:5379
CMP $D8,x ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0160 VC:251 00 FL:5379
PHP ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0190 VC:251 00 FL:5379
LDA $001D ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F2 P:eNVMXdizcHC:0212 VC:251 00 FL:5379
LSR $00 ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:enVMXdiZcHC:0244 VC:251 00 FL:5379
ADC SPR_T13,y ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:enVMXdizCHC:0282 VC:251 00 FL:5379
PLP ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:envMXdiZCHC:0314 VC:251 00 FL:5379
SBC $14D4,x ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0342 VC:251 00 FL:5379
STA $00 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0374 VC:251 00 FL:5379
LDY $01 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0398 VC:251 00 FL:5379
BEQ SPR_L38 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0422 VC:251 00 FL:5379
EOR #$80 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0438 VC:251 00 FL:5379
STA $00 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0454 VC:251 00 FL:5379
SPR_L38: LDA $00 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0478 VC:251 00 FL:5379
BPL RETURN_35 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0502 VC:251 00 FL:5379
BMI ERASE_SPRITE ;A:8AFF X:0002 Y:0000 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0704 VC:184 00 FL:5490

SUB_IS_OFF_SCREEN: LDA $15A0,x ; \ if sprite is on screen, accumulator = 0
ORA $186C,x ; |
RTS ; / return

So. Hiermit haben wir nun das erste von 3 Problemen gelöst. Kommen wir zum nächsten der drei.
$1588,x gibt uns die Richtung des Sprites oder das Zusammenspiel mit einem Objekt an. Das 2 Bit (#$04) ist gesetzt, wenn der Sprite den Boden berührt.Wir dürfen hier nun aber nicht die Geschwindigkeit des Sprites speichern/verändern, wenn der Sprite gerade in der Luft ist. Dies würde die Gravitation des Sprites beeinflussen.
Wir werden nun dieses Problem mit ein paar Zeilen Code beheben. In unserem ASM Code steht:
Code
DontFlip:
LDY $157C,x
LDA XSPEED,y
STA $B6,x
; und so weiter

Wir überprüfen nun einfach bevor wir $157C,x laden, ob der Sprite auf dem Boden ist oder nicht. Ist er nicht auf dem Boden, so ist das Bit nicht gesetzt.
Code
DontFlip:
LDA $1588,x
AND #$04
BEQ NotOnGround
LDY $157C,x
LDA XSPEED,y
STA $B6,x
LDA #$10
STA $AA,x
NotOnGround:
JSL $01802A ; Update der Position des Sprites
JSL $018030

Damit sollte der komplette Code nun so aussehen:
Code
dcb "INIT"
RTL

dcb "MAIN"

PHB ;\
PHK ; | Change the data bank to the one our code is running from.
PLB ; | This is a good practice.
JSR SpriteCode ; | Jump to the sprite's function.
PLB ; | Restore old data bank.
RTL ;/ And return.

;===================================
;Sprite Function
;===================================

RETURN: RTS

SpriteCode:
JSR Graphics

LDA $14C8,x ;\
CMP #$08 ; | If sprite dead,
BNE RETURN ;/ Return.
LDA $9D ;\
BNE RETURN ;/ If locked, return.

JSR SUB_OFF_SCREEN_X0 ; Handle offscreen.

LDA $1588,x ; ... if sprite is touching a wall.
AND #$03 ; (bit 0 and 1)..
BEQ DontFlip ; If bits aren't set, don't flip the sprite.

LDA $157C,x ;\
EOR #$01 ; | Flip sprite's direction when touching wall.
STA $157C,x ;/

DontFlip:
LDA $1588,x
AND #$04 ; If the sprite is in air ..
BEQ NotOnGround ; Don't set speeds and branch.

LDY $157C,x ;\ Get direction into Y.
LDA XSPEED,y ; | Load X speed indexed by Y.
STA $B6,x ;/ Store to X speed table.
LDA #$10 ;\ Y Speed for sprite on ground
STA $AA,x ;/ = 10.

NotOnGround:
JSL $01802A ; Apply speed.
JSL $018032 ; Make the sprite interact with others. Make sure the .cfg setting for this
; is off!
RTS

XSPEED: dcb $08,$F8

;===================================
;Graphics Code
;===================================

TILEMAP: dcb $CA,$CC

PROPERTIES: dcb $47,$07 ; NOTE: Flip the X direction when going right.

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 $157C,x
STA $02 ; Store direction to $02 for use with property routine later.

LDA $00 ;\
STA $0300,y ;/ Draw the X position of the sprite

LDA $01 ;\
STA $0301,y ;/ Draw the Y position of the sprite

PHX
LDA $14
LSR
LSR
LSR
AND #$01
TAX
LDA TILEMAP,x
STA $0302,y

LDX $02 ; We already pushed X, so we don't need to push it again.
LDA PROPERTIES,x ; Get properties indexed by direction ..
ORA $64
STA $0303,y ; Store to properties byte.
PLX ; Pull back X.

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 ; /

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; SUB_OFF_SCREEN
; This subroutine deals with sprites that have moved off screen
; It is adapted from the subroutine at $01AC0D
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

SPR_T12: db $40,$B0
SPR_T13: db $01,$FF
SPR_T14: db $30,$C0,$A0,$C0,$A0,$F0,$60,$90 ;bank 1 sizes
db $30,$C0,$A0,$80,$A0,$40,$60,$B0 ;bank 3 sizes
SPR_T15: db $01,$FF,$01,$FF,$01,$FF,$01,$FF ;bank 1 sizes
db $01,$FF,$01,$FF,$01,$00,$01,$FF ;bank 3 sizes

SUB_OFF_SCREEN_X1: LDA #$02 ; \ entry point of routine determines value of $03
BRA STORE_03 ; | (table entry to use on horizontal levels)
SUB_OFF_SCREEN_X2: LDA #$04 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X3: LDA #$06 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X4: LDA #$08 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X5: LDA #$0A ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X6: LDA #$0C ; |
BRA STORE_03 ; | OMG YOU FOUND THIS HIDDEN z0mg place!111 you win a cookie!
SUB_OFF_SCREEN_X7: LDA #$0E ; |
STORE_03: STA $03 ; |
BRA START_SUB ; |
SUB_OFF_SCREEN_X0: STZ $03 ; /

START_SUB: JSR SUB_IS_OFF_SCREEN ; \ if sprite is not off screen, return
BEQ RETURN_35 ; /
LDA $5B ; \ goto VERTICAL_LEVEL if vertical level
AND #$01 ; |
BNE VERTICAL_LEVEL ; /
LDA $D8,x ; \
CLC ; |
ADC #$50 ; | if the sprite has gone off the bottom of the level...
LDA $14D4,x ; | (if adding 0x50 to the sprite y position would make the high byte >= 2)
ADC #$00 ; |
CMP #$02 ; |
BPL ERASE_SPRITE ; / ...erase the sprite
LDA $167A,x ; \ if "process offscreen" flag is set, return
AND #$04 ; |
BNE RETURN_35 ; /
LDA $13 ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0756 VC:176 00 FL:205
AND #$01 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0780 VC:176 00 FL:205
ORA $03 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0796 VC:176 00 FL:205
STA $01 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0820 VC:176 00 FL:205
TAY ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0844 VC:176 00 FL:205
LDA $1A ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0858 VC:176 00 FL:205
CLC ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0882 VC:176 00 FL:205
ADC SPR_T14,y ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0896 VC:176 00 FL:205
ROL $00 ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizcHC:0928 VC:176 00 FL:205
CMP $E4,x ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:0966 VC:176 00 FL:205
PHP ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:0996 VC:176 00 FL:205
LDA $1B ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdizCHC:1018 VC:176 00 FL:205
LSR $00 ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdiZCHC:1042 VC:176 00 FL:205
ADC SPR_T15,y ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdizcHC:1080 VC:176 00 FL:205
PLP ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F0 P:eNvMXdizcHC:1112 VC:176 00 FL:205
SBC $14E0,x ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1140 VC:176 00 FL:205
STA $00 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:1172 VC:176 00 FL:205
LSR $01 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:1196 VC:176 00 FL:205
BCC SPR_L31 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZCHC:1234 VC:176 00 FL:205
EOR #$80 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZCHC:1250 VC:176 00 FL:205
STA $00 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1266 VC:176 00 FL:205
SPR_L31: LDA $00 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1290 VC:176 00 FL:205
BPL RETURN_35 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1314 VC:176 00 FL:205
ERASE_SPRITE: LDA $14C8,x ; \ if sprite status < 8, permanently erase sprite
CMP #$08 ; |
BCC KILL_SPRITE ; /
LDY $161A,x ;A:FF08 X:0007 Y:0001 D:0000 DB:01 S:01F3 P:envMXdiZCHC:1108 VC:059 00 FL:2878
CPY #$FF ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdiZCHC:1140 VC:059 00 FL:2878
BEQ KILL_SPRITE ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdizcHC:1156 VC:059 00 FL:2878
LDA #$00 ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdizcHC:1172 VC:059 00 FL:2878
STA $1938,y ;A:FF00 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdiZcHC:1188 VC:059 00 FL:2878
KILL_SPRITE: STZ $14C8,x ; erase sprite
RETURN_35: RTS ; return

VERTICAL_LEVEL: LDA $167A,x ; \ if "process offscreen" flag is set, return
AND #$04 ; |
BNE RETURN_35 ; /
LDA $13 ; \
LSR A ; |
BCS RETURN_35 ; /
LDA $E4,x ; \
CMP #$00 ; | if the sprite has gone off the side of the level...
LDA $14E0,x ; |
SBC #$00 ; |
CMP #$02 ; |
BCS ERASE_SPRITE ; / ...erase the sprite
LDA $13 ;A:0000 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:1218 VC:250 00 FL:5379
LSR A ;A:0016 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1242 VC:250 00 FL:5379
AND #$01 ;A:000B X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1256 VC:250 00 FL:5379
STA $01 ;A:0001 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1272 VC:250 00 FL:5379
TAY ;A:0001 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1296 VC:250 00 FL:5379
LDA $1C ;A:001A X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0052 VC:251 00 FL:5379
CLC ;A:00BD X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0076 VC:251 00 FL:5379
ADC SPR_T12,y ;A:00BD X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0090 VC:251 00 FL:5379
ROL $00 ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:enVMXdizCHC:0122 VC:251 00 FL:5379
CMP $D8,x ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0160 VC:251 00 FL:5379
PHP ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0190 VC:251 00 FL:5379
LDA $001D ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F2 P:eNVMXdizcHC:0212 VC:251 00 FL:5379
LSR $00 ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:enVMXdiZcHC:0244 VC:251 00 FL:5379
ADC SPR_T13,y ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:enVMXdizCHC:0282 VC:251 00 FL:5379
PLP ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:envMXdiZCHC:0314 VC:251 00 FL:5379
SBC $14D4,x ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0342 VC:251 00 FL:5379
STA $00 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0374 VC:251 00 FL:5379
LDY $01 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0398 VC:251 00 FL:5379
BEQ SPR_L38 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0422 VC:251 00 FL:5379
EOR #$80 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0438 VC:251 00 FL:5379
STA $00 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0454 VC:251 00 FL:5379
SPR_L38: LDA $00 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0478 VC:251 00 FL:5379
BPL RETURN_35 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0502 VC:251 00 FL:5379
BMI ERASE_SPRITE ;A:8AFF X:0002 Y:0000 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0704 VC:184 00 FL:5490

SUB_IS_OFF_SCREEN: LDA $15A0,x ; \ if sprite is on screen, accumulator = 0
ORA $186C,x ; |
RTS ; / return
geschrieben am 24.02.2013 17:39:05
zuletzt bearbeitet von kooooopa am 24.02.2013 17:43:14.
( Link )
Damit haben wir das 2. unserer 3 Probleme gelöst. Bleibt nur noch 1!
Testet man den Bob-Omb mit dem Code den wir gerade eben geschrieben haben, wir der Bob-Omb an sich Einwand frei funktioniert. Platziert man nun aber rechts und links von Mario einen Bob-Omb, so wird man feststellen, dass ein Bob-Omb NICHT auf Mario zuläuft.
Mit dem folgenden Code sorgen wir dann dafür, dass der Sprite wenn er das erste mal in den Screen kommt immer auf Mario zu läuft. Dafür werden wir etwas Code in der INIT Routine hinzufügen.
Dabei wird uns noch eine andere Routine helfen, welche die SUB_HORZ_POS genannt wird. Diese Routine bestimmt die Richtung des Sprites in Abhängigkeit von Marios Richtung und speichert das ganze dann im Y-Register. Schaut Mario nach Rechts, so wird auch die Sprite Richtung rechts sein.
Diese Routine wird wie immer einfach hinten an den Code angehängt und einmalig durch einen Sprungbefehl aufgerufen. Hier mal die komplette Routine:
Code
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; SUB HORZ POS
;
; $B817 - horizontal mario/sprite check - shared
; Y = 1 if mario left of sprite??
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;org $03B817

SUB_HORZ_POS: LDY #$00 ;A:25D0 X:0006 Y:0001 D:0000 DB:03 S:01ED P:eNvMXdizCHC:1020 VC:097 00 FL:31642
LDA $94 ;A:25D0 X:0006 Y:0000 D:0000 DB:03 S:01ED P:envMXdiZCHC:1036 VC:097 00 FL:31642
SEC ;A:25F0 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizCHC:1060 VC:097 00 FL:31642
SBC $E4,x ;A:25F0 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizCHC:1074 VC:097 00 FL:31642
STA $0F ;A:25F4 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1104 VC:097 00 FL:31642
LDA $95 ;A:25F4 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1128 VC:097 00 FL:31642
SBC $14E0,x ;A:2500 X:0006 Y:0000 D:0000 DB:03 S:01ED P:envMXdiZcHC:1152 VC:097 00 FL:31642
BPL LABEL16 ;A:25FF X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1184 VC:097 00 FL:31642
INY ;A:25FF X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1200 VC:097 00 FL:31642
LABEL16: RTS ;A:25FF X:0006 Y:0001 D:0000 DB:03 S:01ED P:envMXdizcHC:1214 VC:097 00 FL:31642

Im Vergleich zu den anderen ist diese nun relativ klein. Die Routine hört mit einem RTS auf, was bedeutet, wir müssen sie mit einem JSR aufrufen. Das Y Register enthält ja nun die Richtung des Sprites in Abhängigkeit von Mario. Nun können wir dies einfach von Y in A verscgieben und in der Richtungstabelle, welche wir früher ja schon erstellt haben speichern. ($157C,x) Es ist kein direktes Speichern in der Tabelle möglich, da STY $157C,x nicht existiert.
Wir schreiben nun einfach folgenden Code noch an den Anfang:
Code
dcb “INIT“
JSR SUB_HORZ_POS ; Aufruf der Routine
TYA ; Verschieben von Y nach A
STA $157C,x ; Speichern in der Tabelle
RTL

Damit sind wir nun auch am Ende dieses Kapitels angekommen. Testet man den Sprite jetzt wird alles wie geplant funktionieren und der Sprite richtig herumlaufen.
Glückwunsch, wir haben unseren zweiten Sprite in einer ansehbaren Form und es funktioniert alles. Damit sind wir nicht nur mit diesem Kapitel fertig, sondern auch mit den Grundlagen der Sprite Programmierung. Alles was jetzt folgt ( Interaktion mit Mario, 32*32 Sprites, Spawnen von Sprites durch Sprites ) sind schon wesentlich komplexere Themen.



Kapitel 9: Interaktion mit Mario

Wir werden hier weiter mit unserem Bob-Omb Sprite arbeiten. Jetzt werden wir dafür sorgen, dass der Bob-Omb nicht nur mit Objekten, sondern auch mit Mario interagiert.
Ich habe vorhin doch erwähnt, dass JSL $01A7DC überprüft ob Mario mit dem Sprite interagiert, oder nicht. BBC NoContact (oder wie ihr es halt genannt habt) bedeutete, dass es keinen Kontakt gab. Ich bin mir aber sicher, dass ihr in manchen Sprites das ganze auch schon direkt mit einem RTS gesehen habt. Also so:
Code
JSL $01A7DC
RTS

Da ist kein BBC, was überprüft ob es einen Kontakt gibt oder nicht. Wie wird dass denn dann da gemacht?
Um das zu klären müssen wir noch mal viele Kapitel zurück. Erinnert ihr euch daran, was ich beim cfg-Editor über “do not use default interaction with Mario“ gesagt habe? Mit dieser Einstellung wird Mario vom Sprite verletzt, wenn er ihn von der Seite berührt, kann auf ihn drauf springen (ihn dabei aber nicht töten) und halt die kleinen Bonusdinge, die man noch bei $1656 einstellen kann. Default Interaction bedeutet also, dass der Sprite die gewöhnliche Einstellung der smw Sprites (zumindest vieler smw Sprites) an nimt.
Normalerweise möchte man aber ja, dass etwas anderes mit Mario passiert, wenn er den Sprite berührt. Wir möchten hier also unseren eigenen Code haben, der dann z.B. dafür sorgt, dass Mario weg gestoßen wird, etc.
Macht man bei “default interaction with Mario“ also den Hacken weg, so wird sofort zu JSL $01A7DC gesprungen.
Wer also seine eigene Interaktion mit Mario haben möchte macht den Hacken im CFG-Editor weg und fügt unter das JSL $01A7DC ein BBC Label, welches zu Return führt, sollte kein Kontakt vorhanden sein.
Für dieses Kapitel muss im CFG-Editor der Hacken weg sein. Wir wollen schließlich unsere eigene Interaktion erstellen.
Direkt hinter JSL $018032 muss folgendes in die ASM Datei:
Code
JSL $01A7DC
BBC NoContact ;oder wie ihr es halt nennen wollt

Dieses NoContact Label sollte natürlich zu einem RTS führen da kein Kontakt besteht und schreibt doch einfach mal das als Code wenn es einen Kontakt gibt:
Code
LDA  #$02			;\
STA $19 ;/ Ja Mario kriegt eine Feder wenn er den Sprite berührt
NoContact ; Oder wie es halt bei euch heißt
RTS

Jetzt können wir den Sprite schon wieder testen und ihr werdet feststellen, dass sobald Mario ihn berührt ihr eine Feder erhaltet. Das ist natürlich nicht dass, was unser Sprite machen soll. Daher könnt ihr diese Codeteil eigentlich auch wieder löschen um fortzufahren

Wir wollen, dass unser Bob-Omb:
- Mario verletzt (wenn er klein ist getötet) wird sollte er den Sprite von der Seite berühren
- Mario den Sprite tötet sollte er von oben auf den Sprite springen
Es gibt eine relativ komfortable Lösung für die Frage, ob Mario den Sprite von oben berührt. Nach dem $01A7DC aufgerufen wurde beinhaltet $0E Marios Y-Position. Ist diese größer als E6 wird der Sprite Mario verletzen. Dies können wir also als einfache Überprüfung verwenden, ob Mario den Sprite von oben berührt oder nicht.
Code
LDA  $0E
CMP #$E6
BPL SpriteWins ;Oder wie ihr es halt nennen wollt

Ist $0E nun also größer als E6 berührt Mario den Sprite von der Seite, wenn nicht, berührt Mario den Sprite von oben. Springen wir also nicht zu SpriteWins soll der Sprite getötet werden. Direkt nach das BPL SpriteWins muss ein RTS. Um den MarioWins Codeteil kümmern wir uns später.

Nun müssen wir als nächstes natürlich erst mal das Label SpriteWins erstellen. Beim Code ob Mario den Sprite dann von der Seite berührt müssen wir noch überprüfen ob Mario einen Stern hat (dann soll Mario ja nicht sterben).
Code
SpriteWins:				;Hier ist nun erst mal unser Label
LDA $1490 ;Überprüft ob Mario einen Stern hat
BNE HasStar ;Wenn Stern dann zu Label HasStar

Direkt danach können wir nun zur Hurt Routine (JSL $00F5B7) springen. Wenn ihr nicht wollt das Mario verletzt wird könnt ihr den Code hier natürlich anders gestalten. Denkt noch daran das RTS nicht zu vergessen, denn wir haben das Label SpriteWins damit abgeschlossen. Mehr Code muss da nicht hin.

Jetzt müssen wir uns noch um den Codeteil kümmern, der ausgeführt wird wenn Mario gewinnt, also MarioWins. (und natürlich noch was passiert wenn Mario den Stern hat) Wir kümmern uns zuerst um den Teil mit dem Stern. Da dieser Code häufig verwendet wird gibt es das ganze auch einfach als komplett Paket. (Man muss das Rad schließlich nicht neu erfinden) Einfach wie die ganzen anderen komplett Pakete behandeln (am besten vor die Graphics Routine packen)
Code
;===========================================
;Killed by Star Routine
;===========================================

HasStar:

LDA $167A,x ;\ Don't disable clipping when killed by a star.
ORA #$01 ; | NOTE: We do this to not make glitched graphics show up ..
STA $167A,x ; | when the sprite is killed by a star.
;/ You can also check this option in the .cfg editor.

LDA #$02 ;\ Set the sprite status to ..
STA $14C8,x ;/ Killed by a star.
LDA #$D0 ;\
STA $AA,x ;/ Set Y speed so sprite falls down.
INC $18D2 ; Increase number of consective enemies stomped/ killed by star.
LDA $18D2 ; IF consecutive # of enemies killed by star/stomped ..
CMP #$08 ; IS 8 ..
BCC NotEight
LDA #$08 ;\ Keep it static at 8, because you get 1-ups all the time afterwards.
STA $18D2 ;/
NotEight:
JSL $02ACE5 ; This code calls the "give points" routine.
LDY $18D2 ; Get consecutive # of enemies stomped in Y.
CPY #$08 ; If it's less than 8 once again, return.
BCC NoSound
LDA StarSFX,y ;\
STA $1DF9 ;/ Play sounds depending on how many enemies were stomped/killed by star.
NoSound:
RTS ; Return.

StarSFX: dcb $00,$13,$14,$15,$16,$17,$18,$19

Damit fehlt uns jetzt nur noch der MarioWinsCode. Wir müssen hier leider auf STZ $14C8,x verzichten, auch wenn wir es für unseren Pilzsprite benutzt haben. Dies würde nämlich den Sprite entfernen ihn aber nicht töten. Wir wollen das ganze etwas komplizierter. Wir wollen, dass ein Soundeffekt abgespielt wird, Kontaktgrafiken angezeigt werden (sieht einfach besser aus) und der Sprite dann aus dem Screen fällt. Dafür müssen wir jetzt erst mal das RTS hinter BPL SpriteWins entfernen, da da jetzt ja Code hin soll
Code
JSL $01AB99					; Anzeigen der Kontaktgrafiken
JSL $01AA33 ; Verhindert das Mario weiter runter fällt wenn er auf den Sprite trifft
LDA #$10 ;\
STA $AA,x ; |Sprite fällt runter wenn er getroffen wird
LDA #$02 ; |Wird von einem Stern getötet
STA $14C8,x ; |neue Y Geschwindigkeit
JSL $01802A ; |Abspielen des Soundeffektes
LDA #$13 ; |
STA $1DF9 ;/

Hier ist es nun mal wieder an der Zeit den kompletten Spritecode zu zeigen wie er ungefähr aussehen soll:
Code
dcb "INIT"
JSR SUB_HORZ_POS
TYA
STA $157C,x
RTL

dcb "MAIN"

PHB ;\
PHK ; | Change the data bank to the one our code is running from.
PLB ; | This is a good practice.
JSR SpriteCode ; | Jump to the sprite's function.
PLB ; | Restore old data bank.
RTL ;/ And return.

;===================================
;Sprite Function
;===================================

RETURN: RTS

SpriteCode:
JSR Graphics

LDA $14C8,x ;\
CMP #$08 ; | If sprite dead,
BNE RETURN ;/ Return.
LDA $9D ;\
BNE RETURN ;/ If locked, return.

JSR SUB_OFF_SCREEN_X0 ; Handle offscreen.

LDA $1588,x ; ... if sprite is touching a wall.
AND #$03 ; (bit 0 and 1)..
BEQ DontFlip ; If bits aren't set, don't flip the sprite.

LDA $157C,x ;\
EOR #$01 ; | Flip sprite's direction when touching wall.
STA $157C,x ;/

DontFlip:
LDA $1588,x
AND #$04 ; If the sprite is in air ..
BEQ NotOnGround ; Don't set speeds and branch.

LDY $157C,x ;\ Get direction into Y.
LDA XSPEED,y ; | Load X speed indexed by Y.
STA $B6,x ;/ Store to X speed table.
LDA #$10 ;\ Y Speed for sprite on ground
STA $AA,x ;/ = 10.

NotOnGround:
JSL $01802A ; Apply speed.
JSL $018032 ; Make the sprite interact with others. Make sure the .cfg setting for this
; is off!
JSL $01A7DC
BCC NoContact

LDA $0E ;\
CMP #$E6 ; | Determine if Mario touches sprite from sides.
BPL SpriteWins ;/ If he does, branch.

;===========================================
;Mario Hurts Sprite
;===========================================

JSL $01AA33 ;\ Set mario speed.
JSL $01AB99 ;/ Display contact graphic.
LDA #$10 ;\
STA $AA,x ; | Make sprite fall.
JSL $01802A ; |
LDA #$02 ; |
STA $14C8,x ;/
LDA #$13 ;\
STA $1DF9 ;/ Play sound.
RTS
;===========================================
;Sprite Hurts Mario
;===========================================

SpriteWins:
LDA $1490 ;\ IF Mario has star power ..
BNE HasStar ;/ Kill sprite.
JSL $00F5B7 ;\ Otherwise, hurt Mario.
NoContact:
RTS

XSPEED: dcb $08,$F8

;===========================================
;Killed by Star Routine
;===========================================

HasStar:

LDA $167A,x ;\ Don't disable clipping when killed by a star.
ORA #$01 ; | NOTE: We do this to not make glitched graphics show up ..
STA $167A,x ; | when the sprite is killed by a star.
;/ You can also check this option in the .cfg editor.

LDA #$02 ;\ Set the sprite status to ..
STA $14C8,x ;/ Killed by a star.
LDA #$D0 ;\
STA $AA,x ;/ Set Y speed so sprite falls down.
INC $18D2 ; Increase number of consective enemies stomped/ killed by star.
LDA $18D2 ; IF consecutive # of enemies killed by star/stomped ..
CMP #$08 ; IS 8 ..
BCC NotEight
LDA #$08 ;\ Keep it static at 8, because you get 1-ups all the time afterwards.
STA $18D2 ;/
NotEight:
JSL $02ACE5 ; This code calls the "give points" routine.
LDY $18D2 ; Get consecutive # of enemies stomped in Y.
CPY #$08 ; If it's less than 8 once again, return.
BCC NoSound
LDA StarSFX,y ;\
STA $1DF9 ;/ Play sounds depending on how many enemies were stomped/killed by star.
NoSound:
RTS ; Return.

StarSFX: dcb $00,$13,$14,$15,$16,$17,$18,$19
;===================================
;Graphics Code
;===================================

TILEMAP: dcb $CA,$CC

PROPERTIES: dcb $67,$27 ; NOTE: Flip the X direction when going right.

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 $157C,x
STA $02 ; Store direction to $02 for use with property routine later.

LDA $00 ;\
STA $0300,y ;/ Draw the X position of the sprite

LDA $01 ;\
STA $0301,y ;/ Draw the Y position of the sprite

PHX
LDA $14
LSR
LSR
LSR
AND #$01
TAX
LDA TILEMAP,x
STA $0302,y

LDX $02 ; We already pushed X, so we don't need to push it again.
LDA PROPERTIES,x ; Get properties indexed by direction ..
ORA $64
STA $0303,y ; Store to properties byte.
PLX ; Pull back X.

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 ; /

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; SUB_OFF_SCREEN
; This subroutine deals with sprites that have moved off screen
; It is adapted from the subroutine at $01AC0D
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

SPR_T12: db $40,$B0
SPR_T13: db $01,$FF
SPR_T14: db $30,$C0,$A0,$C0,$A0,$F0,$60,$90 ;bank 1 sizes
db $30,$C0,$A0,$80,$A0,$40,$60,$B0 ;bank 3 sizes
SPR_T15: db $01,$FF,$01,$FF,$01,$FF,$01,$FF ;bank 1 sizes
db $01,$FF,$01,$FF,$01,$00,$01,$FF ;bank 3 sizes

SUB_OFF_SCREEN_X1: LDA #$02 ; \ entry point of routine determines value of $03
BRA STORE_03 ; | (table entry to use on horizontal levels)
SUB_OFF_SCREEN_X2: LDA #$04 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X3: LDA #$06 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X4: LDA #$08 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X5: LDA #$0A ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X6: LDA #$0C ; |
BRA STORE_03 ; | OMG YOU FOUND THIS HIDDEN z0mg place!111 you win a cookie!
SUB_OFF_SCREEN_X7: LDA #$0E ; |
STORE_03: STA $03 ; |
BRA START_SUB ; |
SUB_OFF_SCREEN_X0: STZ $03 ; /

START_SUB: JSR SUB_IS_OFF_SCREEN ; \ if sprite is not off screen, return
BEQ RETURN_35 ; /
LDA $5B ; \ goto VERTICAL_LEVEL if vertical level
AND #$01 ; |
BNE VERTICAL_LEVEL ; /
LDA $D8,x ; \
CLC ; |
ADC #$50 ; | if the sprite has gone off the bottom of the level...
LDA $14D4,x ; | (if adding 0x50 to the sprite y position would make the high byte >= 2)
ADC #$00 ; |
CMP #$02 ; |
BPL ERASE_SPRITE ; / ...erase the sprite
LDA $167A,x ; \ if "process offscreen" flag is set, return
AND #$04 ; |
BNE RETURN_35 ; /
LDA $13 ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0756 VC:176 00 FL:205
AND #$01 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0780 VC:176 00 FL:205
ORA $03 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0796 VC:176 00 FL:205
STA $01 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0820 VC:176 00 FL:205
TAY ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0844 VC:176 00 FL:205
LDA $1A ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0858 VC:176 00 FL:205
CLC ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0882 VC:176 00 FL:205
ADC SPR_T14,y ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0896 VC:176 00 FL:205
ROL $00 ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizcHC:0928 VC:176 00 FL:205
CMP $E4,x ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:0966 VC:176 00 FL:205
PHP ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:0996 VC:176 00 FL:205
LDA $1B ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdizCHC:1018 VC:176 00 FL:205
LSR $00 ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdiZCHC:1042 VC:176 00 FL:205
ADC SPR_T15,y ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdizcHC:1080 VC:176 00 FL:205
PLP ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F0 P:eNvMXdizcHC:1112 VC:176 00 FL:205
SBC $14E0,x ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1140 VC:176 00 FL:205
STA $00 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:1172 VC:176 00 FL:205
LSR $01 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:1196 VC:176 00 FL:205
BCC SPR_L31 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZCHC:1234 VC:176 00 FL:205
EOR #$80 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZCHC:1250 VC:176 00 FL:205
STA $00 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1266 VC:176 00 FL:205
SPR_L31: LDA $00 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1290 VC:176 00 FL:205
BPL RETURN_35 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1314 VC:176 00 FL:205
ERASE_SPRITE: LDA $14C8,x ; \ if sprite status < 8, permanently erase sprite
CMP #$08 ; |
BCC KILL_SPRITE ; /
LDY $161A,x ;A:FF08 X:0007 Y:0001 D:0000 DB:01 S:01F3 P:envMXdiZCHC:1108 VC:059 00 FL:2878
CPY #$FF ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdiZCHC:1140 VC:059 00 FL:2878
BEQ KILL_SPRITE ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdizcHC:1156 VC:059 00 FL:2878
LDA #$00 ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdizcHC:1172 VC:059 00 FL:2878
STA $1938,y ;A:FF00 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdiZcHC:1188 VC:059 00 FL:2878
KILL_SPRITE: STZ $14C8,x ; erase sprite
RETURN_35: RTS ; return

VERTICAL_LEVEL: LDA $167A,x ; \ if "process offscreen" flag is set, return
AND #$04 ; |
BNE RETURN_35 ; /
LDA $13 ; \
LSR A ; |
BCS RETURN_35 ; /
LDA $E4,x ; \
CMP #$00 ; | if the sprite has gone off the side of the level...
LDA $14E0,x ; |
SBC #$00 ; |
CMP #$02 ; |
BCS ERASE_SPRITE ; / ...erase the sprite
LDA $13 ;A:0000 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:1218 VC:250 00 FL:5379
LSR A ;A:0016 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1242 VC:250 00 FL:5379
AND #$01 ;A:000B X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1256 VC:250 00 FL:5379
STA $01 ;A:0001 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1272 VC:250 00 FL:5379
TAY ;A:0001 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1296 VC:250 00 FL:5379
LDA $1C ;A:001A X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0052 VC:251 00 FL:5379
CLC ;A:00BD X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0076 VC:251 00 FL:5379
ADC SPR_T12,y ;A:00BD X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0090 VC:251 00 FL:5379
ROL $00 ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:enVMXdizCHC:0122 VC:251 00 FL:5379
CMP $D8,x ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0160 VC:251 00 FL:5379
PHP ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0190 VC:251 00 FL:5379
LDA $001D ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F2 P:eNVMXdizcHC:0212 VC:251 00 FL:5379
LSR $00 ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:enVMXdiZcHC:0244 VC:251 00 FL:5379
ADC SPR_T13,y ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:enVMXdizCHC:0282 VC:251 00 FL:5379
PLP ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:envMXdiZCHC:0314 VC:251 00 FL:5379
SBC $14D4,x ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0342 VC:251 00 FL:5379
STA $00 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0374 VC:251 00 FL:5379
LDY $01 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0398 VC:251 00 FL:5379
BEQ SPR_L38 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0422 VC:251 00 FL:5379
EOR #$80 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0438 VC:251 00 FL:5379
STA $00 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0454 VC:251 00 FL:5379
SPR_L38: LDA $00 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0478 VC:251 00 FL:5379
BPL RETURN_35 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0502 VC:251 00 FL:5379
BMI ERASE_SPRITE ;A:8AFF X:0002 Y:0000 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0704 VC:184 00 FL:5490

SUB_IS_OFF_SCREEN: LDA $15A0,x ; \ if sprite is on screen, accumulator = 0
ORA $186C,x ; |
RTS ; / return
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; SUB HORZ POS
;
; $B817 - horizontal mario/sprite check - shared
; Y = 1 if mario left of sprite??
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;org $03B817

SUB_HORZ_POS: LDY #$00 ;A:25D0 X:0006 Y:0001 D:0000 DB:03 S:01ED P:eNvMXdizCHC:1020 VC:097 00 FL:31642
LDA $94 ;A:25D0 X:0006 Y:0000 D:0000 DB:03 S:01ED P:envMXdiZCHC:1036 VC:097 00 FL:31642
SEC ;A:25F0 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizCHC:1060 VC:097 00 FL:31642
SBC $E4,x ;A:25F0 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizCHC:1074 VC:097 00 FL:31642
STA $0F ;A:25F4 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1104 VC:097 00 FL:31642
LDA $95 ;A:25F4 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1128 VC:097 00 FL:31642
SBC $14E0,x ;A:2500 X:0006 Y:0000 D:0000 DB:03 S:01ED P:envMXdiZcHC:1152 VC:097 00 FL:31642
BPL LABEL16 ;A:25FF X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1184 VC:097 00 FL:31642
INY ;A:25FF X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1200 VC:097 00 FL:31642
LABEL16: RTS ;A:25FF X:0006 Y:0001 D:0000 DB:03 S:01ED P:envMXdizcHC:1214 VC:097 00 FL:31642

Testen wir den Sprite nun stellen wir fest, dass alles genauso funktioniert, wie es soll. Wir nehmen aber noch 2 kleine Veränderungen vor. Springt man mit einem Spinjump oder Yoshi auf einen Rex verschwindet dieser in einer Staubwolke. Das wollen wir auch für unseren Bob-Omb.
Dafür schauen wir einfach mit Hilfe von $140D (Wenn 1, dann macht Mario einen Spinjump) ob Mario einen Spinjump macht und mit $187A (Wenn nicht 0 hat Mario einen Yoshi) ob Mario ainen Yoshi hat. Als letztes brauchen wir dann nur noch etwas Code dass die richtige Sterbesequenz abspielt. Da die kontaktgrafiken und Marios Geschwindigkeit beide auch die Spinjump und Yoshiroutine benutzen können wir unseren Code erst danach anbringe. Dass sollte dann also so aussehen:
Code
JSL $01AB99
JSL $01AA33
LDA $140D ;Spinjump?
ORA $187A ; Yoshi?
BNE SpinKill ;Oder wie das dann halt bei euch heißt
LDA #$10
STA $AA,x
;Rest des Codes für normales Sterben
SpinKill ;das Label
LDA #$04 ;\
STA $14C8,x ;/ Spritestatus 4 (getötet durch einen Spinjump)
LDA #$08 ;\
STA $1DF9 ;/ Spinjump Soundeffekt abspielen
JSL $07FC3B ; Animationseffekt für den Spinjump
RTS

Hier nun noch mal der komplette Code:
Code
dcb "INIT"
JSR SUB_HORZ_POS
TYA
STA $157C,x
RTL

dcb "MAIN"

PHB ;\
PHK ; | Change the data bank to the one our code is running from.
PLB ; | This is a good practice.
JSR SpriteCode ; | Jump to the sprite's function.
PLB ; | Restore old data bank.
RTL ;/ And return.

;===================================
;Sprite Function
;===================================

RETURN: RTS

SpriteCode:
JSR Graphics

LDA $14C8,x ;\
CMP #$08 ; | If sprite dead,
BNE RETURN ;/ Return.
LDA $9D ;\
BNE RETURN ;/ If locked, return.

JSR SUB_OFF_SCREEN_X0 ; Handle offscreen.

LDA $1588,x ; ... if sprite is touching a wall.
AND #$03 ; (bit 0 and 1)..
BEQ DontFlip ; If bits aren't set, don't flip the sprite.

LDA $157C,x ;\
EOR #$01 ; | Flip sprite's direction when touching wall.
STA $157C,x ;/

DontFlip:
LDA $1588,x
AND #$04 ; If the sprite is in air ..
BEQ NotOnGround ; Don't set speeds and branch.

LDY $157C,x ;\ Get direction into Y.
LDA XSPEED,y ; | Load X speed indexed by Y.
STA $B6,x ;/ Store to X speed table.
LDA #$10 ;\ Y Speed for sprite on ground
STA $AA,x ;/ = 10.

NotOnGround:
JSL $01802A ; Apply speed.
JSL $018032 ; Make the sprite interact with others. Make sure the .cfg setting for this
; is off!
JSL $01A7DC
BCC NoContact

LDA $0E ;\
CMP #$E6 ; | Determine if Mario touches sprite from sides.
BPL SpriteWins ;/ If he does, branch.

;===========================================
;Mario Hurts Sprite
;===========================================

JSL $01AA33 ;\ Set mario speed.
JSL $01AB99 ;/ Display contact graphic.

LDA $140D ; IF spin-jumping ..
BNE SpinKill ; Go to spin kill.

LDA #$10 ;\
STA $AA,x ; | Make sprite fall.
JSL $01802A ; |
LDA #$02 ; |
STA $14C8,x ;/
LDA #$13 ;\
STA $1DF9 ;/ Play sound.
RTS

SpinKill:
LDA #$04 ;\
STA $14C8,x ;/ Sprite status = killed by a spin-jump.
LDA #$08 ;\
STA $1DF9 ;/ Sound to play.
JSL $07FC3B ; Show star animation effect.
RTS
;===========================================
;Sprite Hurts Mario
;===========================================

SpriteWins:
LDA $1490 ;\ IF Mario has star power ..
BNE HasStar ;/ Kill sprite.
JSL $00F5B7 ;\ Otherwise, hurt Mario.
NoContact:
RTS

XSPEED: dcb $08,$F8

;===========================================
;Killed by Star Routine
;===========================================

HasStar:

LDA $167A,x ;\ Don't disable clipping when killed by a star.
ORA #$01 ; | NOTE: We do this to not make glitched graphics show up ..
STA $167A,x ; | when the sprite is killed by a star.
;/ You can also check this option in the .cfg editor.

LDA #$02 ;\ Set the sprite status to ..
STA $14C8,x ;/ Killed by a star.
LDA #$D0 ;\
STA $AA,x ;/ Set Y speed so sprite falls down.
INC $18D2 ; Increase number of consective enemies stomped/ killed by star.
LDA $18D2 ; IF consecutive # of enemies killed by star/stomped ..
CMP #$08 ; IS 8 ..
BCC NotEight
LDA #$08 ;\ Keep it static at 8, because you get 1-ups all the time afterwards.
STA $18D2 ;/
NotEight:
JSL $02ACE5 ; This code calls the "give points" routine.
LDY $18D2 ; Get consecutive # of enemies stomped in Y.
CPY #$08 ; If it's less than 8 once again, return.
BCC NoSound
LDA StarSFX,y ;\
STA $1DF9 ;/ Play sounds depending on how many enemies were stomped/killed by star.
NoSound:
RTS ; Return.

StarSFX: dcb $00,$13,$14,$15,$16,$17,$18,$19
;===================================
;Graphics Code
;===================================

TILEMAP: dcb $CA,$CC

PROPERTIES: dcb $67,$27 ; NOTE: Flip the X direction when going right.

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 $157C,x
STA $02 ; Store direction to $02 for use with property routine later.

LDA $00 ;\
STA $0300,y ;/ Draw the X position of the sprite

LDA $01 ;\
STA $0301,y ;/ Draw the Y position of the sprite

PHX
LDA $14
LSR
LSR
LSR
AND #$01
TAX
LDA TILEMAP,x
STA $0302,y

LDX $02 ; We already pushed X, so we don't need to push it again.
LDA PROPERTIES,x ; Get properties indexed by direction ..
ORA $64
STA $0303,y ; Store to properties byte.
PLX ; Pull back X.

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 ; /

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; SUB_OFF_SCREEN
; This subroutine deals with sprites that have moved off screen
; It is adapted from the subroutine at $01AC0D
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

SPR_T12: db $40,$B0
SPR_T13: db $01,$FF
SPR_T14: db $30,$C0,$A0,$C0,$A0,$F0,$60,$90 ;bank 1 sizes
db $30,$C0,$A0,$80,$A0,$40,$60,$B0 ;bank 3 sizes
SPR_T15: db $01,$FF,$01,$FF,$01,$FF,$01,$FF ;bank 1 sizes
db $01,$FF,$01,$FF,$01,$00,$01,$FF ;bank 3 sizes

SUB_OFF_SCREEN_X1: LDA #$02 ; \ entry point of routine determines value of $03
BRA STORE_03 ; | (table entry to use on horizontal levels)
SUB_OFF_SCREEN_X2: LDA #$04 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X3: LDA #$06 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X4: LDA #$08 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X5: LDA #$0A ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X6: LDA #$0C ; |
BRA STORE_03 ; | OMG YOU FOUND THIS HIDDEN z0mg place!111 you win a cookie!
SUB_OFF_SCREEN_X7: LDA #$0E ; |
STORE_03: STA $03 ; |
BRA START_SUB ; |
SUB_OFF_SCREEN_X0: STZ $03 ; /

START_SUB: JSR SUB_IS_OFF_SCREEN ; \ if sprite is not off screen, return
BEQ RETURN_35 ; /
LDA $5B ; \ goto VERTICAL_LEVEL if vertical level
AND #$01 ; |
BNE VERTICAL_LEVEL ; /
LDA $D8,x ; \
CLC ; |
ADC #$50 ; | if the sprite has gone off the bottom of the level...
LDA $14D4,x ; | (if adding 0x50 to the sprite y position would make the high byte >= 2)
ADC #$00 ; |
CMP #$02 ; |
BPL ERASE_SPRITE ; / ...erase the sprite
LDA $167A,x ; \ if "process offscreen" flag is set, return
AND #$04 ; |
BNE RETURN_35 ; /
LDA $13 ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0756 VC:176 00 FL:205
AND #$01 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0780 VC:176 00 FL:205
ORA $03 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0796 VC:176 00 FL:205
STA $01 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0820 VC:176 00 FL:205
TAY ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0844 VC:176 00 FL:205
LDA $1A ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0858 VC:176 00 FL:205
CLC ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0882 VC:176 00 FL:205
ADC SPR_T14,y ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0896 VC:176 00 FL:205
ROL $00 ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizcHC:0928 VC:176 00 FL:205
CMP $E4,x ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:0966 VC:176 00 FL:205
PHP ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:0996 VC:176 00 FL:205
LDA $1B ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdizCHC:1018 VC:176 00 FL:205
LSR $00 ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdiZCHC:1042 VC:176 00 FL:205
ADC SPR_T15,y ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdizcHC:1080 VC:176 00 FL:205
PLP ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F0 P:eNvMXdizcHC:1112 VC:176 00 FL:205
SBC $14E0,x ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1140 VC:176 00 FL:205
STA $00 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:1172 VC:176 00 FL:205
LSR $01 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:1196 VC:176 00 FL:205
BCC SPR_L31 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZCHC:1234 VC:176 00 FL:205
EOR #$80 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZCHC:1250 VC:176 00 FL:205
STA $00 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1266 VC:176 00 FL:205
SPR_L31: LDA $00 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1290 VC:176 00 FL:205
BPL RETURN_35 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1314 VC:176 00 FL:205
ERASE_SPRITE: LDA $14C8,x ; \ if sprite status < 8, permanently erase sprite
CMP #$08 ; |
BCC KILL_SPRITE ; /
LDY $161A,x ;A:FF08 X:0007 Y:0001 D:0000 DB:01 S:01F3 P:envMXdiZCHC:1108 VC:059 00 FL:2878
CPY #$FF ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdiZCHC:1140 VC:059 00 FL:2878
BEQ KILL_SPRITE ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdizcHC:1156 VC:059 00 FL:2878
LDA #$00 ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdizcHC:1172 VC:059 00 FL:2878
STA $1938,y ;A:FF00 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdiZcHC:1188 VC:059 00 FL:2878
KILL_SPRITE: STZ $14C8,x ; erase sprite
RETURN_35: RTS ; return

VERTICAL_LEVEL: LDA $167A,x ; \ if "process offscreen" flag is set, return
AND #$04 ; |
BNE RETURN_35 ; /
LDA $13 ; \
LSR A ; |
BCS RETURN_35 ; /
LDA $E4,x ; \
CMP #$00 ; | if the sprite has gone off the side of the level...
LDA $14E0,x ; |
SBC #$00 ; |
CMP #$02 ; |
BCS ERASE_SPRITE ; / ...erase the sprite
LDA $13 ;A:0000 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:1218 VC:250 00 FL:5379
LSR A ;A:0016 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1242 VC:250 00 FL:5379
AND #$01 ;A:000B X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1256 VC:250 00 FL:5379
STA $01 ;A:0001 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1272 VC:250 00 FL:5379
TAY ;A:0001 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1296 VC:250 00 FL:5379
LDA $1C ;A:001A X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0052 VC:251 00 FL:5379
CLC ;A:00BD X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0076 VC:251 00 FL:5379
ADC SPR_T12,y ;A:00BD X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0090 VC:251 00 FL:5379
ROL $00 ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:enVMXdizCHC:0122 VC:251 00 FL:5379
CMP $D8,x ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0160 VC:251 00 FL:5379
PHP ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0190 VC:251 00 FL:5379
LDA $001D ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F2 P:eNVMXdizcHC:0212 VC:251 00 FL:5379
LSR $00 ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:enVMXdiZcHC:0244 VC:251 00 FL:5379
ADC SPR_T13,y ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:enVMXdizCHC:0282 VC:251 00 FL:5379
PLP ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:envMXdiZCHC:0314 VC:251 00 FL:5379
SBC $14D4,x ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0342 VC:251 00 FL:5379
STA $00 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0374 VC:251 00 FL:5379
LDY $01 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0398 VC:251 00 FL:5379
BEQ SPR_L38 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0422 VC:251 00 FL:5379
EOR #$80 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0438 VC:251 00 FL:5379
STA $00 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0454 VC:251 00 FL:5379
SPR_L38: LDA $00 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0478 VC:251 00 FL:5379
BPL RETURN_35 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0502 VC:251 00 FL:5379
BMI ERASE_SPRITE ;A:8AFF X:0002 Y:0000 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0704 VC:184 00 FL:5490

SUB_IS_OFF_SCREEN: LDA $15A0,x ; \ if sprite is on screen, accumulator = 0
ORA $186C,x ; |
RTS ; / return
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; SUB HORZ POS
;
; $B817 - horizontal mario/sprite check - shared
; Y = 1 if mario left of sprite??
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;org $03B817

SUB_HORZ_POS: LDY #$00 ;A:25D0 X:0006 Y:0001 D:0000 DB:03 S:01ED P:eNvMXdizCHC:1020 VC:097 00 FL:31642
LDA $94 ;A:25D0 X:0006 Y:0000 D:0000 DB:03 S:01ED P:envMXdiZCHC:1036 VC:097 00 FL:31642
SEC ;A:25F0 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizCHC:1060 VC:097 00 FL:31642
SBC $E4,x ;A:25F0 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizCHC:1074 VC:097 00 FL:31642
STA $0F ;A:25F4 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1104 VC:097 00 FL:31642
LDA $95 ;A:25F4 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1128 VC:097 00 FL:31642
SBC $14E0,x ;A:2500 X:0006 Y:0000 D:0000 DB:03 S:01ED P:envMXdiZcHC:1152 VC:097 00 FL:31642
BPL LABEL16 ;A:25FF X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1184 VC:097 00 FL:31642
INY ;A:25FF X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1200 VC:097 00 FL:31642
LABEL16: RTS ;A:25FF X:0006 Y:0001 D:0000 DB:03 S:01ED P:envMXdizcHC:1214 VC:097 00 FL:31642

Damit haben wir nun auch den Bob-Omb, also den 2. Sprite dieses Tutorials abgeschlossen. Wir haben einen voll funktionsfähigen Bob-Omb. Er funktioniert natürlich immer noch nicht wie ein richtiger, aber da wir uns hier bisher eh nur mit Grundlagen beschäftigt haben ist das schon eine ziemlich gute Leistung.
geschrieben am 24.02.2013 17:39:29
( Link )
Kapitel 10: Ein 16*32 Sprite

Wir haben uns nun lange genug mit 16*16 Sprites beschäftigt machen wir nun einen etwas größeren.
Dieser 16*32 Sprite wird ein Block lang und 2 Blöcke hoch sein. Hier wird nun ein bisschen Arbeit mal wieder in der Graphics Routine erforderlich. Als Grundgerüst soll uns dieser Sprite dienen:
Code
dcb "INIT"
JSR SUB_HORZ_POS
TYA
STA $157C,x
RTL

dcb "MAIN"

PHB ;\
PHK ; | Change the data bank to the one our code is running from.
PLB ; | This is a good practice.
JSR SpriteCode ; | Jump to the sprite's function.
PLB ; | Restore old data bank.
RTL ;/ And return.

;===================================
;Sprite Function
;===================================

RETURN: RTS

SpriteCode:
JSR Graphics

LDA $14C8,x ;\
CMP #$08 ; | If sprite dead,
BNE RETURN ;/ Return.
LDA $9D ;\
BNE RETURN ;/ If locked, return.

JSR SUB_OFF_SCREEN_X0 ; Handle offscreen.

RTS
;===================================
;Graphics Code
;===================================

PROPERTIES: dcb $47,$07

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 $157C,x
STA $02 ; Store direction to $02 for use with property routine later.

LDA $00 ;\
STA $0300,y ;/ Draw the X position of the sprite

LDA $01 ;\
STA $0301,y ;/ Draw the Y position of the sprite

LDA #$CA
ORA $64
STA $0302,y ; Sprite tile to draw = CA.

PHX
LDX $02 ;
LDA PROPERTIES,x ; Get properties indexed by direction ..
STA $0303,y ; Store to properties byte.
PLX ; Pull back X.

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 ; /

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; SUB_OFF_SCREEN
; This subroutine deals with sprites that have moved off screen
; It is adapted from the subroutine at $01AC0D
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

SPR_T12: db $40,$B0
SPR_T13: db $01,$FF
SPR_T14: db $30,$C0,$A0,$C0,$A0,$F0,$60,$90 ;bank 1 sizes
db $30,$C0,$A0,$80,$A0,$40,$60,$B0 ;bank 3 sizes
SPR_T15: db $01,$FF,$01,$FF,$01,$FF,$01,$FF ;bank 1 sizes
db $01,$FF,$01,$FF,$01,$00,$01,$FF ;bank 3 sizes

SUB_OFF_SCREEN_X1: LDA #$02 ; \ entry point of routine determines value of $03
BRA STORE_03 ; | (table entry to use on horizontal levels)
SUB_OFF_SCREEN_X2: LDA #$04 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X3: LDA #$06 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X4: LDA #$08 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X5: LDA #$0A ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X6: LDA #$0C ; |
BRA STORE_03 ; | OMG YOU FOUND THIS HIDDEN z0mg place!111 you win a cookie!
SUB_OFF_SCREEN_X7: LDA #$0E ; |
STORE_03: STA $03 ; |
BRA START_SUB ; |
SUB_OFF_SCREEN_X0: STZ $03 ; /

START_SUB: JSR SUB_IS_OFF_SCREEN ; \ if sprite is not off screen, return
BEQ RETURN_35 ; /
LDA $5B ; \ goto VERTICAL_LEVEL if vertical level
AND #$01 ; |
BNE VERTICAL_LEVEL ; /
LDA $D8,x ; \
CLC ; |
ADC #$50 ; | if the sprite has gone off the bottom of the level...
LDA $14D4,x ; | (if adding 0x50 to the sprite y position would make the high byte >= 2)
ADC #$00 ; |
CMP #$02 ; |
BPL ERASE_SPRITE ; / ...erase the sprite
LDA $167A,x ; \ if "process offscreen" flag is set, return
AND #$04 ; |
BNE RETURN_35 ; /
LDA $13 ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0756 VC:176 00 FL:205
AND #$01 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0780 VC:176 00 FL:205
ORA $03 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0796 VC:176 00 FL:205
STA $01 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0820 VC:176 00 FL:205
TAY ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0844 VC:176 00 FL:205
LDA $1A ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0858 VC:176 00 FL:205
CLC ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0882 VC:176 00 FL:205
ADC SPR_T14,y ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0896 VC:176 00 FL:205
ROL $00 ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizcHC:0928 VC:176 00 FL:205
CMP $E4,x ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:0966 VC:176 00 FL:205
PHP ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:0996 VC:176 00 FL:205
LDA $1B ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdizCHC:1018 VC:176 00 FL:205
LSR $00 ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdiZCHC:1042 VC:176 00 FL:205
ADC SPR_T15,y ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdizcHC:1080 VC:176 00 FL:205
PLP ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F0 P:eNvMXdizcHC:1112 VC:176 00 FL:205
SBC $14E0,x ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1140 VC:176 00 FL:205
STA $00 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:1172 VC:176 00 FL:205
LSR $01 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:1196 VC:176 00 FL:205
BCC SPR_L31 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZCHC:1234 VC:176 00 FL:205
EOR #$80 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZCHC:1250 VC:176 00 FL:205
STA $00 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1266 VC:176 00 FL:205
SPR_L31: LDA $00 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1290 VC:176 00 FL:205
BPL RETURN_35 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1314 VC:176 00 FL:205
ERASE_SPRITE: LDA $14C8,x ; \ if sprite status < 8, permanently erase sprite
CMP #$08 ; |
BCC KILL_SPRITE ; /
LDY $161A,x ;A:FF08 X:0007 Y:0001 D:0000 DB:01 S:01F3 P:envMXdiZCHC:1108 VC:059 00 FL:2878
CPY #$FF ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdiZCHC:1140 VC:059 00 FL:2878
BEQ KILL_SPRITE ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdizcHC:1156 VC:059 00 FL:2878
LDA #$00 ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdizcHC:1172 VC:059 00 FL:2878
STA $1938,y ;A:FF00 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdiZcHC:1188 VC:059 00 FL:2878
KILL_SPRITE: STZ $14C8,x ; erase sprite
RETURN_35: RTS ; return

VERTICAL_LEVEL: LDA $167A,x ; \ if "process offscreen" flag is set, return
AND #$04 ; |
BNE RETURN_35 ; /
LDA $13 ; \
LSR A ; |
BCS RETURN_35 ; /
LDA $E4,x ; \
CMP #$00 ; | if the sprite has gone off the side of the level...
LDA $14E0,x ; |
SBC #$00 ; |
CMP #$02 ; |
BCS ERASE_SPRITE ; / ...erase the sprite
LDA $13 ;A:0000 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:1218 VC:250 00 FL:5379
LSR A ;A:0016 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1242 VC:250 00 FL:5379
AND #$01 ;A:000B X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1256 VC:250 00 FL:5379
STA $01 ;A:0001 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1272 VC:250 00 FL:5379
TAY ;A:0001 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1296 VC:250 00 FL:5379
LDA $1C ;A:001A X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0052 VC:251 00 FL:5379
CLC ;A:00BD X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0076 VC:251 00 FL:5379
ADC SPR_T12,y ;A:00BD X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0090 VC:251 00 FL:5379
ROL $00 ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:enVMXdizCHC:0122 VC:251 00 FL:5379
CMP $D8,x ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0160 VC:251 00 FL:5379
PHP ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0190 VC:251 00 FL:5379
LDA $001D ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F2 P:eNVMXdizcHC:0212 VC:251 00 FL:5379
LSR $00 ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:enVMXdiZcHC:0244 VC:251 00 FL:5379
ADC SPR_T13,y ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:enVMXdizCHC:0282 VC:251 00 FL:5379
PLP ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:envMXdiZCHC:0314 VC:251 00 FL:5379
SBC $14D4,x ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0342 VC:251 00 FL:5379
STA $00 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0374 VC:251 00 FL:5379
LDY $01 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0398 VC:251 00 FL:5379
BEQ SPR_L38 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0422 VC:251 00 FL:5379
EOR #$80 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0438 VC:251 00 FL:5379
STA $00 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0454 VC:251 00 FL:5379
SPR_L38: LDA $00 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0478 VC:251 00 FL:5379
BPL RETURN_35 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0502 VC:251 00 FL:5379
BMI ERASE_SPRITE ;A:8AFF X:0002 Y:0000 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0704 VC:184 00 FL:5490

SUB_IS_OFF_SCREEN: LDA $15A0,x ; \ if sprite is on screen, accumulator = 0
ORA $186C,x ; |
RTS ; / return
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; SUB HORZ POS
;
; $B817 - horizontal mario/sprite check - shared
; Y = 1 if mario left of sprite??
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;org $03B817

SUB_HORZ_POS: LDY #$00 ;A:25D0 X:0006 Y:0001 D:0000 DB:03 S:01ED P:eNvMXdizCHC:1020 VC:097 00 FL:31642
LDA $94 ;A:25D0 X:0006 Y:0000 D:0000 DB:03 S:01ED P:envMXdiZCHC:1036 VC:097 00 FL:31642
SEC ;A:25F0 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizCHC:1060 VC:097 00 FL:31642
SBC $E4,x ;A:25F0 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizCHC:1074 VC:097 00 FL:31642
STA $0F ;A:25F4 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1104 VC:097 00 FL:31642
LDA $95 ;A:25F4 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1128 VC:097 00 FL:31642
SBC $14E0,x ;A:2500 X:0006 Y:0000 D:0000 DB:03 S:01ED P:envMXdiZcHC:1152 VC:097 00 FL:31642
BPL LABEL16 ;A:25FF X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1184 VC:097 00 FL:31642
INY ;A:25FF X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1200 VC:097 00 FL:31642
LABEL16: RTS ;A:25FF X:0006 Y:0001 D:0000 DB:03 S:01ED P:envMXdizcHC:1214 VC:097 00 FL:31642

Für einen 16*32 Sprite müssen wir 2 16*16 Teile zeichnen. (das obere und das untere) Da wir diese 2 Teile brauchen müssen wir einen Loop in der Graphics Routine benutzen. Hier nun einen Routine um einen 16*32 Sprite zu zeichnen:
Code
dcb "INIT"
JSR SUB_HORZ_POS
TYA
STA $157C,x
RTL

dcb "MAIN"

PHB ;\
PHK ; | Change the data bank to the one our code is running from.
PLB ; | This is a good practice.
JSR SpriteCode ; | Jump to the sprite's function.
PLB ; | Restore old data bank.
RTL ;/ And return.

;===================================
;Sprite Function
;===================================

RETURN: RTS

SpriteCode:
JSR Graphics

LDA $14C8,x ;\
CMP #$08 ; | If sprite dead,
BNE RETURN ;/ Return.
LDA $9D ;\
BNE RETURN ;/ If locked, return.

JSR SUB_OFF_SCREEN_X0 ; Handle offscreen.

RTS
;===================================
;Graphics Code
;===================================

PROPERTIES: dcb $47,$07
TILEMAP: dcb $CC,$CA

YDISP: dcb $10,$00

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 $157C,x
STA $02 ; Store direction to $02 for use with property routine later.

PHX ;\ Push the sprite index, since we're using it for a loop.
LDX #$01 ;/ X = number of times to loop through. Since we only draw one MORE tile, loop one more time.
Loop:
LDA $00 ;\
STA $0300,y ;/ Draw the X position of the sprite

LDA $01 ;\
SEC ; | Y displacement is added for the Y position, so one tile is higher than the other.
SBC YDISP,x ; | Otherwise, both tiles would have been drawn to the same position!
STA $0301,y ; | If X is 00, i.e. first tile, then load the first value from the table and apply that
;/ as the displacement. For the second tile, F0 is added to make it higher than the first.

LDA TILEMAP,x ; X still contains index for the tile to draw. If it's the first tile, draw first tile in
STA $0302,y ; the table. If X = 01, i.e. second tile, draw second tile in the table.

PHX ; Push number of times to go through loop (01), because we're using it for a table here.
LDX $02 ;\
LDA PROPERTIES,x ; | Set properties based on direction.
ORA $64
STA $0303,y ;/
PLX ; Pull number of times to go through loop.

INY ;\
INY ; | The OAM is 8x8, but our sprite is 16x16 ..
INY ; | So increment it 4 times.
INY ;/

DEX ; After drawing this tile, decrease number of tiles to go through loop. If the second tile
; is drawn, then loop again to draw the first tile.

BPL Loop ; Loop until X becomes negative (FF).

PLX ; Pull back the sprite index! We pushed it at the beginning of the routine.

LDY #$02 ; Y ends with the tile size .. 02 means it's 16x16
LDA #$01 ; A -> number of tiles drawn - 1.
; I drew 2 tiles, so 2-1 = 1. A = 01.

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 ; /

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; SUB_OFF_SCREEN
; This subroutine deals with sprites that have moved off screen
; It is adapted from the subroutine at $01AC0D
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

SPR_T12: db $40,$B0
SPR_T13: db $01,$FF
SPR_T14: db $30,$C0,$A0,$C0,$A0,$F0,$60,$90 ;bank 1 sizes
db $30,$C0,$A0,$80,$A0,$40,$60,$B0 ;bank 3 sizes
SPR_T15: db $01,$FF,$01,$FF,$01,$FF,$01,$FF ;bank 1 sizes
db $01,$FF,$01,$FF,$01,$00,$01,$FF ;bank 3 sizes

SUB_OFF_SCREEN_X1: LDA #$02 ; \ entry point of routine determines value of $03
BRA STORE_03 ; | (table entry to use on horizontal levels)
SUB_OFF_SCREEN_X2: LDA #$04 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X3: LDA #$06 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X4: LDA #$08 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X5: LDA #$0A ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X6: LDA #$0C ; |
BRA STORE_03 ; | OMG YOU FOUND THIS HIDDEN z0mg place!111 you win a cookie!
SUB_OFF_SCREEN_X7: LDA #$0E ; |
STORE_03: STA $03 ; |
BRA START_SUB ; |
SUB_OFF_SCREEN_X0: STZ $03 ; /

START_SUB: JSR SUB_IS_OFF_SCREEN ; \ if sprite is not off screen, return
BEQ RETURN_35 ; /
LDA $5B ; \ goto VERTICAL_LEVEL if vertical level
AND #$01 ; |
BNE VERTICAL_LEVEL ; /
LDA $D8,x ; \
CLC ; |
ADC #$50 ; | if the sprite has gone off the bottom of the level...
LDA $14D4,x ; | (if adding 0x50 to the sprite y position would make the high byte >= 2)
ADC #$00 ; |
CMP #$02 ; |
BPL ERASE_SPRITE ; / ...erase the sprite
LDA $167A,x ; \ if "process offscreen" flag is set, return
AND #$04 ; |
BNE RETURN_35 ; /
LDA $13 ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0756 VC:176 00 FL:205
AND #$01 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0780 VC:176 00 FL:205
ORA $03 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0796 VC:176 00 FL:205
STA $01 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0820 VC:176 00 FL:205
TAY ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0844 VC:176 00 FL:205
LDA $1A ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0858 VC:176 00 FL:205
CLC ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0882 VC:176 00 FL:205
ADC SPR_T14,y ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0896 VC:176 00 FL:205
ROL $00 ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizcHC:0928 VC:176 00 FL:205
CMP $E4,x ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:0966 VC:176 00 FL:205
PHP ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:0996 VC:176 00 FL:205
LDA $1B ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdizCHC:1018 VC:176 00 FL:205
LSR $00 ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdiZCHC:1042 VC:176 00 FL:205
ADC SPR_T15,y ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdizcHC:1080 VC:176 00 FL:205
PLP ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F0 P:eNvMXdizcHC:1112 VC:176 00 FL:205
SBC $14E0,x ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1140 VC:176 00 FL:205
STA $00 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:1172 VC:176 00 FL:205
LSR $01 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:1196 VC:176 00 FL:205
BCC SPR_L31 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZCHC:1234 VC:176 00 FL:205
EOR #$80 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZCHC:1250 VC:176 00 FL:205
STA $00 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1266 VC:176 00 FL:205
SPR_L31: LDA $00 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1290 VC:176 00 FL:205
BPL RETURN_35 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1314 VC:176 00 FL:205
ERASE_SPRITE: LDA $14C8,x ; \ if sprite status < 8, permanently erase sprite
CMP #$08 ; |
BCC KILL_SPRITE ; /
LDY $161A,x ;A:FF08 X:0007 Y:0001 D:0000 DB:01 S:01F3 P:envMXdiZCHC:1108 VC:059 00 FL:2878
CPY #$FF ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdiZCHC:1140 VC:059 00 FL:2878
BEQ KILL_SPRITE ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdizcHC:1156 VC:059 00 FL:2878
LDA #$00 ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdizcHC:1172 VC:059 00 FL:2878
STA $1938,y ;A:FF00 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdiZcHC:1188 VC:059 00 FL:2878
KILL_SPRITE: STZ $14C8,x ; erase sprite
RETURN_35: RTS ; return

VERTICAL_LEVEL: LDA $167A,x ; \ if "process offscreen" flag is set, return
AND #$04 ; |
BNE RETURN_35 ; /
LDA $13 ; \
LSR A ; |
BCS RETURN_35 ; /
LDA $E4,x ; \
CMP #$00 ; | if the sprite has gone off the side of the level...
LDA $14E0,x ; |
SBC #$00 ; |
CMP #$02 ; |
BCS ERASE_SPRITE ; / ...erase the sprite
LDA $13 ;A:0000 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:1218 VC:250 00 FL:5379
LSR A ;A:0016 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1242 VC:250 00 FL:5379
AND #$01 ;A:000B X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1256 VC:250 00 FL:5379
STA $01 ;A:0001 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1272 VC:250 00 FL:5379
TAY ;A:0001 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1296 VC:250 00 FL:5379
LDA $1C ;A:001A X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0052 VC:251 00 FL:5379
CLC ;A:00BD X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0076 VC:251 00 FL:5379
ADC SPR_T12,y ;A:00BD X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0090 VC:251 00 FL:5379
ROL $00 ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:enVMXdizCHC:0122 VC:251 00 FL:5379
CMP $D8,x ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0160 VC:251 00 FL:5379
PHP ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0190 VC:251 00 FL:5379
LDA $001D ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F2 P:eNVMXdizcHC:0212 VC:251 00 FL:5379
LSR $00 ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:enVMXdiZcHC:0244 VC:251 00 FL:5379
ADC SPR_T13,y ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:enVMXdizCHC:0282 VC:251 00 FL:5379
PLP ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:envMXdiZCHC:0314 VC:251 00 FL:5379
SBC $14D4,x ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0342 VC:251 00 FL:5379
STA $00 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0374 VC:251 00 FL:5379
LDY $01 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0398 VC:251 00 FL:5379
BEQ SPR_L38 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0422 VC:251 00 FL:5379
EOR #$80 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0438 VC:251 00 FL:5379
STA $00 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0454 VC:251 00 FL:5379
SPR_L38: LDA $00 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0478 VC:251 00 FL:5379
BPL RETURN_35 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0502 VC:251 00 FL:5379
BMI ERASE_SPRITE ;A:8AFF X:0002 Y:0000 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0704 VC:184 00 FL:5490

SUB_IS_OFF_SCREEN: LDA $15A0,x ; \ if sprite is on screen, accumulator = 0
ORA $186C,x ; |
RTS ; / return
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; SUB HORZ POS
;
; $B817 - horizontal mario/sprite check - shared
; Y = 1 if mario left of sprite??
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;org $03B817

SUB_HORZ_POS: LDY #$00 ;A:25D0 X:0006 Y:0001 D:0000 DB:03 S:01ED P:eNvMXdizCHC:1020 VC:097 00 FL:31642
LDA $94 ;A:25D0 X:0006 Y:0000 D:0000 DB:03 S:01ED P:envMXdiZCHC:1036 VC:097 00 FL:31642
SEC ;A:25F0 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizCHC:1060 VC:097 00 FL:31642
SBC $E4,x ;A:25F0 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizCHC:1074 VC:097 00 FL:31642
STA $0F ;A:25F4 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1104 VC:097 00 FL:31642
LDA $95 ;A:25F4 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1128 VC:097 00 FL:31642
SBC $14E0,x ;A:2500 X:0006 Y:0000 D:0000 DB:03 S:01ED P:envMXdiZcHC:1152 VC:097 00 FL:31642
BPL LABEL16 ;A:25FF X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1184 VC:097 00 FL:31642
INY ;A:25FF X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1200 VC:097 00 FL:31642
LABEL16: RTS ;A:25FF X:0006 Y:0001 D:0000 DB:03 S:01ED P:envMXdizcHC:1214 VC:097 00 FL:31642

Schauen wir uns ausführlich den Graphics Code an, denn der interessiert uns ja. Bis zum ersten PHX sollte eigentlich alles klar sein. Das PHX brauchen wir, um die Anzahl der Teile -1 abbilden zu lassen. Bei uns also 01, da wir ja 2 Teile zeichnen wollen. Wir geben X erst mal den Wert 1,. Das bedeutet, dass zuerst das obere Teil und dann das untere Teil gezeichnet wird und erst dann das untere Teil. Die X Position der Teile können wir direkt speichern, da ja beide Teile die gleiche X-Position haben. Wir können die Y-Position leider nicht direkt speichern, da das eine Teil ja über dem anderen liegt. Damit wir nun das ganze für 2 Y-Werte hinkriegen brauchen wir ein Y-displacement.
YDISP: dcb $10,$00
LDA $01
SEC
SBC YDISP,x
STA $0301,y

Für das obere Teil welches wir ja zuerst zeichnen, da X mit 1 geladen wurde brauchen wir kein Y-Displacement. Bei einem einzigen Teil stellt sich das Problem ja auch nicht. Für das untere Teil müssen wir es um ein 16*16 Feld unter das obere setzen um unser Ziel zu erreichen. Ein Displacement von 10 macht genau das.
Die Teilinformation wird mit Hilfe von X in $0302,y gespeichert. Da X ja 01 ist laden und speichern wir das obere (das zweite) Teil der Tabelle. Wenn X 00 ist, worum wir uns gleich kümmern werden, dann laden und speichern wir das untere Teil (das erste) der Tabelle. In unserem Beispielcode heißt die Tabelle TILEMAP.
Als nächstes steht da nun das PHX, da wir nun die Einstellungen, welche abhängig hinzufügen wollen (haben wir früher ja schon in $02 gespeichert. Nicht jedes einzelne Teil hat hier seine eigenen Information. X steht hier allgemein für die Richtung. Nun kommt wie immer 4 mal LDY und dann sind wir auch schon mit dem ersten Teil fertig.
Für das 2. Teil benutzen wir DEX und setzten so X zu 00. (damit wird das erste Byte der Tabelle geladen) Als nächstes wiederholen wir den zuvor kreierten Loop mit einem BPL.
Benutzen wir nun DEX noch mal, nun aber wenn X = 00, so wäre X -FF. BPL funktioniert aber nur mir positiven Werten und so beendet sich dann der Loop.
Als letztes kommt wieder gewöhnliches Zeug. Zuerst PLX. Dann laden wir Y mit 02 um das neue 16*16 Teil hinzuzufügen und A mit 1, da ja hier die Teilanzahl -1 hin muss. (2-1=1)
Damit sind wir fertig und der Sprite kann getestet werden. Wenn alles richtig ist, solltet ihr jetzt einmal den Walkingframe Des Bob-Ombs sehen und einmal den nicht animierten Teil des Bob-Ombs.
Für den clipping value im CFG-Editor kann man 2A benutzen. (Wert des Rex welcher ja 16*32 ist)
Noch ein kleiner Tipp zum Ende. Verändert man die Geschwindigkeit, so wird automatisch die Geschwindigkeit beider Teile geändert.
Für alle, die mit diesem Text gerade nicht klar kamen, hier nun der ASM Teil Graphics Codes ausführlich kommentiert.
Code
;===================================
;Graphics Code
;===================================

PROPERTIES: dcb $47,$07 ;\
TILEMAP: dcb $CC,$CA ;/ die beiden Tabellen

YDISP: dcb $10,$00 ; das Y-Displacement, damit wir 2 Teile haben

Graphics:
JSR GET_DRAW_INFO ; Aktueller Spritewert in die OAM schieben

LDA $157C,x ;\
STA $02 ;/ Laden und speichern der Richtung in 2

PHX
LDX #$01 ; Die Anzahl hier entspricht, wie oft geloopt wird.
Loop:
LDA $00 ;\
STA $0300,y ;/ Zeichnen der X Position des Sprites

LDA $01 ;\
SEC ; | Hier brauchen wir nun das Y-Displacement um 1 Teil 1
SBC YDISP,x ; | höher als das andere zeichnen zu lassen.
STA $0301,y ;/ 1 Wert der Tabelle laden


LDA TILEMAP,x ; Beinhaltet die Teilinformationen. Ist das erste Teil gezeichnet,
STA $0302,y ; so wird das 2. Teil der Tabelle aufgerufen

PHX ; Anzahl der PHX entspricht Anzahl der Loops
LDX $02 ;\
LDA PROPERTIES,x ; | Einstellungen abhängig von der Richtung
ORA $64 ; |
STA $0303,y ;/
PLX ; Anzahl der PHX entspricht Anzahl der Loops

INY
INY .
INY
INY

DEX
BPL Loop ; So lange die Schleife ausführen, bis Wert ist negativ

PLX ; Zum ausgleichen des PHX vom ganz oben

LDY #$02 ; Teilgröße
LDA #$01 ; Anzahl der Teile -1

JSL $01B7B3
RTS

Ich hoffe, dass nun in Kombination von Text und ASM Code klar wurde wie ein 16*32 Sprite funktioniert. Als nächstes werden wir zu dem Y-displacement noch ein X-displacement hinzufüg
geschrieben am 24.02.2013 17:39:59
( Link )
Kapitel 11: Ein 32*32 Sprite

Hier nun mal der Code eine 32*32 Sprites. Alles wa bisher gemacht wurde, ist das der Loop 4 mal ausgeführt wird, schließlich brauchen wir ja auch 4 Teile. Dies wurde mit bewerkstelligt, indem X einfach zu 03 wurde.
Code
dcb "INIT"
JSR SUB_HORZ_POS
TYA
STA $157C,x
RTL

dcb "MAIN"

PHB ;\
PHK ; | Change the data bank to the one our code is running from.
PLB ; | This is a good practice.
JSR SpriteCode ; | Jump to the sprite's function.
PLB ; | Restore old data bank.
RTL ;/ And return.

;===================================
;Sprite Function
;===================================

RETURN: RTS

SpriteCode:
JSR Graphics

LDA $14C8,x ;\
CMP #$08 ; | If sprite dead,
BNE RETURN ;/ Return.
LDA $9D ;\
BNE RETURN ;/ If locked, return.

JSR SUB_OFF_SCREEN_X0 ; Handle offscreen.

RTS
;===================================
;Graphics Code
;===================================

PROPERTIES: dcb $47,$07
TILEMAP: dcb $CA,$CC,$CA,$CC

YDISP: dcb $F0,$F0,$00,$00
XDISP: dcb $F0,$00,$F0,$00

Graphics:
JSR GET_DRAW_INFO

LDA $157C,x
STA $02 ; Store direction to $02 for use with property routine later.

PHX
LDX #$03
Loop:
LDA $00 ;\
CLC ; | Apply X displacement of the sprite.
ADC XDISP,x ; |
STA $0300,y ;/

LDA $01 ;\
CLC ; | Y displacement is added for the Y position, so one tile is higher than the other.
ADC YDISP,x ; | Otherwise, both tiles would have been drawn to the same position!
STA $0301,y ; | If X is 00, i.e. first tile, then load the first value from the table and apply that
;/ as the displacement. For the second tile, F0 is added to make it higher than the first.
LDA TILEMAP,x ; X still contains index for the tile to draw. If it's the first tile, draw first tile in
STA $0302,y ; the table. If X = 01, i.e. second tile, draw second tile in the table.

PHX ; Push number of times to go through loop (01), because we're using it for a table here.
LDX $02 ;\
LDA PROPERTIES,x ; | Set properties based on direction.
STA $0303,y ;/
PLX ; Pull number of times to go through loop.

INY ;\
INY ; | The OAM is 8x8, but our sprite is 16x16 ..
INY ; | So increment it 4 times.
INY ;/

DEX ; After drawing this tile, decrease number of tiles to go through loop. If the second tile
; is drawn, then loop again to draw the first tile.

BPL Loop ; Loop until X becomes negative (FF).

PLX ; Pull back the sprite index! We pushed it at the beginning of the routine.

LDY #$02 ; Y ends with the tile size .. 02 means it's 16x16
LDA #$03 ; A -> number of tiles drawn - 1.
; I drew 2 tiles, so 2-1 = 1. A = 01.

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 ; | IF YOU READ THIS, YOU WIN A COOKIE! PM ME AND I'LL GIVE YOU A FREE ASM HACK WOOHOO!
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 ; /

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; SUB_OFF_SCREEN
; This subroutine deals with sprites that have moved off screen
; It is adapted from the subroutine at $01AC0D
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

SPR_T12: db $40,$B0
SPR_T13: db $01,$FF
SPR_T14: db $30,$C0,$A0,$C0,$A0,$F0,$60,$90 ;bank 1 sizes
db $30,$C0,$A0,$80,$A0,$40,$60,$B0 ;bank 3 sizes
SPR_T15: db $01,$FF,$01,$FF,$01,$FF,$01,$FF ;bank 1 sizes
db $01,$FF,$01,$FF,$01,$00,$01,$FF ;bank 3 sizes

SUB_OFF_SCREEN_X1: LDA #$02 ; \ entry point of routine determines value of $03
BRA STORE_03 ; | (table entry to use on horizontal levels)
SUB_OFF_SCREEN_X2: LDA #$04 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X3: LDA #$06 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X4: LDA #$08 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X5: LDA #$0A ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X6: LDA #$0C ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X7: LDA #$0E ; |
STORE_03: STA $03 ; |
BRA START_SUB ; |
SUB_OFF_SCREEN_X0: STZ $03 ; /

START_SUB: JSR SUB_IS_OFF_SCREEN ; \ if sprite is not off screen, return
BEQ RETURN_35 ; /
LDA $5B ; \ goto VERTICAL_LEVEL if vertical level
AND #$01 ; |
BNE VERTICAL_LEVEL ; /
LDA $D8,x ; \
CLC ; |
ADC #$50 ; | if the sprite has gone off the bottom of the level...
LDA $14D4,x ; | (if adding 0x50 to the sprite y position would make the high byte >= 2)
ADC #$00 ; |
CMP #$02 ; |
BPL ERASE_SPRITE ; / ...erase the sprite
LDA $167A,x ; \ if "process offscreen" flag is set, return
AND #$04 ; |
BNE RETURN_35 ; /
LDA $13 ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0756 VC:176 00 FL:205
AND #$01 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0780 VC:176 00 FL:205
ORA $03 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0796 VC:176 00 FL:205
STA $01 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0820 VC:176 00 FL:205
TAY ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0844 VC:176 00 FL:205
LDA $1A ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0858 VC:176 00 FL:205
CLC ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0882 VC:176 00 FL:205
ADC SPR_T14,y ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0896 VC:176 00 FL:205
ROL $00 ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizcHC:0928 VC:176 00 FL:205
CMP $E4,x ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:0966 VC:176 00 FL:205
PHP ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:0996 VC:176 00 FL:205
LDA $1B ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdizCHC:1018 VC:176 00 FL:205
LSR $00 ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdiZCHC:1042 VC:176 00 FL:205
ADC SPR_T15,y ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdizcHC:1080 VC:176 00 FL:205
PLP ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F0 P:eNvMXdizcHC:1112 VC:176 00 FL:205
SBC $14E0,x ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1140 VC:176 00 FL:205
STA $00 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:1172 VC:176 00 FL:205
LSR $01 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:1196 VC:176 00 FL:205
BCC SPR_L31 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZCHC:1234 VC:176 00 FL:205
EOR #$80 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZCHC:1250 VC:176 00 FL:205
STA $00 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1266 VC:176 00 FL:205
SPR_L31: LDA $00 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1290 VC:176 00 FL:205
BPL RETURN_35 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1314 VC:176 00 FL:205
ERASE_SPRITE: LDA $14C8,x ; \ if sprite status < 8, permanently erase sprite
CMP #$08 ; |
BCC KILL_SPRITE ; /
LDY $161A,x ;A:FF08 X:0007 Y:0001 D:0000 DB:01 S:01F3 P:envMXdiZCHC:1108 VC:059 00 FL:2878
CPY #$FF ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdiZCHC:1140 VC:059 00 FL:2878
BEQ KILL_SPRITE ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdizcHC:1156 VC:059 00 FL:2878
LDA #$00 ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdizcHC:1172 VC:059 00 FL:2878
STA $1938,y ;A:FF00 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdiZcHC:1188 VC:059 00 FL:2878
KILL_SPRITE: STZ $14C8,x ; erase sprite
RETURN_35: RTS ; return

VERTICAL_LEVEL: LDA $167A,x ; \ if "process offscreen" flag is set, return
AND #$04 ; |
BNE RETURN_35 ; /
LDA $13 ; \
LSR A ; |
BCS RETURN_35 ; /
LDA $E4,x ; \
CMP #$00 ; | if the sprite has gone off the side of the level...
LDA $14E0,x ; |
SBC #$00 ; |
CMP #$02 ; |
BCS ERASE_SPRITE ; / ...erase the sprite
LDA $13 ;A:0000 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:1218 VC:250 00 FL:5379
LSR A ;A:0016 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1242 VC:250 00 FL:5379
AND #$01 ;A:000B X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1256 VC:250 00 FL:5379
STA $01 ;A:0001 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1272 VC:250 00 FL:5379
TAY ;A:0001 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1296 VC:250 00 FL:5379
LDA $1C ;A:001A X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0052 VC:251 00 FL:5379
CLC ;A:00BD X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0076 VC:251 00 FL:5379
ADC SPR_T12,y ;A:00BD X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0090 VC:251 00 FL:5379
ROL $00 ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:enVMXdizCHC:0122 VC:251 00 FL:5379
CMP $D8,x ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0160 VC:251 00 FL:5379
PHP ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0190 VC:251 00 FL:5379
LDA $001D ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F2 P:eNVMXdizcHC:0212 VC:251 00 FL:5379
LSR $00 ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:enVMXdiZcHC:0244 VC:251 00 FL:5379
ADC SPR_T13,y ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:enVMXdizCHC:0282 VC:251 00 FL:5379
PLP ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:envMXdiZCHC:0314 VC:251 00 FL:5379
SBC $14D4,x ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0342 VC:251 00 FL:5379
STA $00 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0374 VC:251 00 FL:5379
LDY $01 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0398 VC:251 00 FL:5379
BEQ SPR_L38 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0422 VC:251 00 FL:5379
EOR #$80 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0438 VC:251 00 FL:5379
STA $00 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0454 VC:251 00 FL:5379
SPR_L38: LDA $00 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0478 VC:251 00 FL:5379
BPL RETURN_35 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0502 VC:251 00 FL:5379
BMI ERASE_SPRITE ;A:8AFF X:0002 Y:0000 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0704 VC:184 00 FL:5490

SUB_IS_OFF_SCREEN: LDA $15A0,x ; \ if sprite is on screen, accumulator = 0
ORA $186C,x ; |
RTS ; / return
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; SUB HORZ POS
;
; $B817 - horizontal mario/sprite check - shared
; Y = 1 if mario left of sprite??
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;org $03B817

SUB_HORZ_POS: LDY #$00 ;A:25D0 X:0006 Y:0001 D:0000 DB:03 S:01ED P:eNvMXdizCHC:1020 VC:097 00 FL:31642
LDA $94 ;A:25D0 X:0006 Y:0000 D:0000 DB:03 S:01ED P:envMXdiZCHC:1036 VC:097 00 FL:31642
SEC ;A:25F0 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizCHC:1060 VC:097 00 FL:31642
SBC $E4,x ;A:25F0 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizCHC:1074 VC:097 00 FL:31642
STA $0F ;A:25F4 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1104 VC:097 00 FL:31642
LDA $95 ;A:25F4 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1128 VC:097 00 FL:31642
SBC $14E0,x ;A:2500 X:0006 Y:0000 D:0000 DB:03 S:01ED P:envMXdiZcHC:1152 VC:097 00 FL:31642
BPL LABEL16 ;A:25FF X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1184 VC:097 00 FL:31642
INY ;A:25FF X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1200 VC:097 00 FL:31642
LABEL16: RTS ;A:25FF X:0006 Y:0001 D:0000 DB:03 S:01ED P:envMXdizcHC:1214 VC:097 00 FL:31642

Was euch sicher auch auffällt, ist die Tatsache, dass die Tabellen für das X, sowie für das Y-displacement nun 4 Werte enthalten. Wir brauchen folgende 4 Teile:
- Xdisplacement von 00 und Ydisplacement von 00
- Xdisplacement von 10 und Ydisplacement von 00
- Ydisplacement von F0 und Xdisplacement von 10
- Ydisplacement von F0 und Xdisplacement von 10


Die Tabelle sieht also wie folgt aus:
Code
YDISP: dcb $F0,$F0,$00,$00
XDISP: dcb $10,$00,$10,$00

Das 4. Byte, bzw. Teil in der Tabelle ist das Teil, welches ganz normal geladen wird. Unser ausgangs Teil. Es ist das Teil unten Rechts.
Das 3. Byte, bzw. Zeil in der Tabelle ist das Teil für welches nur ein Xdisplacement nötig ist. Es ist das Teil unten links.
Das 2. Byte, bzw. Teil in der Tabelle ist das Teil für welches nur ein Ydisplacement nötig ist. Es ist das Teil oben rechts.
Das erste Byte, bzw. Teil in der Tabelle ist das Teil, für welches man sowohl ein X und ein Ydisplacement braucht. Es ist das Teil oben links.

Nun hat jedes Teil seine eigene Position. Wie wissen wir aber nun, welches Teil gerade geladen wird. Eigentlich ist das ganz einfach. Das 1. Byte der der TILEMAP Tabelle ist das Teil oben links, das 2. das Teil oben rechts, das 3. das unten links und das 4. das unten rechts.
Das einzige was nun im Code noch gemacht wurde ist wie oben bereits erwähnt, dass aus dem 01 eine 03 wurde. Einfach wieder Anzahl der Teile -1. (4-1=3)

So, damit sind wir schon fertig. Man kann den Sprite nun testen. Er wird nichts machen, aber er ist 32*32
Naja, ganz so leicht ist es nun nicht. Vielleicht ist es euch beim 16*32 Sprite schon aufgefallen, denn der hat das gleiche Problem. Sobald wir die Geschwindigkeit und die Richtung des Sprites ändern, wenn er auf eine Wand trifft, zerstört das ganze unser X und Ydisplacement.
Um dieses Problem zu beheben müssen wir das X Displacement (nur das Xdisplacement) umwandeln, wenn der Sprite nach links schaut. Hier wurde das schon ein mal gemacht:
Code
dcb "INIT"
JSR SUB_HORZ_POS
TYA
STA $157C,x
RTL

dcb "MAIN"

PHB ;\
PHK ; | Change the data bank to the one our code is running from.
PLB ; | This is a good practice.
JSR SpriteCode ; | Jump to the sprite's function.
PLB ; | Restore old data bank.
RTL ;/ And return.

;===================================
;Sprite Function
;===================================

RETURN: RTS

SpriteCode:
JSR Graphics

LDA $14C8,x ;\
CMP #$08 ; | If sprite dead,
BNE RETURN ;/ Return.
LDA $9D ;\
BNE RETURN ;/ If locked, return.

JSR SUB_OFF_SCREEN_X0 ; Handle offscreen.

LDA $1588,x
AND #$03
BEQ NoFlip
LDA $157C,x
EOR #$01
STA $157C,x
NoFlip:
LDY $157C,x
LDA XSPD,y
STA $B6,x
JSL $01802A
RTS
XSPD: dcb $08,$F8
;===================================
;Graphics Code
;===================================

PROPERTIES: dcb $47,$07

TILEMAP:
dcb $CA,$CC,$EA,$EC
dcb $CA,$CC,$EA,$EC

YDISP: dcb $F0,$F0,$00,$00
dcb $F0,$F0,$00,$00

XDISP: dcb $F0,$00,$F0,$00
dcb $00,$F0,$00,$F0

Graphics:
JSR GET_DRAW_INFO

LDA $157C,x
STA $02 ; Store direction to $02 for use with property routine later.
BNE NoAdd
LDA $03 ;\
CLC ; | If sprite faces left ..
ADC #$04 ; | Adding 4 more bytes to the table.
STA $03 ;/ So we can invert XDISP to not mess up the sprite's appearance.
NoAdd:
PHX ;\ Push sprite index ..
LDX #$03 ;/ And load X with number of tiles to loop through.
Loop:
PHX ; Push number of tiles to loop through.
TXA ;\
ORA $03 ;/ Transfer it to X and add in the "left displacement" if necessary.
TAX ;\ Get it back into X for an index.

LDA $00 ;\
CLC ; | Apply X displacement of the sprite.
ADC XDISP,x ; |
STA $0300,y ;/

LDA $01 ;\
CLC ; | Y displacement is added for the Y position, so one tile is higher than the other.
ADC YDISP,x ; | Otherwise, both tiles would have been drawn to the same position!
STA $0301,y ; | If X is 00, i.e. first tile, then load the first value from the table and apply that
;/ as the displacement. For the second tile, F0 is added to make it higher than the first.
LDA TILEMAP,x ; X still contains index for the tile to draw. If it's the first tile, draw first tile in
STA $0302,y ; the table. If X = 01, i.e. second tile, draw second tile in the table.

PHX ; Push number of times to go through loop + "left" displacement if necessary.
LDX $02 ;\
LDA PROPERTIES,x ; | Set properties based on direction.
ORA $64
STA $0303,y ;/
PLX ; Pull number of times to go through loop.

INY ;\
INY ; | The OAM is 8x8, but our sprite is 16x16 ..
INY ; | So increment it 4 times.
INY ;/

PLX ; Pull current tile back.
DEX ; After drawing this tile, decrease number of tiles to go through loop. If the second tile
; is drawn, then loop again to draw the first tile.

BPL Loop ; Loop until X becomes negative (FF).

PLX ; Pull back the sprite index! We pushed it at the beginning of the routine.

LDY #$02 ; Y ends with the tile size .. 02 means it's 16x16
LDA #$03 ; A -> number of tiles drawn - 1.
; I drew 2 tiles, so 2-1 = 1. A = 01.

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 ; /

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; SUB_OFF_SCREEN
; This subroutine deals with sprites that have moved off screen
; It is adapted from the subroutine at $01AC0D
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

SPR_T12: db $40,$B0
SPR_T13: db $01,$FF
SPR_T14: db $30,$C0,$A0,$C0,$A0,$F0,$60,$90 ;bank 1 sizes
db $30,$C0,$A0,$80,$A0,$40,$60,$B0 ;bank 3 sizes
SPR_T15: db $01,$FF,$01,$FF,$01,$FF,$01,$FF ;bank 1 sizes
db $01,$FF,$01,$FF,$01,$00,$01,$FF ;bank 3 sizes

SUB_OFF_SCREEN_X1: LDA #$02 ; \ entry point of routine determines value of $03
BRA STORE_03 ; | (table entry to use on horizontal levels)
SUB_OFF_SCREEN_X2: LDA #$04 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X3: LDA #$06 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X4: LDA #$08 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X5: LDA #$0A ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X6: LDA #$0C ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X7: LDA #$0E ; |
STORE_03: STA $03 ; |
BRA START_SUB ; |
SUB_OFF_SCREEN_X0: STZ $03 ; /

START_SUB: JSR SUB_IS_OFF_SCREEN ; \ if sprite is not off screen, return
BEQ RETURN_35 ; /
LDA $5B ; \ goto VERTICAL_LEVEL if vertical level
AND #$01 ; |
BNE VERTICAL_LEVEL ; /
LDA $D8,x ; \
CLC ; |
ADC #$50 ; | if the sprite has gone off the bottom of the level...
LDA $14D4,x ; | (if adding 0x50 to the sprite y position would make the high byte >= 2)
ADC #$00 ; |
CMP #$02 ; |
BPL ERASE_SPRITE ; / ...erase the sprite
LDA $167A,x ; \ if "process offscreen" flag is set, return
AND #$04 ; |
BNE RETURN_35 ; /
LDA $13 ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0756 VC:176 00 FL:205
AND #$01 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0780 VC:176 00 FL:205
ORA $03 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0796 VC:176 00 FL:205
STA $01 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0820 VC:176 00 FL:205
TAY ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0844 VC:176 00 FL:205
LDA $1A ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0858 VC:176 00 FL:205
CLC ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0882 VC:176 00 FL:205
ADC SPR_T14,y ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0896 VC:176 00 FL:205
ROL $00 ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizcHC:0928 VC:176 00 FL:205
CMP $E4,x ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:0966 VC:176 00 FL:205
PHP ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:0996 VC:176 00 FL:205
LDA $1B ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdizCHC:1018 VC:176 00 FL:205
LSR $00 ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdiZCHC:1042 VC:176 00 FL:205
ADC SPR_T15,y ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdizcHC:1080 VC:176 00 FL:205
PLP ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F0 P:eNvMXdizcHC:1112 VC:176 00 FL:205
SBC $14E0,x ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1140 VC:176 00 FL:205
STA $00 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:1172 VC:176 00 FL:205
LSR $01 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:1196 VC:176 00 FL:205
BCC SPR_L31 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZCHC:1234 VC:176 00 FL:205
EOR #$80 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZCHC:1250 VC:176 00 FL:205
STA $00 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1266 VC:176 00 FL:205
SPR_L31: LDA $00 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1290 VC:176 00 FL:205
BPL RETURN_35 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1314 VC:176 00 FL:205
ERASE_SPRITE: LDA $14C8,x ; \ if sprite status < 8, permanently erase sprite
CMP #$08 ; |
BCC KILL_SPRITE ; /
LDY $161A,x ;A:FF08 X:0007 Y:0001 D:0000 DB:01 S:01F3 P:envMXdiZCHC:1108 VC:059 00 FL:2878
CPY #$FF ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdiZCHC:1140 VC:059 00 FL:2878
BEQ KILL_SPRITE ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdizcHC:1156 VC:059 00 FL:2878
LDA #$00 ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdizcHC:1172 VC:059 00 FL:2878
STA $1938,y ;A:FF00 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdiZcHC:1188 VC:059 00 FL:2878
KILL_SPRITE: STZ $14C8,x ; erase sprite
RETURN_35: RTS ; return

VERTICAL_LEVEL: LDA $167A,x ; \ if "process offscreen" flag is set, return
AND #$04 ; |
BNE RETURN_35 ; /
LDA $13 ; \
LSR A ; |
BCS RETURN_35 ; /
LDA $E4,x ; \
CMP #$00 ; | if the sprite has gone off the side of the level...
LDA $14E0,x ; |
SBC #$00 ; |
CMP #$02 ; |
BCS ERASE_SPRITE ; / ...erase the sprite
LDA $13 ;A:0000 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:1218 VC:250 00 FL:5379
LSR A ;A:0016 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1242 VC:250 00 FL:5379
AND #$01 ;A:000B X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1256 VC:250 00 FL:5379
STA $01 ;A:0001 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1272 VC:250 00 FL:5379
TAY ;A:0001 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1296 VC:250 00 FL:5379
LDA $1C ;A:001A X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0052 VC:251 00 FL:5379
CLC ;A:00BD X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0076 VC:251 00 FL:5379
ADC SPR_T12,y ;A:00BD X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0090 VC:251 00 FL:5379
ROL $00 ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:enVMXdizCHC:0122 VC:251 00 FL:5379
CMP $D8,x ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0160 VC:251 00 FL:5379
PHP ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0190 VC:251 00 FL:5379
LDA $001D ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F2 P:eNVMXdizcHC:0212 VC:251 00 FL:5379
LSR $00 ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:enVMXdiZcHC:0244 VC:251 00 FL:5379
ADC SPR_T13,y ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:enVMXdizCHC:0282 VC:251 00 FL:5379
PLP ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:envMXdiZCHC:0314 VC:251 00 FL:5379
SBC $14D4,x ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0342 VC:251 00 FL:5379
STA $00 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0374 VC:251 00 FL:5379
LDY $01 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0398 VC:251 00 FL:5379
BEQ SPR_L38 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0422 VC:251 00 FL:5379
EOR #$80 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0438 VC:251 00 FL:5379
STA $00 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0454 VC:251 00 FL:5379
SPR_L38: LDA $00 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0478 VC:251 00 FL:5379
BPL RETURN_35 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0502 VC:251 00 FL:5379
BMI ERASE_SPRITE ;A:8AFF X:0002 Y:0000 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0704 VC:184 00 FL:5490

SUB_IS_OFF_SCREEN: LDA $15A0,x ; \ if sprite is on screen, accumulator = 0
ORA $186C,x ; |
RTS ; / return
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; SUB HORZ POS
;
; $B817 - horizontal mario/sprite check - shared
; Y = 1 if mario left of sprite??
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;org $03B817

SUB_HORZ_POS: LDY #$00 ;A:25D0 X:0006 Y:0001 D:0000 DB:03 S:01ED P:eNvMXdizCHC:1020 VC:097 00 FL:31642
LDA $94 ;A:25D0 X:0006 Y:0000 D:0000 DB:03 S:01ED P:envMXdiZCHC:1036 VC:097 00 FL:31642
SEC ;A:25F0 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizCHC:1060 VC:097 00 FL:31642
SBC $E4,x ;A:25F0 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizCHC:1074 VC:097 00 FL:31642
STA $0F ;A:25F4 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1104 VC:097 00 FL:31642
LDA $95 ;A:25F4 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1128 VC:097 00 FL:31642
SBC $14E0,x ;A:2500 X:0006 Y:0000 D:0000 DB:03 S:01ED P:envMXdiZcHC:1152 VC:097 00 FL:31642
BPL LABEL16 ;A:25FF X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1184 VC:097 00 FL:31642
INY ;A:25FF X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1200 VC:097 00 FL:31642
LABEL16: RTS ;A:25FF X:0006 Y:0001 D:0000 DB:03 S:01ED P:envMXdizcHC:1214 VC:097 00 FL:31642

Was wurde hier nun gemacht?
Wir benutzen dafür einfach freie RAM und laden so dann $03 in X für den Loop. Wir fügen 4 Bytes zu $03 hinzu, wenn Mario nach links schaut, was bedeutet, dass wir unsere Tabelle um 4 Werte erweitern müssen und diese auch spiegeln (da der Sprite nun ja nicht mehr nach rechts, sondern nach links schaut) Das ganze hier noch mal in klein, was oben im Code schon gemacht wurde:
Code
XDISP: dcb $10,$00,$10,$00
dcb $00,$10,$00,$10

Natürlich müssen wir auch die Tabelle des Ydisplacements sowie die TILEMAP Tabell um 4 Werte erweitern, dafür kopieren wir einfach die 4 vorhanden Werte, also so:
Code
TILEMAP:
dcb $CA,$CC,$EA,$EC
dcb $CA,$CC,$EA,$EC

YDISP: dcb $F0, $F0, $00, $00
dcb $F0, $F0, $00, $00

Nun ist der Sprite gefixt. Stellt einen rechts und einen links von Mario auf. Ihr werdet feststellen, dass das Problem behoben ist. Nun könnt ihr euch natürlich noch an den eigentlichen Sprite machen, so wie wir es mit dem 16*16 Sprite gemacht haben.
Im nächsten Kapitel werden wir uns dann mit einem animierten 32*32 Sprite beschäftigen und dass ist dann wirklich nicht mehr leicht.



Kapitel 12: Ein animierter 32*32 Sprite

Wir haben schon einen 16*16 Sprite animiert und das Konzept ist eigentlich genau das gleiche, wie für den 32*32 Sprite. Wir werden nach wie vor den Framecounter benutzen. Nachdem GET_DRAW_INFO aufgerufen wird, aber bevor die Richtung des Sprites in $02 gespeichert wird muss dieser Code hinzugefügt werden:
Code
LDA $13		; Laden des Framecounters
LSR A ;\ Verlangsamen der Animation
LSR A ;/
AND #$01 ; Animation zwischen 2 Frames (0 und 1)
ASL A
ASL A
STA $03 ; Speichern in $03

Die zwei ASL welche jetzt nicht kommentiert sind verlangsamen nicht die Animation. Sie sorgen dafür, dass beim ersten Bild der Animation (0) Frame 1-4 benutzt wird und beim zweiten (1) dann die Frames 5-8. ASL multipliziert das Bit mit 2:
1*2=2
2*2=4 Frames

Das bedeutet also, dass der Framecounter immer zwischen dem Bild 0 und 1 für die Animation wechselt. Dabei wird auch die Tilemap geändert:
Code
TILEMAP:
dcb $CA,$CC,$EA,$EC ; Animation 1 (Bytes 1-4)
dcb $CA,$CC,$EA,$EC ; Animation 2 (Bytes 5-8)

Was natürlich sofort auffält ist die Tatsache, dass die beiden Animationen genau gleich sind. Es werden also genau die gleichen Teile benutzt. Das müssen wir ändern, aber wir können es nich, da:
Code
LDA	$03	;\
CLC ; | Schaut der Sprite nach links
ADC #$04 ; | 4 Bytes zur Tabelle hinzufügen
STA $03 ;/ Umdrehen, des XDISP damit der Sprite noch richtig aussieht

Schaut der Sprite nach links ist der maximale Wert für X:
Max Frame + XDISP = 8+0 = 8
Wir haben genau 8 Bytes in der Tabelle. Das ist also nicht das Problem. Geht der Sprite nun aber nach Links ist unser maximaler Wert für X:
Max Frame + XDISP = 8+4 =12
Wir haben nicht 12 Bytes in der Tabelle. Das würde ja bedeuten:
Code
TILEMAP:
dcb $CA,$CC,$EA,$EC ; Frame 1 Rechts
dcb $CA,$CC,$EA,$EC ; Frame 2 Rechts / Frame 1 Links
dcb $xx,$xx,$xx,$xx ; DIESER CODE EXISTIERT NICHT

Der Sprite probiert also Bytes von der Tabelle zu benutzen, welche überhaupt nicht existieren. Um das zu beheben machen wir einfach ein größere Tabelle. Ist schließlich auch ein größerer Sprite
Code
TILEMAP:
dcb $CA,$CC,$EA,$EC ; Frame 1 Rechts
dcb $C6,$C8,$E6,$E8 ; Frame 2 Rechts

dcb $CA,$CC,$EA,$EC ; Frame 1 Links
dcb $C6,$C8,$E6,$E8 ; Frame 2 Links

So könnte das ganze dann aussehen. So sieht das ganze auch schon übersichtlicher aus. Schaut der Sprite nach rechts werden nun die Bytes 8-16 benutzt. Das Teil an sich ist immer noch das gleiche, aber da $03 vom XDISP beeinflusst wird wenn der Sprite nach rechts schaut, starten die Frames welche animiert werden vom 4 Byte und wollen dann Information von einer nicht existierenden Tabelle. Wir müssen also eine kleine Änderung vornehmen und 8 Bytes hinzufügen anstatt 4, wenn der Sprite nach Rechts schaut:
Code
LDA $157C,x
STA $02
BNE NoAdd
LDA $03
CLC
ADC #$08 ;diese Zeile wurde verändert
STA $03

Jetzt gibt es kein Chaos mehr, da der Sprite nun die Bytes 8-16für die Animation benutzt anstatt 0-1.
Da wir aber 8 Bytes hinzugefügt haben müssen wir auch noch die XDISP und die YDISP Tabellen erweiern. Das sieht dann so aus:
Code
YDISP: dcb $F0, $F0, $00, $00	; Frame 1 Rechts
dcb $F0, $F0, $00, $00 ; Frame 2 Rechts

dcb $F0, $F0, $00, $00 ; Frame 1 Links
dcb $F0, $F0, $00, $00 ; Frame 2 Links

XDISP: dcb $00,$10,$00,$10 ; Frame 1 Rechts
dcb $00,$10,$00,$10 ; Frame 2 Rechts

dcb $10,$00,$10,$00 ; Frame 1 Links
dcb $00,$10,$00,$10 ; Frame 2 Links

Nun ist der Sprite so weit fertig, das er getestet werden kann. Davor hier aber nochmal der Code, wie er nun insgesammt aussehen sollte:
geschrieben am 24.02.2013 17:40:23
( Link )
Code
dcb "INIT"
JSR SUB_HORZ_POS
TYA
STA $157C,x
RTL

dcb "MAIN"

PHB ;\
PHK ; | Change the data bank to the one our code is running from.
PLB ; | This is a good practice.
JSR SpriteCode ; | Jump to the sprite's function.
PLB ; | Restore old data bank.
RTL ;/ And return.

;===================================
;Sprite Function
;===================================

RETURN: RTS

SpriteCode:
JSR Graphics
LDA $14C8,x ;\
CMP #$08 ; | If sprite dead,
BNE RETURN ;/ Return.
LDA $9D ;\
BNE RETURN ;/ If locked, return.

JSR SUB_OFF_SCREEN_X0 ; Handle offscreen.

LDA $1588,x
AND #$03
BEQ NoFlip
LDA $157C,x
EOR #$01
STA $157C,x
NoFlip:
LDY $157C,x
LDA XSPD,y
STA $B6,x
JSL $01802A
JSL $01A7DC
BCC skip
jsl $00f5b7
skip:
RTS
XSPD: dcb $08,$F8
;===================================
;Graphics Code
;===================================

PROPERTIES: dcb $47,$07

;The tables must now have 16 bytes.
;THE LAST 8 ARE ONLY CREATED BECAUSE OF XDISP.

;0-4 BYTE - FRAME 1 RIGHT
;4-8 BYTE - FRAME 2 RIGHT
;8-12 BYTE - FRAME 1 LEFT
;12-16 BYTE - FRAME 2 LEFT

TILEMAP:
dcb $CA,$CC,$EA,$EC ; FRAME 1 ;\ RIGHT
dcb $C6,$C8,$E6,$E8 ; FRAME 2 ;/ RIGHT

dcb $CA,$CC,$EA,$EC ; FRAME 1 ;\ LEFT
dcb $C6,$C8,$E6,$E8 ; FRAME 2 ;/ LEFT

YDISP: dcb $F0,$F0,$00,$00 ; FRAME 1 ;\ RIGHT
dcb $F0,$F0,$00,$00 ; FRAME 2 ;/ RIGHT

dcb $F0,$F0,$00,$00 ; FRAME 1 ;\ LEFT
dcb $F0,$F0,$00,$00 ; FRAME 2 ;/ LEFT

XDISP: dcb $00,$10,$00,$10 ; FRAME 1 ;\ RIGHT
dcb $00,$10,$00,$10 ; FRAME 2 ;/ RIGHT

dcb $10,$00,$10,$00 ; FRAME 1 ;\ LEFT
dcb $10,$00,$10,$00 ; FRAME 2 ;/ LEFT

Graphics:
JSR GET_DRAW_INFO

LDA $14 ;\ Frame counter ..
LSR A ; |
LSR A ; | Add in frame animation rate; More LSRs for slower animation.
AND #$01 ; | 01 means we animate between 2 frames (00 and 01).
ASL A ; |
ASL A ; | ASL x2 (0-4) makes it switch between the first byte and fifth byte,
STA $03 ;/ i.e. first animation and second animation. The result is stored into $03.

LDA $157C,x
STA $02 ; Store direction to $02 for use with property routine later.
BNE NoAdd
LDA $03 ;\
CLC ; | If sprite faces left ..
ADC #$08 ; | Adding 8 more bytes to the table.
STA $03 ;/ So we can invert XDISP to not mess up the sprite's appearance.
NoAdd:
PHX ;\ Push sprite index ..
LDX #$03 ;/ And load X with number of tiles to loop through.
Loop:
PHX ; Push number of tiles to loop through.
TXA ;\
ORA $03 ;/ Transfer it to X and add in the "left displacement" if necessary.
TAX ;\ Get it back into X for an index.

LDA $00 ;\
CLC ; | Apply X displacement of the sprite.
ADC XDISP,x ; |
STA $0300,y ;/

LDA $01 ;\
CLC ; | Y displacement is added for the Y position, so one tile is higher than the other.
ADC YDISP,x ; | Otherwise, both tiles would have been drawn to the same position!
STA $0301,y ; | If X is 00, i.e. first tile, then load the first value from the table and apply that
;/ as the displacement. For the second tile, F0 is added to make it higher than the first.

LDA TILEMAP,x
STA $0302,y

PHX ; Push number of times to go through loop + "left" displacement if necessary.
LDX $02 ;\
LDA PROPERTIES,x ; | Set properties based on direction.
STA $0303,y ;/
PLX ; Pull number of times to go through loop.

INY ;\
INY ; | The OAM is 8x8, but our sprite is 16x16 ..
INY ; | So increment it 4 times.
INY ;/

PLX ; Pull current tile back.
DEX ; After drawing this tile, decrease number of tiles to go through loop. If the second tile
; is drawn, then loop again to draw the first tile.

BPL Loop ; Loop until X becomes negative (FF).

PLX ; Pull back the sprite index! We pushed it at the beginning of the routine.

LDY #$02 ; Y ends with the tile size .. 02 means it's 16x16
LDA #$03 ; A -> number of tiles drawn - 1.
; I drew 2 tiles, so 2-1 = 1. A = 01.

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 ; /

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; SUB_OFF_SCREEN
; This subroutine deals with sprites that have moved off screen
; It is adapted from the subroutine at $01AC0D
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

SPR_T12: db $40,$B0
SPR_T13: db $01,$FF
SPR_T14: db $30,$C0,$A0,$C0,$A0,$F0,$60,$90 ;bank 1 sizes
db $30,$C0,$A0,$80,$A0,$40,$60,$B0 ;bank 3 sizes
SPR_T15: db $01,$FF,$01,$FF,$01,$FF,$01,$FF ;bank 1 sizes
db $01,$FF,$01,$FF,$01,$00,$01,$FF ;bank 3 sizes

SUB_OFF_SCREEN_X1: LDA #$02 ; \ entry point of routine determines value of $03
BRA STORE_03 ; | (table entry to use on horizontal levels)
SUB_OFF_SCREEN_X2: LDA #$04 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X3: LDA #$06 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X4: LDA #$08 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X5: LDA #$0A ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X6: LDA #$0C ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X7: LDA #$0E ; |
STORE_03: STA $03 ; |
BRA START_SUB ; |
SUB_OFF_SCREEN_X0: STZ $03 ; /

START_SUB: JSR SUB_IS_OFF_SCREEN ; \ if sprite is not off screen, return
BEQ RETURN_35 ; /
LDA $5B ; \ goto VERTICAL_LEVEL if vertical level
AND #$01 ; |
BNE VERTICAL_LEVEL ; /
LDA $D8,x ; \
CLC ; |
ADC #$50 ; | if the sprite has gone off the bottom of the level...
LDA $14D4,x ; | (if adding 0x50 to the sprite y position would make the high byte >= 2)
ADC #$00 ; |
CMP #$02 ; |
BPL ERASE_SPRITE ; / ...erase the sprite
LDA $167A,x ; \ if "process offscreen" flag is set, return
AND #$04 ; |
BNE RETURN_35 ; /
LDA $13 ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0756 VC:176 00 FL:205
AND #$01 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0780 VC:176 00 FL:205
ORA $03 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0796 VC:176 00 FL:205
STA $01 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0820 VC:176 00 FL:205
TAY ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0844 VC:176 00 FL:205
LDA $1A ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0858 VC:176 00 FL:205
CLC ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0882 VC:176 00 FL:205
ADC SPR_T14,y ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0896 VC:176 00 FL:205
ROL $00 ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizcHC:0928 VC:176 00 FL:205
CMP $E4,x ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:0966 VC:176 00 FL:205
PHP ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:0996 VC:176 00 FL:205
LDA $1B ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdizCHC:1018 VC:176 00 FL:205
LSR $00 ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdiZCHC:1042 VC:176 00 FL:205
ADC SPR_T15,y ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdizcHC:1080 VC:176 00 FL:205
PLP ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F0 P:eNvMXdizcHC:1112 VC:176 00 FL:205
SBC $14E0,x ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1140 VC:176 00 FL:205
STA $00 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:1172 VC:176 00 FL:205
LSR $01 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:1196 VC:176 00 FL:205
BCC SPR_L31 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZCHC:1234 VC:176 00 FL:205
EOR #$80 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZCHC:1250 VC:176 00 FL:205
STA $00 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1266 VC:176 00 FL:205
SPR_L31: LDA $00 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1290 VC:176 00 FL:205
BPL RETURN_35 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1314 VC:176 00 FL:205
ERASE_SPRITE: LDA $14C8,x ; \ if sprite status < 8, permanently erase sprite
CMP #$08 ; |
BCC KILL_SPRITE ; /
LDY $161A,x ;A:FF08 X:0007 Y:0001 D:0000 DB:01 S:01F3 P:envMXdiZCHC:1108 VC:059 00 FL:2878
CPY #$FF ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdiZCHC:1140 VC:059 00 FL:2878
BEQ KILL_SPRITE ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdizcHC:1156 VC:059 00 FL:2878
LDA #$00 ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdizcHC:1172 VC:059 00 FL:2878
STA $1938,y ;A:FF00 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdiZcHC:1188 VC:059 00 FL:2878
KILL_SPRITE: STZ $14C8,x ; erase sprite
RETURN_35: RTS ; return

VERTICAL_LEVEL: LDA $167A,x ; \ if "process offscreen" flag is set, return
AND #$04 ; |
BNE RETURN_35 ; /
LDA $13 ; \
LSR A ; |
BCS RETURN_35 ; /
LDA $E4,x ; \
CMP #$00 ; | if the sprite has gone off the side of the level...
LDA $14E0,x ; |
SBC #$00 ; |
CMP #$02 ; |
BCS ERASE_SPRITE ; / ...erase the sprite
LDA $13 ;A:0000 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:1218 VC:250 00 FL:5379
LSR A ;A:0016 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1242 VC:250 00 FL:5379
AND #$01 ;A:000B X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1256 VC:250 00 FL:5379
STA $01 ;A:0001 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1272 VC:250 00 FL:5379
TAY ;A:0001 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1296 VC:250 00 FL:5379
LDA $1C ;A:001A X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0052 VC:251 00 FL:5379
CLC ;A:00BD X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0076 VC:251 00 FL:5379
ADC SPR_T12,y ;A:00BD X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0090 VC:251 00 FL:5379
ROL $00 ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:enVMXdizCHC:0122 VC:251 00 FL:5379
CMP $D8,x ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0160 VC:251 00 FL:5379
PHP ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0190 VC:251 00 FL:5379
LDA $001D ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F2 P:eNVMXdizcHC:0212 VC:251 00 FL:5379
LSR $00 ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:enVMXdiZcHC:0244 VC:251 00 FL:5379
ADC SPR_T13,y ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:enVMXdizCHC:0282 VC:251 00 FL:5379
PLP ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:envMXdiZCHC:0314 VC:251 00 FL:5379
SBC $14D4,x ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0342 VC:251 00 FL:5379
STA $00 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0374 VC:251 00 FL:5379
LDY $01 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0398 VC:251 00 FL:5379
BEQ SPR_L38 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0422 VC:251 00 FL:5379
EOR #$80 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0438 VC:251 00 FL:5379
STA $00 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0454 VC:251 00 FL:5379
SPR_L38: LDA $00 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0478 VC:251 00 FL:5379
BPL RETURN_35 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0502 VC:251 00 FL:5379
BMI ERASE_SPRITE ;A:8AFF X:0002 Y:0000 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0704 VC:184 00 FL:5490

SUB_IS_OFF_SCREEN: LDA $15A0,x ; \ if sprite is on screen, accumulator = 0
ORA $186C,x ; |
RTS ; / return
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; SUB HORZ POS
;
; $B817 - horizontal mario/sprite check - shared
; Y = 1 if mario left of sprite??
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;org $03B817

SUB_HORZ_POS: LDY #$00 ;A:25D0 X:0006 Y:0001 D:0000 DB:03 S:01ED P:eNvMXdizCHC:1020 VC:097 00 FL:31642
LDA $94 ;A:25D0 X:0006 Y:0000 D:0000 DB:03 S:01ED P:envMXdiZCHC:1036 VC:097 00 FL:31642
SEC ;A:25F0 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizCHC:1060 VC:097 00 FL:31642
SBC $E4,x ;A:25F0 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizCHC:1074 VC:097 00 FL:31642
STA $0F ;A:25F4 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1104 VC:097 00 FL:31642
LDA $95 ;A:25F4 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1128 VC:097 00 FL:31642
SBC $14E0,x ;A:2500 X:0006 Y:0000 D:0000 DB:03 S:01ED P:envMXdiZcHC:1152 VC:097 00 FL:31642
BPL LABEL16 ;A:25FF X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1184 VC:097 00 FL:31642
INY ;A:25FF X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1200 VC:097 00 FL:31642
LABEL16: RTS ;A:25FF X:0006 Y:0001 D:0000 DB:03 S:01ED P:envMXdizcHC:1214 VC:097 00 FL:31642

Stellt SP4 die GFX 20 ein. Dann werdet ihr feststellen, dass der Sprite ein blauer Mega Mole ist
Wir werden keinen 32*32 Sprite mit mehr als 2 Frames animieren, da das nochmal wesentlich komplexer ist und dieses Tutorial soll ja auch nur die Grundlagen vermitteln.



Kapitel 13: Ein paar Dinge die man noch beachten sollte

Seit dem 5. Kapitel haben wir nur noch mit der Graphics routine gearbeitet. Nun werden wir uns hier wieder auf den Sprite an sich konzentrieren und in diesem Zusammenhang auf Tabellen.
Es gibt sehr viele Spritetabellen, welche benutzt werden können, damit der Sprite etwas macht was wir wollen, wie z.B. das er alle 4 Sekunden springt, oder wir wollen überprüfen, ob jemand auf den Sprite gesprungen ist. Die allgemein am häufigsten benutzten Tabellen sind:
$1528,x
$1534,x
$1510,x
$1504,x
$1594,x
$1540,x
$1558,x
$163E,x ← Diese verringert sich jeden Frame um 1 von alleine
$C2,x

Es gibt natürlich noch mehr, aber allein das hier sind 9, welche wir für den Sprite benutzen können. Die benutzung dieser ist zu vergleichen mit der Benutzung von freier RAM. Es ist nicht so schwer. Sagen wir, wir wollen, dass unser Sprite alle 3 Sekunden springt. Dafür könnten wir eine Tabelle benutzen, die schaut, ob es grade ein bestimmtes Frame ist und wenn ja, dass wir dann die Y-Geschwindigkeit des Sprites ändern. Das könnte dann so aussehen:
Code
LDA $1534,x		; Laden einer Tabelle
CMP #$CC ; Wenn CC Frames vorbei sind.... (ca. 3 Sekunden)
BEQ Jump ; Gehe zu Jump
INC $1534,x ; Verkleinere $1534,x jeden Frame
RTS

Jump:
LDA #$D0
STA $AA,x ; Y-Speed des Sprites
LDA #$01
STA 1DFA ; abspielen vom Sprunggeräusch
JSL $01802A
STZ $1534,x ; Zeit zurücksetzen, damit nicht nur 1 mal gesprungen wird
RTS

In diesem Beispielcode haben wir jetzt $1534,x als Sprungtimer für den Sprite benutzt. Wer diesen Code seinem Sprite verwendet wird feststellen, dass der Sprite alle 3 Sekunden einen kleinen Hüpfer macht. Genau wie hier gezeigt, kann man die anderen Tabellen natürlich einfach für anderes Zeug verwenden. Man muss hier daran denken, dass die Tabellen das X Register verwenden. Beim Verringern der Tabelle braucht man also auch wie hier oben ein ,x!
Als weitere Nutzung für eine Tabelle könnte man $1528,x z.B. als HP Anzeige für Bosse verwenden, was häufig auch gemacht wird. Hier wird dann immer wenn auf den Sprite gesprungen wird die Tabelle um eins verringert, bis sie den Wert 0 erreicht und der Sprite stirbt. Natürlich kann man hier dann auch noch in Abhängigkeit von $1528,x noch andere Sachen passieren lassen. Dazu aber später mehr.

Einen Sprite in einen anderen verändern
Es ist möglich einen normalen Sprite in einen custom Sprite zu verwandeln. Das ist recht einfach. Die Nummer des Sprites wird in $9E,x gespeichert. Der Status des Sprites (08) wird in $14C8,x gespeichert und es wird eine Routine ($07F7D2) durch ein JSL aufgerufen um die Spritetabelle zu reseten. Als Beispeiel lassen wir doch einfach mal einen Sprite sich in einen BulletBill verwandeln.
Code
LDA #$1C 		; Sprite = Bullet Bill
STA $9E,x
LDA #$01
STA $14C8,x
JSL $07F7D2

Fügt man diesen Code hinzu wird sich der Sprite in einen Bullet Bill verwandeln.

Um einen custom Sprite zu spawnen, muss man 3 Dinge beachten:
1. Die Spritnummer zum generieren des Sprites ist nicht in $9E,x gespeichert sondern in $7FAB9E,x
2. Nach $07F7D2 wird noch eine zweite Routine ($0187A7) gebraucht um die Tabellen zu reseten
3. $7FAB10,x muss 08 sein damit der Sprite custom ist.
Code
LDA #$01 		
STA $7FAB9E,x
LDA #$08
STA $14C8,x
JSL $07F7D2
JSL $0187A7
LDA #$08
STA $7FAB10,x

Hier ist nun #$01 die Nummer des Teils, welche generiert wird. Der Status wird in $14C8,x gespeichert. Dann die werden die Tabellen reseted und zuletzt $7FAB10,x auf 8 gesetzt, damit ein custom Sprite generiert wird. $7FAB10 ist übriegens eine Tabelle, welche Sprite Tool benutzt um:
- BIT 3 (08) feststellen, dass ein custom Sprite generiert wird
- BIT 2 (04) kontrollieren ob das Extra Bit gesetzt ist
- BIT 1 (02) kontrollieren des Extra Property Bytes
Es scheint vielleicht seltsam, dass ein custom Sprite plötzlich zu einem anderen Sprite werden sollte. Viele Sprites bentuzen dies aber, um z.B. im Rauch zu verschwinden.


Das Extra Bit
In vielen Sprites wird es benutzt und wir haben schon beim .CFG-Editor darüber gesprochen. Das Extra Bit. In vielen Sprites steht etwa: „USES EXTRA BIT: YES. If set, it will spawn a custom Sprite“
Ist in LunarMagic jetzt noch Extra Info auf 03 (beim einfügen des custom Sprites), verhält sich der Sprite anders. Wir können das ganze jetzt natürlich auch in unsere Sprites einbauen. Alles was wir dafür machen müssen, ist zu überprüfen ist ob BIT 3 von $7FAB10,x gesetzt ist. Dies machen wir wie folgt:
Code
LDA $7FAB9E,x
AND #$04
BEQ BitClear
; Code der ausgeführt wird, wenn das Extra Bit gesetzt ist

Das ganze kann man im Sprite an sich so oft verwenden wie man will um verschiedene Aktionen auszuführen.

Spawnen eines Sprites in Abhängigkeit von Marios Position („eine Art Zufallsgenerator“)
Was wenn wir den F.L.U.D.D. Sprite von Super Mario Sunshine haben wollen, oder die blaue Shell von NSMB? Beide brauchen Angaben über Marios Position, damit sie ihm folgen können, es bzw. dann so aussieht als wären sie auf seinem Rücken oder rechts in seinem Zentrum. Das ganze ist sogar ziemlich einfach, wenn man sich folgende Dinge merkt:
- $94 ist das low byte von Marios X Position
- $95 ist das high byte von Marios X Position
- $96 ist das low byte von Marios Y Position
- $97 ist das high byte von Marios Y Position
- $E4,x ist das low byte der X Position des Sprites
- $14E0,x ist das high byte der X Position des Sprites
- $D8,x ist das low byte der Y Position des Sprites
- $14D4,x ist das high byte der Y Position des Sprites
Ein Beispiel:
$94 würde Marios X Koordinate auf dem Screen markieren. Wenn Mario nun in einen anderen Screen kommt, so würde das high Byte $95 hochgezählt. Das selbe gilt für alle diese Adressen.
Möchte man einen Sprite rechts von Marios Position, so kann man Marios Position (alle 4 Adressen) in der Position des Sprites speichern.
Code
LDA $94
STA $E4,x
LDA $95
STA $14E0,x
LDA $96
STA $D8,x
LDA $97
STA $14D4,x

Jetzt ist Marios Position also in der des Sprites gespeichert. Man speichert nicht die Sprite Position in Marios Position, da so Mario und der Sprite an genau der selben Position wären und Mario würde sterben und dass ist nicht das was man erreichen will. Wenn man den Code nun testet so sieht man einen Sprite der immer an Marios Position gebunden ist Mario aber nicht tötet.
Natürlich ist es auch möglich den Sprite etwas höher oder niedriger von Mario spawnen zu lassen, oder mit ein bisschen Abstand zu Mario. Dafür addiert oder subtrahiert man einfach Offsets. Als Beispiel hier mal ein Code damit der Sprite etwas höher als Mario ist.
Code
LDA $96
SEC ;\ CLC und dann ADC #$10 würden das Gegenteil bewirken
SBC #$10 ;/
STA $D8,x

Man sollte wenn man das Offset verändert nur $E4,x und $D8,x verwenden. Außerdem muss man wenn man das macht noch zusätzlich 01 zum high Byte addieren/subtrahieren , damit das low Byte nicht überläuft im Bezug auf Mario Richtung. Als Beispiel schauen wir einfach an, was zu unserem letzten Beispiel noch hinzu muss damit es funktioniert: (diese mal nur anders herum)
Code
LDA $96
CLC
ADC #$10
STA $D8,x
LDA $95
STA $14E0,x
^ Schlechter Code
LDA $96
CLC
ADC #$10
STA $D8,x
LDY $76 ; Wieso dass hier hin muss hab ich ehrlich gesagt keine Ahnung :P
LDA $95
ADC OFFSET,y
STA $14E0
RTS
OFFSET: dcb $00,$FF
^ Guter Code

Wichtig: Die Carryflag ist bereits gesetzt. Deswegen wird 01 und nicht 00 addiert/subtrahiert. Es ist aber wichtig, 00 zum high Byte zu addieren, weil wenn der Sprite sonst an eine Screengrenze kommt wird dieser unsichtbar.

Ein Sprite spawnt einen custom Sprite
Um einen Sprite einen anderen spawnen zu lassen ruft man eine Routine auf, welchen einen Sprite ins Y Register generiert. Es ist nämlich leichter, wenn sich der Hauptsprite im X Register befindet und der Sprite der von diesem gespawnt wird dann im Y Register ist. Die Routine die wir aufrufen müssen ist $02A9DE. So sieht das ganze dann aus. Ausführlich kommentiert.
Code
JSL $02A9DE
BMI SlotsFull ; Wenn alle Slots für Sprites belegt sind keinen spawnen
PHX ; Wir brauchen das X Register für den Hauptsprite
TYX ; Aufheben der Information des Hauptsprites
LDA #$D0
STA $7FAB9E ;\ Die Spritenummer ist in der RAM Adreese der Custom Sprites gespeichert
TXY ; | Da STA $7FAB9E,y nicht existiert verschieben wir nun X in Y
PLX ;/
LDA #$08 ;\ Das hier ist 8 und bedeutet das ein custom Sprite generiert wird
STA $7FAB10,x ;/
LDA #$01 ;\ Zuerst den INIT Code des Sprites ausführen
STA $14C8,y ;/
LDA $E$,x
STA $00E4,y ; X Position
LDA $14E0,x
STA $14E0,y ; X Position (high)
LDA $D8,x
STA $00D8,y ; Y Position
LDA $14D4,x
STA $14D4,y ; Y Position (high)
PHX ; Push
TYX ; Zurückschieben ins X Register
JSL $07F7D2 ; Reset der Spritetabelle
JSL $0187A7 ; Reset der custom Spritetabelle
PLX ; Pull
geschrieben am 24.02.2013 17:40:44
zuletzt bearbeitet von kooooopa am 24.02.2013 19:56:29.
( Link )
Kapitel 14: Das, worauf alle gewartet haben - ein Boss

Wir erstellen einen custom Boss. Dieser soll:
- Animiert sein (zwischen 2 Frames)
- 5HP haben
- Nach jedem Treffer schneller werden
- In einem bestimmtem Intervall springen, sobald HP 3 oder weniger ist
- Sterben wenn der Boss 0HP hat

Das hier kann als Grundgerüst benutzt werden:
Code
dcb "INIT"
JSR SUB_HORZ_POS
TYA
STA $157C,x
RTL

dcb "MAIN"

PHB ;\
PHK ; | Change the data bank to the one our code is running from.
PLB ; | This is a good practice.
JSR SpriteCode ; | Jump to the sprite's function.
PLB ; | Restore old data bank.
RTL ;/ And return.

;===================================
;Sprite Function
;===================================

RETURN: RTS

SpriteCode:
JSR Graphics
LDA $14C8,x ;\
CMP #$08 ; | If sprite dead,
BNE RETURN ;/ Return.
LDA $9D ;\
BNE RETURN ;/ If locked, return.

LDA $1588,x
AND #$03
BEQ NoFlip ; If touching an object ...
LDA $157C,x
EOR #$01 ; Flip direction.
STA $157C,x

NoFlip:
JSR SUB_OFF_SCREEN_X0 ; Handle offscreen.
RTS

;==================================================================
;Generate Smoke Subroutine
;JSR to DrawSmoke to generate smoke at the sprite's position.
;==================================================================

DrawSmoke: LDY #$03 ; \ find a free slot to display effect
FINDFREE: LDA $17C0,y ; |
BEQ FOUNDONE ; |
DEY ; |
BPL FINDFREE ; |
RTS ; / return if no slots open

FOUNDONE: LDA #$01 ; \ set effect graphic to smoke graphic
STA $17C0,y ; /
LDA #$1F ; \ set time to show smoke
STA $17CC,y ; /
LDA $D8,x ; \ smoke y position = generator y position
STA $17C4,y ; /
LDA $E4,x ; \ load generator x position and store it for later
STA $17C8,y ; /
RTS

;===================================
;Graphics Code
;===================================

PROPERTIES: dcb $47,$07

;The tables must now have 16 bytes.
;THE LAST 8 ARE ONLY CREATED BECAUSE OF XDISP.

;0-4 BYTE - FRAME 1 RIGHT
;4-8 BYTE - FRAME 2 RIGHT
;8-12 BYTE - FRAME 1 LEFT
;12-16 BYTE - FRAME 2 LEFT

TILEMAP:
dcb $CA,$CC,$EA,$EC ; FRAME 1 ;\ RIGHT
dcb $C6,$C8,$E6,$E8 ; FRAME 2 ;/ RIGHT

dcb $CA,$CC,$EA,$EC ; FRAME 1 ;\ LEFT
dcb $C6,$C8,$E6,$E8 ; FRAME 2 ;/ LEFT

YDISP: dcb $F0,$F0,$00,$00 ; FRAME 1 ;\ RIGHT
dcb $F0,$F0,$00,$00 ; FRAME 2 ;/ RIGHT

dcb $F0,$F0,$00,$00 ; FRAME 1 ;\ LEFT
dcb $F0,$F0,$00,$00 ; FRAME 2 ;/ LEFT

XDISP: dcb $00,$10,$00,$10 ; FRAME 1 ;\ RIGHT
dcb $00,$10,$00,$10 ; FRAME 2 ;/ RIGHT

dcb $10,$00,$10,$00 ; FRAME 1 ;\ LEFT
dcb $10,$00,$10,$00 ; FRAME 2 ;/ LEFT

Graphics:
JSR GET_DRAW_INFO

LDA $14 ;\ Frame counter ..
LSR A ; |
LSR A ; | Add in frame animation rate; More LSRs for slower animation.
AND #$01 ; | 01 means we animate between 2 frames (00 and 01).
ASL A ; |
ASL A ; | ASL x2 (0-4) makes it switch between the first byte and fifth byte,
STA $03 ;/ i.e. first animation and second animation. The result is stored into $03.

LDA $157C,x
STA $02 ; Store direction to $02 for use with property routine later.
BNE NoAdd
LDA $03 ;\
CLC ; | If sprite faces left ..
ADC #$08 ; | Adding 8 more bytes to the table.
STA $03 ;/ So we can invert XDISP to not mess up the sprite's appearance.
NoAdd:
PHX ;\ Push sprite index ..
LDX #$03 ;/ And load X with number of tiles to loop through.
Loop:
PHX ; Push number of tiles to loop through.
TXA ;\
ORA $03 ;/ Transfer it to X and add in the "left displacement" if necessary.
TAX ;\ Get it back into X for an index.

LDA $00 ;\
CLC ; | Apply X displacement of the sprite.
ADC XDISP,x ; |
STA $0300,y ;/

LDA $01 ;\
CLC ; | Y displacement is added for the Y position, so one tile is higher than the other.
ADC YDISP,x ; | Otherwise, both tiles would have been drawn to the same position!
STA $0301,y ; | If X is 00, i.e. first tile, then load the first value from the table and apply that
;/ as the displacement. For the second tile, F0 is added to make it higher than the first.

LDA TILEMAP,x
STA $0302,y

PHX ; Push number of times to go through loop + "left" displacement if necessary.
LDX $02 ;\
LDA PROPERTIES,x ; | Set properties based on direction.
STA $0303,y ;/
PLX ; Pull number of times to go through loop.

INY ;\
INY ; | The OAM is 8x8, but our sprite is 16x16 ..
INY ; | So increment it 4 times.
INY ;/

PLX ; Pull current tile back.
DEX ; After drawing this tile, decrease number of tiles to go through loop. If the second tile
; is drawn, then loop again to draw the first tile.

BPL Loop ; Loop until X becomes negative (FF).

PLX ; Pull back the sprite index! We pushed it at the beginning of the routine.

LDY #$02 ; Y ends with the tile size .. 02 means it's 16x16
LDA #$03 ; A -> number of tiles drawn - 1.
; I drew 2 tiles, so 2-1 = 1. A = 01.

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 ; /

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; SUB_OFF_SCREEN
; This subroutine deals with sprites that have moved off screen
; It is adapted from the subroutine at $01AC0D
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

SPR_T12: db $40,$B0
SPR_T13: db $01,$FF
SPR_T14: db $30,$C0,$A0,$C0,$A0,$F0,$60,$90 ;bank 1 sizes
db $30,$C0,$A0,$80,$A0,$40,$60,$B0 ;bank 3 sizes
SPR_T15: db $01,$FF,$01,$FF,$01,$FF,$01,$FF ;bank 1 sizes
db $01,$FF,$01,$FF,$01,$00,$01,$FF ;bank 3 sizes

SUB_OFF_SCREEN_X1: LDA #$02 ; \ entry point of routine determines value of $03
BRA STORE_03 ; | (table entry to use on horizontal levels)
SUB_OFF_SCREEN_X2: LDA #$04 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X3: LDA #$06 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X4: LDA #$08 ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X5: LDA #$0A ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X6: LDA #$0C ; |
BRA STORE_03 ; |
SUB_OFF_SCREEN_X7: LDA #$0E ; |
STORE_03: STA $03 ; |
BRA START_SUB ; |
SUB_OFF_SCREEN_X0: STZ $03 ; /

START_SUB: JSR SUB_IS_OFF_SCREEN ; \ if sprite is not off screen, return
BEQ RETURN_35 ; /
LDA $5B ; \ goto VERTICAL_LEVEL if vertical level
AND #$01 ; |
BNE VERTICAL_LEVEL ; /
LDA $D8,x ; \
CLC ; |
ADC #$50 ; | if the sprite has gone off the bottom of the level...
LDA $14D4,x ; | (if adding 0x50 to the sprite y position would make the high byte >= 2)
ADC #$00 ; |
CMP #$02 ; |
BPL ERASE_SPRITE ; / ...erase the sprite
LDA $167A,x ; \ if "process offscreen" flag is set, return
AND #$04 ; |
BNE RETURN_35 ; /
LDA $13 ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0756 VC:176 00 FL:205
AND #$01 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0780 VC:176 00 FL:205
ORA $03 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0796 VC:176 00 FL:205
STA $01 ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0820 VC:176 00 FL:205
TAY ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0844 VC:176 00 FL:205
LDA $1A ;A:8A01 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizcHC:0858 VC:176 00 FL:205
CLC ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0882 VC:176 00 FL:205
ADC SPR_T14,y ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZcHC:0896 VC:176 00 FL:205
ROL $00 ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizcHC:0928 VC:176 00 FL:205
CMP $E4,x ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:0966 VC:176 00 FL:205
PHP ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:0996 VC:176 00 FL:205
LDA $1B ;A:8AC0 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdizCHC:1018 VC:176 00 FL:205
LSR $00 ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdiZCHC:1042 VC:176 00 FL:205
ADC SPR_T15,y ;A:8A00 X:0009 Y:0001 D:0000 DB:01 S:01F0 P:envMXdizcHC:1080 VC:176 00 FL:205
PLP ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F0 P:eNvMXdizcHC:1112 VC:176 00 FL:205
SBC $14E0,x ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1140 VC:176 00 FL:205
STA $00 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:1172 VC:176 00 FL:205
LSR $01 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:eNvMXdizCHC:1196 VC:176 00 FL:205
BCC SPR_L31 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZCHC:1234 VC:176 00 FL:205
EOR #$80 ;A:8AFF X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdiZCHC:1250 VC:176 00 FL:205
STA $00 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1266 VC:176 00 FL:205
SPR_L31: LDA $00 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1290 VC:176 00 FL:205
BPL RETURN_35 ;A:8A7F X:0009 Y:0001 D:0000 DB:01 S:01F1 P:envMXdizCHC:1314 VC:176 00 FL:205
ERASE_SPRITE: LDA $14C8,x ; \ if sprite status < 8, permanently erase sprite
CMP #$08 ; |
BCC KILL_SPRITE ; /
LDY $161A,x ;A:FF08 X:0007 Y:0001 D:0000 DB:01 S:01F3 P:envMXdiZCHC:1108 VC:059 00 FL:2878
CPY #$FF ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdiZCHC:1140 VC:059 00 FL:2878
BEQ KILL_SPRITE ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdizcHC:1156 VC:059 00 FL:2878
LDA #$00 ;A:FF08 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdizcHC:1172 VC:059 00 FL:2878
STA $1938,y ;A:FF00 X:0007 Y:0000 D:0000 DB:01 S:01F3 P:envMXdiZcHC:1188 VC:059 00 FL:2878
KILL_SPRITE: STZ $14C8,x ; erase sprite
RETURN_35: RTS ; return

VERTICAL_LEVEL: LDA $167A,x ; \ if "process offscreen" flag is set, return
AND #$04 ; |
BNE RETURN_35 ; /
LDA $13 ; \
LSR A ; |
BCS RETURN_35 ; /
LDA $E4,x ; \
CMP #$00 ; | if the sprite has gone off the side of the level...
LDA $14E0,x ; |
SBC #$00 ; |
CMP #$02 ; |
BCS ERASE_SPRITE ; / ...erase the sprite
LDA $13 ;A:0000 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:1218 VC:250 00 FL:5379
LSR A ;A:0016 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1242 VC:250 00 FL:5379
AND #$01 ;A:000B X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1256 VC:250 00 FL:5379
STA $01 ;A:0001 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1272 VC:250 00 FL:5379
TAY ;A:0001 X:0009 Y:00E4 D:0000 DB:01 S:01F3 P:envMXdizcHC:1296 VC:250 00 FL:5379
LDA $1C ;A:001A X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0052 VC:251 00 FL:5379
CLC ;A:00BD X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0076 VC:251 00 FL:5379
ADC SPR_T12,y ;A:00BD X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0090 VC:251 00 FL:5379
ROL $00 ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:enVMXdizCHC:0122 VC:251 00 FL:5379
CMP $D8,x ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0160 VC:251 00 FL:5379
PHP ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0190 VC:251 00 FL:5379
LDA $001D ;A:006D X:0009 Y:0001 D:0000 DB:01 S:01F2 P:eNVMXdizcHC:0212 VC:251 00 FL:5379
LSR $00 ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:enVMXdiZcHC:0244 VC:251 00 FL:5379
ADC SPR_T13,y ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:enVMXdizCHC:0282 VC:251 00 FL:5379
PLP ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F2 P:envMXdiZCHC:0314 VC:251 00 FL:5379
SBC $14D4,x ;A:0000 X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNVMXdizcHC:0342 VC:251 00 FL:5379
STA $00 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0374 VC:251 00 FL:5379
LDY $01 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0398 VC:251 00 FL:5379
BEQ SPR_L38 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0422 VC:251 00 FL:5379
EOR #$80 ;A:00FF X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0438 VC:251 00 FL:5379
STA $00 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0454 VC:251 00 FL:5379
SPR_L38: LDA $00 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0478 VC:251 00 FL:5379
BPL RETURN_35 ;A:007F X:0009 Y:0001 D:0000 DB:01 S:01F3 P:envMXdizcHC:0502 VC:251 00 FL:5379
BMI ERASE_SPRITE ;A:8AFF X:0002 Y:0000 D:0000 DB:01 S:01F3 P:eNvMXdizcHC:0704 VC:184 00 FL:5490

SUB_IS_OFF_SCREEN: LDA $15A0,x ; \ if sprite is on screen, accumulator = 0
ORA $186C,x ; |
RTS ; / return
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; SUB HORZ POS
;
; $B817 - horizontal mario/sprite check - shared
; Y = 1 if mario left of sprite??
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;org $03B817

SUB_HORZ_POS: LDY #$00 ;A:25D0 X:0006 Y:0001 D:0000 DB:03 S:01ED P:eNvMXdizCHC:1020 VC:097 00 FL:31642
LDA $94 ;A:25D0 X:0006 Y:0000 D:0000 DB:03 S:01ED P:envMXdiZCHC:1036 VC:097 00 FL:31642
SEC ;A:25F0 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizCHC:1060 VC:097 00 FL:31642
SBC $E4,x ;A:25F0 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizCHC:1074 VC:097 00 FL:31642
STA $0F ;A:25F4 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1104 VC:097 00 FL:31642
LDA $95 ;A:25F4 X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1128 VC:097 00 FL:31642
SBC $14E0,x ;A:2500 X:0006 Y:0000 D:0000 DB:03 S:01ED P:envMXdiZcHC:1152 VC:097 00 FL:31642
BPL LABEL16 ;A:25FF X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1184 VC:097 00 FL:31642
INY ;A:25FF X:0006 Y:0000 D:0000 DB:03 S:01ED P:eNvMXdizcHC:1200 VC:097 00 FL:31642
LABEL16: RTS ;A:25FF X:0006 Y:0001 D:0000 DB:03 S:01ED P:envMXdizcHC:1214 VC:097 00 FL:31642
In die INIT Routine kommen die Lebenspunkte. Wie schon gesagt wird dafür fast immer $1528,x verwendet. Das machen wir hier dann natürlich auch.
dcb “INIT“
LDA #$05
STA $1528,x
RTL

Wer möchte kann hier noch einstellen, das der Sprite Mario verfolgt. Machen wir aber nicht.
Die INIT Routine ist damit auch schon fertig.
Nun geht es an die Sprite Main Routine. Da der Sprite ja nach jedem Hit schneller werden soll nehmen wir $1528,x um zu schauen wie viele HP der Sprite noch hat.
Code
LDY $1528,x
LDA $157C,x ; Kontrollieren der Bewegungsrichtung des Sprites
BEQ Right
LDA LeftSPd,y
BRA DoSpeed
Right:
LDA RightSpd,y
DoSpeed:
STA $B6,x ; X Geschwindigkeit des Sprites
LDA $#10
STA $AA,x ; Y Geschwindigkeit des Sprites
JSL $01802A
RTS

LeftSpd: dcb $00,$E8,$EB,$EF,$F4,$F8
RightSpd: dcb $00,$18,$15,$11,$C0,$08

Also zuerst werden die HP aus dem Y Register geladen. Dann wird die Richtung des Sprites überprüft, da die Geschwindigkeit positiv ist, wenn der Sprite sich nach rechts bewegt und negativ, wenn sich der Sprite nach links bewegt. Bei LeftSpd geht es dann zu einer 5-byte Tabelle, mit den unterschiedlichen Geschwindigkeiten, welche dann auch in die X Geschwindigkeit des Sprites ($B6,x) gespeichert werden. Das selbe gilt natürlich auch für RightSpd.
Als nächstes geht es noch um die Y Geschwindigkeit des Sprites welche auf 10 gesetzt wird. Nun haben wir einen Sprite, der sich abhängig von seinen HP verschieden schnell bewegt. Wer den Code noch effizienter machen will, kann noch etwas Code hinzufügen, der das Speichern der Geschwindigkeit überspringt, wenn der Sprite in der Luft ist.

Als nächstes soll der Sprite noch in einem bestimmtem Intervall springen. Dafür nehmen wir wieder eine Tabelle. $1594,x
Dieser Code kann direkt hinter den Code von eben.
Code
LDA $1594,x
CMP #$D5
BEQ TIME_TO_JUMP
INC $1594,x
BRA SHARED

TIME_TO_JUMP:
LDA #$C0
STA $AA,x
JSL $01802A
LDA #$01
STA $1DFA
STZ $1594,x
SHARED:
RTS

Noch ein bisschen Interaktion:
Code
SHARED:
JSL $018032 ; Interaktion mit anderen Sprites
JSL $01A7DC
BCC NoContact

LDA $0E
CMP #$E9
BPL SpriteWins

; Mario trifft den Sprite

SpriteWins:
LDA $14900
BNE NoContact ;Tötet den Sprite nicht, wenn man einen Stern hat!
JSL $00F5B7
NoContact:
RTS

Wenn Mario den Sprite verletzt, ihn aber nicht tötet, dann müssen wir den Hitcounter ($1528,x) um eins verringern un danach schauen ob dieser nun 0 ist und wenn ja müssen wir den Sprite töten. Das muss also nach MarioWins:
Code
JSL $01AB99
JSL $01AA33
LDA #$A0
STA $7D
DEC $1528,x
LDA $1528,x
BEQ KillSprite
LDA #$13
STA $1DF9 ;SFX wenn man auf den Sprite springt.

KillSprite:
; Den könnt ihr selber schreiben :)

Für den KillSprite Teil wäre es z.B. Möglich, eine positive X Geschwindigkeit (unter 80) auf $AA,x zu setzen und den Status „killed by a star“ zu aktivieren.
Damit wäre der Bosssprite dann auch schon fertig. Er ist natürlich sehr einfach. Alles was er macht ist sich unterscheidlich schnell zu bewegen und zu springen. Natürlich kann man den Boss noch wesentlich komplexer machen, indem man noch mehr Tabellen benutzt. Nun kann man den Boss testen.



Da dieses Tutorial nun zu Ende ist bleibt die Frage, was man jetzt können sollte. Klar, einfach Sprites (auch Generatoren) sollten jetzt drin sein und auch einfache Bosse sind nun möglich. Mit dem blauen Pilz ganz am Anfang ist es sogar möglich eigene PowerUps zu kreiren.
Zum Abschluss noch einen Satz von Ice Man:
I have spent AGES working on this
geschrieben am 24.02.2013 18:12:02
( Link )
Uh! Ein Tutorial für Sprite-Programmierung!
Um nichts auf der Welt würde ich mir das alles jetzt durchlesen, aber, verdammt nochmal, gute Arbeit!
-Das quadratische Rad neu erfinden-
Mit das quadratische Rad neu erfinden (englisch Reinventing the square wheel) bezeichnet man die Bereitstellung einer schlechten Lösung, wenn eine gute Lösung bereits existiert.

-Slowsort-
Slowsort (von engl. slow: langsam) ist ein langsamer, rekursiver Sortieralgorithmus, der nach dem Prinzip Vervielfache und kapituliere (engl. Multiply and surrender, eine Parodie auf Teile und herrsche) arbeitet.

geschrieben am 24.02.2013 18:35:08
( Link )
Wenn ich mal VIIIIIIIIIIEL Zeit hab, les ich mir das auch mal durch o.O
Ich glaub, da kann man schon den Sprite programmieren, der das Spiel startet o.O
Zitat von Robju am 22.11.2013:
ICH BRING EUCH ALLE UM!!
Zitat von Dominik am 07.08.2013:
Fick dich Rob, ich wusste dass das kommt!

anzeigen
MFG: Wieso binn ich als Ideot genannt?Das ist net nett.
Robbinn, ideot, Satzstellung, Grammatik.
MFGSchonn gut, schon gut, das mit "binn" und "Idoet" ist ein Typo.
RobDas e und i sind 4 tasten voneinander entfernt.
MFGschnelle Finger?
Rob Nein.
MFG *facepalm*
Rob Wenn man Idiot schreibt kommt man nichtmal in die Nähe eines "e" s
MFG Doch, warum hat man denn sonst zwei Finger?
Rob Das hat mir der ANzahl der Finger nichts zu tun.
MFG Ich meine Hände.
Bro ich hab 3 stück
Rob Hat auch kit der Anzahl der Händer nichts zu tun
MFG Und es hat mit der Anzahl der Hände zu tun.
Rob Nein.
MFG Ich schreibe doch mit beiden.
Rob YOU ARE AN IDIOT, AH HAHAHAHAHA HAAAAHAAA HAHAHAHAAAAA!

DIE KONFI
DIE andere KONFI
JJJAAAAAAAH
geschrieben am 24.02.2013 18:41:45
( Link )
Sprites sind einer der wichtigsten Aspekte in SMW - demnach möchte ich dir für diesen Tutorial danken.

(Und dabei geht es mir noch nicht einmal um Bosse: Mir sind einzigartige Gegner in Level wichtiger als Custom Bosse )

Leider habe ich momentan keine Zeit aber ich werde es mir definitiv anschauen
Wie kritisch man doch gegenüber dem System wird, wenn man älter wird...
geschrieben am 24.02.2013 19:07:08
( Link )
Jep, gute Übersetzungsarbeit.

Zitat von Reggiamoto:
Ich glaub, da kann man schon den Sprite programmieren, der das Spiel startet o.O

Ein Sprite startet das Spiel? Wäre mir neu.
geschrieben am 24.02.2013 19:31:57
( Link )
Ich meine, man kann ein Spiel programmieren, der ein anderes Spiel startet, das in dem ROM mit drinnen ist
Zitat von Robju am 22.11.2013:
ICH BRING EUCH ALLE UM!!
Zitat von Dominik am 07.08.2013:
Fick dich Rob, ich wusste dass das kommt!

anzeigen
MFG: Wieso binn ich als Ideot genannt?Das ist net nett.
Robbinn, ideot, Satzstellung, Grammatik.
MFGSchonn gut, schon gut, das mit "binn" und "Idoet" ist ein Typo.
RobDas e und i sind 4 tasten voneinander entfernt.
MFGschnelle Finger?
Rob Nein.
MFG *facepalm*
Rob Wenn man Idiot schreibt kommt man nichtmal in die Nähe eines "e" s
MFG Doch, warum hat man denn sonst zwei Finger?
Rob Das hat mir der ANzahl der Finger nichts zu tun.
MFG Ich meine Hände.
Bro ich hab 3 stück
Rob Hat auch kit der Anzahl der Händer nichts zu tun
MFG Und es hat mit der Anzahl der Hände zu tun.
Rob Nein.
MFG Ich schreibe doch mit beiden.
Rob YOU ARE AN IDIOT, AH HAHAHAHAHA HAAAAHAAA HAHAHAHAAAAA!

DIE KONFI
DIE andere KONFI
JJJAAAAAAAH
geschrieben am 24.02.2013 21:07:08
( Link )
Zitat von Reggiamoto:
Ich meine, man kann ein Spiel programmieren, der ein anderes Spiel startet, das in dem ROM mit drinnen ist


Nur wenn besagtes Spiel schon in der ROM vorhanden ist (weil es z.B. als Patch eingefügt wurde).
-Das quadratische Rad neu erfinden-
Mit das quadratische Rad neu erfinden (englisch Reinventing the square wheel) bezeichnet man die Bereitstellung einer schlechten Lösung, wenn eine gute Lösung bereits existiert.

-Slowsort-
Slowsort (von engl. slow: langsam) ist ein langsamer, rekursiver Sortieralgorithmus, der nach dem Prinzip Vervielfache und kapituliere (engl. Multiply and surrender, eine Parodie auf Teile und herrsche) arbeitet.