# A level made in code

> A level with no file: tiles and cells generated in code, walked on with useTileBody2d, with ledges, a ladder and bricks that break.

Example `tilemap-from-code`, running at https://nacatamalon.com/en/examples/tilemap-from-code. The whole catalogue: https://nacatamalon.com/llms/examples.md.

A level with no file: `createTilemap` takes `doc`, what a `.tilemap` holds, and `atlas`, its sheet as an object. Tiles and cells are both generated here, and it is an ordinary map: `useTileBody2d` walks on it, ledges and ladders are fields of a tile, `setTile` breaks a brick, and the camera stays inside it.

The robot is half a drawing: `mirrorPixels` copies its left half onto the right, and `outlinePixels` draws the border around it, in the room left for it.

Click the preview first so it hears the keyboard. ← → walk, Z jumps, ↑ ↓ climb, X breaks the brick you stand on.

## Code

`index.ts`:

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

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

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

`art/tiles_and_robot.ts`:

```ts
import { mirrorPixels, outlinePixels, pixelsFromRows, sheetFromRows } from 'nacatamalon/pixels';

export const createArt = () => {
    const palette = {
        g: '#6abe30',
        G: '#37946e',
        d: '#8f563b',
        D: '#663931',
        s: '#9badb7',
        S: '#696a6a',
        y: '#fbf236',
        Y: '#df7126',
        b: '#5fcde4',
        B: '#306082',
        r: '#ac3232',
        R: '#d95763',
    };

    const tiles = sheetFromRows({
        ground: ['gggggggg', 'gGggGggG', 'GdGddGdG', 'dddddddd', 'dDdddddD', 'dddDdddd', 'dddddddd', 'DdddddDd'],
        earth:  ['dddddddd', 'dDdddddD', 'dddDdddd', 'dddddddd', 'ddddDddd', 'dDdddddd', 'ddddddDd', 'dddddddd'],
        ledge:  ['ssssssss', 'SSSSSSSS', '.S....S.', '.S....S.', '........', '........', '........', '........'],
        ladder: ['Y......Y', 'YyyyyyyY', 'Y......Y', 'Y......Y', 'Y......Y', 'YyyyyyyY', 'Y......Y', 'Y......Y'],
        brick:  ['rrrRrrrR', 'rrrRrrrR', 'RRRRRRRR', 'rRrrrRrr', 'rRrrrRrr', 'RRRRRRRR', 'rrrRrrrR', 'rrrRrrrR'],
        waterA: ['bbbbbbbb', 'bBbbbBbb', 'BbbBbbBb', 'bbbbbbbb', 'bbbbbbbb', 'bBbbbBbb', 'bbbbbbbb', 'bbbbbbbb'],
        waterB: ['bbbbbbbb', 'bbBbbbBb', 'bBbbBbbB', 'bbbbbbbb', 'bbbbbbbb', 'bbbBbbbB', 'bbbbbbbb', 'bbbbbbbb'],
    }, palette);

    // A little robot: only its left half is drawn, the rest is copied, and the outline goes around it all.
    // The empty rows and columns around it are the room the outline needs.
    const robot = pixelsFromRows(['............', '.....s......', '..ssss......', '..sbbS......', '..sbbS......', '..ssss......', '...SSS......', '..ssss......', '..s.ss......', '....s.......', '...ss.......', '............',], palette);

    mirrorPixels(robot, 'x');
    outlinePixels(robot, '#222034');

    return {
        tiles,
        robot
    };
};
```

`components/hero.ts`:

```ts
import { createSprite, setTile, tileAt, useKeyboard, useTileBody2d, useUpdate } from 'nacatamalon';
import { createPixelTexture } from 'nacatamalon/pixels';

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

export const Hero = ({ robot, map }: { robot: TPixels; map: TTilemap }) => {
    const sprite = createSprite({
        texture: createPixelTexture(robot),
        transform: {
            x: 24,
            y: 40
        }
    });

    const body = useTileBody2d({
        width: 8,
        height: 10
    });

    const keys = useKeyboard();

    useUpdate(() => {
        const walk = (keys.isDown('ArrowRight') ? 1 : 0) - (keys.isDown('ArrowLeft') ? 1 : 0);

        body.velocity.x = walk * 80;

        if (walk !== 0) {
            sprite.flipX = walk < 0;
        }

        if (keys.justPressed('z') && body.isOnGround) {
            body.velocity.y = -270;
        }

        const up = keys.isDown('ArrowUp');
        const down = keys.isDown('ArrowDown');

        if (body.isOnLadder && !body.climbing && ((up && !body.isOnOneWay) || (down && body.isOnOneWay))) {
            body.climbing = true;
        }

        if (body.climbing) {
            body.velocity.y = ((down ? 1 : 0) - (up ? 1 : 0)) * 50;

            if (keys.justPressed('z')) {
                body.climbing = false;
            }
        }

        if (keys.justPressed('x')) {
            const column = Math.floor(sprite.transform.x / map.cell);
            const row = Math.floor((sprite.transform.y + 6) / map.cell);

            if (tileAt(map, 0, column, row) === 5) {
                setTile(map, 0, column, row, 0);
            }
        }

        if (sprite.transform.y > map.height * map.cell + 20) {
            body.setPosition(24, 40);
            body.velocity.y = 0;
        }
    });

    return {
        sprite,
        body
    };
};
```

`components/hud.ts`:

```ts
import { createText, useScreenSpace } from 'nacatamalon';

export const Hud = () => {
    useScreenSpace();
    createText({
        text: '← → Z JUMP ↑ ↓ CLIMB X BREAK',
        transform: {
            x: 6,
            y: 6
        },
        tint: '#222034'
    });
};
```

`components/world.ts`:

```ts
import { createSpriteAtlas, createTilemap } from 'nacatamalon';
import { createPixelTexture } from 'nacatamalon/pixels';

import type { createArt } from '../art/tiles_and_robot';
import { ROWS, LEGEND } from '../data/level';

export const World = ({ tiles }: Pick<ReturnType<typeof createArt>, 'tiles'>) => {
    const { frames } = tiles;

    const map = createTilemap({
        doc: {
            cell: 8,
            width: ROWS[0].length,
            height: ROWS.length,
            tiles: {
                1: {
                    frame: frames.ground,
                    solid: true
                },
                2: {
                    frame: frames.earth,
                    solid: true
                },
                3: {
                    frame: frames.ledge,
                    oneWay: true
                },
                4: {
                    frame: frames.ladder,
                    ladder: true
                },
                5: {
                    frame: frames.brick,
                    solid: true,
                    becomes: 0
                },
                6: {
                    anim: 'water'
                },
                // The top rung is a ledge too, so it can be stood on.
                7: {
                    frame: frames.ladder,
                    ladder: true,
                    oneWay: true
                },
            },
            layers: [
                {
                    name: 'ground',
                    order: 'under',
                    data: ROWS.flatMap((row) => [...row].map((character) => LEGEND[character] ?? 0)),
                }
            ],
        },
        atlas: createSpriteAtlas({
            texture: createPixelTexture(tiles.pixels),
            columns: tiles.columns
        }),
        sequences: {
            water: {
                frames: [frames.waterA, frames.waterB],
                fps: 3
            }
        },
    });

    return map;
};
```

`data/level.ts`:

```ts
// Which tile each character of the level is.
export const LEGEND: Record<string, number> = {
    '#': 1,
    '=': 2,
    '-': 3,
    'H': 4,
    'B': 5,
    '~': 6,
    'T': 7
};
export const ROWS = ['................................................', '................................................', '................................................', '........................BBB.....................', '................................................', '..........BB........T---.............---........', '....................H...........................', '.....---............H.............BB............', '....................H.......---.................', '....................H...........................', '#####.........#########.......########....######', '=====.........=========~~~~~~~========~~~~======', '=====~~~~~~~~~=========~~~~~~~========~~~~======',];
```

`scenes/demo.ts`:

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

import { createArt } from '../art/tiles_and_robot';
import { World } from '../components/world';
import { Hero } from '../components/hero';
import { Hud } from '../components/hud';

export const Level = () => {
    useCamera2d({
        zoom: 2
    });

    const art = createArt();

    const map = useSpawn(World)({
        tiles: art.tiles
    }).api;

    const hero = useSpawn(Hero)({
        robot: art.robot,
        map
    });

    useCameraFollow(hero, {
        bounds: map
    });
    useSpawn(Hud)();

    return createScene();
};
```

`settings.ts`:

```ts
export const WIDTH = 320;
export const HEIGHT = 208;
```
