# Tween group

> tween.all joins several tweens into one, which says so when the last of them ends and plays again as often as you like.

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

`tween.all(a, b, …)` joins several tweens into one. Each keeps its own destination, duration and delay; the group's `onEnd` runs once, when the last of them ends.

It is declared once and played as many times as you like: played again, every coin goes back to where the first drop found it. Its `pause`, `resume`, `stop`, `finish` and `reset` act on all of them; the coins inside it are played by the group, not one by one.

Click the preview first so it hears the keyboard. Space drops the coins (again, from the top), X stops them where they are, F finishes them all and R puts them back up.

## Code

`index.ts`:

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

const game = createGame('#app', {
    width: WIDTH,
    height: HEIGHT,
    background: '#0a0a18'
});

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

`components/coins.ts`:

```ts
import { createSprite } from 'nacatamalon';
import { COUNT, TOP, COINS } from '../settings';

export const Coins = () => {
    const coins = Array.from({
        length: COUNT
    }, (_, i) => createSprite({
        tint: '#f5c518',
        width: COINS.size,
        height: COINS.size,
        transform: {
            x: COINS.x + i * COINS.gap,
            y: TOP
        },
    }));

    return coins;
};
```

`components/drop_controls.ts`:

```ts
import { createText, useKeyboard, useTween, useUpdate, type TSprite } from 'nacatamalon';
import { easeOutBounce } from 'nacatamalon/math';
import { BOTTOM, DROP, WIDTH } from '../settings';

export const DropControls = ({ coins }: { coins: TSprite[] }) => {
    const tween = useTween();
    const keyboard = useKeyboard();

    const status = createText({
        text: 'SPACE TO DROP',
        style: {
            fontSize: 16
        },
        anchor: {
            x: 0.5,
            y: 0
        },
        transform: {
            x: WIDTH / 2,
            y: 270
        },
    });

    // One tween per coin, the further right the later it starts and the longer it falls, and one
    // for all eight. Nothing moves yet.
    const drop = tween.all(...coins.map((coin, i) => tween({
        target: coin,
        to: {
            transform: {
                y: BOTTOM
            }
        },
        duration: DROP.duration + i * DROP.extraDuration,
        delay: i * DROP.delay,
        ease: easeOutBounce,
    })));

    drop.onEnd(() => {
        status.text = 'ALL IN';
    });
    drop.onStop(() => {
        status.text = 'STOPPED';
    });

    useUpdate(() => {
        // Played again, every coin goes back up to where the first drop found it, and falls again.
        if (keyboard.justPressed(' ')) {
            drop.play();
            status.text = 'FALLING';
        }

        // All of them where they are: in the air, or in the tray.
        if (keyboard.justPressed('x')) {
            drop.stop();
        }

        // Every coin straight into the tray, and the group ends.
        if (keyboard.justPressed('f')) {
            drop.finish();
        }

        // Every coin back up, ready for the next drop.
        if (keyboard.justPressed('r')) {
            drop.reset();
            status.text = 'SPACE TO DROP';
        }
    });
};
```

`scenes/demo.ts`:

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

import { Coins } from '../components/coins';
import { DropControls } from '../components/drop_controls';
import { WIDTH, BOTTOM } from '../settings';

export const Scene = () => {
    createSprite({
        tint: '#3a3a55',
        width: 400,
        height: 4,
        transform: {
            x: WIDTH / 2,
            y: BOTTOM + 14
        }
    });

    const coins = useSpawn(Coins)();

    useSpawn(DropControls)({
        coins: coins.api
    });

    return createScene();
};
```

`settings.ts`:

```ts
export const WIDTH = 480;
export const HEIGHT = 320;
export const COUNT = 8;
export const TOP = 60;
export const BOTTOM = 220;
export const DROP = {
    duration: 0.6,
    extraDuration: 0.05,
    delay: 0.12
};
export const COINS = {
    x: 72,
    gap: 48,
    size: 24
};
```
