KistePix

Start › Aseprite-JSON erklärt

Das Aseprite-JSON-Format, erklärt

Wenn ein Pixel-Editor ein Sprite-Sheet exportiert, legt er neben das PNG eine JSON-Datei. Sie beschreibt, wo jeder Frame im Bild liegt und wie lange er zu sehen sein soll. Das Schema stammt von Aseprite und ist zum De-facto-Standard geworden. Hier steht, was jedes Feld bedeutet — und wie du die Datei in einer Engine benutzt.

Wozu die Datei überhaupt gut ist

Ohne sie müsstest du deiner Engine von Hand sagen: Die Frames sind 32 Pixel breit, es sind acht, sie liegen in einer Reihe, jeder ist 100 Millisekunden lang. Änderst du später die Größe oder fügst einen Frame hinzu, musst du das überall nachtragen. Die JSON-Datei trägt genau diese Angaben mit sich — das Sheet beschreibt sich selbst.

Ein vollständiges Beispiel

So sieht die Datei aus, die KistePix neben ein Sheet mit vier Frames zu je 32 × 32 Pixeln schreibt (gekürzt auf zwei Frames):

{
  "frames": [
    {
      "filename": "held 0.png",
      "frame":            { "x": 0,  "y": 0, "w": 32, "h": 32 },
      "rotated": false,
      "trimmed": false,
      "spriteSourceSize": { "x": 0,  "y": 0, "w": 32, "h": 32 },
      "sourceSize":       { "w": 32, "h": 32 },
      "duration": 100
    },
    {
      "filename": "held 1.png",
      "frame":            { "x": 32, "y": 0, "w": 32, "h": 32 },
      "rotated": false,
      "trimmed": false,
      "spriteSourceSize": { "x": 0,  "y": 0, "w": 32, "h": 32 },
      "sourceSize":       { "w": 32, "h": 32 },
      "duration": 150
    }
  ],
  "meta": {
    "app": "kiste-lang.org/kistepix",
    "version": "1.0",
    "image": "held.png",
    "format": "RGBA8888",
    "size": { "w": 128, "h": 32 },
    "scale": "1",
    "loop": true
  }
}

Die Felder eines Frames

FeldBedeutung
filenameName dieses Frames. Historisch der Dateiname, den der Frame als Einzelbild hätte. Viele Engines nutzen ihn als Schlüssel, um einen Frame anzusprechen.
frameDas Rechteck im Sheet: x/y ist die linke obere Ecke, w/h die Größe. Das ist das Feld, das die Engine wirklich braucht — hier wird ausgeschnitten.
rotatedOb der Frame um 90° gedreht im Sheet liegt. Manche Packer drehen Bilder, um Platz zu sparen. KistePix dreht nie, hier steht immer false.
trimmedOb leere Ränder weggeschnitten wurden. Auch das spart Platz, macht aber die Rechnung komplizierter. KistePix schneidet nicht, also false.
spriteSourceSizeWo der (eventuell beschnittene) Ausschnitt innerhalb des ursprünglichen Bildes saß. Ohne Beschneiden ist es das volle Rechteck ab 0/0 — genau das schreibt KistePix.
sourceSizeDie Größe des ursprünglichen, unbeschnittenen Bildes. Wichtig, damit ein beschnittener Frame trotzdem an der richtigen Stelle landet.
durationAnzeigedauer dieses Frames in Millisekunden. Jeder Frame hat seine eigene — darum kann eine Animation ungleichmäßig getaktet sein.

Der wichtigste Punkt: frame beschreibt die Lage im Sheet, sourceSize und spriteSourceSize beschreiben die Lage im Originalbild. Wer beide verwechselt, bekommt versetzte Sprites. Solange trimmed auf false steht, sind sie deckungsgleich und du kannst dich auf frame beschränken.

Der meta-Block

Was KistePix nicht schreibt, damit du nicht darauf baust: Aseprites meta-Block kann zusätzlich frameTags (benannte Abschnitte wie „walk" oder „idle"), layers und slices enthalten. KistePix exportiert eine Animation als eine Reihe von Frames, ohne benannte Abschnitte. Werkzeuge, die zwingend frameTags erwarten — etwa Phasers createFromAseprite — finden also nichts vor. Der Weg über den normalen Atlas-Import funktioniert dafür problemlos.

Array oder Hash?

Aseprite kann die Frames auf zwei Arten ablegen, und beim Import wird das gern verwechselt. In der Array-Form ist frames eine Liste, und der Name steckt in jedem Eintrag als filename:

"frames": [ { "filename": "held 0.png", "frame": { … } }, … ]

In der Hash-Form ist frames ein Objekt, dessen Schlüssel die Namen sind:

"frames": { "held 0.png": { "frame": { … } }, … }

Inhaltlich ist es dasselbe. Nur muss der Importeur wissen, welche Form ihn erwartet: Phaser etwa unterscheidet „JSON Array" und „JSON Hash" beim Laden. KistePix schreibt immer die Array-Form — die Reihenfolge der Frames ist damit schon durch die Liste festgelegt.

In eine Engine laden

Phaser

Die Array-Form ist genau das, was Phaser als Textur-Atlas erwartet:

this.load.atlas('held', 'held.png', 'held.json');

// Frames der Reihe nach abspielen:
this.anims.create({
  key: 'lauf',
  frames: [
    { key: 'held', frame: 'held 0.png' },
    { key: 'held', frame: 'held 1.png' }
  ],
  frameRate: 10,
  repeat: -1
});

Godot

Godot bringt keinen eingebauten Leser für dieses Format mit; dafür gibt es Erweiterungen aus der Community. Du brauchst sie aber gar nicht unbedingt: Weil alle Frames gleich groß sind und in einer Reihe liegen, kannst du das PNG in einem AnimatedSprite2D über SpriteFrames › Add Frames from Sheet zerschneiden lassen — du gibst dort nur die Anzahl der Spalten an. Die Dauern aus der JSON-Datei setzt du im Animationsplayer. Und stelle beim Import des PNG den Filter auf Nearest, sonst wird die Pixel-Art weichgezeichnet.

Unity

Auch Unity liest das Format nicht von sich aus. Weil das Sheet ein gleichmäßiges Raster ist, reicht der Bordweg: Textur-Typ auf Sprite (2D and UI), Sprite Mode auf Multiple, dann im Sprite Editor Slice › Grid By Cell Size mit deiner Frame-Größe. Filter Mode auf Point (no filter) und Compression auf None — sonst verschmiert die Kompression einzelne Pixel.

GameMaker

GameMaker ist der Sonderfall in dieser Runde: Es hat keinen Importeur für Aseprite-JSON, und du kannst die Datei auch nicht nebenbei mitgeben. Das ist aber kein Problem, denn GameMaker braucht sie gar nicht — es liest die einzige Angabe, die ihm fehlt, aus dem Dateinamen.

Endet der Name auf _stripN, wobei N die Anzahl der Frames ist, zerschneidet GameMaker das Bild beim Import selbst. Aus ball.png mit fünf Frames wird also ball_strip5.png. Diese Datei ziehst du einfach in die IDE — sie wird in fünf Frames zerlegt, und den Zusatz entfernt GameMaker anschließend wieder aus dem Sprite-Namen. Wer lieber von Hand geht: Sprite-Editor › Import strip image, dort trägst du Frame-Anzahl bzw. Frame-Breite und -Höhe ein. Die Werte stehen in der JSON unter frames[0].frame.w und .h; Rand und Abstand bleiben auf 0, weil die Zellen lückenlos aneinanderliegen.

KistePix nimmt dir das ab: Wähle beim Export das Format Sprite-Sheet (GameMaker), tippe ball — und du bekommst ball_strip5.png. Die Frame-Anzahl rechnet das Programm selbst aus.

Ein Unterschied bleibt gegenüber den anderen Engines: GameMaker kennt nur eine Geschwindigkeit für das ganze Sprite, keine Dauer pro Frame. Die duration-Werte aus der JSON lassen sich dort also nicht einzeln abbilden — du stellst stattdessen eine passende Sprite-Speed ein.

Selbst auslesen

Wenn du die Datei direkt verarbeitest, ist das eine Handvoll Zeilen — hier in JavaScript:

const sheet  = JSON.parse(text);
const frames = sheet.frames.map(f => ({
  x: f.frame.x, y: f.frame.y,
  w: f.frame.w, h: f.frame.h,
  ms: f.duration
}));
const gesamt = frames.reduce((s, f) => s + f.ms, 0);  // Länge der Schleife

Sheets, die sich selbst beschreiben

KistePix schreibt PNG und JSON in einem Rutsch — mit der Anzeigedauer jedes Frames, so wie du sie auf der Zeitleiste gesetzt hast.

KistePix holen Anleitung: Sprite-Sheet erstellen