like2d 2.3.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -18
- package/dist/adapters/callback/index.d.ts +20 -40
- package/dist/adapters/callback/index.d.ts.map +1 -1
- package/dist/adapters/callback/index.js +29 -79
- package/dist/adapters/scene/index.d.ts +6 -30
- package/dist/adapters/scene/index.d.ts.map +1 -1
- package/dist/adapters/scene/index.js +22 -77
- package/dist/adapters/scene/scene.d.ts +14 -13
- package/dist/adapters/scene/scene.d.ts.map +1 -1
- package/dist/adapters/scene/startup-scene.d.ts +9 -8
- package/dist/adapters/scene/startup-scene.d.ts.map +1 -1
- package/dist/adapters/scene/startup-scene.js +37 -17
- package/dist/core/canvas-config.d.ts +7 -7
- package/dist/core/canvas-config.d.ts.map +1 -1
- package/dist/core/canvas-manager.d.ts +7 -8
- package/dist/core/canvas-manager.d.ts.map +1 -1
- package/dist/core/canvas-manager.js +52 -71
- package/dist/core/events.d.ts +21 -49
- package/dist/core/events.d.ts.map +1 -1
- package/dist/core/graphics.d.ts +38 -32
- package/dist/core/graphics.d.ts.map +1 -1
- package/dist/core/graphics.js +192 -275
- package/dist/core/like.d.ts +54 -0
- package/dist/core/like.d.ts.map +1 -0
- package/dist/core/like.js +1 -0
- package/dist/core/player-movement.d.ts +16 -0
- package/dist/core/player-movement.d.ts.map +1 -0
- package/dist/core/player-movement.js +20 -0
- package/dist/engine.d.ts +9 -28
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +89 -63
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -8,12 +8,21 @@ A web-native 2D game framework inspired by [LÖVE](https://love2d.org/), built f
|
|
|
8
8
|
|
|
9
9
|
## What it is
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- **
|
|
16
|
-
|
|
11
|
+
LIKE is a **curated toolkit** around browser APIs.
|
|
12
|
+
|
|
13
|
+
It does less, because it **does the right thing**. And when that's not the right thing for you, we hand you the wrench.
|
|
14
|
+
|
|
15
|
+
- **Stateless Graphics:** Forget to reset native Canvas state (like LineCap) between calls and things break mysteriously. We make drawing explicit: what you see is what you set.
|
|
16
|
+
|
|
17
|
+
- **Fire-and-forget Assets:** Async asset loading directly on realtime web games is annoying. We let you pretend it's instant and synchronous.
|
|
18
|
+
|
|
19
|
+
- **Physical Joypad:** Our gamepad module auto-maps to physical buttons like "bottom" and "top". Because A isn't always in the same spot.
|
|
20
|
+
|
|
21
|
+
- **Actions System:** Of course you can use device input callbacks just like love2d -- but you can also map inputs to actions and get callbacks on that.
|
|
22
|
+
|
|
23
|
+
- **Scaling Modes:** Pixel art games need pixel-perfect scaling. So we do that: integer nearest -> linear. Or not; turn off pixelart mode to have a canvas that stays at native resolution.
|
|
24
|
+
|
|
25
|
+
- **Sane Architecture:** Everything is built around a centralized event handler for browser-native events. We won't reinvent the wheel.
|
|
17
26
|
|
|
18
27
|
## Installation
|
|
19
28
|
|
|
@@ -25,6 +34,10 @@ pnpm add like2d
|
|
|
25
34
|
|
|
26
35
|
## Quick Start
|
|
27
36
|
|
|
37
|
+
[Like2D Starter Template](https://github.com/44100hertz/Like2D-starter)
|
|
38
|
+
|
|
39
|
+
## Usage Examples
|
|
40
|
+
|
|
28
41
|
### Callback Pattern (Love2D-style)
|
|
29
42
|
|
|
30
43
|
Ideal for small games, jams, or prototyping.
|
|
@@ -77,21 +90,17 @@ const runner = new SceneRunner(document.body);
|
|
|
77
90
|
await runner.start(new MyScene());
|
|
78
91
|
```
|
|
79
92
|
|
|
80
|
-
##
|
|
81
|
-
|
|
82
|
-
Like2D exports pure library functions for math and geometry that work with native arrays.
|
|
93
|
+
## Module Overview
|
|
83
94
|
|
|
84
|
-
|
|
85
|
-
- **Rect:** Rectangle operations using `[x, y, w, h]` tuples.
|
|
86
|
-
- **Graphics:** Stateless drawing commands.
|
|
87
|
-
- **Audio:** Simple source/playback management.
|
|
88
|
-
- **Input:** Action-based input mapping.
|
|
95
|
+
Pick your pattern, import what you need:
|
|
89
96
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
97
|
+
```typescript
|
|
98
|
+
import { love, graphics, input } from 'like2d/callback'; // Love2D-style
|
|
99
|
+
import { SceneRunner, type Scene } from 'like2d/scene'; // Class-based scenes
|
|
100
|
+
import { Vec2, Rect } from 'like2d/math'; // Pure math functions
|
|
101
|
+
```
|
|
93
102
|
|
|
94
|
-
|
|
103
|
+
See the [PHILOSOPHY.md](../../docs/PHILOSOPHY.md) for the principles behind the design.
|
|
95
104
|
|
|
96
105
|
## License
|
|
97
106
|
|
|
@@ -1,43 +1,23 @@
|
|
|
1
|
-
import { Graphics } from '../../core/graphics';
|
|
2
|
-
import { Audio } from '../../core/audio';
|
|
3
|
-
import { Input } from '../../core/input';
|
|
4
|
-
import { Timer } from '../../core/timer';
|
|
5
|
-
import { Keyboard } from '../../core/keyboard';
|
|
6
|
-
import { Mouse } from '../../core/mouse';
|
|
7
|
-
import { Gamepad } from '../../core/gamepad';
|
|
8
|
-
import type { CanvasConfig } from '../../core/canvas-config';
|
|
9
1
|
import type { Like2DEvent } from '../../core/events';
|
|
10
|
-
import type {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
resize: ((size: Vector2, pixelSize: Vector2, fullscreen: boolean) => void) | undefined;
|
|
28
|
-
keypressed: ((scancode: string, keycode: string) => void) | undefined;
|
|
29
|
-
keyreleased: ((scancode: string, keycode: string) => void) | undefined;
|
|
30
|
-
mousepressed: ((x: number, y: number, button: number) => void) | undefined;
|
|
31
|
-
mousereleased: ((x: number, y: number, button: number) => void) | undefined;
|
|
32
|
-
gamepadpressed: ((i: number, b: number, n: string) => void) | undefined;
|
|
33
|
-
gamepadreleased: ((i: number, b: number, n: string) => void) | undefined;
|
|
34
|
-
actionpressed: ((action: string) => void) | undefined;
|
|
35
|
-
actionreleased: ((action: string) => void) | undefined;
|
|
36
|
-
handleEvent: ((event: Like2DEvent) => void) | undefined;
|
|
37
|
-
toggleFullscreen(): void;
|
|
38
|
-
setScaling(config: CanvasConfig): void;
|
|
39
|
-
init(container: HTMLElement): Promise<void>;
|
|
40
|
-
dispose(): void;
|
|
2
|
+
import type { Like } from '../../core/like';
|
|
3
|
+
type LikeWithCallbacks = Like & {
|
|
4
|
+
load?: () => void;
|
|
5
|
+
update?: (dt: number) => void;
|
|
6
|
+
draw?: () => void;
|
|
7
|
+
resize?: (size: import('../../core/vector2').Vector2, pixelSize: import('../../core/vector2').Vector2, fullscreen: boolean) => void;
|
|
8
|
+
keypressed?: (scancode: string, keycode: string) => void;
|
|
9
|
+
keyreleased?: (scancode: string, keycode: string) => void;
|
|
10
|
+
mousepressed?: (x: number, y: number, button: number) => void;
|
|
11
|
+
mousereleased?: (x: number, y: number, button: number) => void;
|
|
12
|
+
gamepadpressed?: (gamepadIndex: number, buttonIndex: number, buttonName: string) => void;
|
|
13
|
+
gamepadreleased?: (gamepadIndex: number, buttonIndex: number, buttonName: string) => void;
|
|
14
|
+
actionpressed?: (action: string) => void;
|
|
15
|
+
actionreleased?: (action: string) => void;
|
|
16
|
+
handleEvent?: (event: Like2DEvent) => void;
|
|
17
|
+
start: () => Promise<void>;
|
|
18
|
+
dispose: () => void;
|
|
41
19
|
};
|
|
42
|
-
export
|
|
20
|
+
export declare function routeEvents(like: LikeWithCallbacks): (event: Like2DEvent) => void;
|
|
21
|
+
export declare function createLike(container: HTMLElement): LikeWithCallbacks;
|
|
22
|
+
export {};
|
|
43
23
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/adapters/callback/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/adapters/callback/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAuB,MAAM,mBAAmB,CAAC;AAC1E,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAM5C,KAAK,iBAAiB,GAAG,IAAI,GAAG;IAC9B,IAAI,CAAC,EAAE,MAAM,IAAI,CAAC;IAClB,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,IAAI,CAAC;IAClB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,oBAAoB,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,oBAAoB,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,KAAK,IAAI,CAAC;IACpI,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1D,YAAY,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9D,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/D,cAAc,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACzF,eAAe,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1F,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IAC3C,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAGF,wBAAgB,WAAW,CAAC,IAAI,EAAE,iBAAiB,GAAG,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAKjF;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,WAAW,GAAG,iBAAiB,CAqBpE"}
|
|
@@ -1,80 +1,30 @@
|
|
|
1
|
-
import { Graphics } from '../../core/graphics';
|
|
2
|
-
import { Audio } from '../../core/audio';
|
|
3
|
-
import { Input } from '../../core/input';
|
|
4
|
-
import { Timer } from '../../core/timer';
|
|
5
|
-
import { Keyboard } from '../../core/keyboard';
|
|
6
|
-
import { Mouse } from '../../core/mouse';
|
|
7
|
-
import { Gamepad } from '../../core/gamepad';
|
|
8
1
|
import { Engine } from '../../engine';
|
|
9
|
-
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
export
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
},
|
|
39
|
-
setScaling(config) {
|
|
40
|
-
engine?.setScaling(config);
|
|
41
|
-
},
|
|
42
|
-
async init(container) {
|
|
43
|
-
engine = new Engine(container);
|
|
44
|
-
graphics = new Graphics(engine.getContext());
|
|
45
|
-
keyboard = new Keyboard();
|
|
46
|
-
mouse = new Mouse((cssX, cssY) => engine.transformMousePosition(cssX, cssY));
|
|
47
|
-
gamepad = new Gamepad();
|
|
48
|
-
input = new Input({ keyboard, mouse, gamepad });
|
|
49
|
-
engine.setDeps({ graphics, input, timer, audio, keyboard, mouse, gamepad });
|
|
50
|
-
await gamepad.init();
|
|
51
|
-
engine.start((event) => {
|
|
52
|
-
// 1. handleEvent first
|
|
53
|
-
like.handleEvent?.(event);
|
|
54
|
-
// 2. Direct handlers
|
|
55
|
-
const handler = like[event.type];
|
|
56
|
-
if (handler) {
|
|
57
|
-
handler(...event.args);
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
},
|
|
61
|
-
dispose() {
|
|
62
|
-
engine?.stop();
|
|
63
|
-
engine?.dispose();
|
|
64
|
-
engine = null;
|
|
65
|
-
this.load = undefined;
|
|
66
|
-
this.update = undefined;
|
|
67
|
-
this.draw = undefined;
|
|
68
|
-
this.resize = undefined;
|
|
69
|
-
this.keypressed = undefined;
|
|
70
|
-
this.keyreleased = undefined;
|
|
71
|
-
this.mousepressed = undefined;
|
|
72
|
-
this.mousereleased = undefined;
|
|
73
|
-
this.gamepadpressed = undefined;
|
|
74
|
-
this.gamepadreleased = undefined;
|
|
75
|
-
this.actionpressed = undefined;
|
|
76
|
-
this.actionreleased = undefined;
|
|
77
|
-
this.handleEvent = undefined;
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
export { like as love };
|
|
2
|
+
// Routes events to callback properties on the like object
|
|
3
|
+
export function routeEvents(like) {
|
|
4
|
+
return (event) => {
|
|
5
|
+
const cb = like[event.type];
|
|
6
|
+
if (cb)
|
|
7
|
+
cb(...event.args);
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export function createLike(container) {
|
|
11
|
+
const engine = new Engine(container);
|
|
12
|
+
// Create the like object that combines engine.like with callback properties and methods
|
|
13
|
+
const like = {
|
|
14
|
+
...engine.like,
|
|
15
|
+
handleEvent: undefined,
|
|
16
|
+
start: async () => {
|
|
17
|
+
const handleEvent = (event) => {
|
|
18
|
+
like.handleEvent?.(event);
|
|
19
|
+
const cb = like[event.type];
|
|
20
|
+
if (cb)
|
|
21
|
+
cb(...event.args);
|
|
22
|
+
};
|
|
23
|
+
await engine.start(handleEvent);
|
|
24
|
+
},
|
|
25
|
+
dispose: () => {
|
|
26
|
+
engine.dispose();
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
return like;
|
|
30
|
+
}
|
|
@@ -1,41 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Audio } from '../../core/audio';
|
|
3
|
-
import { Input } from '../../core/input';
|
|
4
|
-
import { Timer } from '../../core/timer';
|
|
5
|
-
import { Keyboard } from '../../core/keyboard';
|
|
6
|
-
import { Mouse } from '../../core/mouse';
|
|
7
|
-
import { Gamepad } from '../../core/gamepad';
|
|
1
|
+
import { Engine } from '../../engine';
|
|
8
2
|
import type { Scene } from './scene';
|
|
9
|
-
import type { CanvasConfig } from '../../core/canvas-config';
|
|
10
3
|
import { StartupScene } from './startup-scene';
|
|
11
|
-
|
|
4
|
+
import type { Like2DEvent } from '../../core/events';
|
|
12
5
|
export { StartupScene };
|
|
13
|
-
export {
|
|
14
|
-
export
|
|
15
|
-
export { Timer } from '../../core/timer';
|
|
16
|
-
export { Keyboard } from '../../core/keyboard';
|
|
17
|
-
export { Mouse } from '../../core/mouse';
|
|
18
|
-
export { Gamepad, getGPName, GP } from '../../core/gamepad';
|
|
19
|
-
export type { Like2DEvent as Event } from '../../core/events';
|
|
20
|
-
export type { Scene } from './scene';
|
|
21
|
-
export type { Vector2 } from '../../core/vector2';
|
|
22
|
-
export { Vec2 } from '../../core/vector2';
|
|
23
|
-
export { Rect } from '../../core/rect';
|
|
24
|
-
export type { CanvasConfig } from '../../core/canvas-config';
|
|
25
|
-
export { calcFixedScale } from '../../core/canvas-config';
|
|
6
|
+
export type { Scene };
|
|
7
|
+
export declare function createDefaultHandler(scene: Scene, like: Engine['like']): (event: Like2DEvent) => void;
|
|
26
8
|
export declare class SceneRunner {
|
|
27
9
|
private engine;
|
|
28
10
|
private scene;
|
|
29
|
-
readonly graphics: Graphics;
|
|
30
|
-
readonly audio: Audio;
|
|
31
|
-
readonly timer: Timer;
|
|
32
|
-
readonly input: Input;
|
|
33
|
-
readonly keyboard: Keyboard;
|
|
34
|
-
readonly mouse: Mouse;
|
|
35
|
-
readonly gamepad: Gamepad;
|
|
36
11
|
constructor(container: HTMLElement);
|
|
37
|
-
|
|
12
|
+
get like(): import("../..").Like;
|
|
38
13
|
setScene(scene: Scene): void;
|
|
14
|
+
handleEvent: (event: Like2DEvent) => void;
|
|
39
15
|
start(scene: Scene): Promise<void>;
|
|
40
16
|
dispose(): void;
|
|
41
17
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/adapters/scene/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/adapters/scene/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,EAAE,YAAY,EAAE,CAAC;AACxB,YAAY,EAAE,KAAK,EAAE,CAAC;AAGtB,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAMrG;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAsB;gBAEvB,SAAS,EAAE,WAAW;IAIlC,IAAI,IAAI,yBAEP;IAED,QAAQ,CAAC,KAAK,EAAE,KAAK;IAKrB,WAAW,GAAI,OAAO,WAAW,KAAG,IAAI,CAKtC;IAEI,KAAK,CAAC,KAAK,EAAE,KAAK;IAKxB,OAAO;CAIR"}
|
|
@@ -1,23 +1,15 @@
|
|
|
1
|
-
import { Graphics } from '../../core/graphics';
|
|
2
|
-
import { Audio } from '../../core/audio';
|
|
3
|
-
import { Input } from '../../core/input';
|
|
4
|
-
import { Timer } from '../../core/timer';
|
|
5
|
-
import { Keyboard } from '../../core/keyboard';
|
|
6
|
-
import { Mouse } from '../../core/mouse';
|
|
7
|
-
import { Gamepad } from '../../core/gamepad';
|
|
8
1
|
import { Engine } from '../../engine';
|
|
9
2
|
import { StartupScene } from './startup-scene';
|
|
10
|
-
export { Graphics, ImageHandle } from '../../core/graphics';
|
|
11
3
|
export { StartupScene };
|
|
12
|
-
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
4
|
+
// Default handler that routes events to scene methods
|
|
5
|
+
export function createDefaultHandler(scene, like) {
|
|
6
|
+
return (event) => {
|
|
7
|
+
scene.handleEvent?.(like, event);
|
|
8
|
+
const method = scene[event.type];
|
|
9
|
+
if (method)
|
|
10
|
+
method.call(scene, like, ...event.args);
|
|
11
|
+
};
|
|
12
|
+
}
|
|
21
13
|
export class SceneRunner {
|
|
22
14
|
constructor(container) {
|
|
23
15
|
Object.defineProperty(this, "engine", {
|
|
@@ -32,80 +24,33 @@ export class SceneRunner {
|
|
|
32
24
|
writable: true,
|
|
33
25
|
value: null
|
|
34
26
|
});
|
|
35
|
-
Object.defineProperty(this, "
|
|
36
|
-
enumerable: true,
|
|
37
|
-
configurable: true,
|
|
38
|
-
writable: true,
|
|
39
|
-
value: void 0
|
|
40
|
-
});
|
|
41
|
-
Object.defineProperty(this, "audio", {
|
|
42
|
-
enumerable: true,
|
|
43
|
-
configurable: true,
|
|
44
|
-
writable: true,
|
|
45
|
-
value: void 0
|
|
46
|
-
});
|
|
47
|
-
Object.defineProperty(this, "timer", {
|
|
27
|
+
Object.defineProperty(this, "handleEvent", {
|
|
48
28
|
enumerable: true,
|
|
49
29
|
configurable: true,
|
|
50
30
|
writable: true,
|
|
51
|
-
value:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
Object.defineProperty(this, "keyboard", {
|
|
60
|
-
enumerable: true,
|
|
61
|
-
configurable: true,
|
|
62
|
-
writable: true,
|
|
63
|
-
value: void 0
|
|
64
|
-
});
|
|
65
|
-
Object.defineProperty(this, "mouse", {
|
|
66
|
-
enumerable: true,
|
|
67
|
-
configurable: true,
|
|
68
|
-
writable: true,
|
|
69
|
-
value: void 0
|
|
70
|
-
});
|
|
71
|
-
Object.defineProperty(this, "gamepad", {
|
|
72
|
-
enumerable: true,
|
|
73
|
-
configurable: true,
|
|
74
|
-
writable: true,
|
|
75
|
-
value: void 0
|
|
31
|
+
value: (event) => {
|
|
32
|
+
if (!this.scene)
|
|
33
|
+
return;
|
|
34
|
+
this.scene.handleEvent?.(this.engine.like, event);
|
|
35
|
+
const method = this.scene[event.type];
|
|
36
|
+
if (method)
|
|
37
|
+
method.call(this.scene, this.engine.like, ...event.args);
|
|
38
|
+
}
|
|
76
39
|
});
|
|
77
40
|
this.engine = new Engine(container);
|
|
78
|
-
this.graphics = new Graphics(this.engine.getContext());
|
|
79
|
-
this.keyboard = new Keyboard();
|
|
80
|
-
this.mouse = new Mouse((cssX, cssY) => this.engine.transformMousePosition(cssX, cssY));
|
|
81
|
-
this.gamepad = new Gamepad();
|
|
82
|
-
this.input = new Input({ keyboard: this.keyboard, mouse: this.mouse, gamepad: this.gamepad });
|
|
83
|
-
this.timer = new Timer();
|
|
84
|
-
this.audio = new Audio();
|
|
85
41
|
}
|
|
86
|
-
|
|
87
|
-
this.engine.
|
|
42
|
+
get like() {
|
|
43
|
+
return this.engine.like;
|
|
88
44
|
}
|
|
89
45
|
setScene(scene) {
|
|
90
46
|
this.scene = scene;
|
|
91
|
-
|
|
47
|
+
scene.load?.(this.engine.like);
|
|
92
48
|
}
|
|
93
49
|
async start(scene) {
|
|
94
50
|
this.setScene(scene);
|
|
95
|
-
this.engine.
|
|
96
|
-
await this.gamepad.init();
|
|
97
|
-
this.engine.start((event) => {
|
|
98
|
-
// 1. handleEvent runs first
|
|
99
|
-
this.scene?.handleEvent?.(event);
|
|
100
|
-
// 2. Direct handlers
|
|
101
|
-
const handler = this.scene?.[event.type];
|
|
102
|
-
if (handler) {
|
|
103
|
-
handler.apply(this.scene, event.args);
|
|
104
|
-
}
|
|
105
|
-
});
|
|
51
|
+
await this.engine.start(this.handleEvent);
|
|
106
52
|
}
|
|
107
53
|
dispose() {
|
|
108
|
-
this.engine.stop();
|
|
109
54
|
this.engine.dispose();
|
|
110
55
|
this.scene = null;
|
|
111
56
|
}
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import type { Vector2 } from '../../core/vector2';
|
|
2
2
|
import type { Like2DEvent } from '../../core/events';
|
|
3
|
+
import type { Like } from '../../core/like';
|
|
3
4
|
export type Scene = {
|
|
4
|
-
load?(): void;
|
|
5
|
-
update?(dt: number): void;
|
|
6
|
-
draw?(
|
|
7
|
-
resize?(size: Vector2, pixelSize: Vector2, fullscreen: boolean): void;
|
|
8
|
-
keypressed?(scancode: string, keycode: string): void;
|
|
9
|
-
keyreleased?(scancode: string, keycode: string): void;
|
|
10
|
-
mousepressed?(x: number, y: number, button: number): void;
|
|
11
|
-
mousereleased?(x: number, y: number, button: number): void;
|
|
12
|
-
gamepadpressed?(gamepadIndex: number, buttonIndex: number, buttonName: string): void;
|
|
13
|
-
gamepadreleased?(gamepadIndex: number, buttonIndex: number, buttonName: string): void;
|
|
14
|
-
actionpressed?(action: string): void;
|
|
15
|
-
actionreleased?(action: string): void;
|
|
16
|
-
handleEvent?(event: Like2DEvent): void;
|
|
5
|
+
load?(like: Like): void;
|
|
6
|
+
update?(like: Like, dt: number): void;
|
|
7
|
+
draw?(like: Like): void;
|
|
8
|
+
resize?(like: Like, size: Vector2, pixelSize: Vector2, fullscreen: boolean): void;
|
|
9
|
+
keypressed?(like: Like, scancode: string, keycode: string): void;
|
|
10
|
+
keyreleased?(like: Like, scancode: string, keycode: string): void;
|
|
11
|
+
mousepressed?(like: Like, x: number, y: number, button: number): void;
|
|
12
|
+
mousereleased?(like: Like, x: number, y: number, button: number): void;
|
|
13
|
+
gamepadpressed?(like: Like, gamepadIndex: number, buttonIndex: number, buttonName: string): void;
|
|
14
|
+
gamepadreleased?(like: Like, gamepadIndex: number, buttonIndex: number, buttonName: string): void;
|
|
15
|
+
actionpressed?(like: Like, action: string): void;
|
|
16
|
+
actionreleased?(like: Like, action: string): void;
|
|
17
|
+
handleEvent?(like: Like, event: Like2DEvent): void;
|
|
17
18
|
};
|
|
18
19
|
//# sourceMappingURL=scene.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scene.d.ts","sourceRoot":"","sources":["../../../src/adapters/scene/scene.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"scene.d.ts","sourceRoot":"","sources":["../../../src/adapters/scene/scene.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAE5C,MAAM,MAAM,KAAK,GAAG;IAClB,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB,MAAM,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB,MAAM,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,GAAG,IAAI,CAAC;IAClF,UAAU,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACjE,WAAW,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAClE,YAAY,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtE,aAAa,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACvE,cAAc,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACjG,eAAe,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAClG,aAAa,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACjD,cAAc,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAClD,WAAW,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;CACpD,CAAC"}
|
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import type { Like2DEvent } from '../../core/events';
|
|
2
1
|
import type { Scene } from './scene';
|
|
3
|
-
import
|
|
2
|
+
import { ImageHandle } from '../../core/graphics';
|
|
3
|
+
import type { Like } from '../../core/like';
|
|
4
4
|
export type StartupSceneConfig = {
|
|
5
5
|
nextScene: Scene;
|
|
6
|
-
draw?: (
|
|
6
|
+
draw?: (like: Like, logo: ImageHandle) => void;
|
|
7
|
+
logo?: ImageHandle;
|
|
7
8
|
};
|
|
8
9
|
export declare class StartupScene implements Scene {
|
|
9
|
-
private graphics;
|
|
10
10
|
private config;
|
|
11
|
-
private
|
|
11
|
+
private setScene;
|
|
12
12
|
private started;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
private logo;
|
|
14
|
+
constructor(config: StartupSceneConfig, setScene: (scene: Scene) => void);
|
|
15
|
+
draw(like: Like): void;
|
|
16
|
+
mousepressed(_like: Like, _x: number, _y: number, _button: number): void;
|
|
16
17
|
}
|
|
17
18
|
//# sourceMappingURL=startup-scene.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"startup-scene.d.ts","sourceRoot":"","sources":["../../../src/adapters/scene/startup-scene.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"startup-scene.d.ts","sourceRoot":"","sources":["../../../src/adapters/scene/startup-scene.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,WAAW,EAAY,MAAM,qBAAqB,CAAC;AAG5D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAE5C,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,EAAE,KAAK,CAAC;IACjB,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,KAAK,IAAI,CAAC;IAC/C,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB,CAAC;AA4BF,qBAAa,YAAa,YAAW,KAAK;IAKtC,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,QAAQ;IALlB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,IAAI,CAAc;gBAGhB,MAAM,EAAE,kBAAkB,EAC1B,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI;IAK1C,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAItB,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;CAMzE"}
|
|
@@ -1,25 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { newImage } from '../../core/graphics';
|
|
2
|
+
import { Vec2 } from '../../core/vector2';
|
|
3
|
+
import { Rect } from '../../core/rect';
|
|
4
|
+
const LOGO_DATA_URI = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj4KPHN2ZyB3aWR0aD0iMzAwbW0iIGhlaWdodD0iMTA1bW0iIHZlcnNpb249IjEuMSIgdmlld0JveD0iMCAwIDMwMCAxMDUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiA8cmVjdCB4PSIxMCIgeT0iMTEuMjMiIHdpZHRoPSIyODAiIGhlaWdodD0iODMuNTQ0IiBmaWxsPSIjZTQ4MDgwIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIvPgogPGcgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj4KICA8cmVjdCB4PSI5Ny40ODQiIHk9IjExLjIzIiB3aWR0aD0iNTIuNTE2IiBoZWlnaHQ9IjQ2LjIzNyIvPgogIDxyZWN0IHg9IjE1MCIgeT0iMTEuMjMiIHdpZHRoPSIzNS4wMTEiIGhlaWdodD0iNDYuMjM3Ii8+CiAgPHJlY3QgeD0iMTg1LjAxIiB5PSIxMS4yMyIgd2lkdGg9IjUyLjUxNiIgaGVpZ2h0PSI0Ni4yMzciLz4KICA8cmVjdCB4PSIyMzcuNTMiIHk9IjExLjIzIiB3aWR0aD0iNTIuNTE2IiBoZWlnaHQ9IjQ2LjIzNyIvPgogPC9nPgogPGc+CiAgPHJlY3QgeD0iMTMyLjQ5IiB5PSIxMS4yMyIgd2lkdGg9IjE3LjUwNSIgaGVpZ2h0PSIyNy40NjEiLz4KICA8cmVjdCB4PSIxNTAiIHk9IjI5LjMwMiIgd2lkdGg9IjguNzUyNyIgaGVpZ2h0PSIxOC43NzYiLz4KICA8cmVjdCB4PSIxNzYuMjYiIHk9IjI5LjMwMiIgd2lkdGg9IjguNzUyNyIgaGVpZ2h0PSIxOC43NzYiLz4KIDwvZz4KIDxyZWN0IHg9IjE1MCIgeT0iMTEuMjMiIHdpZHRoPSIxNy41MDUiIGhlaWdodD0iOC42ODQ1IiBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KIDxyZWN0IHg9IjE2Ny41MSIgeT0iMTEuMjMiIHdpZHRoPSIxNy41MDUiIGhlaWdodD0iOC42ODQ1IiBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KIDxnPgogIDxwYXRoIGQ9Im0yMzcuNTMgMzguNjkxLTE3LjUwNS05LjM4ODIgMTcuNTA1LTE4LjA3M3oiLz4KICA8cmVjdCB4PSIyMDIuODgiIHk9IjQ4LjA3OSIgd2lkdGg9IjE2Ljc3MiIgaGVpZ2h0PSI5LjM4ODIiLz4KICA8cmVjdCB4PSIyNzIuNTQiIHk9IjIwLjI2NiIgd2lkdGg9IjE2Ljc3MiIgaGVpZ2h0PSI5LjM4ODIiLz4KICA8cmVjdCB4PSIyNzIuNTQiIHk9IjM4LjY5MSIgd2lkdGg9IjE2Ljc3MiIgaGVpZ2h0PSI5LjM4ODIiLz4KICA8cGF0aCBkPSJtMjAyLjUyIDI5LjMwMiAwLjM2Njg1LTE4LjA3M2gxNy4xMzl6Ii8+CiA8L2c+CiA8cGF0aCBkPSJtNjQuMDc4IDEuMDA0Mi0zMy4zNzUgMzMuMzc1LTAuMDE3NDMgMC4wMTc0YTIzLjYxMiAyMy42MTIgMCAwIDAgMCAzMy4zOTIgMjMuNjEyIDIzLjYxMiAwIDAgMCAzMC4wMTIgMi44MDIyIDIzLjYxMiAyMy42MTIgMCAwIDEgN2UtMyAwLjU3MDM0IDIzLjYxMiAyMy42MTIgMCAwIDEtMjMuNjEyIDIzLjYxMmg1My45N2EyMy42MTIgMjMuNjEyIDAgMCAxLTIzLjYxMS0yMy42MTIgMjMuNjEyIDIzLjYxMiAwIDAgMSA3ZS0zIC0wLjU3MDM0IDIzLjYxMiAyMy42MTIgMCAwIDAgMzAuMDEyLTIuODAyOSAyMy42MTIgMjMuNjEyIDAgMCAwLTYuODhlLTQgLTMzLjM5MnoiIGZpbGw9IiM4MGMzZTQiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgogPGcgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utd2lkdGg9Ii41Ij4KICA8Y2lyY2xlIHRyYW5zZm9ybT0icm90YXRlKDEzNSkiIGN4PSItMjAuOTg4IiBjeT0iLTkzLjI0MyIgcj0iMjMuNjEyIi8+CiAgPGNpcmNsZSB0cmFuc2Zvcm09InJvdGF0ZSgxMzUpIiBjeD0iMi42MjM4IiBjeT0iLTY5LjYzMiIgcj0iMjMuNjEyIi8+CiAgPGNpcmNsZSBjeD0iOTEuMDYyIiBjeT0iNzEuMTYxIiByPSIyMy42MTIiLz4KICA8Y2lyY2xlIGN4PSIzNy4wOTMiIGN5PSI3MS4xNjEiIHI9IjIzLjYxMiIvPgogPC9nPgo8L3N2Zz4K';
|
|
5
|
+
const LOGO_WIDTH_RATIO = 0.5;
|
|
6
|
+
function defaultDraw(like, logo) {
|
|
7
|
+
const size = like.gfx.getCanvasSize();
|
|
8
|
+
const center = Vec2.mul(size, 0.5);
|
|
9
|
+
const bottomY = size[1] * 0.85;
|
|
10
|
+
like.gfx.clear('black');
|
|
11
|
+
if (logo.isReady()) {
|
|
12
|
+
const imgSize = logo.size;
|
|
13
|
+
const scale = (size[0] * LOGO_WIDTH_RATIO) / imgSize[0];
|
|
14
|
+
const drawSize = Vec2.mul(imgSize, scale);
|
|
15
|
+
const rect = Rect.fromCenter(center, drawSize);
|
|
16
|
+
like.gfx.draw(logo, Rect.position(rect), { scale: [scale, scale] });
|
|
17
|
+
}
|
|
18
|
+
like.gfx.print('white', 'Click to Start', [0, bottomY], {
|
|
19
|
+
align: 'center',
|
|
20
|
+
limit: size[0],
|
|
21
|
+
font: '32px sans-serif'
|
|
22
|
+
});
|
|
3
23
|
}
|
|
4
24
|
export class StartupScene {
|
|
5
|
-
constructor(
|
|
6
|
-
Object.defineProperty(this, "graphics", {
|
|
7
|
-
enumerable: true,
|
|
8
|
-
configurable: true,
|
|
9
|
-
writable: true,
|
|
10
|
-
value: graphics
|
|
11
|
-
});
|
|
25
|
+
constructor(config, setScene) {
|
|
12
26
|
Object.defineProperty(this, "config", {
|
|
13
27
|
enumerable: true,
|
|
14
28
|
configurable: true,
|
|
15
29
|
writable: true,
|
|
16
30
|
value: config
|
|
17
31
|
});
|
|
18
|
-
Object.defineProperty(this, "
|
|
32
|
+
Object.defineProperty(this, "setScene", {
|
|
19
33
|
enumerable: true,
|
|
20
34
|
configurable: true,
|
|
21
35
|
writable: true,
|
|
22
|
-
value:
|
|
36
|
+
value: setScene
|
|
23
37
|
});
|
|
24
38
|
Object.defineProperty(this, "started", {
|
|
25
39
|
enumerable: true,
|
|
@@ -27,15 +41,21 @@ export class StartupScene {
|
|
|
27
41
|
writable: true,
|
|
28
42
|
value: false
|
|
29
43
|
});
|
|
30
|
-
|
|
44
|
+
Object.defineProperty(this, "logo", {
|
|
45
|
+
enumerable: true,
|
|
46
|
+
configurable: true,
|
|
47
|
+
writable: true,
|
|
48
|
+
value: void 0
|
|
49
|
+
});
|
|
50
|
+
this.logo = config.logo ?? newImage(LOGO_DATA_URI);
|
|
31
51
|
}
|
|
32
|
-
draw(
|
|
33
|
-
(this.config.draw ?? defaultDraw)(this.
|
|
52
|
+
draw(like) {
|
|
53
|
+
(this.config.draw ?? defaultDraw)(like, this.logo);
|
|
34
54
|
}
|
|
35
|
-
|
|
36
|
-
if (
|
|
55
|
+
mousepressed(_like, _x, _y, _button) {
|
|
56
|
+
if (!this.started) {
|
|
37
57
|
this.started = true;
|
|
38
|
-
this.
|
|
58
|
+
this.setScene(this.config.nextScene);
|
|
39
59
|
}
|
|
40
60
|
}
|
|
41
61
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { Vector2 } from './vector2';
|
|
2
|
-
export type CanvasMode =
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
export type CanvasMode = {
|
|
3
|
+
pixelResolution: Vector2 | null;
|
|
4
|
+
fullscreen: boolean;
|
|
5
|
+
};
|
|
6
|
+
export type PartialCanvasMode = {
|
|
7
|
+
pixelResolution?: Vector2 | null;
|
|
8
|
+
fullscreen?: boolean;
|
|
9
9
|
};
|
|
10
10
|
/**
|
|
11
11
|
* Calculate the scale and offset for rendering fixed-size content to a target canvas.
|