like2d 2.7.4 → 2.9.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 +34 -35
- package/dist/core/audio.d.ts +12 -9
- package/dist/core/audio.d.ts.map +1 -1
- package/dist/core/audio.js +7 -4
- package/dist/core/canvas.d.ts +58 -0
- package/dist/core/canvas.d.ts.map +1 -0
- package/dist/core/canvas.js +209 -0
- package/dist/core/events.d.ts +92 -7
- package/dist/core/events.d.ts.map +1 -1
- package/dist/core/events.js +20 -0
- package/dist/core/gamepad-mapping.d.ts +57 -18
- package/dist/core/gamepad-mapping.d.ts.map +1 -1
- package/dist/core/gamepad-mapping.js +23 -223
- package/dist/core/gamepad.d.ts +34 -58
- package/dist/core/gamepad.d.ts.map +1 -1
- package/dist/core/gamepad.js +79 -213
- package/dist/core/graphics.d.ts +175 -64
- package/dist/core/graphics.d.ts.map +1 -1
- package/dist/core/graphics.js +294 -198
- package/dist/core/input-state.d.ts +2 -2
- package/dist/core/input-state.d.ts.map +1 -1
- package/dist/core/input.d.ts +22 -15
- package/dist/core/input.d.ts.map +1 -1
- package/dist/core/input.js +25 -37
- package/dist/core/keyboard.d.ts +7 -7
- package/dist/core/keyboard.d.ts.map +1 -1
- package/dist/core/keyboard.js +24 -31
- package/dist/core/like.d.ts +98 -44
- package/dist/core/like.d.ts.map +1 -1
- package/dist/core/like.js +8 -0
- package/dist/core/mouse.d.ts +45 -22
- package/dist/core/mouse.d.ts.map +1 -1
- package/dist/core/mouse.js +90 -78
- package/dist/core/timer.d.ts +2 -5
- package/dist/core/timer.d.ts.map +1 -1
- package/dist/core/timer.js +2 -14
- package/dist/engine.d.ts +61 -11
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +119 -102
- package/dist/index.d.ts +42 -21
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -14
- package/dist/math/index.d.ts +2 -0
- package/dist/math/index.d.ts.map +1 -0
- package/dist/math/index.js +1 -0
- package/dist/math/rect.d.ts +71 -0
- package/dist/math/rect.d.ts.map +1 -0
- package/dist/{core → math}/rect.js +8 -0
- package/dist/math/vector2.d.ts +78 -0
- package/dist/math/vector2.d.ts.map +1 -0
- package/dist/{core → math}/vector2.js +24 -0
- package/dist/prefab-scenes/index.d.ts +7 -0
- package/dist/prefab-scenes/index.d.ts.map +1 -0
- package/dist/prefab-scenes/index.js +6 -0
- package/dist/prefab-scenes/startScreen.d.ts +59 -0
- package/dist/prefab-scenes/startScreen.d.ts.map +1 -0
- package/dist/{scenes/startup.js → prefab-scenes/startScreen.js} +47 -7
- package/dist/scene.d.ts +103 -5
- package/dist/scene.d.ts.map +1 -1
- package/dist/scene.js +28 -1
- package/package.json +18 -2
- package/dist/core/canvas-config.d.ts +0 -22
- package/dist/core/canvas-config.d.ts.map +0 -1
- package/dist/core/canvas-config.js +0 -14
- package/dist/core/canvas-manager.d.ts +0 -25
- package/dist/core/canvas-manager.d.ts.map +0 -1
- package/dist/core/canvas-manager.js +0 -178
- package/dist/core/gamepad-buttons.d.ts +0 -23
- package/dist/core/gamepad-buttons.d.ts.map +0 -1
- package/dist/core/gamepad-buttons.js +0 -36
- package/dist/core/rect.d.ts +0 -26
- package/dist/core/rect.d.ts.map +0 -1
- package/dist/core/vector2.d.ts +0 -26
- package/dist/core/vector2.d.ts.map +0 -1
- package/dist/gamecontrollerdb.txt +0 -2221
- package/dist/scenes/startup.d.ts +0 -18
- package/dist/scenes/startup.d.ts.map +0 -1
package/dist/core/mouse.js
CHANGED
|
@@ -1,130 +1,142 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { Vec2 } from '../math/vector2';
|
|
2
|
+
const mouseButtons = ["left", "middle", "right"];
|
|
3
|
+
const numToButton = (i) => mouseButtons[i];
|
|
4
|
+
/**
|
|
5
|
+
* Mouse input handling. Bound to canvas. Emits relative movement when pointer locked.
|
|
6
|
+
* Buttons: 1 = left, 2 = middle, 3 = right.
|
|
7
|
+
*/
|
|
8
|
+
export class MouseInternal {
|
|
9
|
+
constructor(canvas, dispatch) {
|
|
10
|
+
Object.defineProperty(this, "canvas", {
|
|
4
11
|
enumerable: true,
|
|
5
12
|
configurable: true,
|
|
6
13
|
writable: true,
|
|
7
|
-
value:
|
|
14
|
+
value: canvas
|
|
8
15
|
});
|
|
9
|
-
Object.defineProperty(this, "
|
|
16
|
+
Object.defineProperty(this, "dispatch", {
|
|
10
17
|
enumerable: true,
|
|
11
18
|
configurable: true,
|
|
12
19
|
writable: true,
|
|
13
|
-
value:
|
|
20
|
+
value: dispatch
|
|
14
21
|
});
|
|
15
|
-
Object.defineProperty(this, "
|
|
16
|
-
enumerable: true,
|
|
17
|
-
configurable: true,
|
|
18
|
-
writable: true,
|
|
19
|
-
value: new Set()
|
|
20
|
-
});
|
|
21
|
-
Object.defineProperty(this, "onMouseEvent", {
|
|
22
|
+
Object.defineProperty(this, "pos", {
|
|
22
23
|
enumerable: true,
|
|
23
24
|
configurable: true,
|
|
24
25
|
writable: true,
|
|
25
|
-
value:
|
|
26
|
+
value: [0, 0]
|
|
26
27
|
});
|
|
27
|
-
Object.defineProperty(this, "
|
|
28
|
+
Object.defineProperty(this, "lastPos", {
|
|
28
29
|
enumerable: true,
|
|
29
30
|
configurable: true,
|
|
30
31
|
writable: true,
|
|
31
|
-
value:
|
|
32
|
+
value: [0, 0]
|
|
32
33
|
});
|
|
33
|
-
|
|
34
|
-
Object.defineProperty(this, "mousemoveHandler", {
|
|
35
|
-
enumerable: true,
|
|
36
|
-
configurable: true,
|
|
37
|
-
writable: true,
|
|
38
|
-
value: void 0
|
|
39
|
-
});
|
|
40
|
-
Object.defineProperty(this, "mousedownHandler", {
|
|
34
|
+
Object.defineProperty(this, "buttons", {
|
|
41
35
|
enumerable: true,
|
|
42
36
|
configurable: true,
|
|
43
37
|
writable: true,
|
|
44
|
-
value:
|
|
38
|
+
value: new Set()
|
|
45
39
|
});
|
|
46
|
-
Object.defineProperty(this, "
|
|
40
|
+
Object.defineProperty(this, "cursorVisible", {
|
|
47
41
|
enumerable: true,
|
|
48
42
|
configurable: true,
|
|
49
43
|
writable: true,
|
|
50
|
-
value:
|
|
44
|
+
value: true
|
|
51
45
|
});
|
|
52
|
-
Object.defineProperty(this, "
|
|
46
|
+
Object.defineProperty(this, "abort", {
|
|
53
47
|
enumerable: true,
|
|
54
48
|
configurable: true,
|
|
55
49
|
writable: true,
|
|
56
|
-
value:
|
|
50
|
+
value: new AbortController()
|
|
57
51
|
});
|
|
58
|
-
this.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
this.
|
|
62
|
-
this.
|
|
63
|
-
this.blurHandler = this.handleBlur.bind(this);
|
|
64
|
-
// Register event listeners
|
|
65
|
-
window.addEventListener('mousemove', this.mousemoveHandler);
|
|
66
|
-
window.addEventListener('mousedown', this.mousedownHandler);
|
|
67
|
-
window.addEventListener('mouseup', this.mouseupHandler);
|
|
68
|
-
window.addEventListener('blur', this.blurHandler);
|
|
69
|
-
}
|
|
70
|
-
setTransform(transformFn) {
|
|
71
|
-
this.transformFn = transformFn;
|
|
52
|
+
this.canvas.addEventListener('like:mousemoved', this.handleMouseMove.bind(this), { signal: this.abort.signal });
|
|
53
|
+
this.canvas.addEventListener('mousedown', this.handleMouseDown.bind(this), { signal: this.abort.signal });
|
|
54
|
+
window.addEventListener('mouseup', this.handleMouseUp.bind(this), { signal: this.abort.signal });
|
|
55
|
+
this.canvas.addEventListener('wheel', this.handleWheel.bind(this), { passive: false, signal: this.abort.signal });
|
|
56
|
+
this.canvas.addEventListener('mouseleave', () => this.buttons.clear(), { signal: this.abort.signal });
|
|
72
57
|
}
|
|
73
58
|
handleMouseMove(e) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
59
|
+
if (this.isPointerLocked()) {
|
|
60
|
+
/** In pointer-lock mode, simulate a real cursor bounded by the canvas. */
|
|
61
|
+
this.pos = Vec2.clamp(Vec2.add(this.pos, e.detail.delta), [0, 0], e.detail.renderSize);
|
|
62
|
+
this.dispatch('mousemoved', [this.pos, e.detail.delta]);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
/** In non-pointer locked mode, calculate deltas ourselves. */
|
|
66
|
+
this.pos = e.detail.pos;
|
|
67
|
+
this.dispatch('mousemoved', [this.pos, Vec2.sub(this.pos, this.lastPos)]);
|
|
68
|
+
}
|
|
69
|
+
this.lastPos = this.pos;
|
|
79
70
|
}
|
|
80
71
|
handleMouseDown(e) {
|
|
81
|
-
|
|
82
|
-
this.
|
|
72
|
+
// hack: ignore right clicks because they cause a refocus
|
|
73
|
+
if (!this.isPointerLocked() && e.button == 2)
|
|
74
|
+
return;
|
|
75
|
+
this.buttons.add(numToButton(e.button));
|
|
76
|
+
this.dispatch('mousepressed', [[e.offsetX, e.offsetY], numToButton(e.button)]);
|
|
77
|
+
this.canvas?.focus();
|
|
83
78
|
}
|
|
84
79
|
handleMouseUp(e) {
|
|
85
|
-
this.buttons.delete(e.button
|
|
86
|
-
this.
|
|
80
|
+
this.buttons.delete(numToButton(e.button));
|
|
81
|
+
this.dispatch('mousereleased', [[e.offsetX, e.offsetY], numToButton(e.button)]);
|
|
87
82
|
}
|
|
88
|
-
|
|
89
|
-
|
|
83
|
+
handleWheel(e) {
|
|
84
|
+
e.preventDefault();
|
|
90
85
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
window.removeEventListener('mousedown', this.mousedownHandler);
|
|
94
|
-
window.removeEventListener('mouseup', this.mouseupHandler);
|
|
95
|
-
window.removeEventListener('blur', this.blurHandler);
|
|
96
|
-
this.buttons.clear();
|
|
86
|
+
_dispose() {
|
|
87
|
+
this.abort.abort();
|
|
97
88
|
}
|
|
89
|
+
/** Mouse position, transformed to canvas pixels. */
|
|
98
90
|
getPosition() {
|
|
99
|
-
|
|
100
|
-
return this.transformFn(this.x, this.y);
|
|
101
|
-
}
|
|
102
|
-
return [this.x, this.y];
|
|
103
|
-
}
|
|
104
|
-
getX() {
|
|
105
|
-
return this.x;
|
|
106
|
-
}
|
|
107
|
-
getY() {
|
|
108
|
-
return this.y;
|
|
91
|
+
return this.pos;
|
|
109
92
|
}
|
|
93
|
+
/** Check if button is held. Button 1 = left, 2 = middle, 3 = right. */
|
|
110
94
|
isDown(button) {
|
|
111
95
|
return this.buttons.has(button);
|
|
112
96
|
}
|
|
97
|
+
/** All currently held buttons. */
|
|
113
98
|
getPressedButtons() {
|
|
114
99
|
return new Set(this.buttons);
|
|
115
100
|
}
|
|
116
|
-
|
|
117
|
-
|
|
101
|
+
/** True when pointer is locked to canvas.
|
|
102
|
+
* {@link lockPointer}
|
|
103
|
+
*/
|
|
104
|
+
isPointerLocked() {
|
|
105
|
+
return document.pointerLockElement !== null;
|
|
118
106
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
107
|
+
/**
|
|
108
|
+
* Whether to lock (capture) the pointer.
|
|
109
|
+
*
|
|
110
|
+
* In locked mode, the cursor cannot escape the canvas.
|
|
111
|
+
* It also becomes invisible.
|
|
112
|
+
*
|
|
113
|
+
* Note that event {@link mousemoved} passes both
|
|
114
|
+
* `pos` and `delta`.
|
|
115
|
+
*
|
|
116
|
+
* Though the emulated cursor in locked mode
|
|
117
|
+
* (locked mode doesn't natively track absolute position)
|
|
118
|
+
* may be stuck on canvas edges, the `delta` field always
|
|
119
|
+
* represents mouse movement.
|
|
120
|
+
*/
|
|
121
|
+
lockPointer(locked) {
|
|
122
|
+
if (!this.canvas)
|
|
123
|
+
return;
|
|
124
|
+
if (locked && document.pointerLockElement !== this.canvas) {
|
|
125
|
+
this.canvas.requestPointerLock();
|
|
122
126
|
}
|
|
123
|
-
else if (
|
|
127
|
+
else if (!locked && document.pointerLockElement === this.canvas) {
|
|
124
128
|
document.exitPointerLock();
|
|
125
129
|
}
|
|
126
130
|
}
|
|
127
|
-
|
|
128
|
-
|
|
131
|
+
/** Show or hide cursor. Unlike pointer lock, cursor can still leave canvas. */
|
|
132
|
+
showCursor(visible) {
|
|
133
|
+
this.cursorVisible = visible;
|
|
134
|
+
if (this.canvas) {
|
|
135
|
+
this.canvas.style.cursor = visible ? 'auto' : 'none';
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/** Current cursor visibility state. */
|
|
139
|
+
isCursorVisible() {
|
|
140
|
+
return this.cursorVisible;
|
|
129
141
|
}
|
|
130
142
|
}
|
package/dist/core/timer.d.ts
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
|
-
export declare class
|
|
1
|
+
export declare class TimerInternal {
|
|
2
2
|
private currentDelta;
|
|
3
3
|
private totalTime;
|
|
4
4
|
private frameCount;
|
|
5
5
|
private fps;
|
|
6
6
|
private fpsAccumulator;
|
|
7
7
|
private sleepUntil;
|
|
8
|
-
|
|
9
|
-
update(dt: number): void;
|
|
10
|
-
resetSceneTime(): void;
|
|
8
|
+
_update(dt: number): void;
|
|
11
9
|
getDelta(): number;
|
|
12
10
|
getFPS(): number;
|
|
13
11
|
getTime(): number;
|
|
14
|
-
getSceneTime(): number;
|
|
15
12
|
isSleeping(): boolean;
|
|
16
13
|
sleep(duration: number): void;
|
|
17
14
|
}
|
package/dist/core/timer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timer.d.ts","sourceRoot":"","sources":["../../src/core/timer.ts"],"names":[],"mappings":"AAAA,qBAAa,
|
|
1
|
+
{"version":3,"file":"timer.d.ts","sourceRoot":"","sources":["../../src/core/timer.ts"],"names":[],"mappings":"AAAA,qBAAa,aAAa;IACxB,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,GAAG,CAAK;IAChB,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,UAAU,CAAuB;IAEzC,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAazB,QAAQ,IAAI,MAAM;IAIlB,MAAM,IAAI,MAAM;IAIhB,OAAO,IAAI,MAAM;IAIjB,UAAU,IAAI,OAAO;IAUrB,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;CAG9B"}
|
package/dist/core/timer.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export class
|
|
1
|
+
export class TimerInternal {
|
|
2
2
|
constructor() {
|
|
3
3
|
Object.defineProperty(this, "currentDelta", {
|
|
4
4
|
enumerable: true,
|
|
@@ -36,14 +36,8 @@ export class Timer {
|
|
|
36
36
|
writable: true,
|
|
37
37
|
value: null
|
|
38
38
|
});
|
|
39
|
-
Object.defineProperty(this, "sceneStartTime", {
|
|
40
|
-
enumerable: true,
|
|
41
|
-
configurable: true,
|
|
42
|
-
writable: true,
|
|
43
|
-
value: 0
|
|
44
|
-
});
|
|
45
39
|
}
|
|
46
|
-
|
|
40
|
+
_update(dt) {
|
|
47
41
|
this.currentDelta = dt;
|
|
48
42
|
this.totalTime += dt;
|
|
49
43
|
this.frameCount++;
|
|
@@ -54,9 +48,6 @@ export class Timer {
|
|
|
54
48
|
this.fpsAccumulator = 0;
|
|
55
49
|
}
|
|
56
50
|
}
|
|
57
|
-
resetSceneTime() {
|
|
58
|
-
this.sceneStartTime = this.totalTime;
|
|
59
|
-
}
|
|
60
51
|
getDelta() {
|
|
61
52
|
return this.currentDelta;
|
|
62
53
|
}
|
|
@@ -66,9 +57,6 @@ export class Timer {
|
|
|
66
57
|
getTime() {
|
|
67
58
|
return this.totalTime;
|
|
68
59
|
}
|
|
69
|
-
getSceneTime() {
|
|
70
|
-
return this.totalTime - this.sceneStartTime;
|
|
71
|
-
}
|
|
72
60
|
isSleeping() {
|
|
73
61
|
if (this.sleepUntil === null)
|
|
74
62
|
return false;
|
package/dist/engine.d.ts
CHANGED
|
@@ -1,20 +1,70 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @module engine
|
|
3
|
+
* @description Core game engine - lifecycle management and event dispatch.
|
|
4
|
+
*
|
|
5
|
+
* You've reached the most evil part of the codebase -- the man
|
|
6
|
+
* behind the curtain.
|
|
7
|
+
*
|
|
8
|
+
* The secret force gluing everything together.
|
|
9
|
+
*
|
|
10
|
+
* If you want to use modules independently, look here first.
|
|
11
|
+
*
|
|
12
|
+
* ## Memory Management
|
|
13
|
+
*
|
|
14
|
+
* Always call `dispose()` when destroying an engine instance:
|
|
15
|
+
* - Removes all event listeners
|
|
16
|
+
* - Stops the game loop
|
|
17
|
+
* - Removes canvas from DOM
|
|
18
|
+
* - Cleans up canvas manager resources
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
import type { LikeInternal } from './core/like';
|
|
22
|
+
export type EngineDispatch = Engine["dispatch"];
|
|
23
|
+
/**
|
|
24
|
+
* Core game engine managing the event loop and subsystems.
|
|
25
|
+
*
|
|
26
|
+
* Normally you don't instantiate this directly - use {@link createLike} instead.
|
|
27
|
+
* The Engine class is exposed for advanced use cases like testing or
|
|
28
|
+
* custom initialization sequences.
|
|
29
|
+
*
|
|
30
|
+
* All subsystems are accessible via the {@link like} property.
|
|
31
|
+
*/
|
|
4
32
|
export declare class Engine {
|
|
33
|
+
private container;
|
|
5
34
|
private canvas;
|
|
6
|
-
private ctx;
|
|
7
35
|
private isRunning;
|
|
8
36
|
private lastTime;
|
|
9
|
-
private
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
37
|
+
private abort;
|
|
38
|
+
/**
|
|
39
|
+
* The Like interface providing access to all engine subsystems.
|
|
40
|
+
* This object is passed to all scene callbacks and game code.
|
|
41
|
+
*/
|
|
42
|
+
readonly like: LikeInternal;
|
|
14
43
|
constructor(container: HTMLElement);
|
|
15
44
|
private dispatch;
|
|
16
|
-
|
|
17
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Start the game loop.
|
|
47
|
+
*
|
|
48
|
+
* @remarks
|
|
49
|
+
* This method:
|
|
50
|
+
* 1. Dispatches the initial `load` event
|
|
51
|
+
* 2. Starts the requestAnimationFrame loop
|
|
52
|
+
*
|
|
53
|
+
* The engine runs until dispose() is called.
|
|
54
|
+
*/
|
|
55
|
+
start(): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Clean up all resources and stop the engine.
|
|
58
|
+
*
|
|
59
|
+
* @remarks
|
|
60
|
+
* This method:
|
|
61
|
+
* - Stops the game loop
|
|
62
|
+
* - Removes all event listeners (keyboard, mouse, window, fullscreen)
|
|
63
|
+
* - Disposes canvas manager (removes resize observer)
|
|
64
|
+
* - Removes the canvas element from the DOM
|
|
65
|
+
*
|
|
66
|
+
* The engine cannot be restarted after disposal - create a new instance.
|
|
67
|
+
*/
|
|
18
68
|
dispose(): void;
|
|
19
69
|
}
|
|
20
70
|
//# sourceMappingURL=engine.d.ts.map
|
package/dist/engine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAUH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIhD,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;AAEhD;;;;;;;;GAQG;AACH,qBAAa,MAAM;IAaL,OAAO,CAAC,SAAS;IAZ7B,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAK;IAErB,OAAO,CAAC,KAAK,CAAyB;IAEtC;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;gBAER,SAAS,EAAE,WAAW;IAmD1C,OAAO,CAAC,QAAQ;IAShB;;;;;;;;;OASG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA4B5B;;;;;;;;;;;OAWG;IACH,OAAO,IAAI,IAAI;CAahB"}
|
package/dist/engine.js
CHANGED
|
@@ -1,20 +1,50 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @module engine
|
|
3
|
+
* @description Core game engine - lifecycle management and event dispatch.
|
|
4
|
+
*
|
|
5
|
+
* You've reached the most evil part of the codebase -- the man
|
|
6
|
+
* behind the curtain.
|
|
7
|
+
*
|
|
8
|
+
* The secret force gluing everything together.
|
|
9
|
+
*
|
|
10
|
+
* If you want to use modules independently, look here first.
|
|
11
|
+
*
|
|
12
|
+
* ## Memory Management
|
|
13
|
+
*
|
|
14
|
+
* Always call `dispose()` when destroying an engine instance:
|
|
15
|
+
* - Removes all event listeners
|
|
16
|
+
* - Stops the game loop
|
|
17
|
+
* - Removes canvas from DOM
|
|
18
|
+
* - Cleans up canvas manager resources
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
import { AudioInternal } from './core/audio';
|
|
22
|
+
import { InputInternal } from './core/input';
|
|
23
|
+
import { TimerInternal } from './core/timer';
|
|
24
|
+
import { KeyboardInternal } from './core/keyboard';
|
|
25
|
+
import { MouseInternal } from './core/mouse';
|
|
26
|
+
import { GamepadInternal } from './core/gamepad';
|
|
27
|
+
import { bindGraphics } from './core/graphics';
|
|
28
|
+
import { CanvasInternal } from './core/canvas';
|
|
29
|
+
import { sceneDispatch } from './scene';
|
|
30
|
+
/**
|
|
31
|
+
* Core game engine managing the event loop and subsystems.
|
|
32
|
+
*
|
|
33
|
+
* Normally you don't instantiate this directly - use {@link createLike} instead.
|
|
34
|
+
* The Engine class is exposed for advanced use cases like testing or
|
|
35
|
+
* custom initialization sequences.
|
|
36
|
+
*
|
|
37
|
+
* All subsystems are accessible via the {@link like} property.
|
|
38
|
+
*/
|
|
9
39
|
export class Engine {
|
|
10
40
|
constructor(container) {
|
|
11
|
-
Object.defineProperty(this, "
|
|
41
|
+
Object.defineProperty(this, "container", {
|
|
12
42
|
enumerable: true,
|
|
13
43
|
configurable: true,
|
|
14
44
|
writable: true,
|
|
15
|
-
value:
|
|
45
|
+
value: container
|
|
16
46
|
});
|
|
17
|
-
Object.defineProperty(this, "
|
|
47
|
+
Object.defineProperty(this, "canvas", {
|
|
18
48
|
enumerable: true,
|
|
19
49
|
configurable: true,
|
|
20
50
|
writable: true,
|
|
@@ -32,54 +62,38 @@ export class Engine {
|
|
|
32
62
|
writable: true,
|
|
33
63
|
value: 0
|
|
34
64
|
});
|
|
35
|
-
Object.defineProperty(this, "
|
|
36
|
-
enumerable: true,
|
|
37
|
-
configurable: true,
|
|
38
|
-
writable: true,
|
|
39
|
-
value: void 0
|
|
40
|
-
});
|
|
41
|
-
Object.defineProperty(this, "canvasManager", {
|
|
42
|
-
enumerable: true,
|
|
43
|
-
configurable: true,
|
|
44
|
-
writable: true,
|
|
45
|
-
value: void 0
|
|
46
|
-
});
|
|
47
|
-
Object.defineProperty(this, "handleEvent", {
|
|
65
|
+
Object.defineProperty(this, "abort", {
|
|
48
66
|
enumerable: true,
|
|
49
67
|
configurable: true,
|
|
50
68
|
writable: true,
|
|
51
|
-
value:
|
|
52
|
-
});
|
|
53
|
-
Object.defineProperty(this, "currentScene", {
|
|
54
|
-
enumerable: true,
|
|
55
|
-
configurable: true,
|
|
56
|
-
writable: true,
|
|
57
|
-
value: null
|
|
69
|
+
value: new AbortController()
|
|
58
70
|
});
|
|
71
|
+
/**
|
|
72
|
+
* The Like interface providing access to all engine subsystems.
|
|
73
|
+
* This object is passed to all scene callbacks and game code.
|
|
74
|
+
*/
|
|
59
75
|
Object.defineProperty(this, "like", {
|
|
60
76
|
enumerable: true,
|
|
61
77
|
configurable: true,
|
|
62
78
|
writable: true,
|
|
63
79
|
value: void 0
|
|
64
80
|
});
|
|
65
|
-
this.canvas =
|
|
66
|
-
this.canvas.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
this.container
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
const gamepad = new Gamepad();
|
|
82
|
-
const input = new Input({ keyboard, mouse, gamepad });
|
|
81
|
+
this.canvas = new CanvasInternal(this.dispatch.bind(this));
|
|
82
|
+
const canvas = this.canvas._displayCanvas;
|
|
83
|
+
canvas.addEventListener("like:updateRenderTarget", (event) => {
|
|
84
|
+
if (!(event instanceof CustomEvent))
|
|
85
|
+
return;
|
|
86
|
+
this.like.gfx = bindGraphics(event.detail.target.getContext('2d'));
|
|
87
|
+
});
|
|
88
|
+
this.container.appendChild(canvas);
|
|
89
|
+
let gfx = bindGraphics(canvas.getContext('2d'));
|
|
90
|
+
const dispatch = this.dispatch.bind(this);
|
|
91
|
+
const audio = new AudioInternal();
|
|
92
|
+
const timer = new TimerInternal();
|
|
93
|
+
const keyboard = new KeyboardInternal(canvas, dispatch);
|
|
94
|
+
const mouse = new MouseInternal(canvas, dispatch);
|
|
95
|
+
const gamepad = new GamepadInternal(dispatch);
|
|
96
|
+
const input = new InputInternal({ keyboard, mouse, gamepad });
|
|
83
97
|
this.like = {
|
|
84
98
|
audio,
|
|
85
99
|
timer,
|
|
@@ -88,61 +102,50 @@ export class Engine {
|
|
|
88
102
|
mouse,
|
|
89
103
|
gamepad,
|
|
90
104
|
gfx,
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
105
|
+
canvas: this.canvas,
|
|
106
|
+
start: this.start.bind(this),
|
|
107
|
+
dispose: this.dispose.bind(this),
|
|
94
108
|
setScene: (scene) => {
|
|
95
|
-
|
|
96
|
-
|
|
109
|
+
if (scene) {
|
|
110
|
+
this.like.handleEvent = (event) => sceneDispatch(scene, this.like, event);
|
|
111
|
+
this.dispatch("load", []);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
this.like.handleEvent = undefined;
|
|
115
|
+
}
|
|
97
116
|
},
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
};
|
|
102
|
-
mouse.onMouseEvent = (clientX, clientY, button, type) => {
|
|
103
|
-
const [x, y] = this.canvasManager.transformMousePosition(clientX, clientY);
|
|
104
|
-
this.dispatch(type === 'mousedown' ? 'mousepressed' : 'mousereleased', [x, y, (button ?? 0) + 1]);
|
|
105
|
-
};
|
|
106
|
-
gamepad.onButtonEvent = (gpIndex, buttonIndex, buttonName, pressed) => {
|
|
107
|
-
this.dispatch(pressed ? 'gamepadpressed' : 'gamepadreleased', [gpIndex, buttonIndex, buttonName]);
|
|
108
|
-
};
|
|
109
|
-
this.canvasManager.onResize = (size, pixelSize, fullscreen) => {
|
|
110
|
-
this.dispatch('resize', [size, pixelSize, fullscreen]);
|
|
111
|
-
};
|
|
112
|
-
document.addEventListener('fullscreenchange', () => {
|
|
113
|
-
const mode = this.canvasManager.getMode();
|
|
114
|
-
const isFullscreen = !!document.fullscreenElement;
|
|
115
|
-
if (mode.fullscreen !== isFullscreen) {
|
|
116
|
-
this.canvasManager.setMode({ ...mode, fullscreen: isFullscreen });
|
|
117
|
+
callOwnHandlers: (event) => {
|
|
118
|
+
if (event.type in this.like)
|
|
119
|
+
this.like[event.type](...event.args);
|
|
117
120
|
}
|
|
118
|
-
}
|
|
121
|
+
};
|
|
122
|
+
window.addEventListener('focus', () => this.dispatch('focus', ['tab']));
|
|
123
|
+
window.addEventListener('blur', () => this.dispatch('blur', ['tab']));
|
|
124
|
+
canvas.addEventListener('focus', () => this.dispatch('focus', ['canvas']));
|
|
125
|
+
canvas.addEventListener('focus', () => this.dispatch('focus', ['canvas']));
|
|
119
126
|
}
|
|
120
127
|
dispatch(type, args) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (this.currentScene) {
|
|
125
|
-
this.currentScene.handleEvent?.(this.like, event);
|
|
126
|
-
const method = this.currentScene[event.type];
|
|
127
|
-
method?.call(this.currentScene, this.like, ...args);
|
|
128
|
+
const event = { type, args, timestamp: this.like.timer.getTime() };
|
|
129
|
+
if (this.like.handleEvent) {
|
|
130
|
+
this.like.handleEvent(event);
|
|
128
131
|
}
|
|
129
132
|
else {
|
|
130
|
-
this.
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
setMode(mode) {
|
|
134
|
-
const currentMode = this.canvasManager.getMode();
|
|
135
|
-
const mergedMode = { ...currentMode, ...mode };
|
|
136
|
-
if (mode.fullscreen !== undefined && mode.fullscreen !== currentMode.fullscreen) {
|
|
137
|
-
mergedMode.fullscreen ? this.container.requestFullscreen().catch(console.error) : document.exitFullscreen();
|
|
133
|
+
this.like.callOwnHandlers(event);
|
|
138
134
|
}
|
|
139
|
-
this.canvasManager.setMode(mode);
|
|
140
135
|
}
|
|
141
|
-
|
|
142
|
-
|
|
136
|
+
/**
|
|
137
|
+
* Start the game loop.
|
|
138
|
+
*
|
|
139
|
+
* @remarks
|
|
140
|
+
* This method:
|
|
141
|
+
* 1. Dispatches the initial `load` event
|
|
142
|
+
* 2. Starts the requestAnimationFrame loop
|
|
143
|
+
*
|
|
144
|
+
* The engine runs until dispose() is called.
|
|
145
|
+
*/
|
|
146
|
+
async start() {
|
|
143
147
|
this.isRunning = true;
|
|
144
148
|
this.lastTime = performance.now();
|
|
145
|
-
await this.like.gamepad.init();
|
|
146
149
|
const loop = () => {
|
|
147
150
|
if (!this.isRunning)
|
|
148
151
|
return;
|
|
@@ -150,27 +153,41 @@ export class Engine {
|
|
|
150
153
|
const dt = (now - this.lastTime) / 1000;
|
|
151
154
|
this.lastTime = now;
|
|
152
155
|
if (!this.like.timer.isSleeping()) {
|
|
153
|
-
this.like.timer.
|
|
154
|
-
const { pressed, released } = this.like.input.
|
|
156
|
+
this.like.timer._update(dt);
|
|
157
|
+
const { pressed, released } = this.like.input._update();
|
|
155
158
|
pressed.forEach(action => this.dispatch('actionpressed', [action]));
|
|
156
159
|
released.forEach(action => this.dispatch('actionreleased', [action]));
|
|
157
160
|
this.dispatch('update', [dt]);
|
|
158
161
|
}
|
|
159
162
|
this.dispatch('draw', []);
|
|
160
|
-
this.
|
|
163
|
+
this.canvas._present();
|
|
161
164
|
requestAnimationFrame(loop);
|
|
162
165
|
};
|
|
163
166
|
this.dispatch('load', []);
|
|
164
167
|
requestAnimationFrame(loop);
|
|
165
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Clean up all resources and stop the engine.
|
|
171
|
+
*
|
|
172
|
+
* @remarks
|
|
173
|
+
* This method:
|
|
174
|
+
* - Stops the game loop
|
|
175
|
+
* - Removes all event listeners (keyboard, mouse, window, fullscreen)
|
|
176
|
+
* - Disposes canvas manager (removes resize observer)
|
|
177
|
+
* - Removes the canvas element from the DOM
|
|
178
|
+
*
|
|
179
|
+
* The engine cannot be restarted after disposal - create a new instance.
|
|
180
|
+
*/
|
|
166
181
|
dispose() {
|
|
182
|
+
const canvas = this.canvas._displayCanvas;
|
|
167
183
|
this.isRunning = false;
|
|
168
|
-
this.like.keyboard.
|
|
169
|
-
this.like.mouse.
|
|
170
|
-
this.like.gamepad.
|
|
171
|
-
this.
|
|
172
|
-
|
|
173
|
-
|
|
184
|
+
this.like.keyboard._dispose();
|
|
185
|
+
this.like.mouse._dispose();
|
|
186
|
+
this.like.gamepad._dispose();
|
|
187
|
+
this.canvas._dispose();
|
|
188
|
+
this.abort.abort();
|
|
189
|
+
if (canvas.parentNode === this.container) {
|
|
190
|
+
this.container.removeChild(canvas);
|
|
174
191
|
}
|
|
175
192
|
}
|
|
176
193
|
}
|