[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:


    • 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.
    • 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).
    • 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.
    • 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
    • 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).
    • 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.

    Code
    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:


    Code
    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.

    Code
    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)


    Code
    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?

    Edited 3 times, last by 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. ^^

  • OMG, what can i say... It's really a great thing (though not for BPRE but i think it won't be a problem to change the .sym). For now I can't say much as i haven't really come into C hacking, but I just reply here to show my admiration on your great work!

  • …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:

    …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?


    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


  • 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 (https://github.com/WodkaRHR/Vi…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.

  • 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_TABLEspecial 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)

  • 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? [Blocked Image: https://wcf.romhackersworld.eu/images/smilies/smile.png]

    [...]


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



  • 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).

  • 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... :(


    [Blocked Image: http://i869.photobucket.com/albums/ab256/jiangzhengwenjz/1_zps6mk3zp44.gif]


  • 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:



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


    OLD EDIT: