Bitplanes werden multipliziert und wieder übereinander gelegt, um das "Zahlen-Raster" zu ergeben, nachdem dann die einzelnen Pixel gemäß der Farbennummer in der Palette wieder gemalt werden.
bitplanes2.jpg (136.37 KiB) 7055 mal betrachtet
versteckter Inhalt:
Um bei dem Bild der Zeichentrick-Folien zu bleiben: Die Folien für die BGs sind wesentlich größer als das Fernsehbild; jede dieser Folien kann individuell "verschoben" werden, um das zu erreichen, was Moi-Même Freude bringt.
Fernsehbildundlayer.jpg (114.97 KiB) 7055 mal betrachtet
Perikles hat geschrieben:Man muss sich das mal reinziehen: die Idee ist scheiße, die theoretische Ausarbeitung ist scheiße, die praktische Umsetzung ist scheiße und der so entstehende Anspruch noch beschissener.
Der Thread ist eine sehr gute Idee. Er zeigt auch schön, wie viel Verständnis für das System es wirklich braucht, um am Ende auch nur irgend etwas auf den Bildschirm zu bekommen. So kapieren all die Leute, die ohne Ahnung schon immer mal ein eigenes Spiel programmieren wollten, WARUM sie es bestimmt nicht gebacken kriegen werden. (So viel Selbstironie muß sein...)
Das ist nun aber etwas negativ aufgefasst. Wer wirklich Interesse und Zeit mitbringt findet bestimmt auch unglaublich konstruktive Energien in diesen tollen Thread! Iwo muss man doch herausfinden wie man loslegt. Ich finds super!
@Chrono: Das sollte nicht negativ aufgefasst rüberkommen. Ganz im Gegenteil. Der Thread ist hochinteressant und sehr informativ (zumindest für mich als Laien). Dazu ist alles (im Gegensatz zur überwiegenden Mehrheit der sonstigen Fachliteratur) so erklärt, daß man sich auch als Normalsterblicher was drunter vorstellen kann. Aber er zeigt eben auch sehr deutlich, wie komplex das Thema ist und wie tief man in die Materie einsteigen muß, bis auch nur ein farbiger Pixel auf dem Bildschirm erscheint. Von einem "richtigen" Spiel ganz zu schweigen.
Freut mich zu hören, dass das hier gelesen wird, und wenn es verständlich geschrieben ist.
Ich versuch mal noch ein wenig zu schreiben, schätze aber, mit diesem "Artikel" nicht fertig zu werden, bin im U'laub, gleich gibts Frühstück.
Da es hier um Physik geht, möchte ich hier ganz speziell, dass man schaut, ob ich das korrekt erkläre. Ich versuche, alle irrelevanten Vokabeln auszulassen oder nur am Rande zu erwähnen, es wird schon kompliziert genug. Außerdem, Memo an mich selbst...
...BILDER HIERZU MALEN!
~~~
U can't touch this - Austastlücken, HBLANK, VBLANK
Ein wenig Verständnis über den Aufbau eines Röhrenfernsehers ist äußerst hilfreich für dieses Thema, da viele Eigenheiten dessen vom SNES genutzt werden. Falls du nicht weißt, was ein Röhrenfernseher ist, und auch nicht weißt, auf was ich mit "U can't touch this" anspiele, möchte ich dich freundlich noch einmal daran erinnern, dass man mit dem Game Maker wesentlich schneller und leichter Spiele entwickeln kann; falls es nun doch das SNES sein soll, guck bei Wikipedia, geh ins Museum oder schau alte Zeitdokumente auf Zelluloid an, um zu erfahren, was ein Röhrenfernseher ist. Und wenn dunicht weißt,was Zelluloidist...
Zuallererst: Erinnerst du dich an deinen Physikunterricht in der Schule? Hattet ihr Spulen? Bei einer Spule ist ein ganz dünnes, ganz langes Kabel ganz eng um einen Kern, z. B. einen "Eisenstab", gewickelt. Wenn du dort nun Strom durch die Leitung jagst, entsteht ein magnetisches Feld. Behalten wir das einen Moment im Hinterkopf.
Ein Röhrenfernseher, englisch CRT, ist ein Kasten, an dessen Rückseite eine Kathode, eine "Elektronen-Kanone", sitzt, die konstant Elektronen von hinten auf die Bildschirmfläche schießt. Dieser Strahl wird mit Hilfe von Spulen abgelenkt. Und zwar sind zwischen der "Kanone" und der Bildschirmfläche jeweils oben und unten und links und rechts vier Spulen. Da Wechselstrom an den Spulen anliegt, wechselt das magnetische Feld der Spulen seine Polarisation, und da Elektronen sich von einem magnetischen Feld anziehen lassen (weil sie negativ geladen ist), "schwingt" der "Stahl" unserer Kanone von links nach rechts und von oben nach unten. Allerdings sind diese beiden Schwingungen nicht gleichschnell: Die Links-Rechts-Schwingung ist wesentlich schneller. Behalten wir das jetzt einen Moment im Hinterkopf.
Der Elektronenstrahl trifft bei der Bildfläche auf ein Raster aus grünen, roten und blauen Punkten. Diese Punkte werden durch die Elektronen erhellt; je mehr, desto heller. Ein Punkt von jeder Farbe ergeben dann einen Pixel. Hier sieht man, wozu die Ablenkung des Elektronenstrahls durch die Spulen notwendig ist: Andernfalls würde die "Elektronenkanone" konstant nur auf die Mitte des Bildschirm schießen und dort dann einen sehr kleinen, sehr hellen Punkt machen.
So würde Fernsehen nicht funktionieren.
Ich erwähnte, dass die Schwingung in senkrechter Richtung wesentlich langsamer ist. Das führt dann dazu, dass der Strahl einmal von links nach rechts (oder andersrum, ist ja auch egal) eine "Bildpunkt"-Reihe, auch bekannt als Bildzeile, "beschießt", und dann durch die vertikale Ablenkung eine Zeile nach unten rutscht. Soweit verständlich?
Es wurde dann alles zeitlich so abgestimmt, dass, wenn man der Röhre sagt: "Jetzt mach aber einen dunklen Pixel!", sie entsprechend rechtzeitig die Menge an "abgeschossenen" Elektronen verringert, dass an die drei gewünschten Farb-Punkte im Raster weniger Elektronen erhalten und dementsprechend weniger leuchten - der Bildpunkt ist weniger hell.
Der nächste Schritt ist: Wenn der Strahl einmal von einer Seite zur anderen "geschwungen" ist, wird der Strahl abgeschaltet, bis die Ablenkung wieder auf der "Anfangsseite" angekommen ist. Soll heißen: Die Bildzeilen werden immer von links nach rechts, von links nach rechts, von links nach rechts aufgebaut. Dazwischen ist die Kanone außer Betrieb. Das nennt sich "horizontale Austastlücke", im Englischen wesentlich prägnanter: HBLANK.
Das Bild wird wiederum immer von oben nach unten aufgebaut. Logischerweise lässt man die Kanone schweigen, wenn durch die Magnetfelder der Strahl von unten nach oben wandert. Das wiederum ist die "vertikale Austastlücke", VBLANK. Logischerweise ist diese Phase wesentlich länger als der HBLANK.
Das ist deshalb wissenswert, weil speziell die Zeitphase des VBLANKs gerne für andere Dinge genutzt wird.
hblank vblank.jpg (65.27 KiB) 7013 mal betrachtet
Jetzt noch einige Zusatzinformationen, die gerade hier passen.
Es gibt außer dem VBLANK und dem HBLANK noch einen FBLANK. Das ist der "forced Blank", die erzwungene Abschaltung der Röhre. Dazu sei gesagt, dass man die Helligkeit des Gesamtbilds auf dem Fernseher mit dem SNES steuern kann. Z. B. kann man so Bild-Aus- und Einblendeffekte erreichen, in dem man die Helligkeit komplett herunter dreht, andere Bilddaten lädt und dann die Helligkeit wieder hochregelt ("fade to black"). Ist die Helligkeit gleich null, ist das ein FBLANK.
Kleine nette Info nebenbei: Das Fernsehprogramm-Signal nutzt die "Pausen" zwischen dem HBLANK und VBLANK, um z. B. die Bilddaten des Videotexts zu übermitteln. Neuere Fernseher, die keine Bildröhre mehr haben, sind allerdings so designt, dass sie mit dieser Datenübertragung umgehen können, die immer noch nach dem Muster erfolgt, dass sie auf CRT-Fernsehern funktioniert ("Abwärtskompatibilität", wenn man so will). Das führt zum Beispiel außerdem dazu, dass man auch SNES auf LCD-Fernsehern spielen kann.
Perikles hat geschrieben:Man muss sich das mal reinziehen: die Idee ist scheiße, die theoretische Ausarbeitung ist scheiße, die praktische Umsetzung ist scheiße und der so entstehende Anspruch noch beschissener.
Der Artikel über den Native/Emulation-Mode muss vor diesem sein, schätzungsweise am besten direkt vor diesem.
Das Statusregister
Das Statusregister ist Bestandteil eines jeden Prozessors.
Da es ein Register ist, heißt das, es enthält eine bestimmte Anzahl an Bits.
Das Statusregister beim Ricoh 5A22, das heißt WDC 65C816, das heißt SNES ist acht Bits, das heißt ein Byte, groß.
Das Statusregister wird auch Flag-Register genannt.
Der Inhalt des Statusregisters dient dazu, bestimmte Signale zugeben oder bestimmte Stadien anzuzeigen. Dazu muss man verstehen, dass die einzelnen Bits im Statusregister sozusagen voneinander losgelöst sind. Sie dienen nicht, wie z. B. beim Akkumulator und den beiden Index-Registern (X und Y), dazu, dass mehrere Bits eine Zahl darstellen sollen, sondern die acht Bit des Statusregisters sind eher "Acht Antworten auf ja/nein-Fragen".
Ich möchte es auf eine zweite Weise illustrieren, nämlich als acht Fahnenmasten, an denen Signalflaggen gehisst werden können:
Der Prozessoraufbau im Allgemeinen; das Statusregister ist im unteren Bereich dargestellt
prozessor.jpg (79.15 KiB) 7009 mal betrachtet
Die Flags werden in unterschiedlichen Zusammenhängen gesetzt, z. B. für einen "Carry", einen Übertrag:
- Du addierst zwei 8-Bit-Zahlen, und das Ergebnis ist so groß, dass es einen neunten Bit braucht. Dazu wird der "Carry"-Bit im Statusregister gesetzt.
- Der "Zero"-Bit zeigt zum Beispiel an, dass der Akkumulator deshalb leer (gleich 0b00000000) ist, weil bei z. B. einer Subtraktion zwei gleichgroße Zahlen von einander abgezogen wurden, und nicht etwa aufgrund eines Fehlers o. ä.
Genauere Erklärungen dazu findet man im offiziellen Handbuch zum WDC 65816, Seite 29-31, wobei darauf hingewiesen werden muss, dass dort das Statusregister des 6502s* erklärt wird. Der Unterschied zwischen beiden ist allerdings nur, dass der 65816 den "Break"-Bit (b) nicht hat und an dessen Stelle der "Memory"- (m) und der "Index"-Bit (x) sind.
Man kann die Flags auch per Befehl setzen oder clearen; im Bezug auf vorgenannte Bits (m und x) wird man das beim Programmieren auch am meisten tun. Diese beiden Bits entscheiden, ob der Akkumulator (m) oder die Index-Register (x) 16 Bit (0b0) oder 8 Bit (0b1) groß sein sollen. Ja, man kann die Größe von Register X und Y nur gemeinsam ändern. Und falls es einen wundert, warum man für 16 Bit die Null genommen hat und für 8 Bit die Eins, der kann sich das vielleicht damit merken, dass für den 65816 der "natürliche Grundzustand" die 16 Bit sind, und er sich nur dem Programmierer/dem Spieler/dem Programm zuliebe in das 8-Bit-Korsett zwängt.
Schön, dass ers bisher immer gemacht hat.
Übrigens wird über das Statusregister auch der "emulation bit" angesprochen, also der Bit, der entscheidet, ob das SNES im Native Mode oder im Emulation Mode ist. Dieser Bit ist nicht wirklich Teil des Statusregisters, sondern muss durch einen "Hintertür-Trick" angesprochen werden:
Der Inhalt des Emulation-Bit bedeutet: 0 = Native Mode, 1 = Emulation Mode.
Es gibt den Befehl XCE, der den Inhalt des Carry-Bits mit dem Emulation-Bit austauscht. Das ist die einzige Möglichkeit, an den Emulation-Bit heranzukommen. Folglich kann man die Modi wechseln, indem man zuerst den Carry-Bit per Hand setzt (SEC) oder cleart (CLC) und dann den Carry-Bit mit dem Emulation-Bit austauscht (XCE).
* Wir erinnern uns: Der 6502 ist der Vorgänger-Chip des 65816 und ist gleichzeitig auch der Prozessor in der Vorgänger-Konsole des SNES. Das Statusregister des 6502 nutzt nur sieben der acht Bits.
Zuletzt geändert von lytron am 10. Oktober 2012, 17:54, insgesamt 2-mal geändert.
Perikles hat geschrieben:Man muss sich das mal reinziehen: die Idee ist scheiße, die theoretische Ausarbeitung ist scheiße, die praktische Umsetzung ist scheiße und der so entstehende Anspruch noch beschissener.
Die Sorge um die Älteren und Schwächeren...
Der Wille, sie noch aktiv am Geschehen teilhaben zu lassen, statt sie zum alten Eisen abzuschieben...
... unter Umständen nennt man das "soziale Gerechtigkeit"...
... oder, unter Umständen, "Abwärtskompatibilität".
Das Thema "Abwärtskompatibilität" (von lat. compiator, "Mitleid haben") ist in Bezug auf das SNES eine ganz eigene Geschichte. Vielleicht wusstest du, dass lange Zeit geplant war, dass das SNES mit dem NES kompatibel sein sollte? Wenn nicht, wäre das Programm "Game Maker" sicherlich interessanterfürdich...
Faktisch steht von Prozessorseite aus gesehen keine Problematik für diese Kompatibilität vorhanden (die Probleme lagen eher (HALBWISSEN)auf Seite der Grafikchips(/HALBWISSEN).
Der Prozessor des SNES (65816) ist nämlich selbst eine verbesserte Variante des Prozessors des NES (6502) und wurde dementsprechend auch dazu ausgelegt, alles das zu können, was der 6502 konnte.
Deswegen besitzt der 65816 die Möglichkeit, zwischen zwei Modi zu wechseln, dem Emulation Mode und dem Native Mode. Beim Emulation Mode kann man sich ja mittlerweile denken, was emuliert wird. Tatsächlich ist der 65816 im Emulation Mode ein 6502 in Reinform.
Der 65816 startet übrigens auch im Emulation Mode (was logischerweise darin endet, dass man in einem der ersten Arbeitsschritte von jedem SNES-Programm den Modus wechselt). Er tut das, weil er als vollwertiger Ersatz des 6502 ersonnen wurde. Soll heißen: Er war dazu gedacht, dass man den 6502 aus seinen Schaltungen rausreißt, den 65816 dort reinsetzt, und man keinen Unterschied merken sollte. Und das ist auch so, weil: Wenn man dem 65816 nicht sacht, dasser mehr machen soll, machter auch nich mehr.*
Aus diesem Grund hat der 65816 auch die Möglichkeit, die Größe des Akkumulators und der Indexregister zwischen 8 und 16 Bit umzuschalten.
Allerdings sei darauf hingewiesen, dass es nicht zwangsläufig Sinn macht, konstant mit einem 16-Bit-Akkumulator zu arbeiten.
Dafür ein Beispiel: Du als Programmierer sagst dem Prozessor, lade den Inhalt von Adresse $x143 in den Akkumulator. Der Akkumulator ist 16 Bit groß, $x143 enthält 8 Bit. Folglich werden die danebenliegenden acht Bit gleich mitgeladen, also Adresse $x144.
Aber es geht noch schlimmer. Beispiel zwei: Du hast einen Wert in deinem 16-Bit-X-Register
und willst den in deine Variable ImportantVariable24 speichern. Dummerweise hast du vergessen, dass du die ImportantVariable24 als Byte-groß definiert hast. Der 65816 macht das, was er immer in solchen Fällen macht. Er speichert die ersten acht Bit in die Variable in ImportantVariable24. Und den Rest "rechts daneben". Und das bedeutet oft genug, dass du dann damit irgendetwas überschreibst, was mal wichtig gewesen ist.
* Der Schlaumeier mag schreien: "Warum sollte man dann diesen Prozessor da reinsetzen, wenn der alte genau dasselbe macht?" Die WDC-Mitarbeiter, die das Handbuch zum 65816 geschrieben haben, führen in selbigem an, dass sie mit dem 65816 einige Bugs ausmerzen konnten, die der 6502 hatte.
Perikles hat geschrieben:Man muss sich das mal reinziehen: die Idee ist scheiße, die theoretische Ausarbeitung ist scheiße, die praktische Umsetzung ist scheiße und der so entstehende Anspruch noch beschissener.
Wenn ein Byte als signed, also "mit Vorzeichen", betrachtet wird, könnte man es sich vorstellen, als wäre er in der Mitte durchgebrochen und die beiden "glatten Enden" zusammengeklebt.
2er-Komp1.jpg (15.86 KiB) 6986 mal betrachtet
versteckter Inhalt:
Vergleich der darstellbaren Wertbereiche eines signed und eines unsigned Byte.
2er-Komp2.jpg (101.19 KiB) 6986 mal betrachtet
TO DO:
- SNES-Header / SMC-Header
- Vektoren & NMI (als Teil der SNES-Header-Erklärung)
Perikles hat geschrieben:Man muss sich das mal reinziehen: die Idee ist scheiße, die theoretische Ausarbeitung ist scheiße, die praktische Umsetzung ist scheiße und der so entstehende Anspruch noch beschissener.
Oben finden wir drei Zeilen mit ".INCLUDE", gefolgt von den Dateinamen der anderen Dateien. Der Sinn dieser Zeilen ist, dass WLA DX weiß, dass es, wenn es diese Textdateien in Nullen und Einsen übersetzt, an dieser Stelle diese Dateien einfügt.
Danach beginnt der Hauptteil dieses Dokuments, beginnend mit den Befehlen .BANK 0 SLOT 0 und .ORG 0. Bevor du anfängst, hier rumzufantasieren, solltest du nochmal in der WLA-DX-Manual nachlesen, was das genau macht, lytron.
Das nächste ist das Label "Start:"
Wannimmer dir eine Zeile mit einem Namen und einem Doppelpunkt am Ende begegnet, handelt es sich um ein Label, das ist eine "Sprungmarke". Du setzt Sprungmarken an der Stelle, wo du dem Programm irgendwann einmal sagen möchtest: "Geh da hin, mach da weiter".*
Das Label "Start" hat eine besondere Bewandnis, dazu kommen wir später.
Als nächstes wird der Befehl "InitSNES" aufgerufen. Dieser Befehl wird in "InitSNES.asm" festgelegt. Es handelt sich hierbei also um nichts festgeschriebenes und auch nicht um einen Assemblerbefehl, sondern um eine ganze Reihe von Assemblerbefehlen, die in einem Mini-Programm, einem Makro, zusammengefasst wurden. Makros kann man auch nach Belieben schreiben, dazu kommen wir irgendwann ganz später. Hier wurde uns dieses Makroschreiben bereits abgenommen.
Als nächstes kommt wieder ein Label, nämlich "forever", gefolgt von -oh noes!- einem Befehl, nach forever zu springen!
Ah!
Eine Endlosschleife!
Willst du denn, dass sich das Programm aufhängt!
Ja.
Quasi.
Wie gesagt, das Programm wird am Ende in eine lange Kette aus Bytes umgewandelt, und der SNES-Prozessor, wie jeder Prozessor, folgt dieser Kette unbeirrbar in eine Richtung. Es ähnelt in seinem Verhalten in etwa Ivy the Kiwi, oder bekannter, den grünhaarigen, lila gekleideten Lemmings. Und genauso wie beide ist auch der Prozessor auf eine führende Hand angewiesen... denn das Endergebnis, wenn man dort nicht eingreift, ist genauso verheerend wie bei beiden genannten Spielen.
In sofern ist dieser "forever-Loop" in etwa soetwas wie zwei Stopper-Lemminge, die die große, zu rettende Masse an Lemmingen zwischen sich einfangen. Denn, der Prozessor ist genauso wie die Lemminge und Ivy the Kiwi: Wie hyperaktiv, nur viel schlimmer. Kann nicht stillstehen. Es sei denn, er ist tot.
Dazu ein Beispiel. Um z. B. einen Startscreen aufzubauen, braucht das SNES einen Bruchteil einer Sekunde. Danach soll es warten, bis man "Start" gedrückt hat. Jetzt ist es nicht damit getan, einmal zu prüfen, ob Start gedrückt wurde, denn dann würde das Programm einfach "schon mal das ausführen, was als nächstes da so im Quelltext rumliegt". Stattdessen soll der Prozessor immer wieder prüfen, ob Start gedrückt wurde ("Wurde Start gedrückt?" - "Nein." - "Und jetzt? Und jetzt? Und jetzt?"), und erst dann weitermachen, wenn das geschehen ist. Vereinfacht als Quelltext dargestellt:
LabelAmAnfang:
BUTTONPRÜFUNG
Wenn button nicht gedrückt wurde, gehe zu LabelAmAnfang
Erster Befehl nach StartButton
Zweiter Befehl nach StartButton
...
Wenn ich diese "Wenn nicht"-Abfrage dort nicht positioniert hätte, würde er einmal prüfen, ob Start gedrückt wurde, und unabhängig von dem Ergebnis einfach mit den nächsten Befehlen weitermachen, die ja eigentlich erst nach dem Button-Push ausgeführt werden.
Tatsächlich laufen Spiele konstant nach diesem Muster ab. Zwei Beispiele:
Shoot 'em Up:
Das Level läuft in einem konstanten Loop aus mehreren Befehlen. Ein Befehl scrollt das Level ein kleines Stück weiter. Eine Funktion prüft, ob gerade Buttons gedrückt sind. Wenn ja, wird der Sprite des eigenen Raumschiffs bewegt oder ein Sprite mit einem Schuss sichtbar gemacht. Eine nächste Funktion prüft ob das Raumschiff mit irgendetwas kollidiert. Wenn ja, wird die Sterbeanimation gestartet, wenn nicht, geht es weiter. Eine nächste Funktion prüft, ob das Ende der Map erreicht wurde. Wenn nicht... gehe zurück zum Anfang.
Rollenspiel:
Eine Funktion kümmert sich darum, ob der Pogopuschel auf dem Bildschirm dich jetzt angreifen soll oder nicht.
Eine Funktion schaut, ob du einen Knopf drückst, um eventuell die Funktion des Ringmenüs aufzurufen, den Helden-Sprite zu bewegen, zu schlagen oder wegzurennen.
Eine Funktion, damit das Gras sich wiegt.
Eine Funktion, die dementsprechend gerade deine Position prüft, ob es die Landschaft scrollen muss oder die Held-Läuft-Aus-Dem-Bild-Animation zu starten, um die nächste Map zu laden. Wenn nicht... gehe zurück zum Anfang.
Praktisch besteht jedes Spiel aus vielen solcher Loops. Weil, wenn der Prozessor nicht immer wieder in solchen Loops "gefangen" wird, macht er einfach weiter, und führt aus, was ihm gerade vor die Flinte kommt. Und, wie ich schon einmal erwähnt habe, das könnten auch Grafikdaten sein, die er für Programmbefehle hält, was dann zu sehr kuriosen Ergebnissen führt und ganz schnell an einem Point of No Return endet.
~~~
Soweit nun dazu. Schauen wir uns noch die restlichen Dateien an. Die "header.inc" beinhaltet den SNES-Header** und auch die Interrupt-Vektoren**, die wir uns jetzt anschauen wollten. Wie erklärt haben Native und Emulation Mode verschiedene Interrupt-Vektoren, und wie erklärt handelt es sich dabei nur um eine Art "Notfallplan", dass der Prozessor weiß, wo er hingehen und was er machen muss, wenn dieser Fall eintritt. Bei den meisten steht ein "Emptyhandler". Was dieser ist, steht ein wenig weiter unten: Der EmptyHandler ist beinhaltet nur einen Befehl, nämlich "RTI", was "Return from Interrupt" bedeutet. Also, wann immer einer dieser Interrupts geschieht, schaut der Prozessor auf den Notfallplan, sieht, dass er zu EmptyHandler gehen muss, und sieht dort ein "Ab jetzt ist der Interrupt verarbeitet, gehe wieder zur Tagesordnung über". Und damit wird der EmptyHandler seinem Namen gerecht: Er macht nichts.
Außerdem finden wir hier bei "RESET" den Verweis auf "Start". Der RESET-Vektor ist derjenige, der aufgerufen wird, sobald das SNES gestartet wird. Logischerweise ist es auch ein Vektor im Emulation-Mode, denn der 65816 startet, wie ich gesagt habe, im Emulation-Mode (weil: solange man nicht mehr von ihm verlangt, gibt er sich als 6502, d. h. quasi NES-Prozessor, aus). Wir erinnern uns an die Blanko.asm, in der direkt nach dem "Start:" das InitSNES steht. Das wiederum finden wir in der InitSNES.asm, in die wir nun auch noch einen kurzen Blick werfen.
Die InitSNES.asm ist beeindruckend wie verängstigend, nicht?
Ich werde das Ganze jetzt nicht Schritt für Schritt erklären, es reicht, wenn man weiß, dass hier viele Register auf einen "Startwert" gebracht werden. Wenn man ein echtes SNES anschaltet, sind diese Register nicht zwangsläufig leer. Soll heißen: Man weiß nicht, welche Werte dort vielleicht noch drin schlummern könnten. Deshalb ist diese Funktion dazu da, reinen Tisch zu machen, dass man sich sicher sein kann, welche Werte dort sind.
Zum Ablauf: Wenn in der Blanko.asm steht: "InitSNES", wird das ausgeführt, was in dieser Datei unter der Zeile ".MACRO InitSNES" steht. Diese ersten drei Zeilen sind dazu zuständig, dass wir in den Native Mode wechseln. Weiter unten finden wir dann ein "JSL $008000", was eine etwas undeutliche Art ist, zu der Subroutine "InitializeSNES" zu springen, die etwas weiter unten steht. Danach folgt eine ganze Reihe an Befehlen, wo, wie gesagt, maßgeblich nullen gespeichert werden. Am Ende der Datei steht ein "RTS", mit dem die Subroutine "InitializeSNES" beendet wird. Das bedeutet, der 65816 erinnert sich, wo er stehen geblieben war, als er zu "InitializeSNES" gegangen war, und kehrt dorthin zurück. Das bedeutet, er geht oben in das Macro "InitSNES" zurück und macht dann nach dem Befehl "JSL $008000" weiter, was ein Befehl namens "SEP #$20" ist, und dann erreicht er ein ".ENDM". Damit ist dann auch dieses Macro beendet. Jetzt geht er dorthin zurück und macht dort weiter, wo dieses Makro aufgerufen wurde. Das war in der "Blanko.asm", und dort kommt er nun zum "forever: JMP forever"-Teil, in dem er auf ewig festhängen wird.
Das ist jetzt der gesamte Ablauf dieses "Blanko"-Programms, das faktisch nichts macht. Es ist nicht schlimm, die InitSNES nicht zu verstehen. Es reicht, grob zu wissen, was sie tut. Du kannst nachher mit größerem Assemblerwissen selbst noch einmal drüber schauen, nach und nach wird es dir deutlich werden, was genau diese Befehle heißen - und am Ende wirst du auch feststellen: Sie tut exakt das, was ich sage, und nichts anderes.
Jetzt sind wir am Ende des Programms angekommen, und haben eine Datei noch gar nicht betrachtet.
Richtig.
Die "define.asm" enthält auch keine Programmiercodes. Schauen wir sie uns an.
Die "define.asm"-Datei stammt nicht von mir. Geschrieben wurde sie von d4s, dem Haudegen, der N-Warp Daisakusen und Super Road Blaster gemacht hat, und zehntausend andere Dinge, von denen ich noch gar nichts weiß.
Er hat sie mir für eins meiner Projekte zu verwenden ans Herz gelegt, um den Sourcecode leichter lesbar zu machen, und deshalb binde ich sie hier gleich ein.
Diese Datei enthält nicht einen einzigen Assembler-Befehl, das sind alles nur Anweisungen für WLA DX. Soll heißen: Ob du diese Datei einbindest oder nicht, macht deine entgültige ROM nicht um einen Byte größer.
In dieser Datei sind ausschließlich ".define"-Befehle. Die folgen dem Muster .define Begriff Bedeutung.
Zum Beispiel steht direkt zu Beginn ".define INIDSP $2100".
Das bedeutet, wenn ich die "define.asm" in mein Programm einbinde, wird, wann immer ich "INIDSP" schreibe, das von WLA-DX mit $2100 ersetzt. Das verhindert einige Fehler, denn es ist schwerer, ein falsches Register anzusteuern, wenn man diese Register in seinem Programm mit Namen anspricht, statt mit einer komischen, nichtssagenden vierstelligen Nummer.
So hat d4s das hier mit allen häufig verwandten Registern gemacht. Darunter, jeweils ein wenig eingerückt, stehen entsprechende Befehle für dieses Register. Nehmen wir dazu die Befehle, die bei BGMODE stehen ($2105). Ganz unten stehen die BG-Modes, ihnen zugeordnet sind die Zahlen von null bis sieben.
WLA DX nimmt diese Zahlen und rechnet sie in Binärzahlen um. Wenn ich sage "LDA BGMODE_MODE_7", dann lädt er in den Akkumulator %00000111 (was wiederum als dezimalzahl exakt der 7 entspricht).
Darüber steht z. B. bei "BGMODE_PRIORITY_LAYER_3" ein 1 << 3. Das bedeutet, dass er zunächst die eins in eine Binärzahl umwandelt (%00000001) und diese Binärzahl dann um drei Stellen nach links verschiebt (%00001000).
d4s hätte auch schreiben können ".define BG_PRIORITY_LAYER_3 8", was auf dasselbe herausgekommen wäre. Allerdings hat er es hier nicht gemacht, weil es so offensichtlicher ist, was dort gemacht wurde. Denn im Register $2105 ist der Bit 3 quasi ein Flag, wo man die Priorität des Layers 3 anschalten kann, wenn man dort eine 1 reinschreibt (was genau damit gemeint ist, erkläre ich jetzt noch nicht).
Aber, so viel dazu. Wenn wir jetzt beim nächsten mal wirklich wirklich ins Programmieren starten, weißt du dann schonmal, woher die ganzen Register-Bezeichnungen kommen.
*Sprungmarken sind übrigens kein Assembler-Befehl, sondern eine Hilfe von WLA DX. Soll heißen: Du kannst eine Datei mit Sprungmarken nach Belieben zukleistern, wenn du die die Datei mit WLA kompilieren (d. h. übersetzen) wirst, sind sie alle weg. Um das zu verstehen, stellen wir uns das einmal vor:
Du hast dein Spiel fertig programmiert und durch den Compiler gejagt. Die fertige ROM öffnest du dann mit einem Hexeditor. Der erste Byte ist ein Befehl, der zweite auch, der dritte und vierte Byte sind eine Adresse, die zu dem Befehl im zweiten Byte gehören, was das SNES ja auch beim Ausführen richtig versteht, da es nach dem Befehl im zweiten Byte zwei Bytes mit einer Adresse erwartet, dann kommt als fünfter Byte ein weiterer Befehl und so weiter, und so weiter.
Sagen wir, du hast eine Sprungadresse vor dem dritten Befehl gesetzt. Wenn der Compiler kompiliert, wandelt er zuerst alles außer diesen Sprungpunkten um. Dann guckt er zum Beispiel nach diesem Label vor dem dritten Befehl. Wenn du ein Label vor den dritten Befehl setzt, heißt das, dass du willst, dass ab dem dritten Befehl nach dem Sprung weitergemacht wird. Folglich guckt sich der Compiler die "Adresse" dieses Befehls an, also, der wie vielte Byte es in der Datei ist. In diesem Fall, wie wir gesagt haben, ist es der fünfte Byte. Das bedeutet, da wir ja beim Programmieren immer mit null anfangen zu zählen, 0004. Jetzt guckt der Compiler nach, woimmer bezug auf dieses Label genommen wird, und ersetzt es mit dieser Adresse.
Perikles hat geschrieben:Man muss sich das mal reinziehen: die Idee ist scheiße, die theoretische Ausarbeitung ist scheiße, die praktische Umsetzung ist scheiße und der so entstehende Anspruch noch beschissener.
Perikles hat geschrieben:Man muss sich das mal reinziehen: die Idee ist scheiße, die theoretische Ausarbeitung ist scheiße, die praktische Umsetzung ist scheiße und der so entstehende Anspruch noch beschissener.
Perikles hat geschrieben:Man muss sich das mal reinziehen: die Idee ist scheiße, die theoretische Ausarbeitung ist scheiße, die praktische Umsetzung ist scheiße und der so entstehende Anspruch noch beschissener.
Statt einfach nur Filedumping zu betreiben, schreibe ich mal etwas, was wohl lange nicht von korrespondierendem Wissen umschlossen werden wird...
Ich schreibe es aus aktuellem Anlass, da ich hiermit einige Stunden hängengeblieben bin.
Knowhow zu Tilemaps
Gucken wir uns zunächst einmal den VRAM an, wie er aussieht, wenn Tiles hineingeladen wurden:
snes3.jpg (52.75 KiB) 6912 mal betrachtet
(Was hier nicht berücksichtigt ist, ist, dass bei Tiles mit mehr als zwei Bitplanes diese Bitplanes um $0100 versetzt abgelegt werden. Das heißt: Eine Hälfte eines 16-Farben-Tiles liegt bei $001A, dann kannst du dir den zweiten Teil bei $011A zusammensuchen)
Wie du hier siehst, sind einige Landschaftstiles abgelegt. Die Spalten und Zeilen sind beschriftet, sodass du sehen kannst: Die Tiles liegen im VRAM von $0000 bis $003F. Ab $0040 liegen Sprite-Tiles (denn Spritegrafiken verwenden genauso wie Tilesets in den VRAM geladen werden. Das heißt: Es können auch Spritegrafiken als Tiles genutzt werden und andersrum. Das wiederum bedeutet, ein besonders kranker Geist könnte ein Spiel machen, in dem man Mr. Fragezeichen-Box steuert, der über einen Super-Mario-Teppich läuft. "Wie gefällt dir das, elender Cape-Träger, hmmm!?").
Wenn man jetzt für einen Background eingestellt hat, dass man 8x8-Tiles benutzt, und z. B. Tile $0000 verwenden möchte, sieht die Auswahl so aus:
0.png (224.97 KiB) 6911 mal betrachtet
Es ist wie in Excel, es wird exakt die "Zelle" ausgewählt, die man sich aus der Spalten- und Zeilenbeschreibung zur Adresse zusammenklamösert. Also: Tile $000F wäre dann in derselben Zeile ganz rechts, usw.
Jetzt gibt es ja auch die Möglichkeit, 16x16-Pixel-Tiles zu verwenden. Hier weiß das SNES, dass es, in 8x8-Pixel-Tiles gesehen, es die angegebene Tileadresse nehmen muss, das dortige 8x8-Pixeltile nehmen soll, dessen Nachbarn mit der nächsthöheren Zahl, dann von der Ausgangsadresse +16 (+$10) Tiles weitergehen soll und auch dessen rechten Nachbarn. Was meine ich? Das meine ich:
1.png (195.27 KiB) 6911 mal betrachtet
Hier anhand des Beispiels von Tile $0000 noch einmal gezeigt. Es wird (wie gesagt: Als 8x8-Pixel-Tile-Raster betrachtet!) das Tile genommen, das bei der Adresse liegt (das linke obere Viertel des 16x16-Pixel-Tiles), dessen Nachbar mit der nächsthöheren Adresse, in diesem Fall $0001 (das rechte obere Viertel), dann +16 (+$10) von der Ausgangsadresse ($0010) und dessen Nachbar ($0011) für die untere Hälfte.
Das macht das SNES bei 16x16-Pixel-Tiles automatisch. Soweit verstanden? Gut. Dann kommen wir jetzt zur Crux.
Die Crux
Die Crux ist: Man muss sich merken, dass sich die Art der Adressierung nicht ändert, ob jetzt 16x16- oder 8x8-Tiles verwandt werden: Jedes Tile, dass du bei 8x8-Pixel-Tiles ansprichst, ist bei 16x16-Tiles das linke obere Viertel.
Verdeutlichen möchte ich es damit, dass ich gerne mal zeigen möchte, was angezeigt wird, wenn ich bei diesem Tileset und bei 16x16-Tiles das Tile $0001 ausgeben möchte:
2.png (223.24 KiB) 6911 mal betrachtet
Gurkig, was?
Das ist, was ich eben gerade meinte mit "Jedes Tile, dass du bei 8x8-Pixel-Tiles ansprichst, ist bei 16x16-Tiles das linke obere Viertel" und "dass sich die Art der Adressierung nicht ändert". Ansonsten passiert soetwas, oder noch schlimmer:
3.png (223.1 KiB) 6911 mal betrachtet
Supergurkig, was?
Das heißt, wenn du mit 16x16-Pixel-Tiles arbeitest, musst du dran denken, dass du ungerade Tilenummern nicht benutzen solltest. Bzw., anders ausgedrückt: ungerade Nummern in den untersten beiden Stellen der Tilenummer.
blanko.png (234.24 KiB) 6911 mal betrachtet
Weil, Preisfrage:
Was ist das Tile nach $000E...?
Naaa?
(Guck nach oben!)
Richtige Antwort wäre: $0020!
Weil: $0010 wäre ja die untere Hälfte des schwarzen Tiles als obere Hälfte und die obere Hälfte des Tür-Gemäuer-Bogens als untere Hälfte. Und das wollen wir ja nicht, woll?
Dieses System ist kompliziert, birgt aber auch Möglichkeiten, die anders nicht gegeben wären. Guck dir mal die 16x16-Tiles bei $0024 und $0026 an. Das ist die linke und rechte Seite für einen Trampelpfad. Dank dieses etwas verschrobenen Tile-Adressierungs-Systems kannst du jetzt aber auch einen breiteren Trampelpfad machen, ohne dafür einen neuen Tile zeichnen und im immer knappen Speicherplatz unterbringen zu müssen. Die Lösung ist:
Tile $0025.
4.png (223.1 KiB) 6911 mal betrachtet
Für einen besonders breiten Trampelpfad müsstest du in deine Tilemap dann nur schreiben: Nimm Tile $0024, $0025, $0025, $0026, und schon hättest du einen 8-Tile-breiten Schmutzweg.
Perikles hat geschrieben:Man muss sich das mal reinziehen: die Idee ist scheiße, die theoretische Ausarbeitung ist scheiße, die praktische Umsetzung ist scheiße und der so entstehende Anspruch noch beschissener.
Nächster Teil nach der zweiteiligen Inspektion der Blanko.zip anzusetzen.
@Moi: Idee/Vorschlag: Wenn die Suppe hier mal aufbereitet werden sollte, vielleicht wäre es hilfreich, dass der Kram auch "kategorisiert" wird und jede Kategorie seine eigene, chice Pastellfarbe zur Sortierung bekommt. Kategorien wären z. B.:
Ich will jetze aber wirklich programmieren, maaaaaaaaaaaaaaan
Junge, mach doch nich die Pferde scheu. >_>
Gut. Also. Heute programmieren wir, Kinder! ^.^
Wascht euch schonmal die Hände und holt eure Ausstechförmchen! ^.^
Wir bringen heute eine Farbe auf den Screen.
Dazu lernen wir die Assemblerbefehle REP, SEP, LDA und STA.
Holt alle schonmal eure Blanko.zip raus und schlagt sie die "Blanko.asm" auf.
REP und SEP sind Abkürzungen für "Reset P" bzw. "Set P".
"Reset" ist hier gleichzusetzen mit "clearen".
"P" ist, und das muss sich keiner merken (wers kann und will, schön), eine Abkürzung für das Statusregister.
Erinnert ihr alle euch noch, was das Statusregister war?
Ja genau, das hier:
Statusregister.jpg (22.15 KiB) 6907 mal betrachtet
Und wenn man bei dem obigen Bild bleibt, ist SEP der Befehl, Flaggen zu hissen, und REP der Befehl, Flaggen wieder einzuholen.
Folglich muss man als nächstes auch sagen, was gesetzt oder gecleart werden soll, und da das Statusregister ein Byte groß ist, muss der nachfolgende Wert ein Byte groß sein.
Jetzt kommen die Fragen: Wozu brauche ich das im Endeeffekt und warum fangen wir ausgerechnet damit an?
Wir erinnern uns, dass wir gesagt haben, dass das SNES seine drei Prozessor-Register A, X und Y, mit denen wir ja die allermeiste Arbeit verrichten, zwischen 16-Bit- und 8-Bit-Größe wechseln können*. Und das wirst du sehr, sehr häufig tun, weil du in der Praxis feststellen wirst, was ich schon einmal erwähnte*, dass es sehr, sehr nützlich ist, wechseln zu können.
Folglich wirst du die Befehle, die Größe ändern du zu können, sehr häufig benutzen. Und das sind SEP und REP.
versteckter Inhalt:
Meine Nichte (6 Monate) hat gerade einen Teil zum Tutorial beigesteuert, den ich gern der Nachwelt erhalten möchte:
ä_
Auf dem Bild vom Flag-Register siehst du unterhalb der Fahnenmasten Buchstaben. Das sind die offiziellen Abkürzungen für die einzelnen Flags. Allerdings muss man dazu wissen: Man muss die Bezeichnungen einmal waagerecht spiegeln, wenn man an die Bitwerte im tätsächlichen Statusregister heranwill**.
Für uns relevant sind die Bits m und x. Das heißt, wenn man den Kram spiegelt und als Bits darstellt:
REP #%00mx0000.
Dabei sei gesagt, dass
- m für "Memory" steht, x für "Index"
- m = Akkumulator und x = X- und Y-Register ist
- 0 = 8-Bit und 1 = 16-Bit bewirkt
Alle Kombinationen dazu habe ich mal in eine Grafik gebracht:
repsep.png (10.05 KiB) 6906 mal betrachtet
Meine Empfehlung: Lern das auswendig.
Als jemand mit Erfahrung mit dem SNES-Programmieren und als jemand, der noch nicht firm darin ist, das aus dem Kopf sagen zu können, meine Empfehlung: Lern es auswendig.
Oder schreib es auf und behalte es als Gedächtnisstütze, zusammen mit einer Liste von allen Hexzahlen mit ihrem binären Gegenstück.
Oder tätowier es dir auf die Nasenscheidewand.
Soweit dazu.
Sobald du aus dem Tätowierstudio zurückbist, können wir weitermachen. LDA und STA.
Heißt "LoadAccumulator" und "Store Accumulator", also "Lade (in den) Akkumulator" und "Verstaue (den Wert im) Akkumulator".
So sehr ich mich auch bemühe, es gibt hier bei weitem (vorerst) nicht so viel zu sagen wie bei SEP und REP.
Nur einige Beispiele, um dir zu helfen, sattelfest zu werden.
LDA $210D
STA $210D
Zwei Befehle, die genau das Gegenteilige tun.
LDA $210D überschreibt den Inhalt des Akkumulators mit dem von Register $210D, ganz egal, was im Akkumulator stand.
STA $210D überschreibt den Inhalt des Registers $210D mit dem des Akkumulators, ganz egal, was im Register $210D stand.
Klaro. Weiter.
LDA #$210D
STA #$210D
Der erste Befehl lädt die Zahl $210D in den Akkumulator***. Das setzt natürlich voraus, dass du vorher REP #$20 oder REP #$30 gemacht hast.
Konsequenterweise... macht der zweite Befehl überhaupt keinen Sinn, weil man nicht in eine feste Zahl etwas hineinspeichern kann, weil: Wo soll diese feste Zahl denn sein?
Wo wir gerade beim Thema sind, auch eine beliebte Stolperfalle (zumindest für mich ):
REP $30
SEP $20
Da ich die Rauten hier vergessen habe, macht dieser Befehl auch keinen Sinn.
In der Tat vergurke ich auch ziemlich viel damit, dass ich konstant vergesse, Rauten zu setzen, wo welche nötig sind, und damit dann das SNES nicht das macht, was ich gemeint habe ("Tu das, was ich meine, nicht das, was ich sage" funktioniert da noch weniger als beim eigenen Kollegen/Kind/Lebensabschnittspartner). Also, wenn du auf Fehlersuche bist (was du beim SNES-Programmieren mit Assembler und WLA immer bist), dann wäre das eine der Sachen, die man als erstes prüfen sollte.
Wo wir gerade bei den Trockenübungen sind:
(.define BG1HOFS $210D)
LDA #BG1HOFS
STA BG1HOFS
Bevor ich es erkläre, kannst du hier ja mal das Sourcecode Lesen üben. Die erste der drei Zeilen ist ein Auszug aus der define.asm, die dir das Lesen erleichtern soll.
Was macht dieses Dingens?
Welcher Wert wird geladen?
Wo steht dieser Wert am Ende?
Am Ende steht in Register $210D der Wert $210D. WLA DX ersetzt jedes "BG1HOFS" mit "$210D". Also steht in der Zeile mit dem LDA-Befehl dann ein "LDA #$210D", was ja bedeutet: "Lade den festen Zahlenwert $210D". Folglich macht soetwas nur bedingt Sinn, es so zu schreiben. Aber egal.
Beim Store-Befehl stehen wir keine Raute, also wird auch wirklich in das entsprechende Register gespeichert. Chic, nicht wahr?
Also, die Lösung ist: In Register $210D steht jetzt: "$210D".
Gut, wenn du es soweit verstanden hast, dann können wir jetzt wirklich, wirklich, wirklich mit dem Programmieren anfangen...
... beim nächsten Mal.
* Siehe Native Mode / Emulation Mode
** Schreit der Schlaumeier: "Ey Alta, wenn das falsch ist, warum postest du es dann?"
Meine Antwort auf derart undistinguierte Einwände:
Ey Alta, Worte beginnen links und steigen nach rechts an, während Zahlen rechts anfangen und nach links aufsteigen. Und ich habe dieses System nicht erfungen, das war Wilhelm Salomon Schreibund-Zahlsystem, damals, im Jahre 1853. Dementsprechend bereite ich meine Leserschaft nur auf die harte Realität da draußen vor, da das im Bezug auf das Statusregister und vielleicht noch ein paar andere Sachen immer mal wieder vorkommt, dass es "falschrum" geschrieben wird. Aber es gibt auch Möglichkeiten, das zu umgehen.
*** Der Schlaumeier mag schreien: "Aus dem Register kann man gar nicht lesen, das ist write-only!"
Stimmt auch.
Aber wenn du das weißt, was liest du dann Anfänger-Tutorials, du Blödhammel?
Zuletzt geändert von lytron am 21. Oktober 2012, 12:47, insgesamt 2-mal geändert.
Perikles hat geschrieben:Man muss sich das mal reinziehen: die Idee ist scheiße, die theoretische Ausarbeitung ist scheiße, die praktische Umsetzung ist scheiße und der so entstehende Anspruch noch beschissener.