yuuna-engine 0.5.0 → 0.6.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 +4 -0
- package/lib/engine/types.d.ts +8 -1
- package/lib/index.cjs +44 -3
- package/lib/index.cjs.map +1 -1
- package/lib/index.js +44 -3
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -107,10 +107,14 @@ hand? Grab one from [`templates/`](templates):
|
|
|
107
107
|
open it in a browser and it runs.
|
|
108
108
|
- **[npm](templates/npm)** — TypeScript + a dev server with hot reload
|
|
109
109
|
(via Vite), for a real local project.
|
|
110
|
+
- **[neutralino-desktop](templates/neutralino-desktop)** — the `npm`
|
|
111
|
+
template wrapped in [Neutralino](https://neutralino.js.org) to run as a
|
|
112
|
+
native desktop window instead of a browser tab.
|
|
110
113
|
|
|
111
114
|
```sh
|
|
112
115
|
npx degit lucy-dot-exe/yuuna/templates/blank my-game
|
|
113
116
|
# or: npx degit lucy-dot-exe/yuuna/templates/npm my-game
|
|
117
|
+
# or: npx degit lucy-dot-exe/yuuna/templates/neutralino-desktop my-game
|
|
114
118
|
```
|
|
115
119
|
|
|
116
120
|
[`degit`](https://github.com/Rich-Harris/degit) copies the folder without
|
package/lib/engine/types.d.ts
CHANGED
|
@@ -174,7 +174,11 @@ export type MusicEndEvent = {
|
|
|
174
174
|
tag: "MUSIC_END";
|
|
175
175
|
id: string;
|
|
176
176
|
};
|
|
177
|
-
export type
|
|
177
|
+
export type FullscreenChangeEvent = {
|
|
178
|
+
tag: "FULLSCREEN_CHANGE";
|
|
179
|
+
isFullscreen: boolean;
|
|
180
|
+
};
|
|
181
|
+
export type GameEvent = TimeEvent | ClickEvent | HoverInEvent | HoverOutEvent | MouseMoveEvent | MouseLeaveEvent | TabBlurEvent | TabFocusEvent | MusicEndEvent | FullscreenChangeEvent;
|
|
178
182
|
export type CustomGameEvent<Custom> = {
|
|
179
183
|
tag: "CUSTOM";
|
|
180
184
|
event: Custom;
|
|
@@ -229,6 +233,7 @@ export type RunEngineProps<State, Custom = never> = {
|
|
|
229
233
|
width?: number;
|
|
230
234
|
height?: number;
|
|
231
235
|
backgroundColor?: string;
|
|
236
|
+
resize?: "none" | "fit" | "stretch";
|
|
232
237
|
};
|
|
233
238
|
camera?: (state: State) => {
|
|
234
239
|
x: number;
|
|
@@ -238,6 +243,8 @@ export type RunEngineProps<State, Custom = never> = {
|
|
|
238
243
|
};
|
|
239
244
|
export type RunEngineFunction = <State, Custom = never>(props: RunEngineProps<State, Custom>) => Promise<{
|
|
240
245
|
sendEvent: (event: Custom) => void;
|
|
246
|
+
requestFullscreen: () => Promise<void>;
|
|
247
|
+
exitFullscreen: () => Promise<void>;
|
|
241
248
|
}>;
|
|
242
249
|
export declare const keyboardKeys: readonly ["ControlLeft", "ControlRight", "AltLeft", "AltRight", "CapsLock", "End", "Delete", "Tab", "Space", "Enter", "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Numpad0", "Numpad1", "Numpad2", "Numpad3", "Numpad4", "Numpad5", "Numpad6", "Numpad7", "Numpad8", "Numpad9", "Digit0", "Digit1", "Digit2", "Digit3", "Digit4", "Digit5", "Digit6", "Digit7", "Digit8", "Digit9", "KeyA", "KeyB", "KeyC", "KeyD", "KeyE", "KeyF", "KeyG", "KeyH", "KeyI", "KeyJ", "KeyK", "KeyL", "KeyM", "KeyN", "KeyO", "KeyP", "KeyQ", "KeyR", "KeyS", "KeyT", "KeyU", "KeyV", "KeyW", "KeyX", "KeyY", "KeyZ"];
|
|
243
250
|
export type KeyboardKeys = (typeof keyboardKeys)[number];
|
package/lib/index.cjs
CHANGED
|
@@ -175,6 +175,31 @@ const runEngine = async (props) => {
|
|
|
175
175
|
// Make the canvas focusable so keyboard input is scoped to it instead of
|
|
176
176
|
// leaking to the rest of the page (e.g. arrow keys scrolling the window).
|
|
177
177
|
canvas.tabIndex = 0;
|
|
178
|
+
// Resizes the *display* size only (CSS width/height) — canvas.width/
|
|
179
|
+
// height above stays the fixed logical resolution every renderable's
|
|
180
|
+
// position is already expressed in, so this never needs to touch any
|
|
181
|
+
// of that math. See RunEngineProps.canvas.resize for the mode semantics.
|
|
182
|
+
const applyResize = () => {
|
|
183
|
+
const mode = props.canvas?.resize;
|
|
184
|
+
if (mode === undefined || mode === "none") {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (mode === "stretch") {
|
|
188
|
+
canvas.style.width = `${window.innerWidth}px`;
|
|
189
|
+
canvas.style.height = `${window.innerHeight}px`;
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const scale = Math.min(window.innerWidth / canvas.width, window.innerHeight / canvas.height);
|
|
193
|
+
canvas.style.width = `${canvas.width * scale}px`;
|
|
194
|
+
canvas.style.height = `${canvas.height * scale}px`;
|
|
195
|
+
};
|
|
196
|
+
applyResize();
|
|
197
|
+
window.addEventListener("resize", applyResize);
|
|
198
|
+
const requestFullscreen = () => canvas.requestFullscreen();
|
|
199
|
+
// exitFullscreen() rejects with a TypeError if nothing is fullscreen —
|
|
200
|
+
// guarded into a no-op instead, so callers don't need to track that
|
|
201
|
+
// state themselves just to call this safely.
|
|
202
|
+
const exitFullscreen = () => window.document.fullscreenElement === null ? Promise.resolve() : window.document.exitFullscreen();
|
|
178
203
|
let state = props.initialState;
|
|
179
204
|
const events = [];
|
|
180
205
|
// Lets a caller report something that happened outside the render loop
|
|
@@ -281,9 +306,11 @@ const runEngine = async (props) => {
|
|
|
281
306
|
};
|
|
282
307
|
// A newer runEngine() call started while this one was still loading
|
|
283
308
|
// resources (e.g. a spritesheet) — abandon this run instead of setting
|
|
284
|
-
// up a second, orphaned render loop alongside the newer one.
|
|
309
|
+
// up a second, orphaned render loop alongside the newer one. The
|
|
310
|
+
// fullscreen functions are no-ops here since this run never gets far
|
|
311
|
+
// enough to own the canvas — the newer run's are the ones that matter.
|
|
285
312
|
if (runId !== latestRunId) {
|
|
286
|
-
return { sendEvent };
|
|
313
|
+
return { sendEvent, requestFullscreen: () => Promise.resolve(), exitFullscreen: () => Promise.resolve() };
|
|
287
314
|
}
|
|
288
315
|
context.imageSmoothingEnabled = false;
|
|
289
316
|
// An offscreen 1x1 canvas used only to resolve a CSS color string (a
|
|
@@ -668,6 +695,17 @@ const runEngine = async (props) => {
|
|
|
668
695
|
events.push({ tag: window.document.hidden ? "TAB_BLUR" : "TAB_FOCUS" });
|
|
669
696
|
};
|
|
670
697
|
window.document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
698
|
+
// Fullscreen is a document-level concern too, and fires for every way
|
|
699
|
+
// fullscreen can change — the requestFullscreen()/exitFullscreen()
|
|
700
|
+
// above, but also things neither of those causes directly, like the
|
|
701
|
+
// user pressing Esc. Re-running applyResize() here (rather than relying
|
|
702
|
+
// solely on the "resize" listener) covers browsers that don't also fire
|
|
703
|
+
// a window resize when fullscreen is toggled.
|
|
704
|
+
const handleFullscreenChange = () => {
|
|
705
|
+
events.push({ tag: "FULLSCREEN_CHANGE", isFullscreen: window.document.fullscreenElement === canvas });
|
|
706
|
+
applyResize();
|
|
707
|
+
};
|
|
708
|
+
window.document.addEventListener("fullscreenchange", handleFullscreenChange);
|
|
671
709
|
context.imageSmoothingEnabled = false;
|
|
672
710
|
// Scale is a canvas transform around the renderable's anchor, applied
|
|
673
711
|
// before its type-specific drawing runs below — everything drawn under
|
|
@@ -946,8 +984,11 @@ const runEngine = async (props) => {
|
|
|
946
984
|
// This one's on `document`, not the canvas — same reasoning as above,
|
|
947
985
|
// just doubly true since `document` isn't even scoped to this canvas.
|
|
948
986
|
window.document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
987
|
+
window.document.removeEventListener("fullscreenchange", handleFullscreenChange);
|
|
988
|
+
// Same pile-up risk as the canvas listeners above, but on `window`.
|
|
989
|
+
window.removeEventListener("resize", applyResize);
|
|
949
990
|
};
|
|
950
|
-
return { sendEvent };
|
|
991
|
+
return { sendEvent, requestFullscreen, exitFullscreen };
|
|
951
992
|
};
|
|
952
993
|
|
|
953
994
|
exports.STOP = STOP;
|
package/lib/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/lib/index.js
CHANGED
|
@@ -173,6 +173,31 @@ const runEngine = async (props) => {
|
|
|
173
173
|
// Make the canvas focusable so keyboard input is scoped to it instead of
|
|
174
174
|
// leaking to the rest of the page (e.g. arrow keys scrolling the window).
|
|
175
175
|
canvas.tabIndex = 0;
|
|
176
|
+
// Resizes the *display* size only (CSS width/height) — canvas.width/
|
|
177
|
+
// height above stays the fixed logical resolution every renderable's
|
|
178
|
+
// position is already expressed in, so this never needs to touch any
|
|
179
|
+
// of that math. See RunEngineProps.canvas.resize for the mode semantics.
|
|
180
|
+
const applyResize = () => {
|
|
181
|
+
const mode = props.canvas?.resize;
|
|
182
|
+
if (mode === undefined || mode === "none") {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (mode === "stretch") {
|
|
186
|
+
canvas.style.width = `${window.innerWidth}px`;
|
|
187
|
+
canvas.style.height = `${window.innerHeight}px`;
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const scale = Math.min(window.innerWidth / canvas.width, window.innerHeight / canvas.height);
|
|
191
|
+
canvas.style.width = `${canvas.width * scale}px`;
|
|
192
|
+
canvas.style.height = `${canvas.height * scale}px`;
|
|
193
|
+
};
|
|
194
|
+
applyResize();
|
|
195
|
+
window.addEventListener("resize", applyResize);
|
|
196
|
+
const requestFullscreen = () => canvas.requestFullscreen();
|
|
197
|
+
// exitFullscreen() rejects with a TypeError if nothing is fullscreen —
|
|
198
|
+
// guarded into a no-op instead, so callers don't need to track that
|
|
199
|
+
// state themselves just to call this safely.
|
|
200
|
+
const exitFullscreen = () => window.document.fullscreenElement === null ? Promise.resolve() : window.document.exitFullscreen();
|
|
176
201
|
let state = props.initialState;
|
|
177
202
|
const events = [];
|
|
178
203
|
// Lets a caller report something that happened outside the render loop
|
|
@@ -279,9 +304,11 @@ const runEngine = async (props) => {
|
|
|
279
304
|
};
|
|
280
305
|
// A newer runEngine() call started while this one was still loading
|
|
281
306
|
// resources (e.g. a spritesheet) — abandon this run instead of setting
|
|
282
|
-
// up a second, orphaned render loop alongside the newer one.
|
|
307
|
+
// up a second, orphaned render loop alongside the newer one. The
|
|
308
|
+
// fullscreen functions are no-ops here since this run never gets far
|
|
309
|
+
// enough to own the canvas — the newer run's are the ones that matter.
|
|
283
310
|
if (runId !== latestRunId) {
|
|
284
|
-
return { sendEvent };
|
|
311
|
+
return { sendEvent, requestFullscreen: () => Promise.resolve(), exitFullscreen: () => Promise.resolve() };
|
|
285
312
|
}
|
|
286
313
|
context.imageSmoothingEnabled = false;
|
|
287
314
|
// An offscreen 1x1 canvas used only to resolve a CSS color string (a
|
|
@@ -666,6 +693,17 @@ const runEngine = async (props) => {
|
|
|
666
693
|
events.push({ tag: window.document.hidden ? "TAB_BLUR" : "TAB_FOCUS" });
|
|
667
694
|
};
|
|
668
695
|
window.document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
696
|
+
// Fullscreen is a document-level concern too, and fires for every way
|
|
697
|
+
// fullscreen can change — the requestFullscreen()/exitFullscreen()
|
|
698
|
+
// above, but also things neither of those causes directly, like the
|
|
699
|
+
// user pressing Esc. Re-running applyResize() here (rather than relying
|
|
700
|
+
// solely on the "resize" listener) covers browsers that don't also fire
|
|
701
|
+
// a window resize when fullscreen is toggled.
|
|
702
|
+
const handleFullscreenChange = () => {
|
|
703
|
+
events.push({ tag: "FULLSCREEN_CHANGE", isFullscreen: window.document.fullscreenElement === canvas });
|
|
704
|
+
applyResize();
|
|
705
|
+
};
|
|
706
|
+
window.document.addEventListener("fullscreenchange", handleFullscreenChange);
|
|
669
707
|
context.imageSmoothingEnabled = false;
|
|
670
708
|
// Scale is a canvas transform around the renderable's anchor, applied
|
|
671
709
|
// before its type-specific drawing runs below — everything drawn under
|
|
@@ -944,8 +982,11 @@ const runEngine = async (props) => {
|
|
|
944
982
|
// This one's on `document`, not the canvas — same reasoning as above,
|
|
945
983
|
// just doubly true since `document` isn't even scoped to this canvas.
|
|
946
984
|
window.document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
985
|
+
window.document.removeEventListener("fullscreenchange", handleFullscreenChange);
|
|
986
|
+
// Same pile-up risk as the canvas listeners above, but on `window`.
|
|
987
|
+
window.removeEventListener("resize", applyResize);
|
|
947
988
|
};
|
|
948
|
-
return { sendEvent };
|
|
989
|
+
return { sendEvent, requestFullscreen, exitFullscreen };
|
|
949
990
|
};
|
|
950
991
|
|
|
951
992
|
export { STOP, runEngine };
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/package.json
CHANGED