# A view of 2D and 3D

> Sprites and turning models in one world, and a view in the corner that sees both through its own 2D camera and a 3D one following it.

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

The floor and the player are sprites, the turning blocks are models placed in the 2D world (`follow2d`). The view in the corner sees both, through its own 2D camera and a 3D one that follows it, drawn in the same order as on the screen; the frame of what the screen shows is drawn by the view itself, on top, and only there.

Click the view and the player goes there (`viewToWorld`; for a view in perspective, `viewToRay`).

Click the preview first so it hears the keyboard. The arrows move.

## 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: '#141a2e'
});

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

`components/blocks.ts`:

```ts
import { createMesh, getColor, useCubeGeometry, useUpdate } from 'nacatamalon';
import type { TMesh } from 'nacatamalon';

export const Blocks = () => {
    const blocks: TMesh[] = [];

    for (let i = 0; i < 9; i++) {
        const x = 160 + (i % 3) * 320;
        const y = 160 + Math.floor(i / 3) * 320;

        blocks.push(createMesh({
            geometry: useCubeGeometry(),
            tint: getColor(['#e06a4a', '#f2c14e', '#5fbf8f'][i % 3]),
            transform: {
                x,
                y,
                scaleX: 48,
                scaleY: 48,
                scaleZ: 48
            }
        }));
    }

    useUpdate((delta) => {
        for (const block of blocks) {
            block.transform.rotationY += delta * 0.8;
            block.transform.rotationX += delta * 0.5;
        }
    });
};
```

`components/floor.ts`:

```ts
import { createSprite, getColor } from 'nacatamalon';

import * as Settings from '../settings';

export const Floor = () => {
    for (let y = 0; y < Settings.ARENA; y += 64) {
        for (let x = 0; x < Settings.ARENA; x += 64) {
            const dark = ((x + y) / 64) % 2 === 0;

            // Under everything: on a tie, sprites are drawn over models.
            createSprite({
                width: 64,
                height: 64,
                tint: getColor(dark ? '#2d3b55' : '#34445f'),
                anchor: {
                    x: 0,
                    y: 0
                },
                zIndex: -1,
                transform: {
                    x,
                    y
                }
            });
        }
    }
};
```

`components/hud.ts`:

```ts
import { createSprite, createText, usePointer, useScreenSpace, viewToWorld, createView } from 'nacatamalon';
import type { TSprite } from 'nacatamalon';
import * as Settings from '../settings';

import { Overview } from './overview';

export const Hud = (player: TSprite) => {
    useScreenSpace();
    createSprite({
        width: Settings.VIEW.width + 4,
        height: Settings.VIEW.height + 4,
        tint: '#0b0f18',
        anchor: {
            x: 1,
            y: 1
        },
        transform: {
            x: Settings.WIDTH - 4,
            y: Settings.HEIGHT - 4
        }
    });

    const shown = createView({
        x: Settings.WIDTH - 6 - Settings.VIEW.width,
        y: Settings.HEIGHT - 6 - Settings.VIEW.height,
        width: Settings.VIEW.width,
        height: Settings.VIEW.height,
        background: '#141a2e'
    }, Overview, player);

    createText({
        text: 'ARROWS MOVE  CLICK THE VIEW',
        transform: {
            x: 8,
            y: 8
        },
        tint: '#9aa7b4'
    });
    usePointer().onDown((info) => {
        const at = viewToWorld(shown, info);

        if (at.inside) {
            player.transform.x = Math.max(0, Math.min(Settings.ARENA, at.x));
            player.transform.y = Math.max(0, Math.min(Settings.ARENA, at.y));
        }
    });
};
```

`components/overview.ts`:

```ts
import { createSprite, useCamera2d, useCamera3d, useUpdate } from 'nacatamalon';
import type { TSprite } from 'nacatamalon';
import * as Settings from '../settings';

export const Overview = (player: TSprite) => {
    const flat = useCamera2d({
        zoom: Settings.VIEW.zoom
    });

    useCamera3d({
        projection: 'orthographic',
        follow2d: true,
        transform: {
            z: 400
        }
    });

    const sight = createSprite({
        width: Settings.WIDTH,
        height: Settings.HEIGHT,
        tint: '#ffffff40',
        anchor: {
            x: 0,
            y: 0
        }
    });

    useUpdate(() => {
        flat.transform.x = player.transform.x - Settings.VIEW.width / 2 / Settings.VIEW.zoom;
        flat.transform.y = player.transform.y - Settings.VIEW.height / 2 / Settings.VIEW.zoom;
        sight.transform.x = player.transform.x - Settings.WIDTH / 2;
        sight.transform.y = player.transform.y - Settings.HEIGHT / 2;
    });
};
```

`scenes/demo.ts`:

```ts
import { createScene, createSprite, useCamera2d, useCamera3d, useCameraFollow, useDirectionalLight, useKeyboard, useSpawn, useUpdate } from 'nacatamalon';

import * as Settings from '../settings';
import { Floor } from '../components/floor';
import { Blocks } from '../components/blocks';
import { Hud } from '../components/hud';

export const Level = () => {
    useCamera2d();
    useCamera3d({
        projection: 'orthographic',
        follow2d: true,
        transform: {
            z: 400
        }
    });
    useDirectionalLight({
        intensity: 0.8,
        ambient: 0.5,
        transform: {
            rotationX: -0.7,
            rotationY: 0.5
        }
    });

    useSpawn(Floor)();
    useSpawn(Blocks)();

    const player = createSprite({
        width: 20,
        height: 20,
        tint: '#ff3b5c',
        transform: {
            x: Settings.ARENA / 2,
            y: Settings.ARENA / 2
        }
    });

    useCameraFollow(player);

    const keys = useKeyboard();

    useUpdate((delta) => {
        player.transform.x += ((keys.isDown('ArrowRight') ? 1 : 0) - (keys.isDown('ArrowLeft') ? 1 : 0)) * delta * 180;
        player.transform.y += ((keys.isDown('ArrowDown') ? 1 : 0) - (keys.isDown('ArrowUp') ? 1 : 0)) * delta * 180;
    });

    useSpawn(Hud)(player);

    return createScene();
};
```

`settings.ts`:

```ts
export const WIDTH = 480;
export const HEIGHT = 270;
export const ARENA = 960;
export const VIEW = {
    width: 160,
    height: 90,
    zoom: 0.125
};
```
