Dumme Tutorials Teil 2: Sprites Animieren:

[Wiederholung: Das Sprite] [Tiles] [Unsere erste Animation] [Unsere Animations Funktion] [Erstellen der Funktion] [Animate mit DMA] [Beim nächstem mal] |
Autor: | Tharo Herberg
Erstellt: | 11.Sep.02 bis 02.Okt.02
Level: | Fortgeschrittene
Kenntnisse: | GBA Sprites DMA Photoshop

Hallo ihr Leute und willkommen zu meinem zweitem GBA Tutorial. Dieses Tutorial soll der Auftakt zu einer kleinen Serie werden in dem wir das A-bis O durchnehmen was wir denn benötigen um ein eigenes kleines Spiel zu machen. Natürlich wird am Rande auch erklärt wie genau wir dort hin kommen ;) Ich werde mich sehr bemühen die ganzen Sachen verständlich zu erklären. Seit mir jedoch nicht böse wenn ich an mancher Stelle einfach sage "Das ist so!". An diesem Punkt hab ich selbst nicht so ganz den Überblick gehabt und wie so oft durch ausprobieren herausgefunden was funktioniert. Das ist übrigens auch mein Tipp an euch: Wenn etwas nicht ganz so funktioniert wie ihr wollt das es funktioniert... Versucht es mal mit ausprobieren. Manchmal hat man Glück. Ehe es nun los geht kommt jedoch erst einmal eine kleine Erläuterung zu diesem Tutorial selbst und damit verbundenen Sachen wie Copyrights, Zielgruppe usw... Aaaalso: Dieses Tutorial wurde von mir in der Absicht geschrieben Leuten zu zeigen wie man mit dem GBA von der Firma Nintendo, die alle Rechte an dem GBA und der kommerziellen Veröffentlichung von GBA Spielen besitzt, umgeht und mit Hilfe anderer Software dafür Programmiert. Ich gebe keinerlei Garantie das was ich euch hier zeige Funktioniert und übernehme keinerlei Haftung für Schäden die eventuell bei euch entstehen während ihr dieses Tutorial durcharbeitet ^^. Dieses Tutorial richtet sich an Programmierer. Dies bedeutet das ihr die Sprache "C" beherrschen müsst und darüber in klarem sein solltet was ein Pointer ist. Auch gehe ich davon aus das ihr bereits in den anderen auf www.devgba.de erhältlichen Tutorials geschmökert habt und das dort beschriebene Fachwissen beherrscht. Ihr solltet wissen was Duale und Hex-Zahlen sind, den Unterschied zwischen Bit und Byte kennen sowohl als auch in der Lage sein diese Werte zumindest mit einem Taschenrechner umzuwandeln. Auf Fragen die eine Programmiersprache selbst bzw. das dort beschriebene Wissen betreffen werde ich nur ungern Antwort geben bzw. euch wahrscheinlich barsch die URL geben damit ihr es selbst lesen könnt. Sachen die in deutsch nicht verfügbar sind werden jedoch gerne von mir, so wahr ich es kann, beantwortet. Überall im Text sind jedoch genaue Quellenangaben zu sehen wo man sich genauer darüber erkundigen kann.

Wiederholung: Das Sprite

Der GBA besitzt eine Hardware Unterstützung für Sprites und damit alles was das sehnsüchtige Spiele Programmierer Herz sich nur wünschen kann. Hardware Sprites sind nicht nur relativ schnell sondern sie verfügen über Features wie horizontales und vertikales spiegeln, Zoomen und sogar Rotation um einige Beispiele zu nennen. [1] Fakten: Der Sprite bzw. Objekt Speicher, OAM (Object Attribute Memory) ist 1024 Byte groß und hat somit Platz für 128 Sprites dessen Eigenschaften jeweils in vier Attributen beschrieben sind. Das letzte Attribut beinhaltet die Rotationsdaten. Es werden 4 Rotationsdaten pro rotierendes Objekt benötigt. Der GBA zählt automatisch beim Index nehmen jeweils 4 mal das 4. Attribut zusammen. Der OAM beginnt bei der internen Speicheradresse 0x7000000 und ist write only, kann also nicht gelesen werden. Würde man sich das GBA Sprite einmal exemplarisch wie eine Klasse in C++ vorstellen so hätte man folgende Eigenschaften und Verweise:
OAM Sprite
| y Koordinate | y Position des Sprites (0-255)
| x Koordinate | x Position des Sprites (0-512)
| Rotations Flag | Ist die Rotation Aktiv? (true/false)
| Rotations Index | ID der Rotationsdaten ausgehend von einem Standart Array.
| Double Flag | Doppelter Anzeigeraum? (true/false)
| Horizontales Spiegel Flag | Soll das Bild horizontal gespiegelt werden? (true/false)
| Vertikales Spiegel Flag | Soll das Bild vertikal gespiegelt werden? (true/false)
| Alpha Blending | Alpha Blending Einstellungen
| Mosaic Flag | Ist der Mosaikeffekt aktiv? (true/false)
| Farb Modus | Welcher Farbmodus genutzt wird. 0 = 16 Farben 1 = 256 Farben
| Farbpaletten Index | Gibt bei 16 Farben Sprites an welche Farbpalette benutzt wird.
| Objekt Forum | Beschreibt die Form des Sprites
| Sprite Größe | Die Größe des Sprites
| Bild Index | Der Index an dem das Sprite im Bildspeicher beginnt. (0-1023)
| Zeichenpriorität | Welches Sprite überzeichnet welches? (0-3)
Uff, args, nun habe ich das wirklich alles noch einmal aufgelistet... Wie auch immer. Wie wir sehen können gibt es drei Verweise innerhalb des Sprites. Einmal haben wir dort den Rotationsindex. Beabsichtigen wir unser Sprite rotieren zu lassen so benötigen wir diesen Index wo sich die Daten befinden die beschreiben in welchem Zustand sich das Sprite befindet. Ist Rotation deaktiviert hat dieses Feld keine Bedeutung. Dann haben wir den Paletten Index. Ist die Farbeigenschaft auf 16 Farben gestellt so wird der GBA Automatisch dessen Farbpalette laden die an dieser Stelle beschrieben ist. Der GBA selbst überprüft nicht ob ihr nun immer 16 Farben bei Sprites benutzt oder nur dieses Sprite auf 16 Farben justiert wurde. Er wird in jedem fall den Index*16 nehmen und an dieser Stelle die 16 nächsten Farben benutzen. Diese Spielereien besprechen wir hier jedoch nicht ^^. Ist das Sprite auf 256 Farben gestellt wird dieser Bereich ebenfalls ignoriert. Wichtig: Alle Sprites die 256 Farben benutzen sind doppelt so groß wie 16 Farben Sprites! Der Bild Index ist das worum sich erst einmal unsere Aufmerksamkeit drehen wird. Hier wird beschrieben wo im "Character Memory" (gefolgt Sprite Speicher genannt) sich unsere Bilddaten befinden. Dieser Speicherbereich beginnt bei 0x6010000 und kann 1024 Tiles fassen (256 Farben Tiles verbrauchen je wieder doppelt so viel Speicher). Benutzen wir einen Bitmap Modus (3-5) so breitet sich dieser über die hälfte des Tile Speichers aus wodurch wir nur noch Tiles mit einem Index ab 512 benutzen können. Der Sprite Speicher kann zusätzlich auf zwei unterschiedliche Arten adressiert werden. Wir beschränken uns jedoch auf die 1D Art die gegenüber der 2D Methode den Vorteil hat das wir die Daten in einem "Happen" benutzen können.

Tiles, eine Art Leben der Bilder

Tiles sind, wie die meisten noch wissen, die kleinen Kacheln in die man eine Levelkarte unterteilt um immer nur die selben Grafikbauklötze benutzen zu können und so eine Menge Zeit zu sparen. Dieser noble Vorsatz wird auch bei Sprites benutzt und bringt uns bei diesen hingegen ein wenig Denkakrobatik denn Tiles bestehen aus jeweils 8x8 Pixeln in 16 oder 256 Farben. Damit haben wir vorerst kein Problem aber nehmen wir uns einmal ein kleines Beispiel: Da ist er. Unser Held! Schlank, sportlich und in den Abmessungen 24 zu 32 Pixeln. So mögen wir ihn in Windows vielleicht sehen, doch da unsere Sprites ebenfalls ihre Bilddaten in Tiles speichern ist er in Wirklichkeit wie ein Schachbrett zersägt. Betrachten wir uns unseren Helden in Tiles geschnippelt einmal im GBA Speicher so würden wir in etwa so etwas hier sehen. (siehe unten). Dies bedeutet das wir auch beim zusammensetzen des ganzen Sprites umdenken müssen. Insbesondere das umwandeln der Grafiken von normalen Bildern in Sprites die einigermaßen Animationstauglich waren hat mir zunächst größere Probleme bereitet als mir lieb war.

Unsere erste Animation

Beginnen wir also mit unserer ersten Animation. Nachdem wir uns im klaren darüber sind wie groß unser Sprite sein soll und wie viele Farben wir benutzen wollen. Öffnen wir einen guten Grafik Editor um dann dort unsere Grafiken die wir benutzen wollen fein säuberlich zusammen zu tragen und im richtigen Abstand zu einander auszurichten. Genau dieses Ausrichten ist eine Sache an der ich persönlich früher im Paintbrush gerne einen Nervenzusammenbruch bekommen habe. Zum Glück gibt es aber heutzutage Programme die ein Raster einblenden können wie zum Beispiel Photoshop. Um die Hilfslinien einzublenden gehen wir im Photoshop Menü (hier 6.0 deutsch) Auf Bearbeitet > Voreinstellungen > Hilfslinien und Raster. Idealer weise stellen wir dann das Raster auf die Breite die unser Sprite haben soll, ich habe hier einmal 32 Pixel genommen, und die Unterteilungen auf einen Wert der 8 Pixel ergibt. In diesem Fall sind es also 8 Unterteilungen. Dann bestätigen wir und aktivieren das Raster unter Ansicht > Extras einblenden und Ansicht > Einblenden > Raster. Bei mir hat es sich außerdem sehr bezahlt gemacht ein paar Menüpunkte weiter unten, also bei Ausrichten an ... ebenfalls Raster einzustellen. Dadurch konnte ich leichter dafür sorgen das meine Figuren am rechten Fleck waren. Wie ihr dort an dem Bild sehen könnt habe ich ein kleines Animation Table von unserem allzeit beliebten (wehe wem nicht *g*) Secret of Mana Heldens gemacht. So in etwa werden heutzutage die Sprites gespeichert damit man sie dann später im Programm gut benutzen kann. Unser Problem nun wird jedoch erstmals die 8x8 Tile Ordnung werden. Alle zur Zeit erhältlichen Bild nach GBA Konverter arbeiten artig in Tiles und von links nach rechts. Wenn wir nun aber unser Schema durchgehen würden hätten wir erst mal nur die Haare, dann nur die Köpfe usw. usw. Kurz: Die Bilddaten währen ohne ein erneutes Umrechnen im GBA untauglich. Zum glück gibt es da einen einfachen Trick, wenn man es überhaupt so nennen darf ;) Wir machen schlicht alle Animationen unter einander. Dadurch wird das Tool automatisch das Bild so umwandeln das es in dem selben Format ist wie wir es später im GBA benötigen. Besser geht's praktisch nicht. An dieser Stelle schon solltet ihr darauf achten das ihr einige Regeln beachtet die euch sonst das Leben zur Hölle machen könnten.
1.) | Achtet nun schon darauf eure Animationen in eine Reihenfolge zu bringen in der dann später ALLE Sprites arbeiten müssen. Also Laufen, Stehen, Rennen, Kämpfen, Sonderaktionen. Alle Sprites in einem großen Spiel in dieses Schema zu pressen geht meistens nicht. Da benutzt man dann sowas wie "Hoch auflösendes Sprite" "Billiges Sprite" "Monster". Also drei Schemen. Endgegner die nach Erwartung des Spieles möglichts supertoll sein sollen macht man in der Regel erst dann wenn man sie brauch per Hand.
2.) | Macht keine Schatten auf oder unter euer Sprite. Wenn euer Held dann spring würde dies ein gigantisches Sprite ergeben drum machen wir die Schatten erst später im Spiel zusätzlich.
3.) | Versucht euer Sprite so auszurichten das ihr einfach nur ohne weiteres die Bilder wechseln müsst. Das geht nicht immer also merkt euch als Faustregel: Wenn wir später im Spiel extra die Sprites in Animation x um 4 Pixel nach links schieben, verbrauchen wir zwar extra Variablen aber diese werden NIEMALS so groß sein wie wenn ihr eure Figur deshalb in ein größeres Sprite packt bei dem jedes Tile 8x8x8xFarbtiefe Bit verbraucht!
So, nachdem wir uns darum gekümmert haben steht dem schreiben einer Funktion die bei unserem Sprite die Bilder auswechselt nichts mehr im Wege.

Unsere Animations Funktion

Ziel unserer ersten Funktion ist es klar einfach nur das Bild eines Sprites auszutauschen. Laufen und ähnliches wollen wir später noch in Ruhe regeln. Im Prinzip gibt es zwei Möglichkeiten wie wir nun vorgehen könnten um die notwendigen Daten zu ermitteln die wir brauchen. Wir könnten sie zusätzlich Speichern etwa in einer eigenen Struktur, oder aber wir können die schon vorhandenen Daten aus dem Hardware Register lesen. Im Prinzip ist es jedem selbst überlassen wie er es nun genau macht. Das eine ist verschwenderischer aber angeblich schneller (Struktur) und das andere zwar sparender aber angeblich langsamer (Auslesen). Ich persönlich sage dazu nur: Das Auslesen funktioniert über Binäre Funktionen die von der CPU schneller verarbeitet werden können als jede + oder - Operation und daher auf jeden Fall schneller als das übergeben einer vom Benutzer definierten Struktur. Einziger Nachteil ist das man man Coden etwas nachdenken muss, aber dafür sind wir ja hier: Um zu lernen, oder? Ich sage ja nich das alle dieser Meinung sein müssen. Stellungnahme hin oder her: Ich bin der Autor und sage wir benutzen die Hardware. Feddich. Beim erstellen der Funktion sollten wir uns erst einmal Gedanken machen was wir nun eigentlich brauchen um die Grafik des Sprites auszutauschen. Wir müssen wissen wo die neuen Bilddaten im Speicher sind. Das muss uns der Benutzer sagen. Wir müssen wissen wo die alten Bilddaten im Sprite Speicher sind. Das kann uns das Sprite sagen. Wir müssen wissen wie lang die Bilddaten sind wozu wir Farbauflösung und Größe wissen müssen. Kann uns das Sprite sagen. Wir müssen wissen welche Animationsphase wir nehmen wollen. Das muss uns logischerweise der Benutzer sagen. So gesehen brauchen wir also drei Angaben um das Bild auszuwechseln: Welches Sprite mit welcher Animation welcher Bilddaten gefüttert werden soll. Wir gehen mal davon aus das die in den Bilddaten befindlichen Bilder auch zu dem Sprite passen, währe ja sinnlos sonst. Unsere grobe Funktionsumschreibung sieht demnach dann in etwa so aus:
0 | void animate(u8 sIndex, u16* BitmapData, u16 aPhase);
Drei Übergaben die später bei der Anwendung leicht von der Hand gehen müssten. Als erstes haben wir da sIndex, den Sprite Index. Dabei gehen wir von der Art und weise aus das wir zuvor schon alle 128 Sprites in einem Array vorliegen haben. Diese Methode benutzt Dovoto in seinem Tutorial [1] und ich werde daher nicht näher darauf eingehen. Dann haben wir mit BitmapData einen Pointer auf das Array in dem die Bilddaten enthalten sind die von der Auflösung her natürlich auch zu dem Sprite passen müssen. Zuletzt haben wir in aPhase die Animationsphase die unser Sprite bekommen soll. Alles in allem kann man die Funktion schon zum erstem füllen des Sprites mit Bilddaten benutzen.

Erstellen der Funktion

So, ich hoffe ihr habt alle schon einmal etwas über die Binären Grundrechenarten AND und OR gelesen die im Laufe dieses Schrittes benutzt werden. Wenn nicht so solltet ihr einmal euren Informatik Lehrer fragen oder das Thema in www.devgba.de auf dem Forum ansprechen. Ich gebe dann auf Wunsch eine kurze Einführung dazu doch an dieser Stelle währe es denen die so etwas bereits beherrschen ungerecht gegenüber alles noch mal durch zu kauen. Zuerst kommen, wie in jeder neuen Funktion, die Parameter in denen wir unsere Rechenergebnisse speichern wollen. Speziell nun wo wir die Register Werte des Sprites filtern müssen ist es wichtig den Überblick zu behalten über das was wir tun.
0 | int lenght; // Länge eines Sprites in tiles
1 | int colord; // Farbtiefe
2 | int charstart; // Wo im Bildspeicher?
3 | int loop = 0; // Loop Variable
So, ab nun geht es richtig los. Für bessere Übersicht habe ich die folgenden Schritte einmal deutlich aufgeteilt. Sie einzeln zu lesen macht dennoch wenig Sinn. Solltet ihr nach wiederholten Schritten nicht wissen wies funktioniert macht euch keine Gedanken, später benutzen wir diese Funktion eh nur noch und dann ist es uns egal zu wissen wie sie funktioniert denn dann freuen wir uns einfach über das was sie kann ^-^; 1. Die Länge unseres Sprites. Die Länge des Sprites befindet sich am ende der 16 Bit Kette des zweiten Sprite Registers. Das bedeutet wir müssen erst einmal die anderen Werte heraus filtern ehe sie unser Ergebnis stark, SEHR stark unbrauchbar machen. Haben wir dies getan haben wir also eine Zahl um die 16 000 die noch umgewandelt werden muss in eine Zahl von 0-3. Schauen wir es uns erst einmal an!
bits | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0
Attribute1 - zweites Reg. | Größe | VF | HF | Rotations-Data Index | X Koordinate
Nun müssen wir das ganze Filtern. Dazu benutzen wir eine UND (zu englisch AND) Verknüpfung. Dies bedeutet das er nur die Werte beibehält die sowohl im Register als auch bei unserem Filter 1 stehen. Ist einer von beiden 0 dann ergibt dies eine null. Erster UND zweiter Wert halt.
bits | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0
z.B.: | 1 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0
Unser Filter: | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0
Ergibt: | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0
Nun gut, nun haben wir die unwichtigen Werte also schon einmal rausgekürzt. Nun interessiert uns aber halt nicht der riesige Wert, sondern der Wert von 0-3 der die Größe des Sprites angibt und den wir im Prinzip auch haben, leider am falschem Ende der Zahlenkollone. Daher benutzen wir nun die nächste Binäre Funktion, dass Schieben. Schrieben bedeutet im Prinzip nur das wir den ganzen Wert um x Bitstellen schieben. test>>2 Schiebt den Inhalt von test um 2 Bitfolgen nach rechts. test<<5 Schiebt den Inhalt von test um 5 Bitfolgen nach links. Eigentlich simpel, oder?
bits | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0
Unser Wert: | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0
>>14 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0
Hmm .... schaun wir nun, wo wir den Wert so haben wie wir ihn wollte ... mal genau hin. Also eigentlich war das Filtern vorher ja unnötig oder? Die Werte stehen ganz am Rand daher brauchen wir das Filtern an dieser Stelle nicht. Ein Ausnahmefall. Wie dem auch sei, so sieht das ganze in unserer Funktion dann aus:
0 | lenght = sprites[sIndex].attribute1>>14;
Gehen wir weiter. Wir haben nun die Länge in einer Zahl von 0-3 isoliert und wissen das die 0 für 8x8 (also ein Tile) die 1 für 16x16 (also 4 Tiles) usw. steht. Nun gibt es sicher einen einfachen Algo. mit dem die Anzahl der Tiles sicherlich leicht berechnett werden kann. Da ich aber zu faul war darüber nachzugrübeln und meine 5 im Abschlusszeugnis Mathe sicher nicht umsonst gehabt habe nehmen wir eine If Anweisung. So ersparen wir uns auch das includen sämtlicher Mathefunktionen in unser Programm.
0 | if(lenght==0) lenght=2*2;
1 | else if(lenght==1) lenght=4*4;
2 | else if(lenght==2) lenght=8*8;
3 | else if(lenght==3) lenght=16*16;
2. Die Lage der Spritedaten Als nächstes wollen wir wissen wo das Sprite seine Bilddaten speichert. Diese sind im 3. Spriteregister enthalten und stehen ganz am Ende, was bedeutet das wir sie nur heraus filtern müssen. Also erstellen wir uns wieder unseren geliebten Filter der nur die ersten 10 Bits stehen lassen soll. 0000 0011 1111 1111 So sieht er aus, schnell in den Taschenrechner eingetippt und in einen Hexwert umgerechnet ergibt das also 0x03FF und folgende Quellcodezeile:
0 | charstart = sprites[sIndex].attribute2 & 0x03FF;
3. Die Farbtiefe bestimmen So, da sind wir endlich beim letzten Wert den wir noch bestimmen müssen: Der Farbtiefe. Wie schon erwähnt ist ein 16 Farben Tile nur halb so groß wie ein 256 Farben Tile. Wenn wir also die Farbtiefe nicht einbeziehen kommen wir in große Schwierigkeiten. Dieses mal interessiert uns also im Prinzip nur ein einziges Bit. Für dieses zu filtern und zu schieben währe demnach eine eigentlich überflüssig komplizierte Angelegenheit da uns ja zu guter letzt nur interessiert ob er da ist oder nicht. Eine If Anweisung tut es da sicherlich auch.
0 | if( (sprites[sIndex].attribute0 & 0x2000) != 0) colord=2;
1 | else colord=1;
Mit dieser Funktion ist im Prinzip alles gesagt. Wir haben wieder unseren Filter erstellt (0010 0000 0000 0000 bzw. 0x2000) und UND Verknüpft. Haben wir nun 256 Farben, müssen also alles mal 2 nehmen, dann haben wir als Ergebnis einen Wert der größer ist als 0. Haben wir nur 16 Farben so ist auch dieses eine Bit eine Null und wir kommen insgesamt auf den selben Wert. Eine Schlichte aber wohl ausreichende Lösung. 4. Die Werte Kopieren Endlich kommen wir zum eigentlichem kopieren. An diesem Punkt stellt sich nun die Frage ob wir die CPU in einer Schleife oder DMA zum kopieren benutzen wollen. Doch egal ob wir CPU oder DMA benutzen, wir brauchen erst einmal folgende 3 Werte. Wie weit, also wie viele Daten müssen wir kopieren? Von wo müssen wir die Daten kopieren? Nach wo müssen wir die Daten kopieren? OOoookai. Beginnen wir mit "Wie weit?". Die genaue Länge unserer Animationsphase im Speicher ist Länge in Tiles*(8 für eine 16bit Farbe*Farbtiefe). In Quellcode ausgedrückt also lenght*(8*colord). Die Klammer hab ich gesetzt damit man genauer sieht wo es um die Tiles und wo es um die Datenlänge geht. Falls man das später ändern muss geht es so leichter hoffe ich. "Von wo?" müssen die Daten wech? Nudenn, wir wollen die Daten ja mit der CPU kopieren, besitzen also zwei Arrays dessen Index es nur anzugeben gilt. Den Index der Daten "von wo" beschreiben wir mit Zählvariable+(AnimationsPhase*(Länge in Teiles*4 für eine 16bit Farbe*farbtiefe)). Ich gebe zu dies ist der kritischste Teil der Funktion und falls mir hier ein Fehler unterlaufen aus welchem resultierend das hier nicht funktioniert, bitte ich diesen zu melden. loop+(aPhase*(lenght*4*colord) ist der Code dazu. Sicherlich ist eure erste Frage aber wieso dort ein 4 benutzt wird obwohl ein 16 Farben Wert doch 8Bit lang ist. Nun aber wir arbeiten hier in einem u16 Array (Siehe Code Download wo es deklariert ist) was zur Folge hat das wir immer gleich zwei Pixel auf einmal kopieren. Ich konnte leider noch keine Test's mit 16 Farben Sprites machen... Wenn Fehler auftreten mailt mir einfach einmal euren Quellcode (vollständig bitte). "Wohin?" müssen die Daten? loop+(charstart*8*colord)! Also Zählvariable+(SpeicherBereich*8*Farbtiefe). Wenn wir uns damit herrumgekloppt haben kommen wir nun endlich zur eigentlichen Funktion wie sie auch funktioniert.
0 | void animate_cpu(u16 sIndex, u16* BitmapData, u16 aPhase) {
1 | int lenght = 8;
2 | int colord = 2;
3 | int charstart;
4 | int loop = 0;
5 |
6 | lenght = sprites[sIndex].attribute1>>14;
7 |
8 | if(lenght==0) lenght=2*2;
9 | else if(lenght==1) lenght=4*4;
10 | else if(lenght==2) lenght=8*8;
11 | else if(lenght==3) lenght=16*16;
12 |
13 | charstart = sprites[sIndex].attribute2 & 0x03FF;
14 |
15 | if( (sprites[sIndex].attribute0 & 0x2000) != 0) colord=2;
16 | else colord=1;
17 |
18 | for(loop=0; loop<(lenght*(8*colord)); loop++) OAMData[loop+(charstart*8*colord)] = BitmapData[loop+(aPhase*(lenght*4*colord))];
19 | }

Animate mit DMA

Nun da wir unsere Funktion fertig haben sollten wir auch einmal einen Gedanken daran verschwenden DMA zu benutzen. Da wir von vorn herein sowohl bei der Umwandlung des Bildes als auch bei der Wahl von 1D Speicher Management darauf geachtet haben das wir alles mit einem Happen kopieren können sollten wir nun auch Gebrauch von dem Hochgeschwindigkeits-Bus machen. Mancher mag sich nun fragen ob das wirklich so viel Sinn macht und ich bin der Meinung: Ja macht es. Nicht nur das die CPU langsamer ist als DMA, sie muss auch bei unserer Schleife quasi nach jedem 16 Bit Wert den sie schreibt neu absetzen, den zählwert neu inkrementieren, den Index neu berechnen und wieder aufsetzen. Wir sehen, das dauert etwas ^^ Gewiss ist bei kleinen Sprites der Geschwindigkeitszuwachs nicht der Rede wert aber schließlich macht es ja die Menge aus. 128 Sprites kosten schon einiges mehr Rechenzeit die wir nicht verschwenden wollen.
0 | void animate_dma(u16 sIndex, u16* BitmapData, u16 aPhase) {
1 | int lenght = 8;
2 | int colord = 2;
3 | int charstart;
4 | int loop = 0;
5 |
6 | lenght = sprites[sIndex].attribute1>>14;
7 |
8 | if(lenght==0) lenght=2*2;
9 | else if(lenght==1) lenght=4*4;
10 | else if(lenght==2) lenght=8*8;
11 | else if(lenght==3) lenght=16*16;
12 |
13 | charstart = sprites[sIndex].attribute2 & 0x03FF;
14 |
15 | if( (sprites[sIndex].attribute0 & 0x2000) != 0) colord=2;
16 | else colord=1;
17 |
18 | DMA3SAD = &BitmapData[aPhase * (lenght * 4 * colord)];
19 | DMA3DAD = 0x6010000 + (charstart*8*colord);
20 | DMA3CNT = (lenght * ( 8 * colord)) | DMA_16NOW;
21 | }

Beim nächstem mal

Gut, nun haben wir eine einfache Möglichkeit jederzeit von allen möglichen Sprites die Grafiken auszutauschen und damit die wichtigste Grundlage für ein Spiel geschaffen. Für einen einfachen vertikal Shooter mag das vielleicht sogar schon reichen doch natürlich werden wir hier an dieser Stelle nicht aufhören. Im nächsten Teil des Tutorials werden wir uns Gedanken darüber machen wie wir ein Sprite effektiv animieren können ohne das wir jedes mal per Hand die Animation ändern und das Bild verschieben. Es soll schließlich genügen dann sprite_do(sprite,HIT); zu sagen damit unser Sprite zuschlägt wie wir es zuvor sagten das er es machen soll. Am Ende des nächsten Tutorials werden wir dann also eine Figur haben mit der wir umher laufen können und die eine beliebige Anzahl von Tätigkeiten ausführen kann. In dem Tutorial darauf (falls ich bis dahin nicht die Lust verloren habe da Tutos schreiben viel Zeit verschlingt) werden wir uns ausführlich mit Maps beschäftigen. Selber welche mit einem Level Editor erstellen und uns einmal eine simple Kollisionsabfrage überlegen. Wie ihr seht haben wir also sehr viel vor ^^ Was danach kommt wage ich nicht zu sagen. Ich denke aber es währe ein kleines zusammenhängendes Spiel und ein paar Diskussionen darüber wie man ein Spiel so programmiert das viele nicht-Coder dran arbeiten können ohne den Quelltext und die Compiler installiert zu haben. Aber woher soll ich schon wissen wie das alles geht? Ich bin ja nur ein dummer Anfänger ;) Bis zum nächstem mal. Quellen: [1]: www.thepernproject.com Downloads: Die Funktion und Beispielcode

DevGBA
www.robsite.de