pixelrendr
pixelrendr
npm i pixelrendr
pixelrendr

pixelrendr

Bare-bones, highly modular game engine for 2D 8-bit games.

by FullScreenShenanigans

0.8.15 (see all)License:MITTypeScript:Built-In
npm i pixelrendr
Readme

PixelRendr

Code Style: Prettier TypeScript: Strict NPM version Join the chat at https://gitter.im/FullScreenShenanigans/community

Extracts images from text blobs in real time with fast cached lookups.

Summary

At its core, PixelRendr is a library. It takes in sprites and string keys to store them under, and offers a fast lookup API. The internal folder structure storing images is at its core a tree, where strings are nodes similar to CSS classNames. See StringFilr for more information on storage, and ChangeLinr for the processing framework.

Sprites Format

To start, each PixelRendr keeps a global "palette" as an Array[]:

[
    [0, 0, 0, 0],
    [255, 255, 255, 255],
    [0, 0, 0, 255],
];

Ignoring compression, sprites are stored as a Number[]. For example:

"00000001112";

Using the above palette, this represents transparent pixels, three white pixels, and a black pixel. Most images are much larger and more complex than this, so a couple of compression techniques are applied:

1. Palette Mapping

It is necessary to have a consistent number of digits in images, as 010 could be [0, 1, 0], [0, 10], or etc. So, for palettes with more than ten colors, [1, 14, 1] would use ["01", "14", "01"]:

"011401011401011401011401011401011401011401";

We can avoid this wasted character space by instructing a sprite to only use a subset of the pre-defined palette:

"p[1,14]010010010010010010010";

The 'p[0,14]' tells the renderer that this sprite only uses colors 0 and 14, so the number 0 should refer to palette number 1, and the number 1 should refer to palette number 14.

2. Character Repetition

Take the following wasteful sprite:

"p[0]0000000000000000000000000000000000000000000000000";

We know the 0 should be printed 35 times, so the following notation is used to indicate "Print ('x') 0 35 times (','))":

"p[0]x035,";

3. Filters

Many sprites are different versions of other sprites, often either identical or differently colored (the only two commands supported so far). So, a library may declare the following filter:

"Sample": [ "palette", { "00": "03" } ]

...along with a couple of sprites:

"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", so "bar" will be equivalent to:

"bar": "p[3,7,14]000111222000111222000111222"

Another instruction you may use is "same", which is equivalent to directly copying a sprite with no changes:

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

[
    "multiple",
    "vertical",
    {
        top: "{upper image data}",
        bottom: "{repeated image data}",
    },
];

Usage

Drawing a simple black square:

import { memCopyU8, pixelRender } from "pixelrendr";

const pixelRender = new PixelRendr({
    paletteDefault: [[0, 0, 0, 255]],
    library: {
        BlackSquare: "x064,",
    },
});

const sizing = {
    spriteWidth: 8,
    spriteHeight: 8,
};

const sprite = PixelRender.decode("BlackSquare", sizing);
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");

canvas.width = sizing.spriteWidth;
canvas.height = sizing.spriteHeight;

const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
PixelRender.memCopyU8(sprite, imageData.data);
context.putImageData(imageData, 0, 0);

Drawing a white square using the black square's sprite as reference for a filter:

const PixelRender = new PixelRendr({
    paletteDefault: [
        [0, 0, 0, 255],
        [255, 255, 255, 255],
    ],
    library: {
        BlackSquare: "x064,",
        WhiteSquare: ["filter", ["BlackSquare"], "Invert"],
    },
    filters: {
        Invert: [
            "palette",
            {
                0: 1,
                1: 0,
            },
        ],
    },
});

const sizing = {
    spriteWidth: 8,
    spriteHeight: 8,
};

const sprite = PixelRender.decode("WhiteSquare", sizing);
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");

canvas.width = sizing.spriteWidth;
canvas.height = sizing.spriteHeight;

const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
PixelRender.memCopyU8(sprite, imageData.data);
context.putImageData(imageData, 0, 0);

Development

This repository is a portion of the EightBittr monorepo. See its docs/Development.md for details on how to get started. 💖

Running Tests

yarn run test

Tests are written in Mocha and Chai. Their files are written using alongside source files under src/ and named *.test.ts?. Whenever you add, remove, or rename a *.test.t* file under src/, watch will re-run yarn run test:setup to regenerate the list of static test files in test/index.html. You can open that file in a browser to debug through the tests, or run yarn test:run to run them in headless Chrome.

Downloads/wk

12

GitHub Stars

40

LAST COMMIT

1yr ago

MAINTAINERS

1

CONTRIBUTORS

11

OPEN ISSUES

61

OPEN PRs

0
VersionTagPublished
0.8.15
latest
3mos ago
No alternatives found
No tutorials found
Add a tutorial
No dependencies found

Rate & Review

100
No reviews found
Be the first to rate