like2d 2.12.0 → 2.13.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 +20 -30
- package/assets/logo-banner-optimized.svg +1 -1
- package/dist/engine.d.ts +13 -7
- package/dist/engine.js +21 -46
- package/dist/events.d.ts +61 -59
- package/dist/graphics/graphics.d.ts +5 -4
- package/dist/graphics/graphics.js +3 -3
- package/dist/graphics/index.d.ts +1 -1
- package/dist/index.d.ts +3 -4
- package/dist/index.js +1 -2
- package/dist/input/gamepad.d.ts +2 -2
- package/dist/input/gamepad.js +2 -2
- package/dist/input/mouse.d.ts +8 -8
- package/dist/input/mouse.js +8 -8
- package/dist/like.d.ts +45 -67
- package/dist/math/rect.d.ts +1 -0
- package/dist/math/rect.js +1 -0
- package/dist/math/vector2.d.ts +3 -0
- package/dist/math/vector2.js +3 -0
- package/dist/scene/index.d.ts +368 -0
- package/dist/scene/index.js +204 -0
- package/dist/scene/prefab/fadeTransition.d.ts +25 -0
- package/dist/scene/prefab/fadeTransition.js +55 -0
- package/dist/scene/prefab/mapGamepad.d.ts +47 -0
- package/dist/scene/prefab/mapGamepad.js +189 -0
- package/dist/scene/prefab/startScreen.d.ts +47 -0
- package/dist/{prefab-scenes → scene/prefab}/startScreen.js +25 -84
- package/package.json +11 -6
- package/dist/prefab-scenes/index.d.ts +0 -10
- package/dist/prefab-scenes/index.js +0 -9
- package/dist/prefab-scenes/mapGamepad.d.ts +0 -42
- package/dist/prefab-scenes/mapGamepad.js +0 -199
- package/dist/prefab-scenes/startScreen.d.ts +0 -58
- package/dist/scene.d.ts +0 -143
- package/dist/scene.js +0 -23
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scenes are a modular component of LÏKE based on setting `like.handleEvent`.
|
|
3
|
+
* The scene system is simple and powerful, once understood.
|
|
4
|
+
*
|
|
5
|
+
* For devs using the built-in callback pattern, they're an easy way
|
|
6
|
+
* to stack functionality on to the current project such as
|
|
7
|
+
* gamepad mapping or debug overlays.
|
|
8
|
+
*
|
|
9
|
+
* For multi-scene games, they codify a common state-management pattern based
|
|
10
|
+
* on switching between (or nesting) event handler callbacks. It's
|
|
11
|
+
* a lot better than switch-casing on each handler, or manually setting/clearing
|
|
12
|
+
* handler functions on each transition.
|
|
13
|
+
*
|
|
14
|
+
* Using scenes for your game also replaces the need to pass around global `like`
|
|
15
|
+
* or `sceneManager` wherever it is used.
|
|
16
|
+
*
|
|
17
|
+
* ## Jump in
|
|
18
|
+
*
|
|
19
|
+
* Get started: {@link SceneManager}
|
|
20
|
+
*
|
|
21
|
+
* Make your own scene: {@link Scene}
|
|
22
|
+
*
|
|
23
|
+
* Check out built-in utility scenes:
|
|
24
|
+
* - {@link scene/prefab/mapGamepad}
|
|
25
|
+
* - {@link scene/prefab/startScreen}
|
|
26
|
+
*
|
|
27
|
+
* @module scene
|
|
28
|
+
*/
|
|
29
|
+
import { likeDispatch } from '../engine';
|
|
30
|
+
/**
|
|
31
|
+
* Scenemanager is the entry point for the LÏKE scene system.
|
|
32
|
+
* Without it, there are no scene functions; it's entirely modular.
|
|
33
|
+
*
|
|
34
|
+
* Usage:
|
|
35
|
+
* ```typescript
|
|
36
|
+
* const like = createLike(document.body);
|
|
37
|
+
* const sceneMan = new SceneManager(like);
|
|
38
|
+
* sceneMan.push(myScene)
|
|
39
|
+
* ```
|
|
40
|
+
* For arbitrary scene management (non stack based),
|
|
41
|
+
* just use {@link SceneManager.set} which switches out the stack top.
|
|
42
|
+
*
|
|
43
|
+
* For stack-based, use {@link SceneManager.push} and {@link SceneManager.pop}.
|
|
44
|
+
* Note that for stack-based games, it is wise to put the first initialization in as
|
|
45
|
+
* a callback-based system rather than going straight to scene. This allows
|
|
46
|
+
* for easy resets / game overs.
|
|
47
|
+
*
|
|
48
|
+
* Note that the SceneManager sets {@link LikeHandlers.handleEvent | like.handleEvent}.
|
|
49
|
+
* To get rid of scene functionality entirely, simply set it back to default.
|
|
50
|
+
* ```typescript
|
|
51
|
+
* like.handleEvent = undefined;
|
|
52
|
+
* ```
|
|
53
|
+
* Otherwise, the `SceneManager` stays allocated even if the scene stack was cleared.
|
|
54
|
+
*/
|
|
55
|
+
export class SceneManager {
|
|
56
|
+
constructor(like) {
|
|
57
|
+
Object.defineProperty(this, "like", {
|
|
58
|
+
enumerable: true,
|
|
59
|
+
configurable: true,
|
|
60
|
+
writable: true,
|
|
61
|
+
value: like
|
|
62
|
+
});
|
|
63
|
+
Object.defineProperty(this, "scenes", {
|
|
64
|
+
enumerable: true,
|
|
65
|
+
configurable: true,
|
|
66
|
+
writable: true,
|
|
67
|
+
value: []
|
|
68
|
+
});
|
|
69
|
+
like.handleEvent = this.handleEvent.bind(this);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get the current scene, or a specific index.
|
|
73
|
+
*
|
|
74
|
+
* Uses `Array.at` under the hood, so -1 is the
|
|
75
|
+
* top scene, -2 is the parent scene, etc.
|
|
76
|
+
*
|
|
77
|
+
* During instantiation, the stack is not shifted
|
|
78
|
+
* relative to during event/lifecycle functions.
|
|
79
|
+
* The only difference is that during load,
|
|
80
|
+
* scene.get(-1) of course returns no value.
|
|
81
|
+
*/
|
|
82
|
+
get(pos = -1) {
|
|
83
|
+
return this.scenes.at(pos)?.instance;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Set the current scene at the top of the scene stack.
|
|
87
|
+
* If the stack is empty, push it onto the stack.
|
|
88
|
+
*
|
|
89
|
+
* The new scene is instantiated after the old one is
|
|
90
|
+
* quit and removed from the stack.
|
|
91
|
+
*
|
|
92
|
+
* Set cannot clear the current scene; for that use {@link pop}.
|
|
93
|
+
*
|
|
94
|
+
* @param scene is a Scene (factory pattern).
|
|
95
|
+
* @param instance is an optional preloaded instance.
|
|
96
|
+
*/
|
|
97
|
+
set(scene, instance) {
|
|
98
|
+
const idx = Math.max(0, this.scenes.length - 1);
|
|
99
|
+
this.deinstance(-1);
|
|
100
|
+
this.scenes[idx] = { instance, factory: scene };
|
|
101
|
+
this.scenes[idx].instance = instance ?? this.instantiate(scene);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Push a scene to the scene stack and run it.
|
|
105
|
+
*
|
|
106
|
+
* @param scene A function that creates and returns a scene instance, which is just event handlers.
|
|
107
|
+
*
|
|
108
|
+
* @param unload
|
|
109
|
+
*
|
|
110
|
+
* If a scene calls `scenes.push(nextScene, true)`, it will be unloaded
|
|
111
|
+
* and re-constructed upon the parent scene calling `scenes.pop()`.
|
|
112
|
+
* Good for resource-intensive
|
|
113
|
+
* scenes or ones that rely heavily on their lifecycle. If you do want
|
|
114
|
+
* the lower scene to know what happened in the upper while unloaded, (i.e. overworld
|
|
115
|
+
* updating with a battle), consider using scene composition instead, or
|
|
116
|
+
* using localStorage to track persistent game state.
|
|
117
|
+
*
|
|
118
|
+
* If a scene calls `scenes.push(nextScene, false)`, it will stay loaded:
|
|
119
|
+
* this means when we pop its parent, it will simply continue running, though
|
|
120
|
+
* `load` will be called. Assets will of course stay loaded in during that time.
|
|
121
|
+
*
|
|
122
|
+
* Further, with unload disabled the upper scene now has the ability to reference
|
|
123
|
+
* the instance that called `scene.push` and call down to it in a generic way
|
|
124
|
+
* via `scene.get(-2)`
|
|
125
|
+
*
|
|
126
|
+
* See {@link Scene} for more detail -- while stacking is good for certain
|
|
127
|
+
* things, you're likely looking for Scene Composition.
|
|
128
|
+
*
|
|
129
|
+
*/
|
|
130
|
+
push(scene, unload) {
|
|
131
|
+
if (unload) {
|
|
132
|
+
this.deinstance(-1);
|
|
133
|
+
}
|
|
134
|
+
const entry = { instance: undefined, factory: scene };
|
|
135
|
+
this.scenes.push(entry);
|
|
136
|
+
entry.instance = this.instantiate(entry.factory);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Pop the current scene off the stack, calling `quit` on it and
|
|
140
|
+
* dropping the instance reference.
|
|
141
|
+
*
|
|
142
|
+
* If the lower scene had called `pushScene` with the second arg (unload)
|
|
143
|
+
* set to true, it will be re-loaded. Otherwise it will continue where it
|
|
144
|
+
* left off. Either way its `load` fill fire.
|
|
145
|
+
*
|
|
146
|
+
* To clear the stack, just run:
|
|
147
|
+
* ```ts
|
|
148
|
+
* while (like.popScene());
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
pop() {
|
|
152
|
+
this.deinstance(-1);
|
|
153
|
+
const oldTop = this.scenes.pop();
|
|
154
|
+
const top = this.scenes.at(-1);
|
|
155
|
+
if (top) {
|
|
156
|
+
if (!top.instance) {
|
|
157
|
+
top.instance = this.instantiate(top.factory);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return oldTop?.factory;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Make a scene into an instance and dispatch `load` into it.
|
|
164
|
+
*/
|
|
165
|
+
instantiate(scene) {
|
|
166
|
+
const inst = scene(this.like, this);
|
|
167
|
+
likeDispatch(inst, { type: 'load', args: [], timestamp: this.like.timer.getTime() });
|
|
168
|
+
return inst;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Unload a parent scene. Only use this if the lower scene requested to be
|
|
172
|
+
* unloaded, or if you're certain that you want to reload the lower
|
|
173
|
+
* completely. Otherwise, this can easily lose state or break functions.
|
|
174
|
+
*/
|
|
175
|
+
deinstance(pos) {
|
|
176
|
+
const scene = this.scenes.at(pos);
|
|
177
|
+
if (scene) {
|
|
178
|
+
if (scene.instance) {
|
|
179
|
+
likeDispatch(scene.instance, { type: 'quit', args: [], timestamp: this.like.timer.getTime() });
|
|
180
|
+
}
|
|
181
|
+
scene.instance = undefined;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
debugDraw() {
|
|
185
|
+
const g = this.like.gfx;
|
|
186
|
+
for (const si in this.scenes) {
|
|
187
|
+
const i = Number(si);
|
|
188
|
+
g.print('white', `${si}: hasInstance: ${!!this.scenes[i].instance}`, [50, i * 20 + 20]);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
handleEvent(event) {
|
|
192
|
+
const top = this.scenes.at(-1);
|
|
193
|
+
if (top) {
|
|
194
|
+
if (!top.instance) {
|
|
195
|
+
throw new Error("expected top scene to be loaded");
|
|
196
|
+
}
|
|
197
|
+
likeDispatch(top.instance, event);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
this.like.callOwnHandlers(event);
|
|
201
|
+
}
|
|
202
|
+
//if (event.type == 'draw') this.debugDraw();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Need some time to load?
|
|
3
|
+
*
|
|
4
|
+
* Or maybe, jump cuts look like garbage. Well, they do.
|
|
5
|
+
*
|
|
6
|
+
* Here's your remedy:
|
|
7
|
+
* ```ts
|
|
8
|
+
* sceneMan.push(fadeTransition(myNextScene, enableUnload), false);
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
import { Scene } from "..";
|
|
12
|
+
import { ColorNum } from "../../graphics";
|
|
13
|
+
export type FadeProps = Partial<{
|
|
14
|
+
color: ColorNum;
|
|
15
|
+
duration: number;
|
|
16
|
+
}>;
|
|
17
|
+
/**
|
|
18
|
+
* The fade scene factory.
|
|
19
|
+
*
|
|
20
|
+
* @param nextF The next scene (not sceneInstance)
|
|
21
|
+
* @param unload Set this rather than unload in `sceneMan.push`, which will cause early unload.`
|
|
22
|
+
* @options color and duration.
|
|
23
|
+
*/
|
|
24
|
+
export declare const fadeTransition: (nextF: Scene, unload: boolean, options?: FadeProps) => Scene;
|
|
25
|
+
//# sourceMappingURL=fadeTransition.d.ts.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Need some time to load?
|
|
3
|
+
*
|
|
4
|
+
* Or maybe, jump cuts look like garbage. Well, they do.
|
|
5
|
+
*
|
|
6
|
+
* Here's your remedy:
|
|
7
|
+
* ```ts
|
|
8
|
+
* sceneMan.push(fadeTransition(myNextScene, enableUnload), false);
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
import { callOwnHandlers, likeDispatch } from "../..";
|
|
12
|
+
/**
|
|
13
|
+
* The fade scene factory.
|
|
14
|
+
*
|
|
15
|
+
* @param nextF The next scene (not sceneInstance)
|
|
16
|
+
* @param unload Set this rather than unload in `sceneMan.push`, which will cause early unload.`
|
|
17
|
+
* @options color and duration.
|
|
18
|
+
*/
|
|
19
|
+
export const fadeTransition = (nextF, unload, options = {}) => (like, scenes) => {
|
|
20
|
+
const baseColor = options.color ? options.color.slice(0, 3) : [0, 0, 0];
|
|
21
|
+
const duration = options.duration ?? 1;
|
|
22
|
+
const prev = scenes.get(-2);
|
|
23
|
+
const next = scenes.instantiate(nextF); // load next scene early
|
|
24
|
+
let time = 0;
|
|
25
|
+
return {
|
|
26
|
+
// stack: [prev: -3, next: -2, this: -1]
|
|
27
|
+
load() { time = 0; },
|
|
28
|
+
update(dt) { time += dt; },
|
|
29
|
+
handleEvent(ev) {
|
|
30
|
+
let elapsed = time / duration * 2;
|
|
31
|
+
if (ev.type == 'draw') {
|
|
32
|
+
if (elapsed < 1) {
|
|
33
|
+
if (prev)
|
|
34
|
+
likeDispatch(prev, ev);
|
|
35
|
+
like.gfx.clear([...baseColor, elapsed]);
|
|
36
|
+
}
|
|
37
|
+
else if (elapsed < 2) {
|
|
38
|
+
if (unload) {
|
|
39
|
+
// if we're unloading, unload the prev scene now.
|
|
40
|
+
scenes.deinstance(-2);
|
|
41
|
+
unload = false;
|
|
42
|
+
}
|
|
43
|
+
if (next) {
|
|
44
|
+
likeDispatch(next, ev);
|
|
45
|
+
}
|
|
46
|
+
like.gfx.clear([...baseColor, 2 - elapsed]);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
scenes.set(nextF, next);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
callOwnHandlers(this, ev);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An automagical gamepad mapper.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* like.gamepadconnected = (index) =>
|
|
6
|
+
* scenes.push(mapGamepad({ buttons: buttonSetGBA, sticks: 0 }, 0), false)
|
|
7
|
+
* ```
|
|
8
|
+
*
|
|
9
|
+
* Add this to your codebase and activating a gamepad causes a button mapping screen to pop up.
|
|
10
|
+
* It will request to map any buttons not already covered by the automapping database.
|
|
11
|
+
*
|
|
12
|
+
* If you're wondering what `scenes` refers to, check out {@link SceneManager} to
|
|
13
|
+
* get started.
|
|
14
|
+
*
|
|
15
|
+
* Note: many browsers only fire gamepadconnected on first button press, so always writing "P2: press any button" is a fine idea.
|
|
16
|
+
* @module scene/prefab/mapGamepad
|
|
17
|
+
*/
|
|
18
|
+
import type { Scene } from "../";
|
|
19
|
+
import { type LikeButton } from "../../input";
|
|
20
|
+
/** All the buttons on an NES */
|
|
21
|
+
export declare const buttonSetNES: Set<LikeButton>;
|
|
22
|
+
/** All the buttons on a GBA -- Like an NES but with L+R */
|
|
23
|
+
export declare const buttonSetGBA: Set<LikeButton>;
|
|
24
|
+
/** All the buttons on a SNES */
|
|
25
|
+
export declare const buttonSetSNES: Set<LikeButton>;
|
|
26
|
+
/** All the buttons on a PS1 -- Like a SNES but with L2+R2 */
|
|
27
|
+
export declare const buttonSetPS1: Set<LikeButton>;
|
|
28
|
+
/** All the buttons -- including the stick buttons. */
|
|
29
|
+
export declare const buttonSetAll: Set<LikeButton>;
|
|
30
|
+
/** What gamepad buttons or sticks are we using?
|
|
31
|
+
*
|
|
32
|
+
* Set buttons to one of:
|
|
33
|
+
* - {@link buttonSetNES}
|
|
34
|
+
* - {@link buttonSetGBA}
|
|
35
|
+
* - {@link buttonSetSNES}
|
|
36
|
+
* - {@link buttonSetPS1}
|
|
37
|
+
* - {@link buttonSetAll}
|
|
38
|
+
* - Custom: `new Set<LikeButton>(...)`
|
|
39
|
+
*
|
|
40
|
+
*/
|
|
41
|
+
export type MapMode = {
|
|
42
|
+
buttons: Set<LikeButton>;
|
|
43
|
+
stickCount: number;
|
|
44
|
+
};
|
|
45
|
+
/** The gamepad mapping scene factory. Call this and pass it into {@link SceneManager.push} */
|
|
46
|
+
export declare const mapGamepad: (mapMode: MapMode, targetPad: number) => Scene;
|
|
47
|
+
//# sourceMappingURL=mapGamepad.d.ts.map
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An automagical gamepad mapper.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* like.gamepadconnected = (index) =>
|
|
6
|
+
* scenes.push(mapGamepad({ buttons: buttonSetGBA, sticks: 0 }, 0), false)
|
|
7
|
+
* ```
|
|
8
|
+
*
|
|
9
|
+
* Add this to your codebase and activating a gamepad causes a button mapping screen to pop up.
|
|
10
|
+
* It will request to map any buttons not already covered by the automapping database.
|
|
11
|
+
*
|
|
12
|
+
* If you're wondering what `scenes` refers to, check out {@link SceneManager} to
|
|
13
|
+
* get started.
|
|
14
|
+
*
|
|
15
|
+
* Note: many browsers only fire gamepadconnected on first button press, so always writing "P2: press any button" is a fine idea.
|
|
16
|
+
* @module scene/prefab/mapGamepad
|
|
17
|
+
*/
|
|
18
|
+
import { callOwnHandlers, likeDispatch } from "../..";
|
|
19
|
+
import { defaultMapping } from "../../input";
|
|
20
|
+
const mapOrder = [
|
|
21
|
+
"BRight",
|
|
22
|
+
"BBottom",
|
|
23
|
+
"Up",
|
|
24
|
+
"Down",
|
|
25
|
+
"Left",
|
|
26
|
+
"Right",
|
|
27
|
+
"MenuLeft",
|
|
28
|
+
"MenuRight",
|
|
29
|
+
// 8: NES buttons
|
|
30
|
+
"L1",
|
|
31
|
+
"R1",
|
|
32
|
+
// 10: GBA buttons
|
|
33
|
+
"BLeft",
|
|
34
|
+
"BTop",
|
|
35
|
+
// 12: SNES buttons
|
|
36
|
+
"L2",
|
|
37
|
+
"R2",
|
|
38
|
+
// 14: PS1 buttons
|
|
39
|
+
"LeftStick",
|
|
40
|
+
"RightStick",
|
|
41
|
+
];
|
|
42
|
+
/** All the buttons on an NES */
|
|
43
|
+
export const buttonSetNES = new Set(mapOrder.slice(0, 8));
|
|
44
|
+
/** All the buttons on a GBA -- Like an NES but with L+R */
|
|
45
|
+
export const buttonSetGBA = new Set(mapOrder.slice(0, 10));
|
|
46
|
+
/** All the buttons on a SNES */
|
|
47
|
+
export const buttonSetSNES = new Set(mapOrder.slice(0, 12));
|
|
48
|
+
/** All the buttons on a PS1 -- Like a SNES but with L2+R2 */
|
|
49
|
+
export const buttonSetPS1 = new Set(mapOrder.slice(0, 14));
|
|
50
|
+
/** All the buttons -- including the stick buttons. */
|
|
51
|
+
export const buttonSetAll = new Set(mapOrder);
|
|
52
|
+
const drawCircButt = (pos, size) => (like, color) => like.gfx.circle("fill", color, pos, size);
|
|
53
|
+
const drawDpadPart = (rot) => (like, color) => {
|
|
54
|
+
like.gfx.withTransform(() => {
|
|
55
|
+
like.gfx.translate([2.5, 6]);
|
|
56
|
+
like.gfx.rotate(rot);
|
|
57
|
+
like.gfx.rectangle("fill", color, [0.5, -0.5, 1.3, 1]);
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
const drawShoulder = (y, width, flip) => (like, color) => {
|
|
61
|
+
const r = 0.8;
|
|
62
|
+
const rectPos = [5 - width, y];
|
|
63
|
+
const circPos = [5 - width - r, y];
|
|
64
|
+
like.gfx.withTransform(() => {
|
|
65
|
+
if (flip) {
|
|
66
|
+
like.gfx.translate([16, 0]);
|
|
67
|
+
like.gfx.scale([-1, 1]);
|
|
68
|
+
}
|
|
69
|
+
like.gfx.circle("fill", color, circPos, r, { arc: [Math.PI, Math.PI * 3 / 2], center: false });
|
|
70
|
+
like.gfx.rectangle("fill", color, [...rectPos, width, r]);
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
// Buttons assume a centered resolution of 16x9px. Transforms exist for a reason lol.
|
|
74
|
+
// LLLLL . RRRRR
|
|
75
|
+
// LLLLLLLLL . RRRRRRRRR
|
|
76
|
+
// .
|
|
77
|
+
// DDD S . S B
|
|
78
|
+
// -.....DDD.....................................
|
|
79
|
+
// DDD DDD . B B
|
|
80
|
+
// DDD LS . RS
|
|
81
|
+
// DDD . B
|
|
82
|
+
// .
|
|
83
|
+
const buttonProps = {
|
|
84
|
+
BLeft: { draw: drawCircButt([12, 6], 0.8) },
|
|
85
|
+
BRight: { draw: drawCircButt([15, 6], 0.8) },
|
|
86
|
+
BTop: { draw: drawCircButt([13.5, 4.5], 0.8) },
|
|
87
|
+
BBottom: { draw: drawCircButt([13.5, 7.5], 0.8) },
|
|
88
|
+
MenuLeft: { draw: drawCircButt([6, 4], 0.5) },
|
|
89
|
+
MenuRight: { draw: drawCircButt([10, 4], 0.5) },
|
|
90
|
+
LeftStick: { draw: drawCircButt([6.5, 7], 1.4) },
|
|
91
|
+
RightStick: { draw: drawCircButt([9.5, 7], 1.4) },
|
|
92
|
+
L1: { draw: drawShoulder(2, 3, false) },
|
|
93
|
+
L2: { draw: drawShoulder(1, 2, false) },
|
|
94
|
+
R1: { draw: drawShoulder(2, 3, true) },
|
|
95
|
+
R2: { draw: drawShoulder(1, 2, true) },
|
|
96
|
+
Right: { draw: drawDpadPart(0) },
|
|
97
|
+
Up: { draw: drawDpadPart(-Math.PI / 2) },
|
|
98
|
+
Left: { draw: drawDpadPart(Math.PI) },
|
|
99
|
+
Down: { draw: drawDpadPart(Math.PI / 2) },
|
|
100
|
+
};
|
|
101
|
+
/** The gamepad mapping scene factory. Call this and pass it into {@link SceneManager.push} */
|
|
102
|
+
export const mapGamepad = (mapMode, targetPad) => (like, scenes) => {
|
|
103
|
+
const currentlyUnmapped = [];
|
|
104
|
+
const mapping = like.gamepad.getMapping(targetPad) ?? defaultMapping(2);
|
|
105
|
+
const alreadyMapped = new Set();
|
|
106
|
+
let held;
|
|
107
|
+
let frameWait = 10;
|
|
108
|
+
const alreadyMappedValues = new Set(Object.values(mapping.buttons));
|
|
109
|
+
for (const btn of mapOrder.reverse()) {
|
|
110
|
+
if (mapMode.buttons.has(btn) && !alreadyMappedValues.has(btn)) {
|
|
111
|
+
currentlyUnmapped.push(btn);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
handleEvent(ev) {
|
|
116
|
+
if (ev.type == 'draw') {
|
|
117
|
+
const parent = scenes.get(-2);
|
|
118
|
+
if (parent) {
|
|
119
|
+
likeDispatch(parent, ev);
|
|
120
|
+
like.gfx.clear([0, 0, 0, 0.5]);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
like.gfx.clear();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
callOwnHandlers(this, ev);
|
|
127
|
+
},
|
|
128
|
+
update() {
|
|
129
|
+
frameWait--;
|
|
130
|
+
},
|
|
131
|
+
draw() {
|
|
132
|
+
const centerText = {
|
|
133
|
+
font: "1px sans-serif",
|
|
134
|
+
align: "center",
|
|
135
|
+
width: 16,
|
|
136
|
+
};
|
|
137
|
+
const active = currentlyUnmapped.at(-1);
|
|
138
|
+
const csize = like.canvas.getSize();
|
|
139
|
+
like.gfx.scale(csize[0] / 16);
|
|
140
|
+
like.gfx.translate([0, 1]);
|
|
141
|
+
like.gfx.print("white", `Map gamepad ${targetPad}`, [8, 0.0], centerText);
|
|
142
|
+
// draw shadows
|
|
143
|
+
like.gfx.withTransform(() => {
|
|
144
|
+
like.gfx.translate([0.1, 0.1]);
|
|
145
|
+
for (const prop of mapMode.buttons.keys()) {
|
|
146
|
+
buttonProps[prop].draw(like, 'black');
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
for (const prop of mapMode.buttons.keys()) {
|
|
150
|
+
const color = held == prop
|
|
151
|
+
? "green"
|
|
152
|
+
: active == prop
|
|
153
|
+
? "red"
|
|
154
|
+
: mapMode.buttons.has(prop)
|
|
155
|
+
? "white"
|
|
156
|
+
: [0, 0, 0, 0];
|
|
157
|
+
// draw shadows
|
|
158
|
+
buttonProps[prop].draw(like, color);
|
|
159
|
+
}
|
|
160
|
+
like.gfx.print("white", active
|
|
161
|
+
? `Press ${like.gamepad.fullButtonName(active)}!`
|
|
162
|
+
: "Press any button to resume.", [2, 10], { font: "1px sans-serif" });
|
|
163
|
+
},
|
|
164
|
+
gamepadpressed(source, _name, num) {
|
|
165
|
+
if (source !== targetPad || held || frameWait > 0)
|
|
166
|
+
return;
|
|
167
|
+
const active = currentlyUnmapped.pop();
|
|
168
|
+
if (active && !alreadyMapped.has(num)) {
|
|
169
|
+
alreadyMapped.add(num);
|
|
170
|
+
mapping.buttons[num] = active;
|
|
171
|
+
held = active;
|
|
172
|
+
}
|
|
173
|
+
else if (!active) {
|
|
174
|
+
like.gamepad.setMapping(targetPad, mapping);
|
|
175
|
+
setTimeout(() => scenes.pop(), 100);
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
gamepadreleased(source, _name, num) {
|
|
179
|
+
if (source !== targetPad)
|
|
180
|
+
return;
|
|
181
|
+
if (held == mapping.buttons[num]) {
|
|
182
|
+
held = undefined;
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
mousepressed() {
|
|
186
|
+
scenes.pop();
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ## Why
|
|
3
|
+
*
|
|
4
|
+
* 1. Because the LIKE logo looks awesome.
|
|
5
|
+
* 2. Autoplay restriction; modern browers don't let you play audio until the page is clicked.
|
|
6
|
+
* 3. You have to click on the game in order to send inputs, anyway.
|
|
7
|
+
* 4. It's polite.
|
|
8
|
+
*
|
|
9
|
+
* ## Usage
|
|
10
|
+
*
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { createLike, createStartScreen } from 'like';
|
|
13
|
+
* import { createGameScene } from './game';
|
|
14
|
+
*
|
|
15
|
+
* // init LIKE with scenes
|
|
16
|
+
* const container = document.getElementById("myGame");
|
|
17
|
+
* const like = createLike(container);
|
|
18
|
+
* const sceneMan = new SceneManager(like);
|
|
19
|
+
*
|
|
20
|
+
* // these callbacks will be ignored until the scene is clicked
|
|
21
|
+
* like.update = function () { ... }
|
|
22
|
+
* like.draw = function () { ... }
|
|
23
|
+
*
|
|
24
|
+
* // Set up the start screen
|
|
25
|
+
* like.start();
|
|
26
|
+
* sceneMan.push(startScreen())
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* Alternatively, copy-paste this code into your own project and modify it freely.
|
|
30
|
+
*
|
|
31
|
+
* ## Custom Rendering
|
|
32
|
+
*
|
|
33
|
+
* Pass a custom draw function to replace the default logo:
|
|
34
|
+
*
|
|
35
|
+
* ```typescript
|
|
36
|
+
* const startup = startScreen((like) => {
|
|
37
|
+
* like.gfx.clear([0, 0, 0, 1]);
|
|
38
|
+
* like.gfx.print([1, 1, 1], 'Click to Start', [100, 100]);
|
|
39
|
+
* });
|
|
40
|
+
* ```
|
|
41
|
+
* @module scene/prefab/startScreen
|
|
42
|
+
*/
|
|
43
|
+
import type { Scene } from '..';
|
|
44
|
+
import type { Like } from '../..';
|
|
45
|
+
/** The start screen scene factory. Call this and pass it into {@link scene.SceneManager.push} */
|
|
46
|
+
export declare const startScreen: (onDraw?: (like: Like) => void) => Scene;
|
|
47
|
+
//# sourceMappingURL=startScreen.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Vec2 } from '
|
|
1
|
+
import { Vec2 } from '../../math/vector2';
|
|
2
2
|
const LOGO = 'data:image/svg+xml;base64,' +
|
|
3
3
|
'PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCEtLSBDcmVhdGVkIHdpdGgg' +
|
|
4
4
|
'SW5rc2NhcGUgKGh0dHA6Ly93d3cuaW5rc2NhcGUub3JnLykgLS0+Cjxzdmcgd2lkdGg9IjI1Nm1t' +
|
|
@@ -31,87 +31,28 @@ const LOGO = 'data:image/svg+xml;base64,' +
|
|
|
31
31
|
'MyAwLjAzNjk4LThlLTMgLTE1LjM3N3oiLz4KICA8ZWxsaXBzZSBjeD0iMTMyLjk5IiBjeT0iMTYu' +
|
|
32
32
|
'MTIyIiByeD0iNi4wMjIxIiByeT0iNi4xMTgyIi8+CiAgPGVsbGlwc2UgY3g9IjE0Ny40OSIgY3k9' +
|
|
33
33
|
'IjE2LjEyMiIgcng9IjYuMDIyMSIgcnk9IjYuMTE4MiIvPgogPC9nPgo8L3N2Zz4K';
|
|
34
|
-
/**
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
*
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
* like.pushScene(new StartScreen())
|
|
57
|
-
* like.start();
|
|
58
|
-
* ```
|
|
59
|
-
*
|
|
60
|
-
* Alternatively, copy-paste this code into your own project and modify it freely.
|
|
61
|
-
* Update imports:
|
|
62
|
-
*
|
|
63
|
-
* ```ts
|
|
64
|
-
* import { type Scene } from 'like/scene';
|
|
65
|
-
* import type { ImageHandle } from 'like/graphics';
|
|
66
|
-
* import { Vec2 } from 'like/math';
|
|
67
|
-
* import { Like } from 'like';
|
|
68
|
-
* ```
|
|
69
|
-
*
|
|
70
|
-
* ## Custom Rendering
|
|
71
|
-
*
|
|
72
|
-
* Pass a custom draw function to replace the default logo:
|
|
73
|
-
*
|
|
74
|
-
* ```typescript
|
|
75
|
-
* const startup = new StartupScene(gameScene, (like) => {
|
|
76
|
-
* like.gfx.clear([0, 0, 0, 1]);
|
|
77
|
-
* like.gfx.print([1, 1, 1], 'Click to Start', [100, 100]);
|
|
78
|
-
* });
|
|
79
|
-
* ```
|
|
80
|
-
*/
|
|
81
|
-
export class StartScreen {
|
|
82
|
-
constructor(onDraw) {
|
|
83
|
-
Object.defineProperty(this, "onDraw", {
|
|
84
|
-
enumerable: true,
|
|
85
|
-
configurable: true,
|
|
86
|
-
writable: true,
|
|
87
|
-
value: onDraw
|
|
88
|
-
});
|
|
89
|
-
Object.defineProperty(this, "logo", {
|
|
90
|
-
enumerable: true,
|
|
91
|
-
configurable: true,
|
|
92
|
-
writable: true,
|
|
93
|
-
value: void 0
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
load(like) {
|
|
97
|
-
this.logo = like.gfx.newImage(LOGO);
|
|
98
|
-
}
|
|
99
|
-
draw(like) {
|
|
100
|
-
if (this.onDraw) {
|
|
101
|
-
this.onDraw(like);
|
|
34
|
+
/** The start screen scene factory. Call this and pass it into {@link scene.SceneManager.push} */
|
|
35
|
+
export const startScreen = (onDraw) => (like, scenes) => {
|
|
36
|
+
const logo = like.gfx.newImage(LOGO);
|
|
37
|
+
like.mouse.setMode({ lock: false, scrollBlock: false });
|
|
38
|
+
return {
|
|
39
|
+
draw() {
|
|
40
|
+
if (onDraw) {
|
|
41
|
+
onDraw(like);
|
|
42
|
+
}
|
|
43
|
+
else if (logo.isReady()) {
|
|
44
|
+
like.gfx.clear([0.5, 0, 0.5, 1]);
|
|
45
|
+
const winSize = like.canvas.getSize();
|
|
46
|
+
const scale = (winSize[0] * 0.5) / logo.size[0];
|
|
47
|
+
like.gfx.draw(logo, Vec2.div(winSize, 2), {
|
|
48
|
+
scale,
|
|
49
|
+
origin: Vec2.div(logo.size, 2),
|
|
50
|
+
});
|
|
51
|
+
like.gfx.print([1, 1, 0, 0.5 + 0.5 * Math.sin(like.timer.getTime() * 3)], "▶️ click to start ◀️", Vec2.mul(winSize, [0.5, 0.8]), { align: "center", font: "25px sans" });
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
mousepressed() {
|
|
55
|
+
scenes.pop();
|
|
102
56
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const winSize = like.canvas.getSize();
|
|
106
|
-
const scale = (winSize[0] * 0.5) / this.logo.size[0];
|
|
107
|
-
like.gfx.draw(this.logo, Vec2.div(winSize, 2), {
|
|
108
|
-
scale,
|
|
109
|
-
origin: Vec2.div(this.logo.size, 2),
|
|
110
|
-
});
|
|
111
|
-
like.gfx.print([1, 1, 0, 0.5 + 0.5 * Math.sin(like.timer.getTime() * 3)], "▶️ click to start ◀️", Vec2.mul(winSize, [0.5, 0.8]), { align: "center", font: "25px sans" });
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
mousepressed(like) {
|
|
115
|
-
like.popScene();
|
|
116
|
-
}
|
|
117
|
-
}
|
|
57
|
+
};
|
|
58
|
+
};
|