Dumme Tutorials Teil 3: Animations-Schemen:

[Wiederholung: Was bisher geschah] [Überlegung: Was brauchen wir?] [Realisierung] [Erstellen der Funktion] [Arbeiten mit der Funktion] [Beim nächstem mal] |
Autor: | Tharo Herberg
Erstellt: | 09.Okt.02
Level: | Fortgeschrittene
Kenntnisse: | Strukturen vorheriges Tuto.

Und schon wieder ist es passiert: noch ein dummes Tutorial. Dieses mal kommen wir endlich zu einem Thema das bei einigen die Augen glitzern lässt: Wir lassen unser Sprite laufen! Ja eigentlich noch ein wenig mehr. Ich werde mit euch in diesem Tutorial eine Möglichkeit erarbeiten die es uns erlaubt einfach nur noch "Sprite: LAUF" zu sagen und das Ding macht den Rest praktisch automatisch. Dazu kommt dann noch das wir Kommandos wie "Lauf" oder "Spring" selbst möglichts einfach erzeugen wollen. Schließlich müssen wir dann noch irgend wie damit klar kommen dass unsere Animationen nicht immer den selben Ausgangspunkt haben und wir damit unser Sprites nicht zucken irgend etwas dagegen unternehmen müssen. 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: Was bisher geschah

Im letztem Tutorial haben wir uns viele Gedanken darum gemacht wie der Aufbau eines Sprites ist und wie wir unser Sprite wohl am einfachsten Animieren könnten. Dazu haben wir uns eine kleine aber feine Funktion geschrieben mit der wir die Sprite-Grafik austauschen können. Egal wo sie ist und wie groß sie ist denn darum kümmert sich die Funktion netterweise selbst. Bei der Erstellung dieser Funktion haben wir viele Nette kleine Sachen kennen gelernt. Darunter das Schieben und die Boolischen Verknüpfungen AND und OR (UND - ODER) die auch dieses mal wieder mit von der Partie sein werden. Alles in einem hatten wir viel Spaß und ich denke nachdem wir ende dieses Tutorials auch etwas damit anfangen können werden wir noch mehr Spaß haben.

Überlegung: Was brauchen wir?

Am Anfang wenn man etwas neues Programmieren sollte muss man sich erst einmal darüber in klarem werden was man überhaupt machen will. Nun, ich denke das wissen wir ganz genau: Wir wollen unser Sprite zum laufen kriegen! ^^ Ok, das haben wir uns ein wenig leicht gemacht also sagen wir es etwas genauer: Wir wollen das unser Sprite und auch andere Sprites bestimmte Bewegungsabläufe ausführen können. Was brauchen wir damit unser Sprite läuft? Laufen tut es ja im Prinzip auch schon in unserem letzten Beispiel. Würden wir dort noch schnell die y Werte verändern würde unser kleines Sprite artig von oben nach unten laufen und würden wir dann alles per Hand so fummeln dann könnte man es sicherlich auch steuern. Sicher toll für die Grundlagen und eine Demo, jedoch ist es ja unser Ziel die Sprites später in einem Spiel zu haben. Wir können es uns nicht leisten alles per Hand zu machen denn schon bei zwei Figuren verliert man dabei den Verstand... Überlegen wir daher einmal logisch welche Features ein Animations-Schema bräuchte nach dem ein Sprite laufen kann. Es muss reichen wenn wir sagen "Lauf" und der GBA weiß was er zu tun hat. Es gibt zwei Schemen: Einmal eine Aktion die unterbrochen werden kann (laufen zum Beispiel) und zum anderen etwas das bis zum Bitterem Ende fortgeführt wird zum Beispiel ein Schlag. Wir müssen irgend wo festlegen welche Animationen unser Sprite bei dieser Aktion macht und wie viele das sind. Unser Sprite muss sich bei der Aktion eventuell vorwärts Bewegen. Mehr noch, vielleicht macht er ja einen Satz zurück und springt erst dann nach vorne. Wir müssen damit rechnen das unsere Animationen nicht perfekt zusammen passen da sie ja sonst nicht in das Sprite passen würden und wir ein größeres Sprite benutzt hätten. Auch dies müssen wir ausgleichen. OK .... Nach dem erstellen einer solchen Auflistung ist es bei professionellen Programmierern üblich erst einmal eine weile den Monitor bzw. das Notizblatt anzustarren und sich verzweifelt zu Fragen wie man das wohl umsetzen könnte. Wir stellen schließlich fest das wir alles haben was wir für unser Animations-Schema brauchen jedoch bleiben viele andere Fragen ungeklärt. Woher soll unser Sprite wissen welches Schema es gerade durchläuft? Woher soll es später überhaupt wissen in welche Richtung es schaut? Ob es schwimmt oder fliegt? Fragen über Fragen denn Fakt ist: Die GBA Hardware kümmert sich nur um das Anzeigen von Bildern die wir hier Sprites nennen doch dies ergibt noch lange keine Figur. Obwohl wir ja eigentlich nichts weiter wollten als unser Sprite laufen lassen sind wir nun doch gezwungen die Eigenschaften des Sprites zu erweitern damit wir überhaupt die Möglichkeit haben unsere Animations-Schemen sinnvoll anzuwenden. Was benötigt also eigentlich eine Figur in unserem Spiel? Sie muss wissen welches Hardware Sprite zu ihr gehört. Sie muss wissen ob sie gerade in einem Schema ist. Optimal währe es wenn sie noch weiß in welchem. Sie muss wissen wie weit sie, ggf. das Schema schon durchlaufen hat. Wir sollten wissen wie wichtig das ist was das Sprite gerade macht. Gehen wird schon unterbrochen wenn man nur eine Taste loslässt. Ein Sturz z.B. nicht. Das ist aber Sache des jeweiligen Spieles und interessiert uns im Moment nicht. Schwimmt unser Sprite? Fliegt es? Auch das ist Sache des Users aber wird dennoch benötigt. In welche Richtung schaut unser Sprite? Wir sollten noch eine Variable übrig lassen in die wir einen Pointer machen können. Auf was wissen wir im Moment zwar nicht aber später würde sich da vieles anbieten wie z.B. die zur Figur gehörende Lebensenergie. Toll. Unfreiwillig ist unser Sprite nun größer als das eigentliche Schema das wir erstellen wollen -_-; Naja was soll es. Ich hoffe nur das diese beiden Strukturen sich später im Spiel auch bewähren und nicht mehr als zu sehr verändert werden müssen.... Lieber lange geplant als lange verbessert.

Realisierung

Genug gefrustet. Versuchen wir unsere Anforderungen einmal in Programmierbare Tatsachen um. Wir wollen bei den Animations-Schemen 5 Wünsche auf einmal erledigt haben. Das können wir mit einer einzelnen Variable natürlich nicht. Daher werden wir unser Schema jeweils in einer Struktur unterbringen. Eine Struktur ist in etwa das selbe wie eine einzelne Variable. Wir können sie genauso behandeln jedoch enthält sie immer mehrere Datensätze auf einmal. So wie praktisch zu einem guten Pokemon-Set ein GameBoy und ein paar Spiele auf einmal gehören (scheiß Beispiel). Mehr Infos dazu auf Anfrage, doch schaut euch einfach mal an wie wir es machen. Vieles erklärt sich von selbst. Sind wir also schon beim dritten Schritt: Welche Animationsphasen (und wie viele) soll unser Sprite machen? Dazu speichern wir also erst einmal klar ab wie viele Phasen wir überhaupt durchlaufen. Die Animationsphasen selbst speichern wir danach einfach in einem Array ab. Es genügt wenn wir für die jeweilige Animationsphase stellvertretend nur eine Zahl speichern die wir mit unserer Animate Funktion aus dem setzten Tutorial benutzen können. Die beiden letzten Punkte auf unserer Liste lassen sich beim näheren hinsehen sehr gut zusammenfassen. Wenn wir eh die Verschiebung unseres Sprites definieren müssen können wir diesen Wert auch gleich mit dazu benutzen unsere Unregelmäßigkeiten auszubessern. Dazu machen wir also zwei Werte (x und y logischerweise) die dann immer angeben um wie viel sich unser Sprite bei dieser Animationsphase wohin bewegt. So. Grob über den Daumen gefeilt kommen wir also zu etwa diesem Ergebnis: Unsere Struktur aSchema
0 | typedef struct aSchema {
1 | u8 phasen;
2 |
3 | u16 aPhase[8];
4 |
5 | s8 xvar[8];
6 | s8 yvar[8];
7 | };
Mit dieser können wir nun also eine Animation beschreiben die maximal 8 Phasen haben kann. Gerne auch mehr (bis 256) dazu müsst ihr einfach nur die Arrays größer machen welche beschreiben wie viele Daten wir umfassen wollen. Die Werte für die x und y Variation sind beide jeweils signed, heißt also sie sind von -128 bis 128 setzbar. Für unseren Zweck also ganz genau richtig. Hui. Nun wo die Struktur steht sieht das ganze doch wirklich noch einfacher aus als es zuerst den Anschein hatte ;) kommen wir also zu der Sprite Erweiterung.
0 | typedef struct gameSprite {
1 | u8 sIndex;
2 | aSchema* running;
3 | u8 step;
4 |
5 | u8 proirity;
6 | u8 ido;
7 | u8 ilook;
8 | u32 point;
9 | }gameSprite;
Das ist sie, frei heraus gedonnert. Ich habe das ganze, unglaublich kreativ wie ich bin, gameSprite genannt. Die ersten drei Atribute sind wie vorher in der Liste schon überlegt einmal der Index zu unserem Hardware Sprite (Dovoto Standart), dann ein Eintrag der uns sagt welches Schema gerade läuft (ansonsten 0) und zuletzt wie weit wir im Schema schon fortgeschritten sind. Die unteren 4 Atribute hingegen enthalten Variablen mit denen der User im jeweiligen Spiel anders umgehen muss und kann. Allein die Priorität hat für uns im Moment in sofern Bedeutung das sich unsere Schemen falls diese 0 ist von allein wiederholen sollen.

Erstellen der Funktion

Fast geschafft. Der Theorie nach müssten die beiden Strukturen wie geplant funktionieren. Wollen wir nun also mal eine Funktion schreiben mit der wir das ganze schließlich auch ausprobieren können. Welche Variablen brauchen wir? Da brauchen wir wohl als erstes einmal unser gameSprite. Da dieses wie die Hardware Sprites nicht von Anfang an in einem Array ist benötigen wir davon den Pointer. Dann wollen wir wissen welches Schema wir ausführen wollen. Logisch, gell? Zuletzt müssen wir noch wissen mit welchen Bilddaten das ganze passieren soll.
0 | void exec_schema(gameSprite* sp, aSchema* schem, u16* BitmapData);
.. macht doch einen guten Eindruck, oder? Durchlauf der Funktion. Allem voran sollten wir prüfen ob dieses Animations-Schema bereits läuft. Ist dem so machen wir dort weiter wo wir aufgehört haben, ist dem nicht so beginnen wir von vorn. Codemäßig sieht das in etwa so aus:
0 | if(sp->running != schem) {
1 | sp->step = 0;
2 | sp->running = schem;
3 | }
Haben uns darum gekümmert können wir im Prinzip bereit die Bilddaten des Sprites ändern. Endlich kommt meine coole Animate Funktion zum zuge ^^
0 | animate_cpu(sp->sIndex, BitmapData, schem->aPhase[sp->step]);
Was ein Wirrwar! Also wie ihr sehen könnt bedienen wir uns bereits in vollen Zügen den übergebenen Daten. Da währe einmal der Sprite Index von sp, der Pointer der bereits ein Pointer ist und daher einfach nur übergeben werden muss (macht blos kein &) und zum Schluss die aktuelle Animationsphase. Nun haben wir also das Bild ausgetauscht und es wird Zeit das wir auch die x und y Position des Sprites so verändern wie wir sie uns wünschen. Dazu addieren wir einfach die gespeicherten Werte auf das jeweilige Register drauf wodurch wir automatisch die richtige Verschiebung nach links oder rechts erhalten. Dadurch das wir einfach nur drüber addieren kann es natürlich bei zu hohen Werten zu überträgen kommen. Wer später Angst hat vor solchen Überträgen der kann hier ja noch eine If Anweisung hinzufügen die solch einen Fehler abfängt.
0 | sprites[sp->sIndex].attribute1 += schem->xvar[sp->step];
1 | sprites[sp->sIndex].attribute0 += schem->yvar[sp->step];
Gut gut! Natürlich sollten wir zwei Sachen am Ende nicht vergessen. Zum einem das wir eine Animationsphase weiter schalten müssen und zum anderem danach zu schauen ob die Animation damit vollendet ist. Wir erinnern uns das wenn das Atribut running im gameSprite 0 ist das unsere Animation darauf vorbereitet wird das sie beim nächstem mal einfach von vorne beginnt. Ist dem nicht so schließen wir das Schema indem wir unseren Pointer löschen.
0 | if(sp->step >= schem->phasen) {
1 | if(sp->proirity==0) sp->step=0;
2 | else sp->running = 0;
3 | }
Hier nun die komplette Funktion auf einem Blick:
0 | void exec_schema(gameSprite* sp, aSchema* schem, u16* BitmapData) {
1 | if(sp->running != schem) {
2 | sp->step=0;
3 | sp->running = schem;
4 | }
5 |
6 | animate_cpu(sp->sIndex, BitmapData, schem->aPhase[sp->step]);
7 |
8 | sprites[sp->sIndex].attribute1 += schem->xvar[sp->step];
9 | sprites[sp->sIndex].attribute0 += schem->yvar[sp->step];
10 |
11 | sp->step++;
12 |
13 | if(sp->step >= schem->phasen) {
14 | if(sp->proirity==0) sp->step=0;
15 | else sp->running = 0;
16 | }
17 | }

Arbeiten mit der Funktion

So ... schließlich haben wir es geschafft: Unsere Funktion und alle dafür notwendigen Strukturen stehen. Ein ordentliches Stück denn anders als in dem letzten Tutorial haben wir ja dieses mal auch eine ganze Menge eigener Strukturen erstellt von denen wir hoffen das sie später im Spiel genauso praktisch laufen wie wir es uns erhoffen. Sagen kann man das leider vorher nie so genau. Es kommt halt auf den Versuch an ;) Doch eben weil dieses mal die Anwendung nicht ganz so klar ist wie das letzte mal wollen wir hier nun noch einmal zusammen ein Beispiel durchgehen wir wir unser Sprite vom letzten mal dieses mal den Bildschirm herunter laufen lassen. Zugegeben: Dieses Beispiel liegt weit, sehr weit, unter dem eigentlichen Potential der Animations-Schemen doch soll es euch ja auch nur kurz zeigen wie wir überhaupt ein Schema erstellen. Also als erstes machen wir wie im Sample des letzten Tutorials unser Sprite und die üblichen Bildschirm Einstellungen, setzen Zähl Variablen und laden die Palette ein usw.
0 | u16 index = 0;
1 | u16 loop = 0;
2 |
3 | SetMode(MODE_2 | OBJ_ENABLE | OBJ_MAP_1D);
4 |
5 | for(loop = 0; loop < 256; loop++) OBJPaletteMem[loop] = sprite_animaPalette[loop];
6 |
7 | InitializeSprites();
8 |
9 | sprites[5].attribute0 = COLOR_256 | SQUARE | 12;
10 | sprites[5].attribute1 = SIZE_32 | 40;
11 | sprites[5].attribute2 = 666;
Dann jedoch erstellen wir noch unser gameSprite und legen es auf das 6. Hardware Sprite fest das wir zuvor eingerichtet hatten.
0 | gameSprite mySprite;
1 | mySprite.sIndex = 5;
Gut. Nun erstellen wir unser erstes Animations-Schema. Es wird 6 Phasen haben, bei jeder das Sprite einen Pixel weiter herunter bewegen und linear die Animationen in unseren Bilddaten durchlaufen.
0 | aSchema runter;
1 | runter.phasen=6;
2 |
3 | runter.aPhase[0]=0;
4 | runter.yvar[0]=1;
5 |
6 | runter.aPhase[1]=1;
7 | runter.yvar[1]=1;
8 |
9 | runter.aPhase[2]=2;
10 | runter.yvar[2]=1;
11 |
12 | runter.aPhase[3]=3;
13 | runter.yvar[3]=1;
14 |
15 | runter.aPhase[4]=4;
16 | runter.yvar[4]=1;
17 |
18 | runter.aPhase[5]=5;
19 | runter.yvar[5]=1;
Nun schnell noch eine billige Schleife mit der wir unsere Animation wieder und wieder und wieder durchlaufen lassen.
0 | while(1) {
1 | if(loop>60) {
2 | exec_schema(&mySprite, &runter, &sprite_animaData);
3 | CopyOAM();
4 | loop=0;
5 | }
6 | loop++;
7 | WaitForVsync();
8 | }
Fertig. Es ist natürlich ratsam wenn man sich mal eine Animations-Schemen Sammlung gemacht hat diese zusammen mit den dazugehörigen Bilddaten in einen Header File auszulagern...

Beim nächsten mal

Uff.. Geschafft. Dieses mal hab ich mich mit dem Tutorial doch wesentlich schwerer getan als das letzte mal. Ich hoffe das es dennoch halbwegs verständlich ist. Ich weiß das es ein riesiger Textblock ist da dieses mal keine Bilder dazwischen sind und ich weiß das dieses mal der Beispielcode etwas spartanisch sein mag.. Aber ich hoffe dennoch das ich euch damit ein mächtiges Tool für eure zukünftigen Spiele in die Hand gedrückt habe mit dem ihr halbwegs lernt umzugehen. Wo wir gerade beim Umgehen sind. Speziell nun wo wir ja wirklich eine Methode kreiert haben mit der wir Animationsabläufe speichern kann währe es umso wünschenswerter ein Tool zu haben mit dem man einfache Grafiken komfortabel umwandeln und gleich noch eben diese Schemen Visuell bearbeiten könnte. Ich hoffe ja immer noch einen Deppen zu finden der mir diese Arbeit abnimmt . . . Sollte mir das jedoch nicht gelingen werd ich es wahrscheinlich selbst schreiben müssen. Im nächsten Tutorial haben wir dann erst einmal genug von Sprites und setzen uns mit Map's auseinander. Bis zum nächsten Tutorial wird es daher etwas länger als nur eine Woche (einen Monat vielleicht?) dauern... Aber woher soll ich schon wissen wie das alles geht? Ich bin ja nur ein dummer Anfänger ;) Bis zum nächstem mal.

DevGBA
www.robsite.de