@simpludify/web 0.1.5 → 0.1.7
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 +3 -3
- package/dist/game.d.ts +5 -3
- package/dist/gamepad-bindings.d.ts +2 -100
- package/dist/gamepads.d.ts +11 -2
- package/dist/geometry-tilemap-visual.d.ts +1 -1
- package/dist/index.js +469 -191
- package/dist/index.js.map +1 -1
- package/dist/input-capture.d.ts +33 -0
- package/dist/pointer.d.ts +18 -6
- package/dist/tilemap-visual.d.ts +3 -3
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "./style.css";
|
|
2
|
-
import { ActionController, Aspect, Camera, CanvasButton as CanvasButton$1, CanvasUI, Engine, EngineError, EventBus, FixedStepRunner, GeometryTilemapLayer, Lifetime, PointerRouting, Renderable, ReplayPlayer, Scene, ScenePointer, SceneState, Shape, TilemapLayer, Transform, VirtualInput, canvasControlText, canvasHoverHighlight, canvasOpacity, canvasScrollbars, canvasSliderGeometry, cleanupAll, containsScreenPoint, createViewTransform, defineEvent, imageSize, intersectUI, isKeyCode, positive, screenPoint, screenToWorld, spriteFrameBounds, worldToScreen, wrapCanvasText } from "@simpludify/core";
|
|
2
|
+
import { ActionController, Aspect, Camera, CanvasButton as CanvasButton$1, CanvasUI, Engine, EngineError, EventBus, FixedStepRunner, GamepadAxis, GamepadBindings, GamepadButton, GeometryTilemapLayer, InputCapture, InputCaptureState, InputDevice, Lifetime, MouseButton, PointerRouting, Renderable, ReplayPlayer, Scene, ScenePointer, SceneState, Shape, TilemapLayer, Transform, VirtualInput, canvasControlText, canvasHoverHighlight, canvasOpacity, canvasScrollbars, canvasSliderGeometry, checkedGamepadBindings, cleanupAll, containsScreenPoint, createViewTransform, defineEvent, gamepadDeadZone, gamepadIndex, imageSize, intersectUI, isKeyCode, isMouseButton, positive, screenPoint, screenToWorld, spriteFrameBounds, worldToScreen, wrapCanvasText } from "@simpludify/core";
|
|
3
3
|
import { Application, CanvasTextMetrics, Container, Graphics, Rectangle, RendererType, Sprite, Text, TextStyle, Texture } from "pixi.js";
|
|
4
4
|
export * from "@simpludify/core";
|
|
5
5
|
// packages/web/src/canvas-node.ts
|
|
@@ -17,135 +17,6 @@ function CanvasButton(text, click, style = {}) {
|
|
|
17
17
|
return click(signal);
|
|
18
18
|
}, style);
|
|
19
19
|
}
|
|
20
|
-
// packages/web/src/gamepad-bindings.ts
|
|
21
|
-
/** Standard mapping physical positions, not product-specific printed labels. */
|
|
22
|
-
var GamepadButton = Object.freeze({
|
|
23
|
-
South: 0,
|
|
24
|
-
East: 1,
|
|
25
|
-
West: 2,
|
|
26
|
-
North: 3,
|
|
27
|
-
LeftShoulder: 4,
|
|
28
|
-
RightShoulder: 5,
|
|
29
|
-
LeftTrigger: 6,
|
|
30
|
-
RightTrigger: 7,
|
|
31
|
-
Select: 8,
|
|
32
|
-
Start: 9,
|
|
33
|
-
LeftStick: 10,
|
|
34
|
-
RightStick: 11,
|
|
35
|
-
DPadUp: 12,
|
|
36
|
-
DPadDown: 13,
|
|
37
|
-
DPadLeft: 14,
|
|
38
|
-
DPadRight: 15,
|
|
39
|
-
Home: 16
|
|
40
|
-
});
|
|
41
|
-
var GamepadAxis = Object.freeze({
|
|
42
|
-
LeftX: 0,
|
|
43
|
-
LeftY: 1,
|
|
44
|
-
RightX: 2,
|
|
45
|
-
RightY: 3
|
|
46
|
-
});
|
|
47
|
-
var axis = (index) => Object.freeze({ axis: index });
|
|
48
|
-
/** Ready-to-use data; game Actions retain application-defined names. */
|
|
49
|
-
var GamepadBindings = Object.freeze({
|
|
50
|
-
LeftStick: Object.freeze({
|
|
51
|
-
x: axis(0),
|
|
52
|
-
y: axis(1)
|
|
53
|
-
}),
|
|
54
|
-
RightStick: Object.freeze({
|
|
55
|
-
x: axis(2),
|
|
56
|
-
y: axis(3)
|
|
57
|
-
}),
|
|
58
|
-
DPad: Object.freeze({
|
|
59
|
-
x: Object.freeze({
|
|
60
|
-
negative: Object.freeze([14]),
|
|
61
|
-
positive: Object.freeze([15])
|
|
62
|
-
}),
|
|
63
|
-
y: Object.freeze({
|
|
64
|
-
negative: Object.freeze([12]),
|
|
65
|
-
positive: Object.freeze([13])
|
|
66
|
-
})
|
|
67
|
-
}),
|
|
68
|
-
Move: Object.freeze({
|
|
69
|
-
x: Object.freeze({
|
|
70
|
-
axis: 0,
|
|
71
|
-
negative: Object.freeze([14]),
|
|
72
|
-
positive: Object.freeze([15])
|
|
73
|
-
}),
|
|
74
|
-
y: Object.freeze({
|
|
75
|
-
axis: 1,
|
|
76
|
-
negative: Object.freeze([12]),
|
|
77
|
-
positive: Object.freeze([13])
|
|
78
|
-
})
|
|
79
|
-
})
|
|
80
|
-
});
|
|
81
|
-
var fail = () => {
|
|
82
|
-
throw new EngineError("INVALID_GAMEPAD_BINDING", "Invalid gamepad binding");
|
|
83
|
-
};
|
|
84
|
-
function gamepadIndex(value) {
|
|
85
|
-
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) return fail();
|
|
86
|
-
return value;
|
|
87
|
-
}
|
|
88
|
-
function gamepadDeadZone(value) {
|
|
89
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value >= 1) return fail();
|
|
90
|
-
return value;
|
|
91
|
-
}
|
|
92
|
-
function record(value, keys) {
|
|
93
|
-
if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((k) => !keys.includes(k))) return fail();
|
|
94
|
-
return Object.fromEntries(Object.entries(value));
|
|
95
|
-
}
|
|
96
|
-
function axisBinding(value) {
|
|
97
|
-
const v = record(value, [
|
|
98
|
-
"axis",
|
|
99
|
-
"invert",
|
|
100
|
-
"negative",
|
|
101
|
-
"positive",
|
|
102
|
-
"deadZone"
|
|
103
|
-
]);
|
|
104
|
-
const indices = (value) => {
|
|
105
|
-
if (value === void 0) return Object.freeze([]);
|
|
106
|
-
if (!Array.isArray(value)) return fail();
|
|
107
|
-
return Object.freeze([...new Set(value.map(gamepadIndex))]);
|
|
108
|
-
};
|
|
109
|
-
if (v.invert !== void 0 && typeof v.invert !== "boolean") return fail();
|
|
110
|
-
return Object.freeze({
|
|
111
|
-
...v.axis === void 0 ? {} : { axis: gamepadIndex(v.axis) },
|
|
112
|
-
invert: v.invert ?? false,
|
|
113
|
-
negative: indices(v.negative),
|
|
114
|
-
positive: indices(v.positive),
|
|
115
|
-
...v.deadZone === void 0 ? {} : { deadZone: gamepadDeadZone(v.deadZone) }
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
function checkedGamepadBindings(actions, value) {
|
|
119
|
-
const v = record(value, Object.keys(actions));
|
|
120
|
-
return Object.freeze(Object.entries(v).map(([name, binding]) => {
|
|
121
|
-
if (!Object.hasOwn(actions, name)) return fail();
|
|
122
|
-
const action = actions[name];
|
|
123
|
-
if (!action) return fail();
|
|
124
|
-
let result;
|
|
125
|
-
if (action.kind === "button") {
|
|
126
|
-
const b = record(binding, ["buttons"]);
|
|
127
|
-
if (b.buttons !== void 0 && !Array.isArray(b.buttons)) return fail();
|
|
128
|
-
result = Object.freeze({ buttons: Object.freeze((b.buttons ?? []).map((item) => {
|
|
129
|
-
if (typeof item === "number") return gamepadIndex(item);
|
|
130
|
-
const b = record(item, ["button", "threshold"]), threshold = b.threshold ?? .5;
|
|
131
|
-
if (typeof threshold !== "number" || !Number.isFinite(threshold) || threshold <= 0 || threshold > 1) return fail();
|
|
132
|
-
return Object.freeze({
|
|
133
|
-
button: gamepadIndex(b.button),
|
|
134
|
-
threshold
|
|
135
|
-
});
|
|
136
|
-
})) });
|
|
137
|
-
} else if (action.kind === "axis1d") result = axisBinding(binding);
|
|
138
|
-
else {
|
|
139
|
-
const keys = action.kind === "axis2d" ? ["x", "y"] : [
|
|
140
|
-
"x",
|
|
141
|
-
"y",
|
|
142
|
-
"z"
|
|
143
|
-
], b = record(binding, keys);
|
|
144
|
-
result = Object.freeze(Object.fromEntries(keys.map((k) => [k, axisBinding(b[k] ?? {})])));
|
|
145
|
-
}
|
|
146
|
-
return Object.freeze([action, result]);
|
|
147
|
-
}));
|
|
148
|
-
}
|
|
149
20
|
// packages/web/src/gamepad-layout.ts
|
|
150
21
|
var positions = Object.fromEntries(Object.entries(GamepadButton).map(([name, index]) => [index, name]));
|
|
151
22
|
var faces = Object.freeze({
|
|
@@ -230,6 +101,9 @@ var GamepadSource = class {
|
|
|
230
101
|
signature;
|
|
231
102
|
zone;
|
|
232
103
|
mapping;
|
|
104
|
+
following;
|
|
105
|
+
bindingRevision;
|
|
106
|
+
waiting = /* @__PURE__ */ new Set();
|
|
233
107
|
constructor(input, controller, options) {
|
|
234
108
|
this.input = input;
|
|
235
109
|
this.controller = controller;
|
|
@@ -238,7 +112,9 @@ var GamepadSource = class {
|
|
|
238
112
|
this.zone = gamepadDeadZone(options.deadZone ?? .15);
|
|
239
113
|
this.mapping = options.mapping ?? "standard";
|
|
240
114
|
if (this.mapping !== "standard" && this.mapping !== "raw") throw new EngineError("INVALID_GAMEPAD_BINDING", "Unknown gamepad mapping policy");
|
|
241
|
-
this.
|
|
115
|
+
this.following = options.bindings === void 0;
|
|
116
|
+
this.bindingRevision = controller.bindingRevision;
|
|
117
|
+
this.bindings = this.following ? this.commonBindings() : checkedGamepadBindings(controller.actions, options.bindings);
|
|
242
118
|
this.source = controller.createSource();
|
|
243
119
|
this.life.onDispose(() => this.source.dispose());
|
|
244
120
|
this.life.onDispose(controller.onDispose(() => this.dispose()));
|
|
@@ -277,19 +153,42 @@ var GamepadSource = class {
|
|
|
277
153
|
}
|
|
278
154
|
rebind(bindings) {
|
|
279
155
|
this.alive();
|
|
280
|
-
const checked = checkedGamepadBindings(this.controller.actions, bindings);
|
|
156
|
+
const checked = bindings === void 0 ? this.commonBindings() : checkedGamepadBindings(this.controller.actions, bindings);
|
|
281
157
|
this.clear();
|
|
282
158
|
this.bindings = checked;
|
|
159
|
+
this.following = bindings === void 0;
|
|
160
|
+
this.bindingRevision = this.controller.bindingRevision;
|
|
161
|
+
}
|
|
162
|
+
commonBindings() {
|
|
163
|
+
return Object.freeze(Object.values(this.controller.actions).flatMap((action) => {
|
|
164
|
+
const binding = this.controller.getBinding(action).gamepad;
|
|
165
|
+
return binding ? [Object.freeze([action, binding])] : [];
|
|
166
|
+
}));
|
|
167
|
+
}
|
|
168
|
+
syncBindings() {
|
|
169
|
+
if (!this.following || this.bindingRevision === this.controller.bindingRevision) return;
|
|
170
|
+
const next = this.commonBindings();
|
|
171
|
+
const before = new Map(this.bindings), after = new Map(next);
|
|
172
|
+
for (const action of /* @__PURE__ */ new Set([...before.keys(), ...after.keys()])) {
|
|
173
|
+
if (JSON.stringify(before.get(action)) === JSON.stringify(after.get(action))) continue;
|
|
174
|
+
this.source.resetBinding(action);
|
|
175
|
+
if (after.has(action)) this.waiting.add(action);
|
|
176
|
+
else this.waiting.delete(action);
|
|
177
|
+
}
|
|
178
|
+
this.bindings = next;
|
|
179
|
+
this.bindingRevision = this.controller.bindingRevision;
|
|
283
180
|
}
|
|
284
181
|
clear() {
|
|
285
182
|
this.source.clear();
|
|
286
183
|
this.armed = false;
|
|
184
|
+
this.waiting.clear();
|
|
287
185
|
}
|
|
288
186
|
alive() {
|
|
289
187
|
if (this.disposed) throw new EngineError("OWNER_DISPOSED", "Gamepad source has been disposed");
|
|
290
188
|
}
|
|
291
189
|
/** @internal */ sample(pad, allowed) {
|
|
292
190
|
if (this.disposed) return;
|
|
191
|
+
this.syncBindings();
|
|
293
192
|
if (!allowed || !this.enabled || !this.controller.active || !pad || this.mapping === "standard" && pad.mapping !== "standard") {
|
|
294
193
|
this.clear();
|
|
295
194
|
this.signature = void 0;
|
|
@@ -310,7 +209,13 @@ var GamepadSource = class {
|
|
|
310
209
|
if (next.every(([, value]) => neutral(value))) this.armed = true;
|
|
311
210
|
return;
|
|
312
211
|
}
|
|
313
|
-
for (const [action, value] of next)
|
|
212
|
+
for (const [action, value] of next) {
|
|
213
|
+
if (this.waiting.has(action)) {
|
|
214
|
+
if (!neutral(value)) continue;
|
|
215
|
+
this.waiting.delete(action);
|
|
216
|
+
}
|
|
217
|
+
this.source.set(action, value);
|
|
218
|
+
}
|
|
314
219
|
}
|
|
315
220
|
/** @internal */ onDispose(callback) {
|
|
316
221
|
return this.life.onDispose(callback);
|
|
@@ -331,6 +236,7 @@ var GamepadInput = class {
|
|
|
331
236
|
readable = false;
|
|
332
237
|
poll;
|
|
333
238
|
active;
|
|
239
|
+
observe;
|
|
334
240
|
constructor(input, options = {}) {
|
|
335
241
|
this.input = input;
|
|
336
242
|
this.life.onDispose(() => this.changes.dispose());
|
|
@@ -338,6 +244,7 @@ var GamepadInput = class {
|
|
|
338
244
|
throw new Error("Gamepad API unavailable");
|
|
339
245
|
})());
|
|
340
246
|
this.active = options.active ?? (() => true);
|
|
247
|
+
this.observe = options.observe;
|
|
341
248
|
this.life.onDispose(input.addAdapter(this));
|
|
342
249
|
const events = options.events ?? (typeof window === "undefined" ? void 0 : window), abort = new AbortController();
|
|
343
250
|
this.life.onDispose(() => abort.abort());
|
|
@@ -412,7 +319,8 @@ var GamepadInput = class {
|
|
|
412
319
|
axisCount: p.axes.length,
|
|
413
320
|
inferredLayout: inferredGamepadLayout(p.id)
|
|
414
321
|
})));
|
|
415
|
-
|
|
322
|
+
this.observe?.(pads);
|
|
323
|
+
const allowed = this.active() && this.input.liveInput;
|
|
416
324
|
for (const source of this.sources) if (source.disposed) this.sources.delete(source);
|
|
417
325
|
else source.sample(pads.get(source.index), allowed);
|
|
418
326
|
this.publish(listed, wasReadable !== this.readable);
|
|
@@ -520,35 +428,287 @@ var BrowserInput = class {
|
|
|
520
428
|
this.clear();
|
|
521
429
|
}
|
|
522
430
|
};
|
|
431
|
+
// packages/web/src/input-capture.ts
|
|
432
|
+
var mouseMasks$1 = [
|
|
433
|
+
[0, 1],
|
|
434
|
+
[1, 4],
|
|
435
|
+
[2, 2],
|
|
436
|
+
[3, 8],
|
|
437
|
+
[4, 16]
|
|
438
|
+
];
|
|
439
|
+
/** @internal One Game-owned observer; existing adapters retain gameplay ownership. */
|
|
440
|
+
var InputCaptureManager = class {
|
|
441
|
+
host;
|
|
442
|
+
input;
|
|
443
|
+
scene;
|
|
444
|
+
releasePointers;
|
|
445
|
+
report;
|
|
446
|
+
abort = new AbortController();
|
|
447
|
+
current;
|
|
448
|
+
keys = /* @__PURE__ */ new Set();
|
|
449
|
+
blockedKeys = /* @__PURE__ */ new Set();
|
|
450
|
+
blockedPointers = /* @__PURE__ */ new Set();
|
|
451
|
+
blockedAuxiliary = /* @__PURE__ */ new Set();
|
|
452
|
+
mouseButtons = 0;
|
|
453
|
+
needsPadSample = false;
|
|
454
|
+
pads = /* @__PURE__ */ new Map();
|
|
455
|
+
readyPads = /* @__PURE__ */ new Set();
|
|
456
|
+
dead = false;
|
|
457
|
+
constructor(host, input, scene, releasePointers, report) {
|
|
458
|
+
this.host = host;
|
|
459
|
+
this.input = input;
|
|
460
|
+
this.scene = scene;
|
|
461
|
+
this.releasePointers = releasePointers;
|
|
462
|
+
this.report = report;
|
|
463
|
+
const options = {
|
|
464
|
+
capture: true,
|
|
465
|
+
signal: this.abort.signal
|
|
466
|
+
};
|
|
467
|
+
window.addEventListener("keydown", (event) => this.guard(() => this.keyDown(event)), options);
|
|
468
|
+
window.addEventListener("keyup", (event) => this.guard(() => {
|
|
469
|
+
if (isKeyCode(event.code)) {
|
|
470
|
+
this.keys.delete(event.code);
|
|
471
|
+
this.blockedKeys.delete(event.code);
|
|
472
|
+
}
|
|
473
|
+
this.arm();
|
|
474
|
+
}), options);
|
|
475
|
+
for (const kind of [
|
|
476
|
+
"pointerdown",
|
|
477
|
+
"pointermove",
|
|
478
|
+
"pointerup",
|
|
479
|
+
"pointercancel"
|
|
480
|
+
]) window.addEventListener(kind, (event) => this.guard(() => this.pointer(event)), options);
|
|
481
|
+
for (const kind of ["auxclick", "contextmenu"]) window.addEventListener(kind, (event) => {
|
|
482
|
+
if (this.surface(event.target) && isMouseButton(event.button) && this.blockedAuxiliary.has(event.button)) {
|
|
483
|
+
this.consume(event);
|
|
484
|
+
if (kind === "auxclick") this.blockedAuxiliary.delete(event.button);
|
|
485
|
+
}
|
|
486
|
+
}, options);
|
|
487
|
+
window.addEventListener("blur", (event) => {
|
|
488
|
+
if (event.target === window) this.interrupt();
|
|
489
|
+
}, options);
|
|
490
|
+
document.addEventListener("visibilitychange", () => {
|
|
491
|
+
if (document.hidden) this.interrupt();
|
|
492
|
+
}, options);
|
|
493
|
+
host.addEventListener("focusout", (event) => {
|
|
494
|
+
if (!(event.relatedTarget instanceof Node) || !host.contains(event.relatedTarget) || blocksGameKeyboard(event.relatedTarget)) this.interrupt();
|
|
495
|
+
}, options);
|
|
496
|
+
host.addEventListener("focusin", (event) => {
|
|
497
|
+
if (blocksGameKeyboard(event.target)) this.interrupt();
|
|
498
|
+
}, options);
|
|
499
|
+
}
|
|
500
|
+
start(options = {}) {
|
|
501
|
+
if (this.dead) throw new EngineError("GAME_DISPOSED", "Capture host has been disposed");
|
|
502
|
+
if (this.current) throw new EngineError("INPUT_CAPTURE_ACTIVE", "Cancel the active capture before starting another");
|
|
503
|
+
const scene = this.scene();
|
|
504
|
+
if (scene.state !== SceneState.Active || document.hidden || !document.hasFocus()) throw new EngineError("INPUT_CAPTURE_UNAVAILABLE", "Capture requires an active Scene and focused visible document");
|
|
505
|
+
const capture = new InputCapture(options);
|
|
506
|
+
let release;
|
|
507
|
+
try {
|
|
508
|
+
capture.onDispose(scene.onDispose(() => capture.cancel()));
|
|
509
|
+
capture.onDispose(() => {
|
|
510
|
+
if (this.current === capture) this.current = void 0;
|
|
511
|
+
this.pads.clear();
|
|
512
|
+
this.readyPads.clear();
|
|
513
|
+
this.needsPadSample = false;
|
|
514
|
+
release?.();
|
|
515
|
+
});
|
|
516
|
+
this.current = capture;
|
|
517
|
+
this.needsPadSample = capture.devices.includes(InputDevice.Gamepad);
|
|
518
|
+
for (const key of this.keys) this.blockedKeys.add(key);
|
|
519
|
+
for (const [button, mask] of mouseMasks$1) if (this.mouseButtons & mask) this.blockedAuxiliary.add(button);
|
|
520
|
+
this.releasePointers();
|
|
521
|
+
if (capture.disposed) return capture;
|
|
522
|
+
release = this.input.suspendLiveInput(() => capture.cancel());
|
|
523
|
+
if (capture.disposed) {
|
|
524
|
+
release();
|
|
525
|
+
return capture;
|
|
526
|
+
}
|
|
527
|
+
this.host.focus({ preventScroll: true });
|
|
528
|
+
this.arm();
|
|
529
|
+
return capture;
|
|
530
|
+
} catch (error) {
|
|
531
|
+
capture.cancel();
|
|
532
|
+
throw error;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
arm() {
|
|
536
|
+
const capture = this.current;
|
|
537
|
+
if (!capture || capture.state !== InputCaptureState.Arming || this.needsPadSample) return;
|
|
538
|
+
if (capture.devices.includes(InputDevice.Keyboard) && this.keys.size) return;
|
|
539
|
+
if (capture.devices.includes(InputDevice.Mouse) && this.mouseButtons) return;
|
|
540
|
+
if (capture.devices.includes(InputDevice.Gamepad) && [...this.pads.values()].some((pad) => (capture.gamepadIndex === void 0 || capture.gamepadIndex === pad.index) && pad.buttons.size)) return;
|
|
541
|
+
capture.arm();
|
|
542
|
+
}
|
|
543
|
+
keyDown(event) {
|
|
544
|
+
if (event.isComposing || event.keyCode === 229 || !isKeyCode(event.code)) return;
|
|
545
|
+
const previous = this.keys.has(event.code);
|
|
546
|
+
this.keys.add(event.code);
|
|
547
|
+
if (!(event.target instanceof Node) || !this.host.contains(event.target)) return;
|
|
548
|
+
if (blocksGameKeyboard(event.target)) return;
|
|
549
|
+
if (event.target instanceof HTMLElement && event.target.closest("button")) return;
|
|
550
|
+
const capture = this.current;
|
|
551
|
+
if (!capture && !this.blockedKeys.has(event.code)) return;
|
|
552
|
+
this.blockedKeys.add(event.code);
|
|
553
|
+
this.consume(event);
|
|
554
|
+
if (capture && !previous && !event.repeat) capture.accept({
|
|
555
|
+
device: InputDevice.Keyboard,
|
|
556
|
+
code: event.code
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
surface(target) {
|
|
560
|
+
return target === this.host || target instanceof HTMLCanvasElement && target.parentElement === this.host;
|
|
561
|
+
}
|
|
562
|
+
pointer(event) {
|
|
563
|
+
const capture = this.current, mouse = event.pointerType === "mouse", old = this.mouseButtons;
|
|
564
|
+
if (mouse) this.mouseButtons = event.type === "pointercancel" ? 0 : event.buttons;
|
|
565
|
+
if (event.type === "pointerdown" && !capture) {
|
|
566
|
+
this.blockedPointers.delete(event.pointerId);
|
|
567
|
+
if (isMouseButton(event.button)) this.blockedAuxiliary.delete(event.button);
|
|
568
|
+
}
|
|
569
|
+
const blocked = this.blockedPointers.has(event.pointerId);
|
|
570
|
+
if (capture && this.surface(event.target)) {
|
|
571
|
+
if (event.type === "pointerdown") this.host.focus({ preventScroll: true });
|
|
572
|
+
this.blockedPointers.add(event.pointerId);
|
|
573
|
+
this.consume(event);
|
|
574
|
+
if (mouse && isMouseButton(event.button)) {
|
|
575
|
+
const mask = mouseMasks$1.find(([button]) => button === event.button)?.[1] ?? 0;
|
|
576
|
+
if (event.buttons & mask && !(old & mask)) {
|
|
577
|
+
this.blockedAuxiliary.add(event.button);
|
|
578
|
+
capture.accept({
|
|
579
|
+
device: InputDevice.Mouse,
|
|
580
|
+
button: event.button
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
} else if (blocked) this.consume(event);
|
|
585
|
+
if (event.type === "pointerup" || event.type === "pointercancel" || mouse && event.buttons === 0) this.blockedPointers.delete(event.pointerId);
|
|
586
|
+
this.arm();
|
|
587
|
+
}
|
|
588
|
+
/** Same validated gamepad poll as gameplay; no navigator polling in this manager. */
|
|
589
|
+
sample(pads) {
|
|
590
|
+
const capture = this.current;
|
|
591
|
+
if (!capture?.devices.includes(InputDevice.Gamepad)) return;
|
|
592
|
+
const before = this.pads, first = this.needsPadSample, next = /* @__PURE__ */ new Map();
|
|
593
|
+
for (const pad of pads.values()) {
|
|
594
|
+
const buttons = /* @__PURE__ */ new Set();
|
|
595
|
+
pad.buttons.forEach((button, index) => {
|
|
596
|
+
if (Number.isFinite(button?.value) && button.value >= .5) buttons.add(index);
|
|
597
|
+
});
|
|
598
|
+
const signature = JSON.stringify([
|
|
599
|
+
pad.id,
|
|
600
|
+
pad.mapping,
|
|
601
|
+
pad.buttons.length
|
|
602
|
+
]);
|
|
603
|
+
const entry = {
|
|
604
|
+
index: pad.index,
|
|
605
|
+
id: pad.id,
|
|
606
|
+
mapping: pad.mapping,
|
|
607
|
+
signature,
|
|
608
|
+
buttons
|
|
609
|
+
};
|
|
610
|
+
next.set(pad.index, entry);
|
|
611
|
+
if (first || before.get(pad.index)?.signature !== signature) this.readyPads.delete(pad.index);
|
|
612
|
+
if (!buttons.size) this.readyPads.add(pad.index);
|
|
613
|
+
}
|
|
614
|
+
for (const index of this.readyPads) if (!next.has(index)) this.readyPads.delete(index);
|
|
615
|
+
this.pads = next;
|
|
616
|
+
this.needsPadSample = false;
|
|
617
|
+
this.arm();
|
|
618
|
+
if (first || capture.state !== InputCaptureState.Listening) return;
|
|
619
|
+
for (const pad of [...next.values()].sort((a, b) => a.index - b.index)) {
|
|
620
|
+
if (!this.readyPads.has(pad.index) || before.get(pad.index)?.signature !== pad.signature) continue;
|
|
621
|
+
for (const button of pad.buttons) if (!before.get(pad.index)?.buttons.has(button) && capture.accept({
|
|
622
|
+
device: InputDevice.Gamepad,
|
|
623
|
+
index: pad.index,
|
|
624
|
+
id: pad.id,
|
|
625
|
+
mapping: pad.mapping,
|
|
626
|
+
button
|
|
627
|
+
})) return;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
consume(event) {
|
|
631
|
+
event.preventDefault();
|
|
632
|
+
event.stopImmediatePropagation();
|
|
633
|
+
}
|
|
634
|
+
guard(action) {
|
|
635
|
+
try {
|
|
636
|
+
action();
|
|
637
|
+
} catch (error) {
|
|
638
|
+
this.report(error);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
interrupt() {
|
|
642
|
+
this.guard(() => this.current?.cancel());
|
|
643
|
+
this.keys.clear();
|
|
644
|
+
this.blockedKeys.clear();
|
|
645
|
+
this.blockedPointers.clear();
|
|
646
|
+
this.blockedAuxiliary.clear();
|
|
647
|
+
this.mouseButtons = 0;
|
|
648
|
+
}
|
|
649
|
+
dispose() {
|
|
650
|
+
if (this.dead) return;
|
|
651
|
+
this.dead = true;
|
|
652
|
+
this.abort.abort();
|
|
653
|
+
this.interrupt();
|
|
654
|
+
}
|
|
655
|
+
};
|
|
523
656
|
// packages/web/src/pointer.ts
|
|
657
|
+
var mouseMasks = [
|
|
658
|
+
[MouseButton.Left, 1],
|
|
659
|
+
[MouseButton.Middle, 4],
|
|
660
|
+
[MouseButton.Right, 2],
|
|
661
|
+
[MouseButton.Back, 8],
|
|
662
|
+
[MouseButton.Forward, 16]
|
|
663
|
+
];
|
|
524
664
|
/** One DOM capture-phase adapter per Game; Pixi federated events never route these widgets. */
|
|
525
665
|
var BrowserPointer = class {
|
|
526
666
|
host;
|
|
527
667
|
project;
|
|
528
668
|
report;
|
|
529
669
|
flush;
|
|
670
|
+
input;
|
|
530
671
|
routing;
|
|
531
672
|
abort = new AbortController();
|
|
532
|
-
|
|
673
|
+
mouse;
|
|
674
|
+
mouseStates = /* @__PURE__ */ new Map();
|
|
675
|
+
blockedMouse = /* @__PURE__ */ new Set();
|
|
676
|
+
auxiliary = /* @__PURE__ */ new Set();
|
|
677
|
+
offInput;
|
|
678
|
+
constructor(host, project, report, flush, input) {
|
|
533
679
|
this.host = host;
|
|
534
680
|
this.project = project;
|
|
535
681
|
this.report = report;
|
|
536
682
|
this.flush = flush;
|
|
683
|
+
this.input = input;
|
|
537
684
|
this.routing = new PointerRouting((id) => {
|
|
538
685
|
if (host.hasPointerCapture(id)) host.releasePointerCapture(id);
|
|
539
686
|
});
|
|
687
|
+
this.mouse = input?.createMouseSource();
|
|
688
|
+
this.offInput = input?.addAdapter({
|
|
689
|
+
sample: () => {},
|
|
690
|
+
reset: () => this.resetMouse(),
|
|
691
|
+
dispose: () => this.dispose()
|
|
692
|
+
});
|
|
540
693
|
const options = {
|
|
541
694
|
signal: this.abort.signal,
|
|
542
695
|
capture: true
|
|
543
696
|
};
|
|
544
697
|
host.addEventListener("pointerdown", (e) => this.guard(() => {
|
|
545
698
|
if (!this.surface(e.target)) return;
|
|
699
|
+
if (e.pointerType === "mouse") {
|
|
700
|
+
this.releaseMouse(e.pointerId);
|
|
701
|
+
this.blockedMouse.delete(e.pointerId);
|
|
702
|
+
if (isMouseButton(e.button)) this.auxiliary.delete(e.button);
|
|
703
|
+
}
|
|
546
704
|
this.flush();
|
|
547
705
|
const data = this.data(e);
|
|
706
|
+
if (!data) return this.release();
|
|
548
707
|
try {
|
|
549
708
|
host.setPointerCapture(e.pointerId);
|
|
550
709
|
} catch {}
|
|
551
710
|
const target = this.routing.down(data);
|
|
711
|
+
this.updateMouse(e);
|
|
552
712
|
if (this.routing.has(e.pointerId)) {
|
|
553
713
|
e.preventDefault();
|
|
554
714
|
if (target) focusGameSurface(host);
|
|
@@ -561,7 +721,9 @@ var BrowserPointer = class {
|
|
|
561
721
|
host.addEventListener("wheel", (e) => this.guard(() => {
|
|
562
722
|
if (!this.surface(e.target)) return;
|
|
563
723
|
this.flush();
|
|
564
|
-
const
|
|
724
|
+
const projected = this.project(e);
|
|
725
|
+
if (!projected) return;
|
|
726
|
+
const { screen } = projected;
|
|
565
727
|
const unit = e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? host.clientHeight : 1;
|
|
566
728
|
if (this.routing.scroll(screen.x, screen.y, e.deltaX * unit, e.deltaY * unit)) e.preventDefault();
|
|
567
729
|
}), {
|
|
@@ -570,37 +732,98 @@ var BrowserPointer = class {
|
|
|
570
732
|
});
|
|
571
733
|
window.addEventListener("pointermove", (e) => this.guard(() => {
|
|
572
734
|
if (!this.routing.owns(e.pointerId) && !this.surface(e.target)) {
|
|
573
|
-
if (
|
|
735
|
+
if (e.pointerType === "mouse") this.releaseMouse(e.pointerId);
|
|
736
|
+
if (this.routing.hasHover(e.pointerId)) {
|
|
737
|
+
const data = this.data(e);
|
|
738
|
+
if (data) this.routing.leave(data);
|
|
739
|
+
else this.release();
|
|
740
|
+
}
|
|
574
741
|
return;
|
|
575
742
|
}
|
|
576
743
|
this.flush();
|
|
577
|
-
this.
|
|
744
|
+
const data = this.data(e);
|
|
745
|
+
if (!data) return this.release();
|
|
746
|
+
this.updateMouse(e);
|
|
747
|
+
this.routing.move(data);
|
|
578
748
|
}), options);
|
|
579
749
|
window.addEventListener("pointerup", (e) => this.guard(() => {
|
|
750
|
+
if (e.pointerType === "mouse") this.releaseMouse(e.pointerId);
|
|
580
751
|
if (this.routing.owns(e.pointerId)) {
|
|
581
752
|
e.preventDefault();
|
|
582
753
|
this.flush();
|
|
583
|
-
this.
|
|
754
|
+
const data = this.data(e);
|
|
755
|
+
if (data) this.routing.up(data);
|
|
756
|
+
else this.release();
|
|
584
757
|
}
|
|
585
758
|
}), options);
|
|
586
|
-
window.addEventListener("pointercancel", (e) => this.guard(() =>
|
|
587
|
-
|
|
588
|
-
|
|
759
|
+
window.addEventListener("pointercancel", (e) => this.guard(() => {
|
|
760
|
+
this.releaseMouse(e.pointerId);
|
|
761
|
+
this.blockedMouse.delete(e.pointerId);
|
|
762
|
+
this.routing.cancel(e.pointerId, false);
|
|
763
|
+
}), options);
|
|
764
|
+
host.addEventListener("lostpointercapture", (e) => this.guard(() => {
|
|
765
|
+
this.releaseMouse(e.pointerId);
|
|
766
|
+
if (e.pointerType === "mouse" && e.buttons !== 0) this.blockedMouse.add(e.pointerId);
|
|
767
|
+
else this.blockedMouse.delete(e.pointerId);
|
|
768
|
+
this.routing.cancel(e.pointerId);
|
|
769
|
+
}), options);
|
|
770
|
+
host.addEventListener("pointerleave", (e) => this.guard(() => {
|
|
771
|
+
const data = this.data(e);
|
|
772
|
+
if (data) this.routing.leave(data);
|
|
773
|
+
else this.release();
|
|
774
|
+
}), options);
|
|
589
775
|
window.addEventListener("blur", (event) => {
|
|
590
776
|
if (event.target === window) this.release();
|
|
591
777
|
}, options);
|
|
592
778
|
host.addEventListener("focusout", (event) => this.guard(() => {
|
|
593
|
-
if (!(event.relatedTarget instanceof Node) || !host.contains(event.relatedTarget) || blocksGameKeyboard(event.relatedTarget))
|
|
779
|
+
if (!(event.relatedTarget instanceof Node) || !host.contains(event.relatedTarget) || blocksGameKeyboard(event.relatedTarget)) {
|
|
780
|
+
this.resetMouse();
|
|
781
|
+
this.routing.releaseVirtual();
|
|
782
|
+
}
|
|
594
783
|
}), options);
|
|
595
784
|
document.addEventListener("visibilitychange", () => {
|
|
596
785
|
if (document.hidden) this.release();
|
|
597
786
|
}, options);
|
|
787
|
+
for (const kind of ["auxclick", "contextmenu"]) host.addEventListener(kind, (event) => {
|
|
788
|
+
if (this.surface(event.target) && isMouseButton(event.button) && this.auxiliary.has(event.button) && input?.acceptsMouse(event.button)) {
|
|
789
|
+
event.preventDefault();
|
|
790
|
+
if (kind === "auxclick") this.auxiliary.delete(event.button);
|
|
791
|
+
}
|
|
792
|
+
}, options);
|
|
793
|
+
}
|
|
794
|
+
applyMouse() {
|
|
795
|
+
let buttons = 0;
|
|
796
|
+
for (const state of this.mouseStates.values()) buttons |= state;
|
|
797
|
+
for (const [button, mask] of mouseMasks) this.mouse?.set(button, (buttons & mask) !== 0);
|
|
798
|
+
}
|
|
799
|
+
releaseMouse(id) {
|
|
800
|
+
if (this.mouseStates.delete(id)) this.applyMouse();
|
|
801
|
+
}
|
|
802
|
+
updateMouse(event) {
|
|
803
|
+
if (event.pointerType !== "mouse" || !this.mouse) return;
|
|
804
|
+
if (event.buttons === 0) this.blockedMouse.delete(event.pointerId);
|
|
805
|
+
const world = this.routing.worldOwns(event.pointerId) && !this.blockedMouse.has(event.pointerId) && this.input?.liveInput;
|
|
806
|
+
if (world) this.mouseStates.set(event.pointerId, event.buttons);
|
|
807
|
+
else this.mouseStates.delete(event.pointerId);
|
|
808
|
+
if (isMouseButton(event.button)) {
|
|
809
|
+
if (world && this.input?.acceptsMouse(event.button)) this.auxiliary.add(event.button);
|
|
810
|
+
else this.auxiliary.delete(event.button);
|
|
811
|
+
}
|
|
812
|
+
this.applyMouse();
|
|
813
|
+
}
|
|
814
|
+
resetMouse() {
|
|
815
|
+
for (const id of this.mouseStates.keys()) this.blockedMouse.add(id);
|
|
816
|
+
this.mouseStates.clear();
|
|
817
|
+
this.auxiliary.clear();
|
|
818
|
+
this.mouse?.clear();
|
|
598
819
|
}
|
|
599
820
|
surface(target) {
|
|
600
821
|
return target === this.host || target instanceof HTMLCanvasElement && target.parentElement === this.host;
|
|
601
822
|
}
|
|
602
823
|
data(e) {
|
|
603
|
-
const
|
|
824
|
+
const projected = this.project(e);
|
|
825
|
+
if (!projected) return void 0;
|
|
826
|
+
const { screen, world, view } = projected;
|
|
604
827
|
return Object.freeze({
|
|
605
828
|
pointerId: e.pointerId,
|
|
606
829
|
pointerType: e.pointerType,
|
|
@@ -620,10 +843,15 @@ var BrowserPointer = class {
|
|
|
620
843
|
}
|
|
621
844
|
}
|
|
622
845
|
release() {
|
|
846
|
+
this.resetMouse();
|
|
623
847
|
this.guard(() => this.routing.release());
|
|
624
848
|
}
|
|
625
849
|
dispose() {
|
|
626
850
|
this.abort.abort();
|
|
851
|
+
this.offInput?.();
|
|
852
|
+
this.resetMouse();
|
|
853
|
+
this.mouse?.dispose();
|
|
854
|
+
this.blockedMouse.clear();
|
|
627
855
|
this.guard(() => this.routing.dispose());
|
|
628
856
|
}
|
|
629
857
|
};
|
|
@@ -1063,6 +1291,7 @@ var CanvasVisual = class {
|
|
|
1063
1291
|
},
|
|
1064
1292
|
wheel: (event) => {
|
|
1065
1293
|
const rect = this.host.getBoundingClientRect();
|
|
1294
|
+
if (rect.width <= 0 || rect.height <= 0) return;
|
|
1066
1295
|
const unit = event.deltaMode === 1 ? 16 : event.deltaMode === 2 ? this.host.clientHeight : 1;
|
|
1067
1296
|
if (this.routing.scroll((event.clientX - rect.x) * this.host.clientWidth / rect.width, (event.clientY - rect.y) * this.host.clientHeight / rect.height, event.deltaX * unit, event.deltaY * unit)) event.preventDefault();
|
|
1068
1297
|
}
|
|
@@ -1536,11 +1765,11 @@ var GeometryTilemapVisual = class {
|
|
|
1536
1765
|
g.clear().poly(this.map.geometry.vertices(x, y).flatMap((p) => [p.x, p.y]), true).fill(color);
|
|
1537
1766
|
this.stats.updatedTiles++;
|
|
1538
1767
|
}
|
|
1539
|
-
render(view) {
|
|
1768
|
+
render(view, enabled = true) {
|
|
1540
1769
|
if (this.disposed) return;
|
|
1541
1770
|
const m = this.map;
|
|
1542
1771
|
this.display.position.set(m.x, m.y);
|
|
1543
|
-
this.display.visible = m.visible && m.enabled;
|
|
1772
|
+
this.display.visible = enabled && m.visible && m.enabled;
|
|
1544
1773
|
if (!this.display.visible) {
|
|
1545
1774
|
this.invalid = true;
|
|
1546
1775
|
this.bulk = true;
|
|
@@ -1775,7 +2004,7 @@ var TilemapVisual = class {
|
|
|
1775
2004
|
invalid = true;
|
|
1776
2005
|
off;
|
|
1777
2006
|
offDispose;
|
|
1778
|
-
constructor(map,
|
|
2007
|
+
constructor(map, isCurrent, acquire, changed, onError, released) {
|
|
1779
2008
|
this.map = map;
|
|
1780
2009
|
this.changed = changed;
|
|
1781
2010
|
this.onError = onError;
|
|
@@ -1793,7 +2022,7 @@ var TilemapVisual = class {
|
|
|
1793
2022
|
const controller = this.controller;
|
|
1794
2023
|
if (!controller) throw Error("Missing tilemap request controller");
|
|
1795
2024
|
acquire(map.sheet.url, controller.signal).then((lease) => {
|
|
1796
|
-
if (this.disposed || map.disposed ||
|
|
2025
|
+
if (this.disposed || map.disposed || !isCurrent()) {
|
|
1797
2026
|
lease.release();
|
|
1798
2027
|
return;
|
|
1799
2028
|
}
|
|
@@ -1855,12 +2084,12 @@ var TilemapVisual = class {
|
|
|
1855
2084
|
sprite.height = this.map.tileHeight;
|
|
1856
2085
|
this.stats.updatedTiles++;
|
|
1857
2086
|
}
|
|
1858
|
-
render(view) {
|
|
2087
|
+
render(view, enabled = true) {
|
|
1859
2088
|
if (this.disposed) return;
|
|
1860
2089
|
this.sync();
|
|
1861
2090
|
const m = this.map;
|
|
1862
2091
|
this.display.position.set(m.x, m.y);
|
|
1863
|
-
this.display.visible = m.visible && m.enabled;
|
|
2092
|
+
this.display.visible = enabled && m.visible && m.enabled;
|
|
1864
2093
|
if (!this.display.visible) {
|
|
1865
2094
|
this.invalid = true;
|
|
1866
2095
|
return;
|
|
@@ -2260,6 +2489,7 @@ var PixiRenderer = class {
|
|
|
2260
2489
|
root.mask = mask;
|
|
2261
2490
|
const visuals = /* @__PURE__ */ new Map();
|
|
2262
2491
|
const tilemaps = /* @__PURE__ */ new Map();
|
|
2492
|
+
const geometryTilemaps = /* @__PURE__ */ new Map();
|
|
2263
2493
|
const drawOrder = [];
|
|
2264
2494
|
let depthDirty = true;
|
|
2265
2495
|
let revision = -1, disposed = false, clipX = NaN, clipY = NaN, clipWidth = NaN, clipHeight = NaN, removeSceneCleanup;
|
|
@@ -2459,20 +2689,30 @@ var PixiRenderer = class {
|
|
|
2459
2689
|
entry.dispose();
|
|
2460
2690
|
visuals.delete(actor);
|
|
2461
2691
|
}
|
|
2462
|
-
for (const [actor, entry] of tilemaps)
|
|
2463
|
-
|
|
2464
|
-
if (
|
|
2465
|
-
|
|
2466
|
-
|
|
2692
|
+
for (const [actor, entry] of tilemaps) {
|
|
2693
|
+
const layer = actor.get(TilemapLayer.type);
|
|
2694
|
+
if (actor.disposed || actor.scene !== scene || !layer || layer.disposed || layer.map !== entry.map || entry.disposed) {
|
|
2695
|
+
entry.dispose();
|
|
2696
|
+
this.tilemaps.delete(entry);
|
|
2697
|
+
tilemaps.delete(actor);
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
for (const [actor, entry] of geometryTilemaps) {
|
|
2701
|
+
const layer = actor.get(GeometryTilemapLayer.type);
|
|
2702
|
+
if (actor.disposed || actor.scene !== scene || !layer || layer.disposed || layer.map !== entry.map || entry.disposed) {
|
|
2703
|
+
entry.dispose();
|
|
2704
|
+
this.geometryTilemaps.delete(entry);
|
|
2705
|
+
geometryTilemaps.delete(actor);
|
|
2706
|
+
}
|
|
2467
2707
|
}
|
|
2468
2708
|
let order = 0;
|
|
2469
2709
|
for (const actor of scene.actors) {
|
|
2470
2710
|
const layer = actor.get(TilemapLayer.type);
|
|
2471
|
-
if (layer) {
|
|
2711
|
+
if (layer && !layer.disposed && !layer.map.disposed) {
|
|
2472
2712
|
let tile = tilemaps.get(actor);
|
|
2473
2713
|
if (!tile) {
|
|
2474
|
-
tile = new TilemapVisual(layer.map, scene, (url, signal) => this.acquireImage(url, signal), changed, this.onError, () => {
|
|
2475
|
-
if (tile
|
|
2714
|
+
tile = new TilemapVisual(layer.map, () => actor.scene === scene && actor.get(TilemapLayer.type)?.map === layer.map, (url, signal) => this.acquireImage(url, signal), changed, this.onError, () => {
|
|
2715
|
+
if (tile) this.tilemaps.delete(tile);
|
|
2476
2716
|
depthDirty = true;
|
|
2477
2717
|
});
|
|
2478
2718
|
world.addChild(tile.display);
|
|
@@ -2482,8 +2722,8 @@ var PixiRenderer = class {
|
|
|
2482
2722
|
tile.order = order;
|
|
2483
2723
|
}
|
|
2484
2724
|
const geometryLayer = actor.get(GeometryTilemapLayer.type);
|
|
2485
|
-
if (geometryLayer) {
|
|
2486
|
-
let tile =
|
|
2725
|
+
if (geometryLayer && !geometryLayer.disposed && !geometryLayer.map.disposed) {
|
|
2726
|
+
let tile = geometryTilemaps.get(actor);
|
|
2487
2727
|
if (!tile) {
|
|
2488
2728
|
const created = new GeometryTilemapVisual(geometryLayer.map, () => {
|
|
2489
2729
|
this.geometryTilemaps.delete(created);
|
|
@@ -2491,7 +2731,7 @@ var PixiRenderer = class {
|
|
|
2491
2731
|
});
|
|
2492
2732
|
tile = created;
|
|
2493
2733
|
world.addChild(tile.display);
|
|
2494
|
-
|
|
2734
|
+
geometryTilemaps.set(actor, tile);
|
|
2495
2735
|
this.geometryTilemaps.add(created);
|
|
2496
2736
|
}
|
|
2497
2737
|
tile.order = order;
|
|
@@ -2511,7 +2751,7 @@ var PixiRenderer = class {
|
|
|
2511
2751
|
const sync = () => {
|
|
2512
2752
|
reconcile();
|
|
2513
2753
|
for (const entry of visuals.values()) update(entry);
|
|
2514
|
-
for (const entry of tilemaps.values())
|
|
2754
|
+
for (const entry of tilemaps.values()) entry.sync();
|
|
2515
2755
|
};
|
|
2516
2756
|
const prepared = {
|
|
2517
2757
|
ready: async () => {
|
|
@@ -2524,7 +2764,6 @@ var PixiRenderer = class {
|
|
|
2524
2764
|
if (entry.controller) pending = true;
|
|
2525
2765
|
}
|
|
2526
2766
|
for (const entry of tilemaps.values()) {
|
|
2527
|
-
if (!(entry instanceof TilemapVisual)) continue;
|
|
2528
2767
|
if (entry.error) throw entry.error;
|
|
2529
2768
|
if (entry.controller) pending = true;
|
|
2530
2769
|
}
|
|
@@ -2566,8 +2805,15 @@ var PixiRenderer = class {
|
|
|
2566
2805
|
entry.display.scale.set(scaleX, scaleY);
|
|
2567
2806
|
}
|
|
2568
2807
|
}
|
|
2569
|
-
for (const entry of tilemaps
|
|
2570
|
-
entry.render(view);
|
|
2808
|
+
for (const [actor, entry] of tilemaps) {
|
|
2809
|
+
entry.render(view, actor.enabled && actor.get(TilemapLayer.type)?.enabled === true);
|
|
2810
|
+
if (entry.depth !== entry.map.zIndex) {
|
|
2811
|
+
entry.depth = entry.map.zIndex;
|
|
2812
|
+
depthDirty = true;
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
for (const [actor, entry] of geometryTilemaps) {
|
|
2816
|
+
entry.render(view, actor.enabled && actor.get(GeometryTilemapLayer.type)?.enabled === true);
|
|
2571
2817
|
if (entry.depth !== entry.map.zIndex) {
|
|
2572
2818
|
entry.depth = entry.map.zIndex;
|
|
2573
2819
|
depthDirty = true;
|
|
@@ -2585,6 +2831,11 @@ var PixiRenderer = class {
|
|
|
2585
2831
|
order: entry.order,
|
|
2586
2832
|
zIndex: entry.map.zIndex
|
|
2587
2833
|
});
|
|
2834
|
+
for (const entry of geometryTilemaps.values()) drawOrder.push({
|
|
2835
|
+
display: entry.display,
|
|
2836
|
+
order: entry.order,
|
|
2837
|
+
zIndex: entry.map.zIndex
|
|
2838
|
+
});
|
|
2588
2839
|
drawOrder.sort((a, b) => a.zIndex - b.zIndex || a.order - b.order);
|
|
2589
2840
|
for (let i = 0; i < drawOrder.length; i++) {
|
|
2590
2841
|
const entry = drawOrder[i];
|
|
@@ -2609,10 +2860,14 @@ var PixiRenderer = class {
|
|
|
2609
2860
|
() => {
|
|
2610
2861
|
for (const entry of tilemaps.values()) {
|
|
2611
2862
|
entry.dispose();
|
|
2612
|
-
|
|
2613
|
-
else this.geometryTilemaps.delete(entry);
|
|
2863
|
+
this.tilemaps.delete(entry);
|
|
2614
2864
|
}
|
|
2615
2865
|
tilemaps.clear();
|
|
2866
|
+
for (const entry of geometryTilemaps.values()) {
|
|
2867
|
+
entry.dispose();
|
|
2868
|
+
this.geometryTilemaps.delete(entry);
|
|
2869
|
+
}
|
|
2870
|
+
geometryTilemaps.clear();
|
|
2616
2871
|
},
|
|
2617
2872
|
() => root.destroy({
|
|
2618
2873
|
children: true,
|
|
@@ -2912,6 +3167,11 @@ var Game = class {
|
|
|
2912
3167
|
gamepads;
|
|
2913
3168
|
renderer;
|
|
2914
3169
|
keyboard;
|
|
3170
|
+
captures;
|
|
3171
|
+
captureInput(options = {}) {
|
|
3172
|
+
this.assertAlive();
|
|
3173
|
+
return this.captures.start(options);
|
|
3174
|
+
}
|
|
2915
3175
|
pointers;
|
|
2916
3176
|
pointerChannels = /* @__PURE__ */ new WeakMap();
|
|
2917
3177
|
virtualInputs = /* @__PURE__ */ new WeakMap();
|
|
@@ -2985,7 +3245,7 @@ var Game = class {
|
|
|
2985
3245
|
height = 450;
|
|
2986
3246
|
ratio = 1;
|
|
2987
3247
|
scenes = /* @__PURE__ */ new Map();
|
|
2988
|
-
|
|
3248
|
+
pendingScene;
|
|
2989
3249
|
defaultUI = [];
|
|
2990
3250
|
ui;
|
|
2991
3251
|
prepared;
|
|
@@ -3054,18 +3314,22 @@ var Game = class {
|
|
|
3054
3314
|
request: (callback) => requestAnimationFrame(callback),
|
|
3055
3315
|
cancel: (handle) => cancelAnimationFrame(handle)
|
|
3056
3316
|
};
|
|
3317
|
+
this.captures = new InputCaptureManager(this.element, this.engine.input, () => this.engine.scene, () => this.pointers.release(), this.report);
|
|
3057
3318
|
this.keyboard = new BrowserInput(this.element, this.engine.input);
|
|
3058
|
-
this.gamepads = new GamepadInput(this.engine.input, {
|
|
3319
|
+
this.gamepads = new GamepadInput(this.engine.input, {
|
|
3320
|
+
observe: (pads) => this.captures.sample(pads),
|
|
3321
|
+
active: () => !document.hidden && document.hasFocus() && this.element.contains(document.activeElement) && !blocksGameKeyboard(document.activeElement)
|
|
3322
|
+
});
|
|
3059
3323
|
this.pointers = new BrowserPointer(this.element, (event) => {
|
|
3060
3324
|
const rect = this.element.getBoundingClientRect();
|
|
3061
|
-
if (rect.width <= 0 || rect.height <= 0)
|
|
3325
|
+
if (rect.width <= 0 || rect.height <= 0) return;
|
|
3062
3326
|
const screen = screenPoint((event.clientX - rect.left) * this.width / rect.width, (event.clientY - rect.top) * this.height / rect.height);
|
|
3063
3327
|
return {
|
|
3064
3328
|
screen,
|
|
3065
3329
|
world: this.screenToWorld(screen),
|
|
3066
3330
|
view: this.view
|
|
3067
3331
|
};
|
|
3068
|
-
}, this.report, () => this.updateProjection());
|
|
3332
|
+
}, this.report, () => this.updateProjection(), this.engine.input);
|
|
3069
3333
|
document.addEventListener("visibilitychange", () => {
|
|
3070
3334
|
this.previousTime = void 0;
|
|
3071
3335
|
this.fixed?.reset();
|
|
@@ -3164,12 +3428,17 @@ var Game = class {
|
|
|
3164
3428
|
}
|
|
3165
3429
|
scene(id, setup) {
|
|
3166
3430
|
this.assertAlive();
|
|
3431
|
+
if (typeof id !== "string" || !id.length) throw new EngineError("INVALID_VALUE", "Scene id must not be empty");
|
|
3432
|
+
if (typeof setup !== "function") throw new EngineError("INVALID_VALUE", "Scene setup must be a function");
|
|
3167
3433
|
if (this.scenes.has(id)) throw new EngineError("DUPLICATE_SCENE", "Scene id is already registered", { sceneId: id });
|
|
3168
3434
|
this.scenes.set(id, setup);
|
|
3169
3435
|
return this;
|
|
3170
3436
|
}
|
|
3171
3437
|
wait(promise) {
|
|
3172
|
-
if (this.dead)
|
|
3438
|
+
if (this.dead) {
|
|
3439
|
+
promise.catch(() => {});
|
|
3440
|
+
return Promise.reject(new EngineError("GAME_DISPOSED", "Game has been disposed"));
|
|
3441
|
+
}
|
|
3173
3442
|
const signal = this.shutdown.signal;
|
|
3174
3443
|
return new Promise((resolve, reject) => {
|
|
3175
3444
|
const abort = () => {
|
|
@@ -3205,7 +3474,7 @@ var Game = class {
|
|
|
3205
3474
|
})();
|
|
3206
3475
|
return this.startPromise;
|
|
3207
3476
|
}
|
|
3208
|
-
|
|
3477
|
+
changeScene(id) {
|
|
3209
3478
|
if (this.dead) return Promise.reject(new EngineError("GAME_DISPOSED", "Game has been disposed"));
|
|
3210
3479
|
if (!this.ready) return Promise.reject(new EngineError("GAME_NOT_READY", "Await run before navigating"));
|
|
3211
3480
|
return this.navigate(id);
|
|
@@ -3213,8 +3482,7 @@ var Game = class {
|
|
|
3213
3482
|
navigate(id) {
|
|
3214
3483
|
const setup = this.scenes.get(id);
|
|
3215
3484
|
if (!setup) return Promise.reject(new EngineError("SCENE_NOT_FOUND", "Unknown scene", { sceneId: id }));
|
|
3216
|
-
|
|
3217
|
-
if (existing) return existing;
|
|
3485
|
+
if (this.pendingScene?.id === id) return this.pendingScene.task;
|
|
3218
3486
|
const task = this.queue.then(async () => {
|
|
3219
3487
|
this.assertAlive();
|
|
3220
3488
|
const scene = new Scene(id), nodes = [], controller = new AbortController();
|
|
@@ -3256,7 +3524,9 @@ var Game = class {
|
|
|
3256
3524
|
onDispose: (callback) => scene.onDispose(callback)
|
|
3257
3525
|
})));
|
|
3258
3526
|
this.assertAlive();
|
|
3259
|
-
await this.present(scene, nodes, canvas)
|
|
3527
|
+
await this.present(scene, nodes, canvas, () => {
|
|
3528
|
+
if (this.pendingScene?.task === task) this.pendingScene = void 0;
|
|
3529
|
+
});
|
|
3260
3530
|
} catch (error) {
|
|
3261
3531
|
try {
|
|
3262
3532
|
scene.dispose();
|
|
@@ -3268,13 +3538,16 @@ var Game = class {
|
|
|
3268
3538
|
if (this.staging === scene) this.staging = void 0;
|
|
3269
3539
|
}
|
|
3270
3540
|
}).finally(() => {
|
|
3271
|
-
if (this.
|
|
3541
|
+
if (this.pendingScene?.task === task) this.pendingScene = void 0;
|
|
3272
3542
|
});
|
|
3273
|
-
this.
|
|
3543
|
+
this.pendingScene = {
|
|
3544
|
+
id,
|
|
3545
|
+
task
|
|
3546
|
+
};
|
|
3274
3547
|
this.queue = task.catch(() => {});
|
|
3275
3548
|
return task;
|
|
3276
3549
|
}
|
|
3277
|
-
async present(scene, nodes, canvas) {
|
|
3550
|
+
async present(scene, nodes, canvas, beforeActivate) {
|
|
3278
3551
|
let prepared, ui, committed = false;
|
|
3279
3552
|
let canvasPresentation;
|
|
3280
3553
|
let virtualPresentation;
|
|
@@ -3300,6 +3573,7 @@ var Game = class {
|
|
|
3300
3573
|
}
|
|
3301
3574
|
virtualPresentation = this.prepareVirtual(scene);
|
|
3302
3575
|
scene.markReady();
|
|
3576
|
+
beforeActivate?.();
|
|
3303
3577
|
this.errorBox.hidden = true;
|
|
3304
3578
|
try {
|
|
3305
3579
|
if (this.playback && this.playbackScene !== scene) this.stopReplay();
|
|
@@ -3315,18 +3589,21 @@ var Game = class {
|
|
|
3315
3589
|
this.canvas = canvas;
|
|
3316
3590
|
this.canvasPresentation = canvasPresentation;
|
|
3317
3591
|
this.virtualPresentation = virtualPresentation;
|
|
3318
|
-
this.pointers.routing.setScene(this.channelFor(scene), canvas);
|
|
3319
3592
|
committed = true;
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
()
|
|
3323
|
-
|
|
3324
|
-
(
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3593
|
+
try {
|
|
3594
|
+
this.pointers.routing.setScene(this.channelFor(scene), canvas);
|
|
3595
|
+
this.element.append(ui.element);
|
|
3596
|
+
} finally {
|
|
3597
|
+
for (const cleanup of [
|
|
3598
|
+
() => oldPrepared?.dispose(),
|
|
3599
|
+
() => oldUI?.dispose(),
|
|
3600
|
+
() => oldCanvas?.dispose(),
|
|
3601
|
+
() => oldVirtual?.dispose()
|
|
3602
|
+
]) try {
|
|
3603
|
+
cleanup();
|
|
3604
|
+
} catch (error) {
|
|
3605
|
+
this.report(error);
|
|
3606
|
+
}
|
|
3330
3607
|
}
|
|
3331
3608
|
this.assertAlive();
|
|
3332
3609
|
this.previousTime = void 0;
|
|
@@ -3544,6 +3821,7 @@ var Game = class {
|
|
|
3544
3821
|
for (const cleanup of [
|
|
3545
3822
|
() => this.stopReplay(),
|
|
3546
3823
|
() => this.staging?.dispose(),
|
|
3824
|
+
() => this.captures.dispose(),
|
|
3547
3825
|
() => this.pointers.dispose(),
|
|
3548
3826
|
() => this.gamepads.dispose(),
|
|
3549
3827
|
() => this.keyboard.dispose(),
|
|
@@ -3570,7 +3848,7 @@ var Game = class {
|
|
|
3570
3848
|
this.defaultCanvas = void 0;
|
|
3571
3849
|
this.defaultUI.length = 0;
|
|
3572
3850
|
this.scenes.clear();
|
|
3573
|
-
this.
|
|
3851
|
+
this.pendingScene = void 0;
|
|
3574
3852
|
}
|
|
3575
3853
|
};
|
|
3576
3854
|
function validFontFamily(value) {
|