Half-Life Sprite Spezifikation

Allgemeines

Das Sprite in Half-Life ist eine RGB Bitmap mit 8 Bit Farbpalette (256 Farben). Das technische Grundgerüst stammt direkt vom Quake Spriteformat ab, wobei es aber einige signifikante Unterschiede zwischen dem Quake Format und dem Half-Life Format gibt! Im Klartext: Quake Sprites und Half-Life Sprites sind technisch inkompatibel zueinander. Valve hatte seinerzeit einige wichtige und sinnvolle Erweiterungen vorgenommen, z.B. das jedes Sprite seine eigene Farbpalette besitzt.

Im Kern besteht das Sprite aus drei Komponenten:

  1. Dem Kopfteil, Header genannt
  2. Der Farbpalette, Palette genannt
  3. Den Bilderdaten, Frames genannt

Der Header besitzt genau wie die Palette eine feste Größe. Der einzig variable Teil sind die Frames.

Sprite Header

Hinweis: Die benutzten Datentypen entsprechen der allgemeinen Beschreibung und sind nicht an eine bestimmte Programmiersprache gebunden. In diesem Artikel findet ihr eine kurze Definition der internationalisierten Bezeichnungen.

Gesamte Größe = 40 Bytes (0x00 bis 0x27)

Byte offset Typ Inhalt Beschreibung
0x00-0x03 BYTE[4] „IDSP“ enthält die Zeichenkombination „IDSP“ ( id Software Sprite ).
0x04-0x07 DWORD Version enthält die Versionsnummer des Spriteformats. 1 = Quake, 2 = Half-Life
0x08-0x0B DWORD Orientierung enthält die Orientierung (Fixierungstyp) des Sprites siehe » Orientierung
0x0C-0x0E DWORD Renderformat enthält das Renderformat des Sprites siehe » Renderformate
0x10-0x13 FLOAT Radius enthält den Radius des Sprites. Hiermit ist die sogenannte Bounding Box des Sprites vom grafischen Mittelpunkt aus gemeint, wie man sie im Editor sehen kann. Im Allgemeinen ist der Radius 1.0 und kann im Editor geändert werden. Man spricht daher auch vom Skalierungsfaktor.
0x14-0x17 DWORD Breite enthält die Breite des Sprites in Pixel. Die Angaben hier beziehen sich auf ein Radius von 1.0, also die Ursprungsmaße des Sprites. Technisch betrachtet entspricht die Breite dem Offset des Bitmapbereichs zwischen zwei Pixelreihen, also z.B. zwischen dem Beginn der ersten Reihe und dem Beginn der nachfolgenden Reihe. Weitere Anmerkungen dazu am Ende
0x18-0x1B DWORD Höhe enthält die Höhe des Sprites in Pixel. Die Angaben hier beziehen sich auf ein Radius von 1.0, also die Ursprungsmaße des Sprites. Technisch betrachtet entspricht die Höhe der Anzahl der Pixelreihen des Bitmapbereichs. Weitere Anmerkungen dazu am Ende
0x1C-0x1E DWORD Anzahl der Frames enthält die Anzahl der Frames, die das Sprite gespeichert hat. Valves Maximum liegt bei 1000 möglichen Frames.
0x20-0x23 FLOAT Beamlänge enthält die Beamlänge des Sprites. Die Beamlänge definiert eine Verschiebung der Ursprungskoordinaten des Sprites siehe » Beamlänge
0x24-0x27 DWORD Synchronisierung 0 = synchron, 1 = zufällig. Die Synchronisierung legt fest, ob ein Sprite synchron oder zufällig durch seine Framesgruppen geht siehe » Framesgruppen

Hinweis Das Quake Spriteformat ist dagegen nur 36 Bytes groß, denn in Version 1 der Spezifikation (welches für Quake Sprites gilt) fehlt die Angabe zum Renderformat.

C/C++ Code des Spriteheaders

struct spriteheader
{
    int8_t idsp[4];
    int32_t version;
    int32_t type;
    int32_t renderformat;
    float radius;
    int32_t maxwidth;
    int32_t maxheight;
    int32_t frames;
    float beamlength;
    int32_t synctype;
};

Hinweis: Der Header wurde ursprünglich von id-Software/Valve für 32 Bit Systeme entworfen, wo long auch 32 Bit breit ist. Aktuelle 64 Bit Systeme können für long allerdings 64 Bit bereitstellen, weshalb es sich empfiehlt fixed stdint Types zu verwenden (wie geschehen) die in <stdint.h> zu finden sind.

Java Code des Spriteheaders

public class spriteheader {
    public byte idsp[] = new byte[4];
    public int version;
    public int type;
    public int renderformat;
    public float radius;
    public int maxwidth;
    public int maxheight;
    public int frames;
    public float beamlength;
    public int synctype;
}

Hinweis: Die Variable id muß später beim Anzeigen auf dem Bildschirm usw. vorher entweder in ein char Array oder gleich in einen String konvertiert werden. In Java ist der Typ int immer 4 Bytes groß.

Header Wertetabelle

Nachfolgend die Werte für einen Spriteheader mit seinen Standard-, Minimum- und Maximumwerten:

Bezeichnung Standard Minimum Maximum Hinweise
IDSP „IDSP“ Muß immer „IDSP“ sein.
Version 2 1 2 Muß immer 2 sein.
Orientierung 2 0 4
Renderformat 0 0 3
Radius 1.0 > 0.0 Das Maximum ist mir nicht bekannt, sollte aber realistisch bleiben. Ein Radius von 5000.0 würde ein Sprite theoretisch 5000 mal größer als das Original skalieren. Ebenso verhält es sich mit dem Minimum. Ein Radius von 0.001 würde ein Sprite theoretisch 1000 mal kleiner als das Original skalieren.
Breite 16 16 256 Die Breite sollte gleich der Höhe sein, wobei nichtquadratische Sprites auch möglich sind.
Höhe 16 16 256 Dasselbe gilt auch für die Höhenangabe.
Anzahl der Frames 1 1 1000 Valve hat definiert, daß es maximal 1000 Frames geben darf.
Beamlänge 0.0 -1.0 1.0 Eine Beamlänge von z.B. 0.5 verschiebt den Ursprung des Sprites um die Hälfte seiner Breite und Höhe.
Synchronisierung 1 0 1

Renderformate

Wert Typ Beschreibung
0x00 Normal Die Pixel des Sprite werden ohne Farbberechnungen dargestellt
0x01 Additive Alle Schwarzanteile einer Farbe werden mit den Pixeln des Hintergrund verrechnet
0x02 Indexalpha Graustufen. Die Palettenposition 255 enthält die Farbe, in der das Sprite dann nachträglich getönt wird.
0x03 Alphatest Die Palettenposition 255 enhält die Alphafarbe des Sprite. Alle Pixel, die diese Palettenposition benutzen, werden transparent dargestellt

Orientierung

Wert Typ Beschreibung
0x00 VP Parallel Upright Z Achse ist fixiert
0x01 Facing Upright Schiefe Ansicht, frei beweglich *)
0x02 VP Parallel Frei beweglich (Standardwert)
0x03 Oriented X, Y und Z Achsen sind fixiert
0x04 VP Parallel Oriented Schiefe Ansicht, Z Achse ist fixiert *)

*) = Diese Orientierungen sind im Spiel selber untauglich und sehen mehr als merkwürdig aus. Sie sollte daher nicht verwendet werden, außer man hat Augenkrebs… oder will ihn bekommen.

Beamlänge

Die Beamlänge dient der Verschiebung der Ursprungskoordinaten eines Sprites. Diese Verschiebung wird erst nach vollständigen Ausrichtung der angegebenen Orientierung durchgeführt. Der Koordinatenursprung eines Sprites liegt standardmäßig genau in der Mitte des Sprites:

Breite = 32 Pixel, Höhe = 32 Pixel ⇒ (16, 16)

Die Beamlänge kann einen negativen als auch einen positiven Wert haben. Allerdings darf dabei das Minimum von -1.0 bzw. das Maximum von 1.0 nicht unter- bzw. überschritten werden (anscheinend ist die maximale Ausdehung des Sprites in Pixeln die natürliche Grenze). Die Formel dazu ist simpel:

(Breite / 2 * Beamlänge, Höhe / 2 * Beamlänge)

Folgende Beispieltabelle gibt Aufschluß bei einem Sprite mit der Größe 64×64 Pixel:

Beamlänge Ursprung Neuer Ursprung
0.0 32,32 32,32
0.5 32,32 16,16
-0.2 32,32 6.4,6.4 → 6,6


So wie es aussieht kann die Ursprungsverschiebung nur synchron auf beide Dimensionen durchgeführt werden. Da ich mir aber da nicht so sicher bin, werde ich diesbezüglich nochmals Nachforschungen anstellen.

Sprite Palette

Direkt nach dem Header kommt die Palette des Sprites. Eingeleitet wird sie durch einen 2 Byte großen Farbzähler, welcher angibt, wieviele Farben die Palette besitzt. In der Regel sind das immer 256 Farben. Die Farben selbst sind als 3 Byte RGB-Wert nacheinander gespeichert, so daß man also im Endeffekt 256*3 = 768 Bytes an Farbinformationen hat. Ein einzelner RGB Eintrag ist definiert in RR:GG:BB, d.h. jeder einzelne Farbwert liegt im Bereich von 0 (0x00 → aus) bis 255 (0xFF → volle Stärke).

Je nach Prozessorbauart (Little Endian oder Big Endian) können die drei Werte als RR:GG:BB oder (was häufiger ist) als BB:GG:RR vorliegen. Dann muß man entsprechend die Werte umformen. Außerdem sollte man hier byteweise lesen, denn einen 3 Byte großen Datentypen gibt es nicht als nativen Datentypen. Wer ein 4 Bytes großes DWORD als Lesetyp benutzt, der bekommt zwangsläufig einen Byteanteil der nächsten Farbe mit. Also Vorsicht!

Gesamte Größe = 770 Bytes (0x0028 bis 0x0329)

Absoluter Byte offset Relativer Byte offset Typ Inhalt Beschreibung
0x0028-0x0029 0x0000-0x0002 WORD Farbzähler Hat immer den Wert 256 (0x0100)
0x002A-0x0329 0x0003-0x0302 RGB Farbdaten Hier stehen die Farbdaten drin

Hinweis: Die Angaben zum absoluten Byte Offset beziehen sich auf die gesamte Datei, während der relative Byte Offset sich auf den Anfang der Palettensignatur bezieht. Die absoluten Angaben kann man daher direkt als Einstiegspunkt in Byte gemessen für die Palettensignatur betrachten.

Sprite Frames

Die eigentlichen Bilder des Sprites sind direkt nach dem Header gespeichert und werden Frames genannt. Da es keine Offset-Zeiger im Header für die einzelnen Frames gibt, sollte man das Sprite also komplett laden, puffern und sich dann byteweise durch den Puffer vorarbeiten. Wo ein neuer Frame anfängt, kann man so pauschal nicht von vornherein sagen, denn die Half-Life Spezifikation unterscheidet zwischen SINGLE (einzel) und GROUP (gruppierte) Frames. Beide Sorten haben unterschiedliche Strukturgrößen.

Single- und Groupframes

Die Spezifikation unterscheidet zwischen zwei Sorten von Sprites, nämlich zum einen die Sorte, wo nur ein einzelnes Bild gespeichert ist (Singleframe) und zum anderen die Sorte, wo mehrere Bilder nacheinander in einem einzigen Frame (Groupframe) gespeichert sind.

Singleframe

Singleframes (Einzelbilder) haben folgende Erkennungssignatur:

Relativer Byte offset Typ Inhalt Beschreibung
0x00-0x03 DWORD Typsignatur 0 = Singleframe
0x04-0x07 DWORD Ursprung X-Achse Ein Integerwert mit der Koordinate des Ursprungs auf der X-Achse
0x08-0x0B DWORD Ursprung Y-Achse Ein Integerwert mit der Koordinate des Ursprungs auf der Y-Achse
0x0C-0x0F DWORD Breite in Pixel Hier wird nochmals die Breite des Singleframes in Pixel ausgelesen. Er sollte logischerweise mit der Breitenangabe des Headers übereinstimmen.
0x10-0x13 DWORD Höhe in Pixel Selbiges gilt für die Höhe des Singleframes.
0x14-MAX BYTE Pixeldaten Jedes einzelne Byte entspricht einem Pixel. Der Wert ist der Index einer Farbe in der Farbpalette.

Hinweis: Die Angaben zum relativen Byte Offset beziehen sich auf den Anfang der Framesignatur. Einen absoluten Offset gibt es so nicht bzw. ab dem 810. Byte (0x032A) der Datei beginnen die Framesignaturen. Für weitere Informationen siehe weiter unten!

Der ominöse Bezeichner MAX steht für einen Grenzwert, der das Ende eines Singleframes definiert. Er errechnet sich aus dem Produkt von Framebreite und Framehöhe (Breite x Höhe). Bei einem Frame mit der Dimension 64×64 Pixel wäre MAX also 4096, d.h. es sind 4096 Bytes an Pixeldaten einzulesen.

Groupframe

Groupframes (Multibilder in einem Frame) haben folgende Erkennungssignatur:

Relativer Byte offset Typ Inhalt Beschreibung
0x00-0x04 DWORD Typsignatur 1 = Groupframe
0x05-0x08 DWORD Anzahl der Groupframes Ein Integerwert mit der Anzahl der auszulesenden Groupframes


Jetzt folgen ab dem Byte 0x09 die sogenannten Intervalle. Das sind FLOAT Werte, welche die Anzeigesequenz der Groupframes bestimmen und die als FLOAT Array ausgelesen werden müssen. Man kann so bestimmen, welcher Groupframe wann in der Reihenfolge erscheinen soll.

Angefangen wird mit dem Wert 0.0 (als erstes anzeigen) und aufgehört wird mit 1.0 (als letztes anzeigen). Da die Intervalle auch unsortiert vorliegen dürfen, ist eine Mischsequenz möglich. Intervallwerte für eine lineare Sequenz erhält man, indem man 1.0 durch die Anzahl der Groupframes teilt (1.0 / n). Bei 5 Groupframes wäre das also der Wert 0.2. Der erste Groupframe bekommt die 0.0, der zweite die 0.2, der dritte die 0.4, der vierte die 0.6 und der letzte die 1.0.

Wenn ein Sprite beispielsweise aus 5 Groupframes besteht, dann beziehen die Indizes des Intervallarrays auf den jeweiligen Groupframe:

Index ist Groupframe
0 1
1 2
N N+1


Aus unerfindlichen Gründen ist das Intervallarray bei jedem einzelnden Groupframe immer wieder mit dabei, obwohl man es nur einmal auslesen müsste. Wer also die Integrität der Intervalle testen möchte, der kann sollte die Intervallarrays aller Groupframes vergleichen. Sie müssen alle dieselben FLOAT Werte besitzen. Trifft dies nicht zu, dann wurde entweder ein Lesefehler gemacht oder das betreffende Intervallarray des Sprites wurde damals bereits mit anderen Werten belegt. Es empfiehlt sich daher, das Intervallarray nur beim ersten Groupframe auszulesen und nachfolgend bei allen anderen Groupframes des Sprites zu überspringen.

Ich muß allerdings zugeben, daß mir bisher kein Groupframe unter die Augen gekommen ist und ich so nicht sagen kann, ob das mit den Intervallen bzw. mit ihrer Bedeutung auch so stimmt. Die vorliegenden Daten habe ich aus diversen Quellen zusammengetragen und ich lehne jede Verantwortung für die Richtigkeit dieser Angaben ab!

Jedes FLOAT ist 4 Byte groß, multipliziert mit der Anzahl der Groupframes ergibt sich so die Größe der einzulesenden FLOAT Werte (z.B. 4 Byte * 5 Frames = 20 Byte). Daher ist der Anfang der nächsten Werte unbestimmt. Ich habe das so gemacht, daß OFFSET das Ende der Intervallwerte darstellt, also nicht verzweifeln:

Relativer Byte offset Typ Inhalt Beschreibung
OFFSET - OFFSET+0x04 DWORD Ursprung X-Achse Ein Integerwert mit der Koordinate des Ursprungs auf der X-Achse
OFFSET+0x05 - OFFSET+0x08 DWORD Ursprung Y-Achse Ein Integerwert mit der Koordinate des Ursprungs auf der Y-Achse
OFFSET+0x09 - OFFSET+0x0C DWORD Breite in Pixel Hier wird nochmals die Breite des Groupframes in Pixel ausgelesen. Er sollte logischerweise mit der Breitenangabe des Headers übereinstimmen.
OFFSET+0x0D - OFFSET+0x10 DWORD Höhe in Pixel Selbiges gilt für die Höhe des Groupframes.
OFFSET+0x11 - MAX BYTE Pixeldaten Jedes einzelne Byte entspricht einem Pixel. Der Wert ist der Index einer Farbe in der Farbpalette.


Der Bezeichner MAX steht hier ebenfalls für einen Grenzwert, der das Ende eines Groupframes definiert. Er errechnet sich aus dem Produkt von Framebreite und Framehöhe (Breite x Höhe). Bei einem Frame mit der Dimension 64×64 Pixel wäre MAX also 4096, d.h. es sind 4096 Bytes an Pixeldaten einzulesen.

Hinweise zu den Frames

Einleseschleife

Das Einlesen der Frames erfolgt in einer Schleifenform, d.h. wenn es 18 Frames gibt, dann muß diese Einleseschleife 18 mal durchlaufen werden. Das erste was man in der Schleife tun muß ist das Einlesen der Typsignatur. Daran entscheidet man, ob man einen Single- oder einen Groupframe einliest:

// C/C++/Java Schema
int i;
 
for(i = 0; i < frames; ++i)
{
    // Typsignatur einlesen
    ...
    // Fallunterscheidung
    if(type == SINGLE)
    {
        // Singleframe einlesen
        ...
    }
    else if(type == GROUP)
    {
        // Groupframe einlesen
        ...
    }
    else
    {
        // Fehlerhafte Typsignatur
        ...
    }
}

Vorzeichenlose Bytes in Java

In Java ist ein Byte (Datentyp byte) immer signed, d.h. vorzeichenbehaftet. Der Wertebereich geht von -128 bis 127. Beim byteweisen Einlesen vom Eingabestream müssen alle Bytes zwingend als unsigned (vorzeichenlos, 0 bis 255) interpretiert werden, denn sonst ist Schluß mit Lustig! Daher müssen die Bytes in einen höherwertigen Datentyp gecastet werden, vorzugsweise nach int. Das erledigt folgender Code in Java:

int unsignedByte = (int)signedByte & 0xFF;

Alle Bytes sind wichtig!

Es werden grundsätzlich alle Bytes einer Spritedatei eingelesen und alle Bytes haben auch einen Sinn! Es gibt keine leere oder unnütze Bytes! Wer also prüfen will, ob alles seine Richtigkeit hat, der muß fehlerfrei die gesamte Spritedatei in Bytes einlesen können. Fehlen Bytes am Ende oder bleiben welche übrig, dann hat man falsch eingelesen bzw. zuviele oder zuwenige Bytes beim Einleseprozeß verbraucht.

Die Verwendung aller Dokumente einschließlich der Abbildungen ausschließlich zu nichtkommerziellen Zwecken. Verbreitung des Dokuments auf Speichermedien, (insbesondere auf CD-ROMs als Beilage zu Zeitschriften und Magazinen oder sog. "Mission-Packs" etc.) ist untersagt.
 
goldsrc/specification/spr.txt · Zuletzt geändert: 2010/06/17 00:36 von Adrian_Broher