# Models that follow the 2D camera

> follow2d on an orthographic 3D camera: models placed in the 2D world like sprites, moved by the 2D camera.

Example `follow-2d`, running at https://nacatamalon.com/en/examples/follow-2d. The whole catalogue: https://nacatamalon.com/llms/examples.md.

`useCamera3d({ projection: 'orthographic', follow2d: true })` puts the models in the 2D world: a model at `(300, 70)` is drawn where a sprite at `(300, 70)` is, `y` going down, in the same pixels. The scene's 2D camera moves, turns and zooms both alike, so a 2D game can have pieces built in 3D without placing each one by hand every frame.

The models stay upright and facing as they were built, and a light aimed down still lights them from above. The 3D camera's own `x`, `y` and `zoom` are not read; its `z` and `far` are.

Click the preview first so it hears the keyboard. The arrows scroll the level, and each gem stays over its ledge.

## 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: '#1b2138',
    scaling: 'integer'
});

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

`components/gems.ts`:

```ts
import { createMesh, getColor, useIcoSphereGeometry, useUpdate } from 'nacatamalon';
import * as Settings from '../settings';

export const Gems = () => {
    // The 3D: a gem over each ledge, placed with the same numbers as the ledge, a little above it.
    const gem = useIcoSphereGeometry({
        radius: 8,
        subdivisions: 0
    });

    const colours = ['#ff6b8a', '#ffd166', '#6ee7d8', '#c9a7ff', '#7fb8ff'];

    const gems = Settings.LEDGES.map(([x, y], i) => createMesh({
        geometry: gem,
        tint: getColor(colours[i]!),
        transform: {
            x,
            y: y - 20
        }
    }));

    useUpdate((_delta, time) => {
        gems.forEach((mesh, i) => {
            mesh.transform.rotationY = time * 1.5 + i;
            mesh.transform.rotationX = time * 0.7;
        });
    });
};
```

`components/hud.ts`:

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

export const Hud = () => {
    useScreenSpace();
    createText({
        text: '← → SCROLL',
        tint: '#9a8fc7',
        transform: {
            x: 8,
            y: 8
        }
    });
};
```

`components/terrain.ts`:

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

export const Terrain = () => {
    // The 2D: sky, ground and a few ledges along the level, behind the models. Sprites and models
    // share one order, zIndex, so the sky drawn last would cover the gems.
    createSprite({
        width: Settings.LEVEL,
        height: Settings.GROUND,
        tint: '#1b2138',
        anchor: {
            x: 0,
            y: 0
        },
        zIndex: -10
    });
    createSprite({
        width: Settings.LEVEL,
        height: Settings.HEIGHT - Settings.GROUND,
        tint: '#3a4470',
        anchor: {
            x: 0,
            y: 0
        },
        transform: {
            y: Settings.GROUND
        },
        zIndex: -10
    });

    for (const [x, y] of Settings.LEDGES) {
        createSprite({
            width: 64,
            height: 8,
            tint: '#5a6aa8',
            transform: {
                x,
                y
            },
            zIndex: -5
        });
    }
};
```

`scenes/demo.ts`:

```ts
import { createScene, useCamera2d, useCamera3d, useKeyboard, useDirectionalLight, useUpdate, useSpawn } from 'nacatamalon';
import * as Settings from '../settings';

import { clamp } from 'nacatamalon/math';
import { Terrain } from '../components/terrain';
import { Gems } from '../components/gems';
import { Hud } from '../components/hud';

export const Level = () => {
    const camera = useCamera2d();

    useCamera3d({
        projection: 'orthographic',
        follow2d: true
    });
    useDirectionalLight({
        intensity: 1.1,
        ambient: 0.35,
        transform: {
            rotationX: -0.9
        }
    });

    useSpawn(Terrain)();
    useSpawn(Gems)();
    useSpawn(Hud)();

    const keys = useKeyboard();

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

        camera.transform.x = clamp(camera.transform.x + dx * Settings.SPEED * delta, 0, Settings.LEVEL - Settings.WIDTH);
    });

    return createScene();
};
```

`settings.ts`:

```ts
export const WIDTH = 320;
export const HEIGHT = 180;
export const LEVEL = 960;
export const GROUND = 150;

export const LEDGES = [[120, 110], [300, 90], [520, 120], [700, 80], [860, 105]] as const;
export const SPEED = 160;
```
