yuuna-engine 0.8.0 → 0.10.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 +1 -1
- package/lib/engine/types.d.ts +20 -1
- package/lib/index.cjs +62 -10
- package/lib/index.cjs.map +1 -1
- package/lib/index.js +62 -10
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -84,7 +84,7 @@ runEngine<GameState>({
|
|
|
84
84
|
`CIRCLE`, `TEXT`, `SPRITE`, `ANIMATED_SPRITE`, `LINE`, and `GROUP`. Give
|
|
85
85
|
one an `id` plus `isClickable`/`isHoverable` to make it interactive.
|
|
86
86
|
- **Events** — `nextState` receives one `GameEvent` per call: `TIME`,
|
|
87
|
-
`CLICK`, `HOVER_IN`, `HOVER_OUT`, `MOUSE_MOVE`, `MOUSE_LEAVE`,
|
|
87
|
+
`CLICK`, `RIGHT_CLICK`, `HOVER_IN`, `HOVER_OUT`, `MOUSE_MOVE`, `MOUSE_LEAVE`,
|
|
88
88
|
`MUSIC_END`, or a `CUSTOM` event of a type you define yourself, for
|
|
89
89
|
reporting things like an async `fetch()` resolving back into your
|
|
90
90
|
state machine.
|
package/lib/engine/types.d.ts
CHANGED
|
@@ -117,6 +117,18 @@ export type ClickEvent = {
|
|
|
117
117
|
y: number;
|
|
118
118
|
};
|
|
119
119
|
};
|
|
120
|
+
export type RightClickEvent = {
|
|
121
|
+
tag: "RIGHT_CLICK";
|
|
122
|
+
id?: string;
|
|
123
|
+
mouse: {
|
|
124
|
+
x: number;
|
|
125
|
+
y: number;
|
|
126
|
+
};
|
|
127
|
+
worldMouse: {
|
|
128
|
+
x: number;
|
|
129
|
+
y: number;
|
|
130
|
+
};
|
|
131
|
+
};
|
|
120
132
|
export type HoverInEvent = {
|
|
121
133
|
tag: "HOVER_IN";
|
|
122
134
|
id?: string;
|
|
@@ -178,7 +190,7 @@ export type FullscreenChangeEvent = {
|
|
|
178
190
|
tag: "FULLSCREEN_CHANGE";
|
|
179
191
|
isFullscreen: boolean;
|
|
180
192
|
};
|
|
181
|
-
export type GameEvent = TimeEvent | ClickEvent | HoverInEvent | HoverOutEvent | MouseMoveEvent | MouseLeaveEvent | TabBlurEvent | TabFocusEvent | MusicEndEvent | FullscreenChangeEvent;
|
|
193
|
+
export type GameEvent = TimeEvent | ClickEvent | RightClickEvent | HoverInEvent | HoverOutEvent | MouseMoveEvent | MouseLeaveEvent | TabBlurEvent | TabFocusEvent | MusicEndEvent | FullscreenChangeEvent;
|
|
182
194
|
export type CustomGameEvent<Custom> = {
|
|
183
195
|
tag: "CUSTOM";
|
|
184
196
|
event: Custom;
|
|
@@ -196,6 +208,11 @@ export type NextStateProps<State, Custom = never> = {
|
|
|
196
208
|
isJustPressed: boolean;
|
|
197
209
|
isJustReleased: boolean;
|
|
198
210
|
};
|
|
211
|
+
rightMouseButton: {
|
|
212
|
+
isPressed: boolean;
|
|
213
|
+
isJustPressed: boolean;
|
|
214
|
+
isJustReleased: boolean;
|
|
215
|
+
};
|
|
199
216
|
playSound: (id: string) => void;
|
|
200
217
|
playMusic: (id: string) => void;
|
|
201
218
|
pauseMusic: () => void;
|
|
@@ -240,6 +257,7 @@ export type RunEngineProps<State, Custom = never> = {
|
|
|
240
257
|
height?: number;
|
|
241
258
|
backgroundColor?: string;
|
|
242
259
|
resize?: "none" | "fit" | "stretch";
|
|
260
|
+
disableContextMenu?: boolean;
|
|
243
261
|
pixelRatio?: number | true;
|
|
244
262
|
};
|
|
245
263
|
camera?: (state: State) => {
|
|
@@ -248,6 +266,7 @@ export type RunEngineProps<State, Custom = never> = {
|
|
|
248
266
|
zoom: number;
|
|
249
267
|
};
|
|
250
268
|
timeScale?: (state: State) => number;
|
|
269
|
+
maxFps?: (state: State) => number;
|
|
251
270
|
};
|
|
252
271
|
export type RunEngineFunction = <State, Custom = never>(props: RunEngineProps<State, Custom>) => Promise<{
|
|
253
272
|
sendEvent: (event: Custom) => void;
|
package/lib/index.cjs
CHANGED
|
@@ -680,6 +680,9 @@ const runEngine = async (props) => {
|
|
|
680
680
|
};
|
|
681
681
|
const nextStateFns = Array.isArray(props.nextState) ? props.nextState : [props.nextState];
|
|
682
682
|
let lastFrame = Date.now();
|
|
683
|
+
// When the next tick is due under RunEngineProps.maxFps — only read
|
|
684
|
+
// or advanced while a cap is set (see the top of the loop below).
|
|
685
|
+
let nextTickAt = 0;
|
|
683
686
|
let hoveredId = null;
|
|
684
687
|
// How much this tick's simulated time is scaled by (see
|
|
685
688
|
// RunEngineProps.timeScale) — set once per tick, right before that
|
|
@@ -689,21 +692,33 @@ const runEngine = async (props) => {
|
|
|
689
692
|
// one tick rather than each recomputing props.timeScale(state)
|
|
690
693
|
// separately (state may itself have just changed this same tick).
|
|
691
694
|
let currentTimeScale = 1;
|
|
692
|
-
// Shared by the mouse "click"
|
|
693
|
-
// firing a CLICK is the same "is
|
|
694
|
-
// isClickable" check either way; only how
|
|
695
|
-
// differs (a real click event vs. a lifted
|
|
696
|
-
|
|
695
|
+
// Shared by the mouse "click"/"contextmenu" listeners and the touch
|
|
696
|
+
// handlers below — firing a CLICK (or RIGHT_CLICK) is the same "is
|
|
697
|
+
// whatever's currently hovered isClickable" check either way; only how
|
|
698
|
+
// `mouse` was determined differs (a real click event vs. a lifted
|
|
699
|
+
// finger), and which button it was.
|
|
700
|
+
const fireClick = (mouse, tag = "CLICK") => {
|
|
697
701
|
if (hoveredId === null)
|
|
698
702
|
return;
|
|
699
703
|
const { renderables, camera } = renderState(state);
|
|
700
704
|
const hovered = renderables.find((e) => e.id === hoveredId);
|
|
701
705
|
if (hovered !== undefined && hovered.isClickable) {
|
|
702
|
-
events.push({ tag
|
|
706
|
+
events.push({ tag, id: hovered.id, mouse, worldMouse: toWorldPosition(mouse, camera) });
|
|
703
707
|
}
|
|
704
708
|
};
|
|
705
709
|
const handleClick = (ev) => fireClick(getCanvasPosition(ev));
|
|
706
710
|
canvas.addEventListener("click", handleClick);
|
|
711
|
+
// contextmenu rather than mouseup with button 2 — it's the browser's
|
|
712
|
+
// own "secondary click" (Ctrl+click on macOS included), the same way
|
|
713
|
+
// "click" above is its primary one, and it's the event that has to be
|
|
714
|
+
// cancelled to keep the browser's menu from opening anyway.
|
|
715
|
+
const handleContextMenu = (ev) => {
|
|
716
|
+
if (props.canvas?.disableContextMenu ?? true) {
|
|
717
|
+
ev.preventDefault();
|
|
718
|
+
}
|
|
719
|
+
fireClick(getCanvasPosition(ev), "RIGHT_CLICK");
|
|
720
|
+
};
|
|
721
|
+
canvas.addEventListener("contextmenu", handleContextMenu);
|
|
707
722
|
const initialState = {
|
|
708
723
|
keyboardState: createRecord(keyboardKeys, () => false),
|
|
709
724
|
};
|
|
@@ -732,22 +747,30 @@ const runEngine = async (props) => {
|
|
|
732
747
|
}
|
|
733
748
|
};
|
|
734
749
|
canvas.addEventListener("keyup", handleKeyUp);
|
|
735
|
-
// mouseButton state (see NextStateProps.mouseButton)
|
|
736
|
-
// exactly like keyboardState above,
|
|
750
|
+
// mouseButton/rightMouseButton state (see NextStateProps.mouseButton)
|
|
751
|
+
// — double-buffers exactly like keyboardState above, one per button.
|
|
737
752
|
let previousMouseButtonState = { isPressed: false };
|
|
738
753
|
const currentMouseButtonState = { isPressed: false };
|
|
754
|
+
let previousRightMouseButtonState = { isPressed: false };
|
|
755
|
+
const currentRightMouseButtonState = { isPressed: false };
|
|
739
756
|
const handleMouseDown = (event) => {
|
|
740
|
-
//
|
|
741
|
-
//
|
|
757
|
+
// Primary and secondary buttons only — matching CLICK and
|
|
758
|
+
// RIGHT_CLICK respectively. Middle/back/forward stay ignored.
|
|
742
759
|
if (event.button === 0) {
|
|
743
760
|
currentMouseButtonState.isPressed = true;
|
|
744
761
|
}
|
|
762
|
+
else if (event.button === 2) {
|
|
763
|
+
currentRightMouseButtonState.isPressed = true;
|
|
764
|
+
}
|
|
745
765
|
};
|
|
746
766
|
canvas.addEventListener("mousedown", handleMouseDown);
|
|
747
767
|
const handleMouseUp = (event) => {
|
|
748
768
|
if (event.button === 0) {
|
|
749
769
|
currentMouseButtonState.isPressed = false;
|
|
750
770
|
}
|
|
771
|
+
else if (event.button === 2) {
|
|
772
|
+
currentRightMouseButtonState.isPressed = false;
|
|
773
|
+
}
|
|
751
774
|
};
|
|
752
775
|
// On window rather than the canvas — so releasing the button after
|
|
753
776
|
// having dragged off the canvas while still holding it down still
|
|
@@ -1002,6 +1025,27 @@ const runEngine = async (props) => {
|
|
|
1002
1025
|
const intervalId = setInterval(() => {
|
|
1003
1026
|
const now = Date.now();
|
|
1004
1027
|
const rawDelta = now - lastFrame;
|
|
1028
|
+
// Skipped rather than delayed (see RunEngineProps.maxFps) — returning
|
|
1029
|
+
// before anything else runs leaves `events`, keyboard/mouse state and
|
|
1030
|
+
// lastFrame all untouched, so whatever happened in the meantime is
|
|
1031
|
+
// still delivered by the next tick that does run, and its TIME delta
|
|
1032
|
+
// covers this skipped one too.
|
|
1033
|
+
const maxFps = props.maxFps?.(state);
|
|
1034
|
+
if (maxFps !== undefined && maxFps > 0) {
|
|
1035
|
+
if (now < nextTickAt) {
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
// Scheduled from the previous target rather than from `now`: the
|
|
1039
|
+
// interval only fires every ~4ms, so "at least 1000/maxFps since
|
|
1040
|
+
// the last tick" would always round late (a 60 cap landing on
|
|
1041
|
+
// 20ms ticks, i.e. 50 FPS), while this lets the early and late
|
|
1042
|
+
// ticks average out to the cap itself. Falling more than a whole
|
|
1043
|
+
// interval behind (a hitch, a backgrounded tab, the cap having
|
|
1044
|
+
// just been turned on) restarts the schedule from now instead of
|
|
1045
|
+
// bursting through every missed tick to catch up.
|
|
1046
|
+
const interval = 1000 / maxFps;
|
|
1047
|
+
nextTickAt = now - nextTickAt > interval ? now + interval : nextTickAt + interval;
|
|
1048
|
+
}
|
|
1005
1049
|
// Read from state as this tick starts (i.e. still last tick's own
|
|
1006
1050
|
// result) — resolveAnimatedSprite (below, during this same tick's
|
|
1007
1051
|
// render pass) reads this same currentTimeScale rather than calling
|
|
@@ -1024,12 +1068,18 @@ const runEngine = async (props) => {
|
|
|
1024
1068
|
isJustPressed: currentMouseButtonState.isPressed && !previousMouseButtonState.isPressed,
|
|
1025
1069
|
isJustReleased: !currentMouseButtonState.isPressed && previousMouseButtonState.isPressed,
|
|
1026
1070
|
};
|
|
1071
|
+
const rightMouseButton = {
|
|
1072
|
+
isPressed: currentRightMouseButtonState.isPressed,
|
|
1073
|
+
isJustPressed: currentRightMouseButtonState.isPressed && !previousRightMouseButtonState.isPressed,
|
|
1074
|
+
isJustReleased: !currentRightMouseButtonState.isPressed && previousRightMouseButtonState.isPressed,
|
|
1075
|
+
};
|
|
1027
1076
|
for (const nextState of nextStateFns) {
|
|
1028
1077
|
const result = nextState({
|
|
1029
1078
|
state,
|
|
1030
1079
|
event,
|
|
1031
1080
|
keyboard,
|
|
1032
1081
|
mouseButton,
|
|
1082
|
+
rightMouseButton,
|
|
1033
1083
|
playSound,
|
|
1034
1084
|
playMusic,
|
|
1035
1085
|
pauseMusic,
|
|
@@ -1161,6 +1211,7 @@ const runEngine = async (props) => {
|
|
|
1161
1211
|
lastFrame = now;
|
|
1162
1212
|
previousState.keyboardState = { ...currentState.keyboardState };
|
|
1163
1213
|
previousMouseButtonState = { ...currentMouseButtonState };
|
|
1214
|
+
previousRightMouseButtonState = { ...currentRightMouseButtonState };
|
|
1164
1215
|
}, 0);
|
|
1165
1216
|
resetCanvas = () => {
|
|
1166
1217
|
clearInterval(intervalId);
|
|
@@ -1182,6 +1233,7 @@ const runEngine = async (props) => {
|
|
|
1182
1233
|
// slow enough device or long enough playground session, that pile-up
|
|
1183
1234
|
// is what "clicks stop working" actually looks like.
|
|
1184
1235
|
canvas.removeEventListener("click", handleClick);
|
|
1236
|
+
canvas.removeEventListener("contextmenu", handleContextMenu);
|
|
1185
1237
|
canvas.removeEventListener("keydown", handleKeyDown);
|
|
1186
1238
|
canvas.removeEventListener("keyup", handleKeyUp);
|
|
1187
1239
|
canvas.removeEventListener("mousedown", handleMouseDown);
|
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
|
@@ -678,6 +678,9 @@ const runEngine = async (props) => {
|
|
|
678
678
|
};
|
|
679
679
|
const nextStateFns = Array.isArray(props.nextState) ? props.nextState : [props.nextState];
|
|
680
680
|
let lastFrame = Date.now();
|
|
681
|
+
// When the next tick is due under RunEngineProps.maxFps — only read
|
|
682
|
+
// or advanced while a cap is set (see the top of the loop below).
|
|
683
|
+
let nextTickAt = 0;
|
|
681
684
|
let hoveredId = null;
|
|
682
685
|
// How much this tick's simulated time is scaled by (see
|
|
683
686
|
// RunEngineProps.timeScale) — set once per tick, right before that
|
|
@@ -687,21 +690,33 @@ const runEngine = async (props) => {
|
|
|
687
690
|
// one tick rather than each recomputing props.timeScale(state)
|
|
688
691
|
// separately (state may itself have just changed this same tick).
|
|
689
692
|
let currentTimeScale = 1;
|
|
690
|
-
// Shared by the mouse "click"
|
|
691
|
-
// firing a CLICK is the same "is
|
|
692
|
-
// isClickable" check either way; only how
|
|
693
|
-
// differs (a real click event vs. a lifted
|
|
694
|
-
|
|
693
|
+
// Shared by the mouse "click"/"contextmenu" listeners and the touch
|
|
694
|
+
// handlers below — firing a CLICK (or RIGHT_CLICK) is the same "is
|
|
695
|
+
// whatever's currently hovered isClickable" check either way; only how
|
|
696
|
+
// `mouse` was determined differs (a real click event vs. a lifted
|
|
697
|
+
// finger), and which button it was.
|
|
698
|
+
const fireClick = (mouse, tag = "CLICK") => {
|
|
695
699
|
if (hoveredId === null)
|
|
696
700
|
return;
|
|
697
701
|
const { renderables, camera } = renderState(state);
|
|
698
702
|
const hovered = renderables.find((e) => e.id === hoveredId);
|
|
699
703
|
if (hovered !== undefined && hovered.isClickable) {
|
|
700
|
-
events.push({ tag
|
|
704
|
+
events.push({ tag, id: hovered.id, mouse, worldMouse: toWorldPosition(mouse, camera) });
|
|
701
705
|
}
|
|
702
706
|
};
|
|
703
707
|
const handleClick = (ev) => fireClick(getCanvasPosition(ev));
|
|
704
708
|
canvas.addEventListener("click", handleClick);
|
|
709
|
+
// contextmenu rather than mouseup with button 2 — it's the browser's
|
|
710
|
+
// own "secondary click" (Ctrl+click on macOS included), the same way
|
|
711
|
+
// "click" above is its primary one, and it's the event that has to be
|
|
712
|
+
// cancelled to keep the browser's menu from opening anyway.
|
|
713
|
+
const handleContextMenu = (ev) => {
|
|
714
|
+
if (props.canvas?.disableContextMenu ?? true) {
|
|
715
|
+
ev.preventDefault();
|
|
716
|
+
}
|
|
717
|
+
fireClick(getCanvasPosition(ev), "RIGHT_CLICK");
|
|
718
|
+
};
|
|
719
|
+
canvas.addEventListener("contextmenu", handleContextMenu);
|
|
705
720
|
const initialState = {
|
|
706
721
|
keyboardState: createRecord(keyboardKeys, () => false),
|
|
707
722
|
};
|
|
@@ -730,22 +745,30 @@ const runEngine = async (props) => {
|
|
|
730
745
|
}
|
|
731
746
|
};
|
|
732
747
|
canvas.addEventListener("keyup", handleKeyUp);
|
|
733
|
-
// mouseButton state (see NextStateProps.mouseButton)
|
|
734
|
-
// exactly like keyboardState above,
|
|
748
|
+
// mouseButton/rightMouseButton state (see NextStateProps.mouseButton)
|
|
749
|
+
// — double-buffers exactly like keyboardState above, one per button.
|
|
735
750
|
let previousMouseButtonState = { isPressed: false };
|
|
736
751
|
const currentMouseButtonState = { isPressed: false };
|
|
752
|
+
let previousRightMouseButtonState = { isPressed: false };
|
|
753
|
+
const currentRightMouseButtonState = { isPressed: false };
|
|
737
754
|
const handleMouseDown = (event) => {
|
|
738
|
-
//
|
|
739
|
-
//
|
|
755
|
+
// Primary and secondary buttons only — matching CLICK and
|
|
756
|
+
// RIGHT_CLICK respectively. Middle/back/forward stay ignored.
|
|
740
757
|
if (event.button === 0) {
|
|
741
758
|
currentMouseButtonState.isPressed = true;
|
|
742
759
|
}
|
|
760
|
+
else if (event.button === 2) {
|
|
761
|
+
currentRightMouseButtonState.isPressed = true;
|
|
762
|
+
}
|
|
743
763
|
};
|
|
744
764
|
canvas.addEventListener("mousedown", handleMouseDown);
|
|
745
765
|
const handleMouseUp = (event) => {
|
|
746
766
|
if (event.button === 0) {
|
|
747
767
|
currentMouseButtonState.isPressed = false;
|
|
748
768
|
}
|
|
769
|
+
else if (event.button === 2) {
|
|
770
|
+
currentRightMouseButtonState.isPressed = false;
|
|
771
|
+
}
|
|
749
772
|
};
|
|
750
773
|
// On window rather than the canvas — so releasing the button after
|
|
751
774
|
// having dragged off the canvas while still holding it down still
|
|
@@ -1000,6 +1023,27 @@ const runEngine = async (props) => {
|
|
|
1000
1023
|
const intervalId = setInterval(() => {
|
|
1001
1024
|
const now = Date.now();
|
|
1002
1025
|
const rawDelta = now - lastFrame;
|
|
1026
|
+
// Skipped rather than delayed (see RunEngineProps.maxFps) — returning
|
|
1027
|
+
// before anything else runs leaves `events`, keyboard/mouse state and
|
|
1028
|
+
// lastFrame all untouched, so whatever happened in the meantime is
|
|
1029
|
+
// still delivered by the next tick that does run, and its TIME delta
|
|
1030
|
+
// covers this skipped one too.
|
|
1031
|
+
const maxFps = props.maxFps?.(state);
|
|
1032
|
+
if (maxFps !== undefined && maxFps > 0) {
|
|
1033
|
+
if (now < nextTickAt) {
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
// Scheduled from the previous target rather than from `now`: the
|
|
1037
|
+
// interval only fires every ~4ms, so "at least 1000/maxFps since
|
|
1038
|
+
// the last tick" would always round late (a 60 cap landing on
|
|
1039
|
+
// 20ms ticks, i.e. 50 FPS), while this lets the early and late
|
|
1040
|
+
// ticks average out to the cap itself. Falling more than a whole
|
|
1041
|
+
// interval behind (a hitch, a backgrounded tab, the cap having
|
|
1042
|
+
// just been turned on) restarts the schedule from now instead of
|
|
1043
|
+
// bursting through every missed tick to catch up.
|
|
1044
|
+
const interval = 1000 / maxFps;
|
|
1045
|
+
nextTickAt = now - nextTickAt > interval ? now + interval : nextTickAt + interval;
|
|
1046
|
+
}
|
|
1003
1047
|
// Read from state as this tick starts (i.e. still last tick's own
|
|
1004
1048
|
// result) — resolveAnimatedSprite (below, during this same tick's
|
|
1005
1049
|
// render pass) reads this same currentTimeScale rather than calling
|
|
@@ -1022,12 +1066,18 @@ const runEngine = async (props) => {
|
|
|
1022
1066
|
isJustPressed: currentMouseButtonState.isPressed && !previousMouseButtonState.isPressed,
|
|
1023
1067
|
isJustReleased: !currentMouseButtonState.isPressed && previousMouseButtonState.isPressed,
|
|
1024
1068
|
};
|
|
1069
|
+
const rightMouseButton = {
|
|
1070
|
+
isPressed: currentRightMouseButtonState.isPressed,
|
|
1071
|
+
isJustPressed: currentRightMouseButtonState.isPressed && !previousRightMouseButtonState.isPressed,
|
|
1072
|
+
isJustReleased: !currentRightMouseButtonState.isPressed && previousRightMouseButtonState.isPressed,
|
|
1073
|
+
};
|
|
1025
1074
|
for (const nextState of nextStateFns) {
|
|
1026
1075
|
const result = nextState({
|
|
1027
1076
|
state,
|
|
1028
1077
|
event,
|
|
1029
1078
|
keyboard,
|
|
1030
1079
|
mouseButton,
|
|
1080
|
+
rightMouseButton,
|
|
1031
1081
|
playSound,
|
|
1032
1082
|
playMusic,
|
|
1033
1083
|
pauseMusic,
|
|
@@ -1159,6 +1209,7 @@ const runEngine = async (props) => {
|
|
|
1159
1209
|
lastFrame = now;
|
|
1160
1210
|
previousState.keyboardState = { ...currentState.keyboardState };
|
|
1161
1211
|
previousMouseButtonState = { ...currentMouseButtonState };
|
|
1212
|
+
previousRightMouseButtonState = { ...currentRightMouseButtonState };
|
|
1162
1213
|
}, 0);
|
|
1163
1214
|
resetCanvas = () => {
|
|
1164
1215
|
clearInterval(intervalId);
|
|
@@ -1180,6 +1231,7 @@ const runEngine = async (props) => {
|
|
|
1180
1231
|
// slow enough device or long enough playground session, that pile-up
|
|
1181
1232
|
// is what "clicks stop working" actually looks like.
|
|
1182
1233
|
canvas.removeEventListener("click", handleClick);
|
|
1234
|
+
canvas.removeEventListener("contextmenu", handleContextMenu);
|
|
1183
1235
|
canvas.removeEventListener("keydown", handleKeyDown);
|
|
1184
1236
|
canvas.removeEventListener("keyup", handleKeyUp);
|
|
1185
1237
|
canvas.removeEventListener("mousedown", handleMouseDown);
|
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