# Repeat and yoyo

> A sprite’s run and a tween go round a number of times, or there and back, in the same words: repeat, yoyo and onRepeat.

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

The same three words for a sprite’s run, a model’s movement and a tween. `repeat` is how many **extra** times it goes after the first, so `2` is three times and a negative number is for ever. `yoyo: true` comes back the other way each time, showing each end once. `onRepeat((clip, times) => …)` says each time it starts again, and `onEnd` still says the last.

On the left, a walk of six pictures played with `yoyo`: a pace forward and one back, and the way back is never drawn. In the middle, an attack with `repeat: 2`: three blows and it stops, counted by `onRepeat`. Space starts it again. On the right, a lamp that a tween dims and lights again for ever, counting its blinks.

With no `repeat`, a run’s `loop` still says for ever or once. A run in an `.atlas` file takes `repeat` and `yoyo` too. Marks (`events`) are passed on the way there, not on the way back.

## Code

`index.ts`:

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

import { Game } from './scenes/demo';

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

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

`components/fighter.ts`:

```ts
import { createSprite, createSpriteAtlas, createText, useKeyboard, useLoadTexture, useSpriteAnimation, useUpdate } from 'nacatamalon';
import * as Settings from '../settings';

export const Fighter = () => {
    const keys = useKeyboard();

    const attackSheet = createSpriteAtlas({
        texture: useLoadTexture({
            src: Settings.ATTACK,
            key: 'attack'
        }),
        columns: 6
    });

    // Three blows and it stops: the first, and two repeats.
    const fighter = createSprite({
        atlas: attackSheet,
        width: 96,
        height: 96,
        transform: {
            x: 240,
            y: 120,
            scaleX: -1
        }
    });

    const attack = useSpriteAnimation(fighter, {
        clips: {
            attack: {
                frames: [0, 1, 2, 3, 4, 5],
                fps: 14,
                repeat: 2
            }
        },
        play: 'attack'
    });

    createText({
        text: 'repeat: 2',
        tint: Settings.DIM,
        anchor: {
            x: 0.5,
            y: 0
        },
        transform: {
            x: 240,
            y: 180
        }
    });

    const blows = createText({
        text: 'BLOW 1 OF 3',
        anchor: {
            x: 0.5,
            y: 0
        },
        transform: {
            x: 240,
            y: 196
        }
    });

    attack.onRepeat((_clip: string, times: number) => {
        blows.text = `BLOW ${times + 1} OF 3`;
    });
    attack.onEnd(() => {
        blows.text = 'DONE · SPACE';
    });

    useUpdate(() => {
        if (keys.justPressed(' ')) {
            attack.play('attack');
            blows.text = 'BLOW 1 OF 3';
        }
    });
};
```

`components/lamp.ts`:

```ts
import { createSprite, createText, useTween } from 'nacatamalon';
import * as Settings from '../settings';

export const Lamp = () => {
    const tween = useTween();

    // A tween in the same words: dimmed and lit again for ever, a blink every two times round.
    const lamp = createSprite({
        width: 40,
        height: 40,
        tint: '#ffd166',
        transform: {
            x: 400,
            y: 120
        }
    });

    const blinks = createText({
        text: 'BLINKS 0',
        anchor: {
            x: 0.5,
            y: 0
        },
        transform: {
            x: 400,
            y: 196
        }
    });

    createText({
        text: 'tween yoyo',
        tint: Settings.DIM,
        anchor: {
            x: 0.5,
            y: 0
        },
        transform: {
            x: 400,
            y: 180
        }
    });

    const blink = tween({
        target: lamp,
        to: {
            tint: {
                a: 0.15
            }
        },
        duration: 0.4,
        yoyo: true,
        loop: true
    });

    blink.onRepeat((times: number) => {
        blinks.text = `BLINKS ${Math.floor(times / 2)}`;
    });
    blink.play();
};
```

`components/pacer.ts`:

```ts
import { createSprite, createSpriteAtlas, createText, useLoadTexture, useSpriteAnimation, useUpdate } from 'nacatamalon';
import * as Settings from '../settings';

export const Pacer = () => {
    const walkSheet = createSpriteAtlas({
        texture: useLoadTexture({
            src: Settings.WALK,
            key: 'walk'
        }),
        columns: 6
    });

    // There and back: 0 1 2 3 4 5 4 3 2 1 0 1…, each end shown once.
    const pacer = createSprite({
        atlas: walkSheet,
        width: 96,
        height: 96,
        transform: {
            x: 80,
            y: 120
        }
    });

    const pace = useSpriteAnimation(pacer, {
        clips: {
            pace: {
                frames: [0, 1, 2, 3, 4, 5],
                fps: 10,
                yoyo: true
            }
        },
        play: 'pace'
    });

    createText({
        text: 'yoyo',
        tint: Settings.DIM,
        anchor: {
            x: 0.5,
            y: 0
        },
        transform: {
            x: 80,
            y: 180
        }
    });

    const picture = createText({
        text: '',
        anchor: {
            x: 0.5,
            y: 0
        },
        transform: {
            x: 80,
            y: 196
        }
    });

    useUpdate(() => {
        picture.text = `PICTURE ${pace.frame}`;
    });
};
```

`scenes/demo.ts`:

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

import { Pacer } from '../components/pacer';
import { Fighter } from '../components/fighter';
import { Lamp } from '../components/lamp';

export const Game = () => {
    useSpawn(Pacer)();
    useSpawn(Fighter)();
    useSpawn(Lamp)();

    return createScene();
};
```

`settings.ts`:

```ts
export const WIDTH = 480;
export const HEIGHT = 260;
import { getColor } from 'nacatamalon';

export const WALK = '/example-assets/assets/charracters/business_enemies/1/Walk.png';
export const ATTACK = '/example-assets/assets/charracters/business_enemies/1/Attack.png';
export const DIM = getColor('#7d86b0');
```
