[Pokémon Feurrote Edition] Animations-Engine

    • [Pokémon Feurrote Edition] Animations-Engine

      Hallo Leute,

      hiermit veröffentliche ein relativ großes C-Projekt, welches für alle deutschen Feuerrot-Roms kompatibel sein wird. Dabei handelt es sich um die Animations Script Engine, mit welcher es möglich ist, Filmsequenzen zu coden und abzuspielen. Für die Scriptsprache existiert derzeit noch kein Compiler, sodass diese in Hex gecoded werden muss. Die Entwicklung eines Compilers ist erwünscht, ich werde das aber nicht machen.
      Nichtsdestotrotz wird diese Engine nun publik. Sie ist in die main der Spiels integriert und kann paralell zu den schon laufenden Animationen abgespielt werden. Dies bringt leider auch den Nachteil mit sich, dass bestehende Animationan ausgeschaltet werden sollten, wenn man die Engine vom Overworld aus aufruft (z.B. Tileset Animationen, wenn man selbst auf den BG schreibt).

      1. Einfügen der Engine

      Das Einfügen der Engine gestaltet sich für einen C-unerfahrenen eventuell schwierig. Im readme-File des Downloads sind die einzelnen Schritte kurz erklärt, im Folgenden werden sie auch noch einmal beschrieben:

      1. Benötigte Programme:
        - MinGW / Cygwin
        - Devkit Pro
        Diese Programme werden benötigt, um den C und ASM Code zu kompilieren und daraus einen ausführbaren Maschinencode zu machen.
      2. PATH-Variable
        Damit der Building Vorgang korrekt ablaufen kann, muss der Verzeichnis des gcc der DevkitPro Toolchain zur Path-Variable hinzugefügt werden: Konkret: Das Unterverzeichnis des devkitArm-Ordners bin. (z.B. könnte ein Pfad so aussehen C:\Programme\devkitPro\devkitARM\bin (rot ist das Unterverzeichnis markiert). Der genaue Pfad hängt natürlich davon ab, wo ihr devkitPro installiert habt )
        Um einen Dateipfad zur Path-Variable hinzuzufügen wählt ihr (Windows) Systemsteuerung -> System -> Erweiterte Systemeinstellungen -> Umgebungsvariablen.
        Unter Systemvariablen wählt ihr die Variable mit dem Namen PATH aus. Dort fügt ihr (über ; getrennt) den obigen Dateipfad ein. Eventuell müsst ihr den PC neustarten (eigentlich aber nicht).
      3. Linker Script konfigurieren
        Um die Engine zu bauen, muss im Vorhinein festgelegt werden, an welches Offset ihr die Engine später schreibt, da alle Abhängigkeiten auf dieses Offset abgestimmt werden müssen. Beachtet dabei, das ROM-Offsets generell +0x8000000 gerechnet werden, aus dem Offset 0x800000 wird also 0x8800000, aus dem Offset 0x1300000 wird 0x9300000.
        Plant ca. 0x1100 Bytes für die Engine ein. Habt ihr ein Offset gewählt, so öffent ihr die im Download enthaltene linker.lsc Datei mit einem Texteditor auf. Das Offset bei ORIGIN ändert ihr jetzt zu dem gewählten Offset ab. Speichern nicht vergessen.
      4. Engine bauen
        Jetzt, wenn alle vorherigen Schritte korrekt ausgeführt wurden, müsst ihr bloß noch die build.bat aufrufen. War alles erfolgreich erscheinen im Downloadordner die Datein engine.bin und cmdx1A_callasm.bin
      5. Jetzt fügt ihr engine.bin am gewählten Offset ein. Dann fügt ihr cmdx1A_callasm.bin ebenfalls ein (z.B. direkt dahinter). Merkt euch beide Offsets. Jetzt navigiert ihr zu dem Offset der Engine+0xF14. Dort findet ihr (byteverkehrt) den Wert 0xAA AA AA AA. Diesen ersetzt ihr mit einem Pointer auf das Offset von cmdx1A_callasm.bin + 1 (byteverkehrt).
      6. Die Engine kann num aufgerufen werden, indem man die ASM Routine mit dem Offset [Offset von Engine.bin + 0x374] (+1) aufruft (z.B.: über callasm)
      Wie benutzt man die Engine?

      Die Engine ist eine Scriptengine und erwartet demnach einen Script, den sie auswerten kann. Das Offset dieses Scripts muss, bevor die Engine aufgerufen wird, am Offset 0x03000F14 stehen. Dies kann man manuell machen oder auf den Scriptbefehl loadpointer zurückgreifen. Gibt man als Pointerbank 0x0 an, so wird das Offset automatisch an das richtige Offset geladen.

      Quellcode

      1. loadpointer 0x0 0x8800000
      würde das Offset des Scripts bei 0x800000 laden. Dannach kann die Engine über einen callasm Aufruf ausgeführt werden.
      Funktionsweise
      Die Engine ist eine Keyframe-Engine. Sie animiert Objekte, indem man für verschiedene Frames Ereignisse definiert. Man sagt zum Beispiel, dass in Frame 0 ein Objekt erscheinen soll. In Frame 8 sagt man, dass das Objekt für 16 Frames eine bestimmte Bewegung vollführen soll. Indem man verschiedene Ereignisse kombiniert, ergibt sich so ein animierter Ablauf. Das ganze klingt verwirrend, wird aber im Folgenden genauer erläutert.
      Das Spiel zeigt pro Sekunde ca. 60 Frames. Jeder Frame wird mit einer Nummer betitelt. Der erste Frame, der ausgewertet wird, trägt die Nummer 0x0. Es sind Framenummern bis zu 0xFFFF (ca 65 000 ~ über 1000 Sekunden) möglich. Generell folgt ein Frame folgendem, groben Aufbau:

      Quellcode

      1. Framenummer : HWord
      2. ...Liste von Ereignissen
      3. Frame Ende : Byte


      Auf diese Weise können wir beliebig viele Frames definieren. Jedoch dürfen keine zwei Frames die gleichen Nummern haben. Außerdem müssen die Nummern fortschreitend sein, also Frame 4 darf nicht vor Frame 0 definiert werden (was ja auch irgendwo Sinn macht).

      Ein fertiges Script wäre demnach also eine Liste von Frames, z.B.

      Quellcode

      1. Frame 0
      2. ... Ereignisse
      3. Frame 0 Ende
      4. Frame 8
      5. ... Ereignisse
      6. Frame 8 Ende


      Mit diesem Wissen kann ich nun erläutern, wie das ganze Konkret aussieht. Die Framenummer wird durch ein Hword dargestellt, welches genau dieser Nummer entspricht. Die Ereignisse innerhalb des Frames folgen direkt aufgelistet nach der Framenummer. Diese Ereignisse sind die "Befehle" der Engine, welche nacheinander ausgeführt werden (jedoch alle im selben Frame, also für den Spieler nach außen hin parallel, für den Code jedoch nacheinander). Das Frameende wird durch den Byte 0xFF repräsentiert, der an die Ereignisliste angehängt wird. Ein Beispielhafter Frame könnte so aussehen: (Die Bedeutung der Hex Werte wird als Kommentar angegeben)

      Quellcode

      1. 0x0004 //Definition des Frames 4
      2. 0x1 // Ereignis mit der ID 0x1
      3. 0x3 // Ereignis mit der Id 0x3
      4. 0xFF //Ende des Frames


      Indem wir solche Frames direkt hinteinander schreiben, ergibt sich unser Script.


      Interne Variablen

      Die Engine verfügt über interne Variablen mit einer Speichergröße von 16-Bit. In diesen Variablen können Zwischenergebnisse gelagert werden, z.B. Objekt-IDs o.Ä. Es existieren 16 Variablen die von 0x8000 bis 0x800F betitelt werden. Diese Variablen repräsentieren NICHT die aus XSE bekannten Variablen 0x8000 - 0x800F. Viele Befehle erlauben es, dass man ihnen als Paremter keinen festen Wert, sondern eine Variable übergibt, aus welcher sie den Wert dann auslesen. Dabei interpretiert die Engine einen Paremter, der dieses Verfahren unterstützt, als Konstante, wenn der Wert < 0x8000 ist. Sollte größer sein, so wird er als Variable anerkannt und von dort ausgelesen.

      Befehlsreferenz

      Paramter mit einem (?) unterstützen es, statt Konstanten eine Variable anzugeben.

      Jetzt wissen wir also schon, wie die Engine arbeitet. Natürlich müssen wir jetzt auch alle Ereignisse kennen, die die Engine unterstützt. Ein Ereignis wird dabei immer durch seine ID und ggf. Parameter dahinter dargestellt. Die Parameter sind byteverkehrt (LE encoded). Welche Befehle es gibt, welche ID sie besitzen und welche Parameter erwartet werden, wird im folgenden erläutert. Wichtig ist, dass alle Paraemter in voller Länge angegeben werden müssen. Ein einziger Vergessener Paremter verursacht bereits einen Game-Crash (deswegen wäre ein Compiler wünschenswert, da das von Hand sehr mühsam ist).

      Steuerbefehle

      • 0x0: end
        Paremter: Keine
        Der einfachste Befehl: Er beendet das Script. Wichtig ist, dass, auch wenn das end-Command abgelaufen ist, trotzdem noch ein Frame-Ende (0xFF) definiert werden sollte! Sollte man sich nämlich derzeit innerhalb eines Subscripts befinden (siehe 0x1: call), so beendet end nur den Subscript und kehrt zum übergeordneten Script zurück.
      • 0x1: call
        Paremter: Subscript (Pointer), Frame_start (Hword)
        Call ermöglicht es, einen Unterscript im eigentlichen Script aufzurufen. Dazu muss das Offset dieses Unterscripts im Parameter "Subscript" angegeben werden. Sollte im Unterscript ein 0x0: end erreicht werden, so kehrt die Engine automatisch zum übergeordneten Script zurück. Zusätzlich unterstützt die Engine das Feature, für den Subscript einen Startframe zu definieren. Der Subscript wird also nicht mit dem derzeiten Frame weiter ausgeführt, sondern "startet von vorne". Ich erkläre das an einem Beispiel:
        Wir führen in Frame 0x8 das call_Ereignis aus. Wir können jetzt in dem Parameter "Frame_start" einen Frame definieren, welcher den Start des Subscripts simuliert. Geben wir also 0x0 an, so wird der Subscript nicht so ausgeführt, als wäre das Programm bereits im Frame 0x8. Stattdessen wird für den Subscript die Ausführung im Frame 0x0 neu gestartet. So können wir den gleichen Subscript zu mehreren Zeitpunkten aufrufen. Hierzu wieder ein Beispiel:

        In Frame 8 rufen wir den Subscript 0x800000 auf. In Frame 16 können wir den gleichen Subscript 0x800000 aufrufen, da für den Subscript der Startframe jedes Mal neu definiert werden kann. Ich hoffe dieser Punkt ist einigermaßen ersichtlich, denn das erspart viel Arbeit, falls ihr Teilanimationen mehrmals nutzen wollt. Terminiert ein Subscript, so wird der übergeordnete Script selbstverständlich dort weitermachen, wo er aufgehört hat, im Beispiel also im Frame 0x8 bzw 16. (Ich empfehle stark, "Frame_start" generell mit 0x0000 zu belegen und alle Subscripte so zu schreiben, als würden sie als normale Scripte bei Frame 0x0 starten).
      • 0x3: goto
        Paremter: Subscript (Pointer), Frame_start (Hword)
        Dieser Befehl ist analog zu 0x2: call mit dem Unterschied, dass er kein Unterprogramm aufruft, sondern die Ausführung an einer bestimmten Adresse fortsetzt. Wichtig ist, dass ihr auch hier den Frame_start justieren könnt.
      OAM-Befehle

      Bevor ihr euch mit diesen Befehlen befasst, lege ich euch nahe, meine Dokumentation über die OAMs und deren Möglichkeiten zur Animation anzusehen. Diese Animationsmöglichkeit ist auch diejenige, auf die Engine zurückgreift und welche sie indirekt steuern kann.
      • 0x3: oam_new
        Paremter: oam_template (Pointer), x (Hword), y (Hword), undokumentierter Parameter (Byte), Zielvariable (Hword)
        Dieser Befehl kreiert ein neues OAM-Objekt auf dem Bildschirm mit den Koordinaten x und y (Bildmittelpunkt!). Da jedes OAM-Objekt eine ID erhält und diese ID zum Zugriff auf das OAM-Objekt benötigt wird, speichert der Befehl die ID in eine Zielvariable. Hier muss eine gültige Variable (0x8000 - 0x800F) angegeben werden. Der undokumentierte Parameter ist üblicherweise mit 0x0 besetzt.
      • 0x4: oam_delete
        Paremter: OAM-Id (?) (Hword)
        Dieser
        Befehl löscht ein OAM-Objekt, gibt jedoch seine Ressourcen nicht wieder frei. Als Paremter erwartet er die ID des OAMS, der gelöscht werden soll. Diese kann dabei aus einer Variable gelesen werden.
      • 0x5: oam_load_gfx
        Paremter: gfx_ressource (Pointer)
        Dieser Befehl lädt eine Grafikressource, die für OAMs benutzt werden kann ins VRAM (siehe Dokumentation). Dabei erwartet er das Offset der zu ladenden Ressource.
      • 0x6: oam_free_gfx
        Paremter: OAM_ID (?) (Hword)
        Dieser
        Befehl gibt den VRAM für eine Grafikressource, die zuvor geladen wurde, wieder frei. Dabei erwartet sie als Übergabe die ID eines OAM-Objekts, welches die Ressource derzeit benutzt. Diese ID kann aus einer Variable gelesen werden.
      • 0x7: oam_delete_and_free
        Paremter: OAM_ID (?) (Hword)
        Dieser Befehl löscht einen OAM und gibt alle von ihm verwendeten Ressourcen (GFX, Rotscal, Palette) wieder frei. Dabei erwartet er als Übergabe die ID eines OAM-Objekts, welche aus einer Variable gelesen werden kann.
      • 0xF: oam_load_palette
        Paremter: Palette_Tag (Hword), Palette (Pointer), Modus (Byte)
        Dieser Befehl lädt eine Palette für OAMs in den PALRAM. Dabei erwartet er den Palette_Tag, über welchen die Palette identifziert und angesprochen wird (von OAMs) sowie einen Pointer auf die Palette. Noch dazu gibt der Modus an, ob die Palette unkomprimiert oder lz77 komprimiert vorliegt (0=n.komprimiert, 1= lz77 komprimiert)
      • 0x10: oam_free_palette
        Paremter: Palette_Tag (Hword)
        Dieser
        Befehl gibt eine Palette für OAMs wieder frei. Er erwartet einen Palette_Tag um die Palette anzusprechen.
      • 0x19: oam_move
        Paremter: OAM_ID (?) (Hword), Dauer (Hword), x (Hword), y (Hword)
        Dieser Befehl lässt einen OAM eine lineare Bewegung vollführen. Der OAM wird über seine ID, welche aus einer Variable gelesen werden kann, angesprochen. Die Dauer gibt an, wie viele Frames insgesammt die Bewegung andauern soll. X und Y sagen an, um wie viele Pixel sich der OAM insgesammt bewegt.
      • 0x1B: oam_gfx_anim_new
        Paremter: OAM_ID (?) (Hword), Anim_ID (Byte)
        Dieser
        Befehl intialisiert eine neue GFX_Animation, die für den OAM bereits definiert ist. Er spricht den OAM über seine ID (kann aus Variable gelesen werden) an und weißt ihm eine neue Animation über deren Id in der GFX_Anim_Table zu (siehe Dokumentation).
      • 0x1C: oam_rs_anim_new
        Paremter: OAM_ID (?) (Hword), Anim_ID (Byte)
        Dieser

        Befehl intialisiert eine neue Rotscal_Animation, die für den OAM bereits
        definiert ist. Er spricht den OAM über seine ID (kann aus Variable
        gelesen werden) an und weißt ihm eine neue Animation über deren Id in
        der Rotscal_Anim_Table zu (siehe Dokumentation).
      • 0x24: oam_reset
        Paremter: Keine
        Dieser Befehl löscht alle OAMs und gibt alle Ressourcen für OAMs frei.
      BG-Befehle
      Diese Befehle sind zur Kontrolle der 4 BGs implementiert.
      • 0x9: bg_reset
        Paremter: Keine
        Dieser Befehl setzt alle BGs zurück und macht sie unsichtbar.
      • 0xA: bg_setup
        Paremter: tilemode (Byte), Konfigurationsliste (Pointer), Anzahl an enthaltenen Konfigurationen (Byte)
        Dieser Befehl konfiguriert BGs entsprechend verschiedener Konfigurationen neu. Tilemode wird üblicherweise mit 0x0 belegt, während der Befehl weiterhin eine Konfigurationsliste mit maximal vier verschiedenen Konfigurationen (pro BG eine oder keine) benötigt. Diese Konfigurationsliste ist eine Liste von Words, wobei jedes Word einer einzelnen Konfiguration entspricht. Das Word stellt dabei ein Bitfeld dar, wobei einzelne Bits BG-Attribute repräsentieren. Das Bitfeld sieht wie folgt aus:
        • 0 - 1 : ID des BGs der Konfiguriert wird
        • 2 - 3 : Charbase (ergibt sich aus 0x06000000 + x * 0x4000)
        • 4 - 8 : Mapbase (ergibt sich aus 0x06000000 + x * 0x800)
        • 9 - 10 : Größe
        • 11 : Farbmodus (0 = 16/16, 1 = 256/1)
        • 12-13: Priorität
        • 14-31 : Unbenutz
        Mittels dieser Bitfelder können die BGs neu konfiguriert werden.

      • 0xB: bg_sync_and_show
        Paremter: BG_ID (Byte)
        Dieser Befehl synchronisiert einen BG mit seinen Konfigurationen und macht ihn wieder sichtbar. BG_ID repräsentiert den BG der angepeilt wird.
      • 0xC: bg_hide
        Paremter: BG_ID (Byte)
        Dieser Befehl macht einen BG (über BG_ID angesprochen) unsichtbar.
      • 0xD: bg_display_sync
        Paremter: Keine
        Dieser Befehl synchronisiert das Display des Gameboys mit den Sichtbarkeitseinstellungen und Konfigurationen der einzelnen BGs.
      • 0xE: bg_override
        Paremter: BG_ID (Byte), Quelle (Pointer), Datengröße (Hword), Start_Tile (Hword), Modus (Byte)
        Dieser Befehl überschreibt die Grafiken eines derzeitigen BGs mit einer neuen. Dabei wird der BG über BG_ID angesprochen. Quelle verweist entweder auf eine lz77 Tilemap oder ein lz77 Tileset. Datengröße gibt dabei an, wie groß die Ressource, die ins VRAM geladen werden soll unkomprimiert ist (also die Datengrößte in Bytes). Für 256x256 Tilemaps sind das genau 0x800 Bytes (meistens bei Tilemaps diese Größe angeben, sofern nicht größer). Für Tilesets errechnet sich die Datengröße für 4bpp aus Width * Height / 2 bzw für 8bpp aus Width * Height. Start_Tile gibt an, wie weit die neue Grafik verschoben ist. Man kann z.B. eine Teilgrafik an das Tile z.B.: 0x80 laden und dann einen kleinen Block überschreiben, wenn man wollte. Der Modus gibt auskunft darüber, ob die Quelle als Tileset oder Tilemap behandelt werden soll (1 = Tileset / 2 = Tilemap)
      IO-Befehle

      Diese Befehle erlauben den Zugriff auf die internen IO-Register des Gameboys im IORAM .
      • 0x11: io_get
        Paremter: Zielvariable (Hword), IO_Register (Hword)
        Dieser Befehl lädt den Wert eines IO Registers (angesprochen über IO_Register) in eine bestimmte Zielvariable (angesprochen über Zielvariable).
      • 0x12: io_set
        Paremter: Quellvariable (Hword), IO_Register (Hword)
        Dieser
        Befehl setzt den Wert eines IO_Registers (angesprochen über IO_Register) auf den Wert einer Variable (angesprochen über Quellvariable).
      • 0x13: io_seti
        Paremter: Wert (Hword), IO_Register (Hword)
        Dieser
        Befehl setzt den Wert eines IO_Registers (angesprochen über
        IO_Register) auf einen bestimmten Wert (Wert).
      Textbefehle
      Diese Befehle dienen zur Unterstützung von statischen und animierten Texten innerhalb der Engine. Sie sind die mit Abstand am komplexesten Befehle.

      • 0x14: prepare_textbox
        Paremter: Zielvariable (Hword), BG_ID (Byte), x (Byte), y (Byte), Breite (Byte), Höhe (Byte), Palette_ID (Byte), Start_Tile (Hword)
        Dieser
        Befehl bereitet das Erscheinen eines Textes vor, indem er eine virtuelle Box erschafft, in welcher der Text gerendert werden kann. Die Box wird über eine Box_ID angesprochen, welche der Befehl in die Variable "Zielvariable" abspeichert.
        BG_ID gibt an, auf welchem BG die Box erscheinen soll. x,y,Breite und Höhe geben die Maße der Textbox an, wobei sie immer Vielfache von 8 sind. Palette_ID sagt aus, welche Palette die Box nutzen soll (einfach am Original orientieren). Start_Tile ist die wohl am schwersten kontrollierbare Angelegenheit, da sie aussagt, wo in das VRAM-Tileset der Text geschrieben werden soll. Generell braucht man pro viruteller Box immer Breite*Höhe Tiles. Natürlich muss man auch beachten, dass schon bestimmte Plätze für eingebaute Textboxen reserviert sind... Einfach ausprobieren, wo Platz ist zur Ausführung.
      • 0x15: display_static_text
        Paremter: Box_ID (?) (Hword), Schriftart (Byte), unbekannt (Byte), Zentrierung (Byte), Linienabstand_o (Byte), Linienabstand_u (Byte), Schriftfarbenmap (Pointer), Display_Flag (Byte), Text (Pointer), BG_ID (Byte)
        Dieser Befehl zeigt einen statischen Text sofort auf dem Hintergrund an. Dabei erwartet er eine bereits zuvor erzeugte virutelle Box zum Rendern des Textes, welche er über Box_ID (kann aus einer Variable gelesen werden) anspricht. Die standard-Schriftart ist 0x2. Unbekannt ist für gewöhnlich auf 0x0. Die Zentrierung sagt aus, wie weit der Text von den Kanten der virtuellen, nicht sichtbaren Box entfernt ist. Linienabstand_o / u sagen aus, wie groß der Abstand zwischen den einzelnen Zeilen ist (o steht für obere Zeile zur unteren Kante, u steht für untere Zeile zur oberen Kante). Diese Angaben sind, wie die Zentrierung in Pixel. Die Schriftfarbenmap ist ein Pointer auf einen Datensatz, welcher aus 4 Bytes besteht. Diese 4 Bytes sagen an, welche Farben der gewählten Palette ein bestimmter Bestandteil der Schrift nutzen soll:
        • 0x0: Farbe des Hintergrunds (0 ist die transparente Farbe)
        • 0x1: Farbe des Textes
        • 0x2 : Farbe der Buchstabenränder
        • 0x3: unbenutz
        Die Display_Flag sagt aus, ob die Box auch wirklich angezeigt oder nur zum Anzeigen vorbereitet werden soll: 0xFF = alle vorbereiteten Boxen inklusive dieser darstellen, 0x0 = Box nur vorbereiten. Das wird bei mehreren Boxen sinnvoll, da dann der Hintergrund nicht mehrmals neu gezeichnet werden muss.Text pointet auf den Text, der angezeigt werden soll, BG_ID gibt den Hintergrund an, auf welchem der Text erscheint. Diese Information muss identisch mit der in der virutellen Box sein.

      • 0x16: clear_static_text
        Paremter: Box_ID (?) (Hword)
        Dieser Befehl löscht einen !statischen! Text samt virtueller Box, welche über Box_ID (kann aus einer Variable gelesen werden) angepeilt wird. (Einen animierten Text darf man so nicht löschen!)
      • 0x17: display_animated_text
        Paremter: Zielvariable (Hword), Box_ID (?) (Hword), Textgeschwindigkeit (Byte), Schriftart (Byte), unbekannt (Byte), Zentrierung (Byte), Linienabstand_o
        (Byte), Linienabstand_u (Byte), Schriftfarbenmap (Pointer),
        Display_Flag (Byte), Text (Pointer), BG_ID (Byte)
        Dieser
        Befehl ist analog zu 0x15: display_static_text und verwendet annähernd gleiche Parameter. Im Gegensatz zu 0x15 erlaubt dieser Text jedoch eine Animation der Buchstaben, sprich der Text wird nach und nach eingeblendet. Auch Events wie \p oder \n können innerhalb dieser animierten Textbox verarbeitet werden. Dazu erzeugt der Befehl ein Animationscallback, welches über eine ID identifiziert wird. Diese ID wird in Zielvariable abgelegt. Noch dazu kann die Textgeschwindigkeit festgelegt werden. Die Textbox hält die Animation an, wenn sie an ein Textbox-Event kommt und keine Erlaubnis hat, dieses Event auszuführen. Auf diese Weise können Events wie neue, leere Boxen oder Umbrüche gesteuert werden. Die Erlaubnis ein Event auszuführen, geben wir mit dem Befehl 0x18: animated_text_event an ein Animationscallback.
      • 0x18: animated_text_event
        Paremter: Animationscallback_ID (?) (Hword), Event (Byte)
        Dieser Befehl gibt einem Animationscallback die Erlaubnis, ein bestimmtes Textevent zu passieren. Dabei kann eine Box nie mehr als eine Erlaubnis pro Event haben, aber es kann beliebig viele verschiedene Erlaubnisse speichern. Zum Beispiel kann ein Animationscallback die Erlaubnis haben, ein \n und ein \p Event zu passieren. Folgende drei Textevents werden unterstützt:
        • 0x0: \n = Neue Zeile
        • 0x1: \p = Neue, leere Box
        • 0x2: Ende eines Textes = schließt die animierte Textbox und löscht die virtuelle Box
        Für welches Event wir dem Animationscallback die Erlaubnis geben wollen, legen wir in Event fest. Das Animationscallback peilen wir über Animationscallback_ID (kann aus einer Variable gelesen werden) an.



      Soundbefehle

      Diese Befehle unterstützen Soundfunktionen.
      • 0x20: sound
        Paremter: Sound_ID (Hword)
        Spielt den Sound mit der ID = Sound_ID ab.
      • 0x21: song
        Paremter: Song_ID (Hword)
        Spielt den Song mit der ID = Song_ID ab.
      • 0x22: cry
        Paremter: Poke_ID (Hword), Modulation (Byte)
        Spielt den Cry des Pokemons mit der ID = Poke_ID ab.
      Palettenbefehle
      Diese Funktionen beziehen sich auf Palettenmodifikationen.

      • 0x1D: load_palette
        Paremter: Palette (Pointer), Zielfarbe (Hword), Farbenanzahl (Hword), Modus (Byte)
        Lädt die Farben an "Palette" ins PALRAM an die Farbe mit der ID = Zielfarbe. Farbenanzahl legt dabei fest, wie viele Farben geladen werden. Modus gibt an, ob die Palette lz77 komprimiert oder unkomprimiert vorliegt (0 = n.komprimiert, 1 = lz77 komprimiert)
      • 0x1E: fadescreen
        Paremter: Blendfarbe (Hword), Zielfarbe (Hword), Farbenanzahl (Hword), Dauer (Byte), Umkehrung (Byte)
        Blendet Farben in eine andere Farbe über oder zurück. In welche Farbe übergeblendet wird, wird mit "Blendfarbe" festgelegt. Die erste Farbe, die geblendet wird, wird durch "Zielfarbe" determiniert. Wie viele Farben, ausgehend von dieser ersten Farbe geblendet werden, wird von "Farbenanzahl" bestimmt. Die Dauer gibt in Frames an, wie lange der Blendevorgang dauert. Umkehrt sagt aus, ob die Farben zur Blendfarbe hin geblendet werden sollen, oder von der Blendfarbe zu ihren Ursprungswerten zurück geblendet werden. (0 = hin zur Blendfarbe, 1 = zurück zum Ursprung).
      • 0x1F: invert_colors
        Paremter: Zielfarbe (Hword), Farbenanzahl (Hword)
        Invertiert Farben ausgehend von "Zielfarbe". Wie viele Farben betroffen sind wird von "Farbenanzahl" festgelegt.
      Scriptkollaboration
      • 0x23: script_notify
        Paremter: Keine
        Um einen XSE Script mit einem Animationsscript zu synchronisieren, wird der script_notify Befehl verwendet. Dieser Befehl löst ein waistate eines XSE Befehls auf. (Ohne waitstate würde der XSE Script paralell zum Animationsscript weiterlaufen)
      ASM-Schnittstelle

      Die Engine unterstützt auch eine Schnittstelle, ASM-Routinen aufzurufen bzw. selbst callbacks zu initialisieren.
      • 0x8: spawn_big_callback
        Paremter: ASM_Funktion (Pointer), Priorität (Byte), Anzahl_Parameter (Byte), { parameter (Hword) * x)
        Dieser Befehl erzeugt ein neues Callback mit einer eigens angegebenen ASM Funktion. (Siehe callbacks ). Zusätzlich kann, sofern der Parameter "Anzahl_Parameter" nicht 0 ist, noch ein beliebig großer Datensatz (na gut, nicht beliebig groß, da er in das lokale Memory des Callbacks passen muss) geladen werden. Die zusätzlichen Parameter (durch Hwords dargestellt) werden einfach vom Scriptbefehl in den lokalen privaten Speicher des Callbacks trasnferiert. Die Anzahl an 16-Bit Parametern muss jedoch mit dem Parameter "Anzahl_Parameter" übereinstimmen.
      • 0x1A: callasm
        Paremter: ASM_Funktion (Pointer), Anzahl_Paramter (Byte), { r0- sp + X (Word) }
        Dieser Befehl erlaubt es, ASM Routinen aus der Engine heraus aufzurufen. Dieser ASM Routine können gemäß der C-Konvention (also auf r0-r3 bzw. dem Stack) 32-Bit Parameter übergeben werden. Die Parameter, die analog zu 0x8: spawn_big_callback gelistet sind (in diesem Fall jedoch keine Hwords sondern 32-Bit Words) werden automatisch auf r0-r3 und den Stack gemappt. So kann man jede beliebige Funktion aufrufen, als wäre man noch im C-Code. Bsp:
        setflag ( u16 flag ) wird durch 0x1A: callasm ( ASM_Funktion = setflag + 1, Anzahl_Parameter = 1, FLAG ) repräsentiert.
      Download

      Nun habt ihr es geschafft, das ist die gesamte Befehlsreferenz. Fragen und Anregungen bitte hier posten. Wer will kann dieses Turoial und die Engine auch exportieren. Zu guter Letzt noch den Downloadlink:


      Wodka
      Wo war Gondor, als meine Klausurenphase begann?

      Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von Wodka ()

    • Hallo,

      ich habe das Thema einmal verschoben, da es doch soweit fertig und anwendbar ist und daher in das Ressourcenforum gehört. :)
      Von dem Projekt wusste ich ja schon, schön das du es nun veröffentlicht hast. Ich habe nun kurz überflogen, sieht aber recht solide aus, hoffentlich habe ich demnächst einmal Zeit, ein paar Dinge auszuprobieren.
      Wie ich sehe, ist die Person, die du für den Compiler beauftragt hattest, leider abgesprungen, aber vielleicht findet sich noch ein anderes schlaues Kerlchen. ^^
    • …This. Is. AMAZING!

      Thank you SO much, Wodka, for open-sourcing this for everyone to use! It’s an incredibly generous thing to do…

      (I remember seeing the Groudon cutscenes in Pokémon Violet and thinking, ‘wow, that must have been so hard to do!’ And now you’re showing us all how to make our own games look that good!)

      ***

      I do have a few questions, if you have time to answer them! But first, an apology – my first language is English, and I’m relying on Google Translate to understand the German documentation. So if I seem stupid or slow, that might be why… I’m very sorry!

      It’s my eventual goal to port this over to BPRE, if I can. (I think you gave permission for that, at the end of the document?) But I wanted to get it working in BPRD first!

      So…

      1) I compiled the C code, and inserted it into a BPRD ROM:
      a. Engine: 0x800000
      b. cmdx1A: 0x801000
      c. I changed the AA AA AA AA pointer to 01 10 80 08 [cmdx1A +1].

      2) I hex edited the following:
      a. GFX template at 0x900000: 01 00 02 00 00 02 90 08 00 00 91 08 00 00 96 08 8D 75 00 08
      b. final_sprite, at 0x900200: 00 00 00 C0 00 00 00 00
      c. GFX structure table, at 0x910000: 00 00 92 08 08 00 92 08
      d. GFX structure data, at 0x920000: 48 A0 D5 08 08 00 00 01 48 A0 D5 08 08 00 00 01 48 A0 D5 08 08 00 00 01 00 00 93 08 08 00 00 01
      e. Image data, at 0x930000: [nothing yet, I’m testing with Abra’s sprite at 0xD5A048)
      f. rotscal_animation_table, at 0x960000: 00 00 97 08
      g. rotscal_animation script data, at 0x970000: 00 01 00 01 00 00 00 00 02 00 02 00 01 80 00 00 00 02 00 02 80 00 00 00 FE FF FE FF FF 80 00 00 FE 7F 00 00 00 00 00 00 FF FF FF FF FF FF FF FF

      3) I wrote an XSE script, and gave it to the TV signpost in ALABASTIA 4.1:

      C-Quellcode

      1. #dynamic 0xA00000
      2. #include stdpoke.rbh
      3. #include stditems.rbh
      4. #include stdattacks.rbh
      5. #include stdmove.rbh
      6. #org @start
      7. lock
      8. msgbox @t1 0x6
      9. //loadpointer 0x0 0x8980000 // OAM script - not working :(
      10. loadpointer 0x0 0x8981000 // BG, cry and palette script
      11. callasm 0x800379 // call animation engine - callasm 0x[offset of engine.bin + 0x374] (+1)]
      12. waitstate
      13. sound 0xB
      14. msgbox @t2 0x6
      15. release
      16. end
      17. #org @t1
      18. = Test begins.
      19. #org @t2
      20. = Test ends.
      21. #org 0x981000
      22. #raw byte 0x00 // frame number 0
      23. #raw byte 0x00 // frame number 0
      24. #raw byte 0x09 // bg_reset
      25. #raw byte 0x0D // bg_display_sync
      26. #raw byte 0x22 // play cry
      27. #raw byte 0x19 // Pikachu
      28. #raw byte 0x00 // Pikachu
      29. #raw byte 0xFF // end of frame
      30. #raw byte 0x00 // frame number 01
      31. #raw byte 0x01 // frame number 01
      32. #raw byte 0x1D // load_palette
      33. #raw pointer 0x8D5A31C // pointer to Abra's 16-color palette
      34. #raw byte 0x00 // destination_color
      35. #raw byte 0x00 // destination_color
      36. #raw byte 0x00 // no_of_colors_to_load
      37. #raw byte 0x0F // no_of_colors_to_load
      38. #raw byte 0x01 // mode (0 is uncompressed, 1 is LZ77 compressed)
      39. #raw byte 0x23 // script_waitstate_notify
      40. #raw byte 0x00 // end_command
      41. #raw byte 0xFF // end of frame
      42. #raw byte 0xFF // free-space
      43. #raw byte 0xFF // free-space
      44. #raw byte 0xFF // free-space
      45. #raw byte 0xFF // free-space
      46. #raw byte 0xFF // free-space
      47. /*
      48. #org 0x980000
      49. #raw 0x00 // Frame number 0
      50. #raw 0x00 // Frame number 0
      51. #raw 0x05 // load resource
      52. #raw pointer 0x8D5A048 // Abra
      53. #raw 0x03 // oam_new
      54. #raw pointer 0x8900000
      55. #raw 0x00
      56. #raw 0x00 // X coordinate
      57. #raw 0x00
      58. #raw 0x00 // Y coordinate
      59. #raw 0x00 // unknown
      60. #raw 0x00 // destination var (8000)
      61. #raw 0x80 // destination var (8000)
      62. #raw 0x0 // end
      63. #raw 0xFF // End of frame
      64. */
      Alles anzeigen
      …When I compile this script, the BGs are made invisible, and I hear Pikachu’s cry (yay!) But only frame number 0 seems to work – Abra’s palette is never loaded, and then the script hangs, never returning.

      Looking at the C code for the animation_engine_release, it looks like the commands stop after 0x22_cry… which seems strange. Could that be why the script never notifies the waitstate to begin executing again?

      C-Quellcode: anim_engine.c

      1. /**
      2. / Command Functions
      3. **/
      4. void cmdx00_end(memory* mem);
      5. void cmdx01_call(memory* mem);
      6. void cmdx02_jump(memory* mem);
      7. void cmdx03_oam_new(memory* mem);
      8. void cmdx04_oam_delete(memory* mem);
      9. void cmdx05_oam_vram_load(memory* mem);
      10. void cmdx06_oam_vram_free(memory* mem);
      11. void cmdx07_oam_despawn(memory* mem);
      12. void cmdx08_spawn_callback(memory* mem);
      13. void cmdx09_bg_reset(memory* mem);
      14. void cmdx0A_bg_setup(memory* mem);
      15. void cmdx0B_bg_sync_and_show(memory* mem);
      16. void cmdx0C_bg_hide(memory* mem);
      17. void cmdx0D_bg_display_sync();
      18. void cmdx0E_bg_override(memory* mem);
      19. void cmdx0F_load_obj_pal (memory* mem);
      20. void cmdx10_free_obj_pal(memory* mem);
      21. void cmdx11_get_io(memory* mem);
      22. void cmdx12_set_io_to_var(memory* mem);
      23. void cmdx13_set_io_to_value(memory*mem);
      24. void cmdx14_prepare_tbox (memory*mem);
      25. void cmdx15_display_text_inst (memory*mem);
      26. void cmdx16_clear_textbox (memory*mem);
      27. void cmdx17_display_rendered_tbox (memory*mem);
      28. void cmdx18_rendered_tbox_event (memory* mem);
      29. void cmdx19_objmove (memory* mem);
      30. void cmdx1B_gfx_anim_set(memory*mem);
      31. void cmdx1C_rs_anim_set (memory*mem);
      32. void cmdx1D_loadpal (memory*mem);
      33. void cmdx1E_fade (memory*mem);
      34. void cmdx1F_invertcolors (memory* mem);
      35. void cmdx20_sound (memory* mem);
      36. void cmdx21_song (memory* mem);
      37. void cmdx22_cry (memory* mem);
      38. void init_callback(){
      39. u8 callback_id = spawn_big_callback((void*)callback, 0);
      40. u32 callback_offset = (u32)(0x03004FE0 + 0x28*callback_id);
      41. (*((u32*)(callback_offset+0xC))) = (u32)(malloc_fill(sizeof(memory)));
      42. memory* mem = (*((memory**)(callback_offset+0xC)));
      43. //initalising values
      44. mem->current_programm = *((u32*)0x03000f14);
      45. mem->callback_id = callback_id;
      46. mem->active = true;
      47. }
      Alles anzeigen
      I’ve attached an IPS patch for my current BPRD… is there any chance you could take a look and see what I might be doing wrong? I would LOVE to get this working – it could add so much to my game!

      Huge thanks again for all your hard work!

      -Viv

      Spoiler anzeigen
      (ALL ADDRESSES FOR BPRD 1.1:)

      Engine: 0x800000
      cmdx1A: 0x801000

      Actual 24-byte long GFX templates: 0x900000
      Table of pointers to GFX structures: 0x910000
      Actual GFX structure data: 0x920000
      Image data begins: 0x930000
      Table of pointers to all rotscal animations: 0x960000
      Actual rotscal animation scripts: 0x970000
      Actual OAM animation scripts: 0x980000


      XSE scripts start at: 0xA00000


      Abra 16 color sprite: 0xD5A048
      Abra 16 color palette: 0xD5A31C
      Dateien
    • Uh, first of all thank you, I honestly thought that no one even cared about this publication so I did not put any effort in keeping it up to date. The engine has changed quite a bit, some changes were made and commands added, the latest version is always part of my git-repository, I strongly recommend checking the corresponding folder (github.com/WodkaRHR/Violet_Sou…ee/master/src/anim_engine).

      As for your problem: The documentation is somewhat incorrect, cry takes two parameters, the first one being the species id whereas the second one is some kind of modulation (similiar as what the second parameter of XSE's cry command does). You can always check the parameter structure by looking into the C-Code of the engine. Whenever anim_engine_read_XXX (and XXX is either byte, hword or word) a value is read from the paremter structure of the command. As you can see for the cry command a hword and a byte are read. Sorry that it was wrong in this documentation.
      Wo war Gondor, als meine Klausurenphase begann?
    • Haha, well, I definitely care about this stuff! It's the coolest thing I've seen anyone come up with in a long time - finally, we can have custom cutscenes?? That's awesome! :D

      Thank you for the link to the updated version - that looks really cool, you've added a whole bunch of commands! (cmdx2B_bg_scroll looks especially intriguing... I've always wanted to scroll a moving background behind the player. Maybe I could make a moving train, or a zeppelin...)

      I know you must be very busy, but is there any chance you would consider writing up a quick tutorial on how to compile the new updated engine? I've tried cloning the git-repository and copying the relevant sections, but I get lots of error messages like 'undefined reference to...') I've screencapped my error messages and attached them, in case that helps.



      ********

      It would also be awesome to get a quick tutorial on how to generate an OAM, for example, under the new system. (Studying the new code, it looks as though I don't have to use loadpointer 0x0 any more?? Are the scripts now kept in a table?)

      Thanks so much in advance!
    • Everything is pretty much the same, all functions that are listed as "undefined reference to XXX" must be included to your header and symbol file. E.g. you should define bg_set_tilemap in C code (which is not really necessary but prevents warnings) and also you HAVE to add an offset for this function in your .sym file. Also you have to compile the code in the other files in the directory and link them together (using ld), this will clear your reneferences to maintain and cmd_x1A_callasm. To be honest the eninge right now is not made to be included in a project that does not contain any code or makefile, it could be pretty hard to get all the references and stuff.

      Also I hardly recommend working on some C code, as it might clear some things up. Yes, the codes are now stored in a table so they can convienently called via special. You set the pointer in the special table to the init_anim_engine_by_table function and then can easily use the engine by writing a script like:
      setvar 0x8004 INDEX_IN_TABLE special XXXXX

      That said it is not to easy to find the offset to link into the special table (I dont know where in the binary blob the init_anim_engine_by_table function will be placed, which might also depend on how you link your stuff). The way to do is using a Makefile and Armips(which is a really helpfull tool for C and ASM Hacking). In the patches directory you can find a file called specials.asm, which will be executed by armips and automatically create a correct reference to the function.


      I do apologize, that this has kind of lost its transparency, but if you can still use this engine (which works pretty fine, however can not do everything the current version can), if you desire to learn more about how to use a makefile for your Romhacking I recommend reading the tutorial by Sbird or even contact me privatly (However this might not be something to be learned so fast)
      Wo war Gondor, als meine Klausurenphase begann?
    • Ah, thank you - I'll definitely read up about C code, and checkout Sbird's tutorial. I think it might be somewhat above my skill level right now, though, so I'm taking your advice and just working with the original engine you posted here for now! It does so much already :)

      Speaking of which - THANK YOU, so much, again! I finally, finally managed to get a 256-color image displaying, glitch-free, in the game :D :D which is something I have wanted to do forever. The engine works magnificently, even without its new additions! :D

      ...I am still struggling a bit with the OAM functions, though. Whatever I try, I can't seem to get a 64x64 OAM to display... :/

      Do you see anything in this script that's obviously wrong?

      [...]

      // Part of XSE script
      // Generate OAM in Animation Engine:

      #org @animation_script_OAM
      #raw word 0 // frame number 0

      #raw byte 0x05 // oam_load_gfx
      #raw pointer 0x8D5A048 // Abra

      #raw byte 0xFF // end of frame


      #raw word 1 // frame number 01

      #raw 0x3 // oam_new
      #raw pointer 0x8900000 // template data
      #raw word 0x0 // X coordinate
      #raw word 0x0 // Y coordinate
      #raw byte 0x0 // filler byte
      #raw word 0x8000 // destination var (8000)

      #raw byte 0xFF // end of frame


      #raw word 2 // frame 2

      #raw byte 0xF // oam_load_palette
      #raw word 0x8000 // palette tag
      #raw pointer 0x8D5A31C // pointer to Abra's 16-color palette
      #raw byte 0x1 // uncompressed [0] or LZ77 [1]?

      //#raw byte 0x23 // script_waitstate_notify <- CRASHES if left in :(

      #raw byte 0xFF // end of frame


      #raw word 3 // frame 3

      #raw byte 0x00 // end_command
      #raw byte 0xFF // end of frame

      [...]


      //---------------------------------------

      My template and table data:

      • template, at 0x900000: 02 00 01 00 30 00 90 08 40 00 90 08 B0 00 90 08 60 00 90 08 A1 00 90 08
      • final_sprite, at 0x900030: 00 00 00 C0 00 00 00 00
      • GFX_animation_table, at 900040: 50 00 90 08
      • GFX_animation_frames, at 900050: 01 02 00 00 FE FF 00 00
      • GFX_table, at 9000B0: C0 00 90 08
      • GFX_structures, at 9000C0: 48 A0 D5 08 08 00 02 00
      • rotscal_animation_table, at 900060: 70 00 90 08
      • rotscal_animation_frames, at 900070: 00 01 00 01 00 00 00 00 02 00 02 00 01 80 00 00 00 02 00 02 80 00 00 00 FE FF FE FF FF 80 00 00 FE 7F 00 00 00 00 00 00
      • callback, at 9000A0: 70 47 00 00

      RAW HEX DATA BELOW:
      Spoiler anzeigen
      STARTS AT 0x900000:
      02 00 01 00 30 00 90 08 40 00 90 08 B0 00 90 08
      60 00 90 08 A1 00 90 08 FF FF FF FF FF FF FF FF

      2D 2D 2D 2D 2D 2D 2D 2D 2D 2D 2D 2D 2D 2D 2D 2D

      00 00 00 C0 00 00 00 00 FF FF FF FF FF FF FF FF

      50 00 90 08 FF FF FF FF FF FF FF FF FF FF FF FF

      01 02 00 00 FE FF 00 00 FF FF FF FF FF FF FF FF

      70 00 90 08 FF FF FF FF FF FF FF FF FF FF FF FF

      00 01 00 01 00 00 00 00 02 00 02 00 01 80 00 00

      00 02 00 02 80 00 00 00 FE FF FE FF FF 80 00 00

      FE 7F 00 00 00 00 00 00 FF FF FF FF FF FF FF FF

      70 47 00 00 FF FF FF FF FF FF FF FF FF FF FF FF

      C0 00 90 08 FF FF FF FF FF FF FF FF FF FF FF FF

      48 A0 D5 08 08 00 02 00 FF FF FF FF FF FF FF FF


    • Uhm as far as I can see everything looks pretty normal to me, have you tried excluding several parts of the whole stuff and figure out what exactly causes problems by doing so? For example you could use a rotscale-Null Animation (and also for the gfx) (there is likely something like that in FRE as well, you should check the offsets in my code repository and find out the english ones). Also do you see something or does the game freeze? And what exacly is at 0x8D5A048? There should be an 8-Byte structure of the gfx like this:
      [tileset image of abra : pointer] [tileset size in bytes : hword] [tag : hword] (much like the original sprite table is stuctured). Also I recommand using the original sprite table since you can use Abra's Tag (which should be its species ID). Furthermore you will need to load a palette before displaying the oam, which can also be done by using the palette structure [pallette : pointer] [tag: hword] [00 00] that is already given for every pokemon (here the tag should also match Abra's ID).
      Wo war Gondor, als meine Klausurenphase begann?
    • Thanks for the quick reply! (I'm actually using BPRD right now for these first tests, so the offsets should be correct...)

      I did try 00-ing out each pointer, but nothing seems to help. I did change the part of my script you suggested, so it now reads:

      #raw word 0 // frame number 0

      #raw byte 0x05 // oam_load_gfx
      #raw pointer 0x89000C0 // GFX image table

      #raw byte 0xFF // end of frame


      ...but it doesn't seem to do anything. I also reordered the frames so that Abra's palette is loaded before the OAM is displayed, but the picture still didn't display.

      ***

      The game doesn't freeze, but I don't see any OAM, unfortunately. Strangely, Abra's palette seems to load fine, and the OAM viewer in VBA actually does change its attributes to 64x64. But it just doesn't display any image!

      BEFORE:


      AFTER:


      I hope that makes things clearer! :) And thanks again for being kind enough to help me out!


      ...I saw in the other thread that jiang managed to display a Bulbasaur and rotate and scale him as a test, haha. I'd love to be able to write code that could do that... :(



    • I never thought of doing that! :D Yes, here you go:





      ...so it looks like the tiles are definitely being loaded! They just don't display...

      //-----------------

      EDIT:

      YESSSSSSSSSSS!!!



      THANK YOU WODKA! Your engine is AMAZING :D :D :D and thank you for showing me where to look to fix my problem!

      ...It was the animation frames I was getting wrong, after all. Changing 0x900050 to:

      00 00 00 00 FE FF 00 00

      ...fixed my problem!

      From your very handy documentation:

      Spoiler anzeigen

      Mode 1:

      In the first case, the OAM refers to a single GFX object. This is decompressed directly into the VRAM when the OAM is created, and the start_tile is immediately set to 0x0. All necessary graphics are therefore already stored in the VRAM by the time the animation executes, so they can be displayed by changing which tile_id is read.

      For example: my sprite contains four 32x32 pictures, which are all loaded into the VRAM. The position of the first image would be tile_id + 0; the position of the second image would be tile_id + (32 x 32/2 = 512), the position of the third tile_id + 1024; and so on. The fact that all resources are stored at once in the VRAM can make this type of animation fast and useful. However, for pictures larger than 16x16, this animation type is not particularly suitable – the OBJ VRAM does not have much space to spare!

      The structure of a frame is as follows:

      CODE:
      [start_tile (2 bytes)] [pause in frames (2 bytes)]

      • The first two bytes specify how many tiles start_tile should be moved from its original location. If I want to display the second frame, for instance, I would need to write the value 512 (0x200). If I want the third frame, I write the value 1024 (0x400), etc. This value is limited to 10-bits, which allows a range of 0x0 - 0x3FF. (OBJ VRAM is limited in space, so this method is not recommended for 32x32 graphics).
      • Wait time contains two pieces of information, encoded in bits. Bits 0-5 contain a wait time, measured in frames, to be executed after the start_tile changes. Bits 6-7 contain boolean values that indicate whether the image should be mirrored horizontally and / or vertically – where 1 is a reflection and 0 is not a reflection.


      Frames can alternatively use the following control commands to handle looping animations:

      CODE:
      [Control command (2 bytes)] [parameter (2 bytes)]

      The following control commands also exist:

      • 0xFFFC: Marks the beginning of a loop: the parameter is ignored and the following command is executed immediately. (This type of loop is NOT recommended, since it resets all the OAM’s animations!)
      • 0xFFFF: Terminates the current animation.
      0xFFFD: Jump to the start of the previous loop, and reset the animation completely. (This also resets the rotscal animations?)
      0xFFFE: Jump to the frame with the index in [parameter]. Recommended for endless loops.

      An example animation is below:

      CODE:
      0x0000 0x0020 0x0040 0x0028 0x0060 0x0068 0xFFFE 0x0000

      This code would result in:
      1. Display the first frame (start_tile + 0, or 0x0) for 32 frames (0x20).
      2. Then loop back to the first (0th) command in the animation (i.e. display start_tile + 0 for 32 frames). This would cause a continuous animation that is active as long as the OAM is shown.
      2.Display the second frame (start_tile + 64, or 0x40) for 40 frames (bits 0-5 are 0x28 and bits 6-7 are both zero, for no reflections).

      3.Display the third frame (start_tile + 96, or 0x60) for 40 frames (bit 0-5 in 0x68 are 0x28). Also, mirror horizontally (bit 6 in 0x68 is 1).


      ******************

      OLD EDIT:
      Spoiler anzeigen

      ***

      EDIT: Okay, your reply got me thinking - could I have messed up the six-byte final_sprite structure controlling the OAM? When I look in the OAM viewer, it tells me it's reading from tile 549:



      ...where nothing is stored, obviously. (I think Abra's tiles begin at tile 48.)

      But I can't seem to fix my six-byte structure. :/ Right now, it's:

      00 00 00 C0 C0 D0

      This I worked out from GBATEK:

      OBJ Attribute 0 (R/W)

      Bit Expl.
      0-7 Y-Coordinate (0-255)
      8 Rotation/Scaling Flag (0=Off, 1=On)
      When Rotation/Scaling used (Attribute 0, bit 8 set):
      9 Double-Size Flag (0=Normal, 1=Double)
      When Rotation/Scaling not used (Attribute 0, bit 8 cleared):
      9 OBJ Disable (0=Normal, 1=Not displayed)
      10-11 OBJ Mode (0=Normal, 1=Semi-Transparent, 2=OBJ Window, 3=Prohibited)
      12 OBJ Mosaic (0=Off, 1=On)
      13 Colors/Palettes (0=16/16, 1=256/1)
      14-15 OBJ Shape (0=Square,1=Horizontal,2=Vertical,3=Prohibited)




      Caution: A very large OBJ (of 128 pixels vertically, ie. a 64 pixels OBJ in a Double Size area) located at Y>128 will be treated as at Y>-128, the OBJ is then displayed parts offscreen at the TOP of the display, it is then NOT displayed at the bottom.



      00 00 00 00
      0
      0
      00
      0
      0
      00

      00 0 0 00 0 0 00 00 00 00


      0x0000



      [reversed: 00 00]



      OBJ Attribute 1 (R/W)

      Bit Expl.
      0-8 X-Coordinate (0-511)
      When Rotation/Scaling used (Attribute 0, bit 8 set):
      9-13 Rotation/Scaling Parameter Selection (0-31)
      (Selects one of the 32 Rotation/Scaling Parameters that
      can be defined in OAM, for details read next chapter.)
      When Rotation/Scaling not used (Attribute 0, bit 8 cleared):
      9-11 Not used
      12 Horizontal Flip (0=Normal, 1=Mirrored)
      13 Vertical Flip (0=Normal, 1=Mirrored)
      14-15 OBJ Size (0..3, depends on OBJ Shape, see Attr 0)
      Size Square Horizontal Vertical
      0 8x8 16x8 8x16
      1 16x16 32x8 8x32
      2 32x32 32x16 16x32
      3 64x64 64x32 32x64




      00 00 00 00 0
      00 0
      0
      0
      11 (3)

      11 0 0 0 00 0 00 00 00 00
      0xC000


      [reversed: 00 C0]


      OBJ Attribute 2 (R/W)

      Bit Expl.
      0-9 Character Name (0-1023=Tile Number)
      10-11 Priority relative to BG (0-3; 0=Highest)
      12-15 Palette Number (0-15) (Not used in 256 color/1 palette mode)






      00 11 00 00 00 – 48
      00 - 0
      1101 – 13




      1101 00 00 11 00 00 00
      0xD0C0


      [reversed: C0 D0]


      **********

      But, weirdly, my sprite seems determined to read its tiles from 549, no matter what I change the last two bytes to... :/

      Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von Vivacity ()