diff --git a/Sprites.md b/Sprites.md new file mode 100644 index 0000000..6a284b1 --- /dev/null +++ b/Sprites.md @@ -0,0 +1,104 @@ +# Sprites + +This is a guide for how sprite data is stored, parsed, and drawn to the visual canvas in GameStartr projects. +It's built on the [PixelDrawr](https://github.com/FullScreenShenanigans/PixelDrawr) and [PixelRendr](https://github.com/FullScreenShenanigans/PixelRendr) modules. + +## Storage + +A GameStartr instance's PixelRendr keeps sprite data stored as a library using a [StringFilr](https://github.com/FullScreenShenanigans/StringFilr). +Each sprite is stored as a series of numbers that represents the ordered pixels in a rectangle. + +```javascript +"00000001112" +``` + +A mapping of which numbers represent which color are stored in a global "palette". + +```javascript +[ + [0, 0, 0, 0], // transparent + [255, 255, 255, 255], // white + [0, 0, 0, 255], // black + // ... and so on +] +``` + +Using the above palette, the sprite represents seven transparent pixels, three white pixels, and a black pixel. + +Most images are much larger and more complex so a few compression techniques are applied. + +1. **[Indexed Color](https://en.wikipedia.org/wiki/Indexed_color)** + + It is necessary to have a consistent number of digits in images, as `010` could be `[0, 1, 0]`, `[0, 10]`, or etc. + Palettes with more than ten colors therefore have to prefix single-digit numbers with `0`. + For example, `[1, 14, 1]` would use `["01", "14", "01"]`: + + ```javascript + "011401011401011401011401011401011401011401" + ``` + + We can avoid this wasted character space by instructing a sprite to only use a subset of the pre-defined palette: + + ```javascript + "p[1,14]010010010010010010010" + ``` + + The `p[0,14]` tells the renderer that this sprite only uses colors 0 and 14. + The number 0 then refers to palette number 1 and the number 1 should refer to palette number 14. + +2. **[Run-length Encoding](https://en.wikipedia.org/wiki/Run-length_encoding)** + + Take the following wasteful sprite: + + ```javascript + "p[0]0000000000000000000000000000000000000000000000000" + ``` + + We know the 0 should be repeated 35 times, so the following notation is used to indicate "repeat ('x') 0 35 times (','))": + + ```javascript + "p[0]x035," + ``` + +3. **Filters** + + Many sprites are different versions of other sprites, often simply identical or miscolored (the only two commands supported so far). + A library may declare a filter: + + ```javascript + "Sample": [ "palette", { "00": "03" } ] + ``` + + ...along with its sprites: + + ```javascript + "foo": "p[0,7,14]000111222000111222000111222", + "bar": [ "filter", ["foo"], "Sample"] + ``` + + The `"bar"` sprite will be a filtered version of foo, using the `"Sample"` filter. + The `"Sample"` filter instructs the sprite to replace all instances of `"00"` with `"03"`, making `"bar"` equivalent to: + + ```javascript + "bar": "p[3,7,14]000111222000111222000111222" + ``` + + Another instruction you may use is `"same"`, which is equivalent to directly copying a sprite with no changes: + + ```javascript + "baz": [ "same", ["bar"] ] + ``` + +4. **"Multiple" sprites** + + Sprites are oftentimes of variable height. + Pipes in Mario, for example, have a top opening and a shaft of potentially infinite height. + Rather than use two objects to represent the two parts, sprites may be directed to have one sub-sprite for the top/bottom or left/right, with a single sub-sprite filling in the middle. + Pipes, then, would use a top and middle. + + ```javascript + [ "multiple", "vertical", { + "top": "{upper image data}", + "bottom": "{repeated image data}" + } ] + ```