# Touch pad

> An on-screen pad for phones, drawn by the engine and read through actions: a thumb sliding between buttons, two thumbs at once, and a shot charged by holding.

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

Each button is an ordinary component that says which action it presses: `useTouchAction('left')` in its body holds `'left'` while a finger is on it. One `Button` component made four times with four props is the whole pad, with no names to keep in step. The game reads `actions.isDown('left')` whatever pressed it; a thumb sliding from ◀ to ▶ moves from one action to the other, and two thumbs work at once.

Hold Ⓑ to charge a shot: `actions.heldFor('fire')` says for how long, still set on the frame it is let go. The buttons light with `usePointer().isPressing(button)`. On a computer the keys work too (← → Z X), and the mouse presses the buttons.

## Code

`index.ts`:

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

const game = createGame('#app', {
    width: WIDTH,
    height: HEIGHT,
    step: 60,
    background: '#0d1020',
    actions: ACTIONS
});

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

`components/button.ts`:

```ts
import { createSprite, createText, getColor, useScreenSpace, useTouchAction, useSelf, usePointer, useUpdate } from 'nacatamalon';
import { HEIGHT } from '../settings';

type TButtonProps = { x: number; label: string; colour: string; action: string };

/**
 * A button: a square with its label, lighter while a finger is on it, holding its action.
 */
export const Button = ({ x, label, colour, action }: TButtonProps) => {
    useScreenSpace();
    useTouchAction(action);

    const self = useSelf();
    const idle = getColor(colour);
    const pressed = getColor('#c8d2f0');
    const y = HEIGHT - 26;

    const base = createSprite({
        width: 34,
        height: 34,
        tint: idle,
        transform: {
            x,
            y
        }
    });

    createText({
        text: label,
        anchor: {
            x: 0.5,
            y: 0.5
        },
        transform: {
            x: x + 1,
            y: y + 1
        }
    });

    const pointer = usePointer();

    // Lit while a finger is on it: the same question the action asks.
    useUpdate(() => {
        base.tint = pointer.isPressing(self) ? pressed : idle;
    });
};
```

`components/hero.ts`:

```ts
import { createSprite, useActions, useSpawn, useUpdate } from 'nacatamalon';
import { WIDTH, FLOOR, HERO } from '../settings';
import { Shot } from './shot';

export const Hero = () => {
    const actions = useActions();

    const spawnShot = useSpawn(Shot, {
        parent: 'scene'
    });

    const hero = createSprite({
        width: 12,
        height: 16,
        tint: '#ffcc33',
        anchor: {
            x: 0.5,
            y: 1
        },
        transform: {
            x: 80,
            y: FLOOR
        }
    });

    const charge = createSprite({
        width: 0,
        height: 3,
        tint: '#7df9ff',
        anchor: {
            x: 0,
            y: 0.5
        },
        transform: {
            x: 74,
            y: FLOOR - 22
        }
    });

    let vy = 0;
    let height = 0;

    useUpdate((delta) => {
        const move = (actions.isDown('right') ? 1 : 0) - (actions.isDown('left') ? 1 : 0);

        hero.transform.x = Math.min(Math.max(hero.transform.x + move * HERO.speed * delta, 10), WIDTH - 10);

        if (actions.justPressed('jump') && height === 0) {
            vy = HERO.jumpSpeed;
        }

        if (vy !== 0 || height > 0) {
            height += vy * delta;
            vy -= HERO.gravity * delta;

            if (height <= 0) {
                height = 0;
                vy = 0;
            }
        }

        hero.transform.y = FLOOR - height;

        // The charge bar grows while Ⓑ is held; the shot leaves on release, as big as the charge.
        const held = actions.heldFor('fire');

        charge.width = actions.isDown('fire') ? Math.min(held, 1) * 24 : 0;
        charge.transform.x = hero.transform.x - 12;
        charge.transform.y = hero.transform.y - 22;

        if (actions.justReleased('fire')) {
            spawnShot({
                x: hero.transform.x + 8,
                y: hero.transform.y - 8,
                charge: held
            });
        }
    });
};
```

`components/hud.ts`:

```ts
import { createText, usePointer, useScreenSpace, useUpdate } from 'nacatamalon';
import { WIDTH } from '../settings';

export const Hud = () => {
    useScreenSpace();

    const pointer = usePointer();

    createText({
        text: '♥♥♥',
        transform: {
            x: 8,
            y: 8
        },
        tint: '#ff5a6e'
    });
    createText({
        text: '◀ ▶ MOVE  Ⓐ JUMP  HOLD Ⓑ: CHARGE',
        transform: {
            x: 8,
            y: 22
        },
        tint: '#9aa4c0'
    });

    const fingers = createText({
        text: '',
        transform: {
            x: WIDTH - 8,
            y: 8
        },
        anchor: {
            x: 1,
            y: 0
        },
        tint: '#6b7390'
    });

    useUpdate(() => {
        fingers.text = `FINGERS: ${pointer.touches().length}`;
    });
};
```

`components/shot.ts`:

```ts
import { createSprite, destroy, getColor, useTransform, useSelf, useUpdate } from 'nacatamalon';
import { WIDTH, SHOT_SPEED } from '../settings';

export const Shot = ({ x, y, charge }: { x: number; y: number; charge: number }) => {
    const size = 4 + Math.min(charge, 1) * 12;

    const place = useTransform({
        x,
        y
    });

    createSprite({
        width: size,
        height: size,
        tint: getColor(charge >= 1 ? '#7df9ff' : '#ffe680'),
        blend: 'additive',
        transform: {
            x: 0,
            y: 0
        }
    });

    const self = useSelf();

    useUpdate((delta) => {
        place.x += SHOT_SPEED * delta;

        if (place.x > WIDTH + 20) {
            destroy(self);
        }
    });
};
```

`scenes/demo.ts`:

```ts
import { createScene, createSprite, useSpawn } from 'nacatamalon';
import { WIDTH, HEIGHT, FLOOR } from '../settings';
import { Hero } from '../components/hero';
import { Hud } from '../components/hud';
import { Button } from '../components/button';

export const Level = () => {
    createSprite({
        width: WIDTH,
        height: HEIGHT - FLOOR,
        tint: '#262d48',
        anchor: {
            x: 0,
            y: 0
        },
        transform: {
            x: 0,
            y: FLOOR
        }
    });
    useSpawn(Hero)();
    useSpawn(Hud)();

    const button = useSpawn(Button);

    button({
        x: 30,
        label: '◀',
        colour: '#3a5a8c',
        action: 'left'
    });
    button({
        x: 72,
        label: '▶',
        colour: '#3a5a8c',
        action: 'right'
    });
    button({
        x: WIDTH - 72,
        label: 'Ⓐ',
        colour: '#8c3a4a',
        action: 'jump'
    });
    button({
        x: WIDTH - 30,
        label: 'Ⓑ',
        colour: '#8c6a3a',
        action: 'fire'
    });

    return createScene();
};
```

`settings.ts`:

```ts
import type { TActionDef } from 'nacatamalon';

export const WIDTH = 320;
export const HEIGHT = 180;
export const FLOOR = 112;
export const HERO = {
    speed: 90,
    jumpSpeed: 280,
    gravity: 900
};
export const SHOT_SPEED = 260;
export const ACTIONS: TActionDef[] = [
    // The touch is the buttons' own (useTouchAction), so the map binds only keys and pad.
    {
        name: 'left',
        bindings: [
            {
                type: 'key',
                key: 'ArrowLeft'
            },
            {
                type: 'button',
                button: 'left'
            }
        ]
    },
    {
        name: 'right',
        bindings: [
            {
                type: 'key',
                key: 'ArrowRight'
            },
            {
                type: 'button',
                button: 'right'
            }
        ]
    },
    {
        name: 'jump',
        bindings: [
            {
                type: 'key',
                key: 'z'
            },
            {
                type: 'button',
                button: 'a'
            }
        ]
    },
    {
        name: 'fire',
        bindings: [
            {
                type: 'key',
                key: 'x'
            },
            {
                type: 'button',
                button: 'b'
            }
        ]
    },
];
```
