# Pixel art in rows

> Pixel art written as rows of characters and a palette: a frog in four frames by name, hearts, and a tile stamped along the ground.

Example `pixel-rows`, running at https://nacatamalon.com/en/examples/pixel-rows. The whole catalogue: https://nacatamalon.com/llms/examples.md.

Pixel art written the way it looks: `pixelsFromRows` takes rows of characters, one a pixel, and a palette saying what colour each one is, with `'.'` for nothing. A grid reads in a diff and is edited by hand, where working out the corners of a `fillRect` for every patch of colour is easy to get wrong.

The frog's frames are `sheetFromRows`, by name, laid side by side for `createSpriteAtlas`; `hero.frames.stepA` is a frame's number for its animation. The hearts and the ground's tile are one picture each, and the tile is stamped along the ground with `blitPixels`.

## Code

`index.ts`:

```ts
import { createGame } from 'nacatamalon';
import { WIDTH, HEIGHT } from './settings';
import { Meadow } from './scenes/demo';

const game = createGame('#app', {
    width: WIDTH,
    height: HEIGHT,
    background: '#9bd4ff',
    scaling: 'contain'
});

// Declare Scenes
game({
    Meadow
});
```

`art/meadow.ts`:

```ts
import { blitPixels, createPixels, pixelsFromRows, sheetFromRows } from 'nacatamalon/pixels';

export const createArt = () => {
    const palette = {
        g: '#5bc236',
        G: '#3a8a26',
        w: '#ffffff',
        k: '#14141f',
        y: '#ffb627',
        r: '#f80058',
        R: '#fc98b8',
        t: '#6abe30',
        T: '#37946e',
        d: '#8f563b',
        b: '#663931',
    };

    // A frog in four frames, by name: the numbers come back in hero.frames.
    const body = ['..........', '..gg..gg..', '.gwkggwkg.', '.gggggggg.', 'gggggggggg', 'gGggggggGg', '.gGGGGGGg.', '..gggggg..',];

    const hero = sheetFromRows({
        stand: [...body, '..y....y..', '.yy....yy.'],
        stepA: [...body, '...y...y..', '..yy...yy.'],
        stepB: [...body, '..y...y...', '.yy...yy..'],
        jump:  ['..gg..gg..', '.gwkggwkg.', '.gggggggg.', 'gggggggggg', 'gGggggggGg', '.gGGGGGGg.', '..gggggg..', '.y......y.', 'yy......yy', '..........'],
    }, palette);

    const heart = pixelsFromRows(['.rr.rr.', 'rRrrrrr', 'rrrrrrr', '.rrrrr.', '..rrr..', '...r...',], palette);

    // One tile, stamped along the ground.
    const tile = pixelsFromRows(['tttttttt', 'tTtttTtt', 'TdTddTdT', 'dddddddd', 'dbdddddb', 'dddbdddd', 'dddddddd', 'bdddddbd',], palette);

    const ground = createPixels(160, 16);

    for (let x = 0; x < 160; x += 8) {
        blitPixels(ground, tile, x, 0);
        blitPixels(ground, tile, x, 8, {
            flipX: true
        });
    }

    return {
        hero,
        heart,
        ground
    };
};
```

`components/frog.ts`:

```ts
import { createSprite, createSpriteAtlas, useSpriteAnimation, useUpdate } from 'nacatamalon';
import { createPixelTexture } from 'nacatamalon/pixels';

import type { createArt } from '../art/meadow';

export const Frog = ({ hero }: Pick<ReturnType<typeof createArt>, 'hero'>) => {
    const frog = createSprite({
        atlas: createSpriteAtlas({
            texture: createPixelTexture(hero.pixels),
            columns: hero.columns
        }),
        anchor: {
            x: 0.5,
            y: 1
        },
        transform: {
            x: 40,
            y: 74
        },
    });

    const { frames } = hero;

    const anim = useSpriteAnimation(frog, {
        play: 'walk',
        clips: {
            walk: {
                frames: [frames.stepA, frames.stand, frames.stepB, frames.stand],
                fps: 8
            },
            jump: {
                frames: [frames.jump],
                fps: 1
            },
        },
    });

    let x = 40;
    let heading = 1;
    let jumping = false;

    useUpdate((delta, time) => {
        x += heading * 18 * delta;

        if (x > 150 || x < 10) {
            heading = -heading;
            frog.flipX = heading < 0;
        }

        // A hop every three seconds.
        const hop = time % 3;
        const up = hop < 0.5 ? Math.sin((hop / 0.5) * Math.PI) * 12 : 0;

        if ((up > 0) !== jumping) {
            jumping = up > 0;
            anim.play(jumping ? 'jump' : 'walk');
        }

        frog.transform.x = Math.round(x);
        frog.transform.y = 74 - Math.round(up);
    });
};
```

`components/terrain.ts`:

```ts
import { createSprite } from 'nacatamalon';
import { createPixelTexture } from 'nacatamalon/pixels';

import type { TPixels } from 'nacatamalon/pixels';

export const Terrain = ({ ground, heart }: { ground: TPixels; heart: TPixels }) => {
    createSprite({
        texture: createPixelTexture(ground),
        anchor: {
            x: 0,
            y: 0
        },
        transform: {
            x: 0,
            y: 74
        }
    });

    const heartTexture = createPixelTexture(heart);

    for (let i = 0; i < 3; i++) {
        createSprite({
            texture: heartTexture,
            anchor: {
                x: 0,
                y: 0
            },
            transform: {
                x: 6 + i * 9,
                y: 6
            }
        });
    }
};
```

`scenes/demo.ts`:

```ts
import { createScene, useSpawn } from 'nacatamalon';

import { createArt } from '../art/meadow';
import { Terrain } from '../components/terrain';
import { Frog } from '../components/frog';

export const Meadow = () => {
    const art = createArt();

    useSpawn(Terrain)(art);
    useSpawn(Frog)(art);

    return createScene();
};
```

`settings.ts`:

```ts
export const WIDTH = 160;
export const HEIGHT = 90;
```
