@makefully/platypus 4.1.20 → 4.1.22
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/CHANGELOG.md +12 -0
- package/lib/platypus.js +1 -1
- package/package.json +1 -1
- package/src/Game.js +80 -1
- package/src/components/HandlerController.js +55 -10
package/package.json
CHANGED
package/src/Game.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* global document, platypus, window */
|
|
2
2
|
import {Application} from 'springroll';
|
|
3
|
-
import {CullerPlugin, Application as PixiApplication, Ticker, extensions} from 'pixi.js';
|
|
3
|
+
import {CullerPlugin, Application as PixiApplication, Rectangle, Ticker, extensions} from 'pixi.js';
|
|
4
4
|
import {arrayCache, greenSlice, greenSplice, union} from './utils/array.js';
|
|
5
5
|
import Data from './Data.js';
|
|
6
6
|
import Entity from './Entity.js';
|
|
@@ -224,6 +224,9 @@ class Game extends Messenger {
|
|
|
224
224
|
this.ticker.remove(this.tickInstance);
|
|
225
225
|
this.paused = true;
|
|
226
226
|
sound.pauseAll();
|
|
227
|
+
// Release held keys so pause does not leave sticky input,
|
|
228
|
+
// and so HandlerController can pass keyboard through to HTML.
|
|
229
|
+
this.triggerOnChildren('blur-control');
|
|
227
230
|
}
|
|
228
231
|
} else {
|
|
229
232
|
if (this.paused) {
|
|
@@ -820,6 +823,82 @@ class Game extends Messenger {
|
|
|
820
823
|
}
|
|
821
824
|
return entities;
|
|
822
825
|
}
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* Captures a screenshot of the currently visible game viewport (the canvas /
|
|
829
|
+
* camera view), not the full stage or world bounds.
|
|
830
|
+
*
|
|
831
|
+
* Prefer this over `renderer.extract.*(stage)` without a frame — extracting
|
|
832
|
+
* the bare stage uses content bounds and can include off-screen world area.
|
|
833
|
+
*
|
|
834
|
+
* @param {Object} [options]
|
|
835
|
+
* @param {string} [options.format='png'] Image format for data URLs (`png`, `jpg`/`jpeg`, `webp`).
|
|
836
|
+
* @param {number} [options.quality] Encoder quality for lossy formats (0–1).
|
|
837
|
+
* @param {boolean} [options.render=true] Render the stage before capturing.
|
|
838
|
+
* @param {string} [options.as='dataURL'] Return type: `dataURL` or `canvas`.
|
|
839
|
+
* @return {Promise<string|HTMLCanvasElement|null>} Screenshot, or null if unavailable.
|
|
840
|
+
**/
|
|
841
|
+
async captureViewport (options = {}) {
|
|
842
|
+
const
|
|
843
|
+
{
|
|
844
|
+
format = 'png',
|
|
845
|
+
quality,
|
|
846
|
+
render = true,
|
|
847
|
+
as = 'dataURL'
|
|
848
|
+
} = options,
|
|
849
|
+
{renderer, stage} = this;
|
|
850
|
+
|
|
851
|
+
if (!renderer || !stage) {
|
|
852
|
+
return null;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
if (render) {
|
|
856
|
+
renderer.render(stage);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
const
|
|
860
|
+
{resolution, screen} = renderer,
|
|
861
|
+
frame = new Rectangle(0, 0, screen.width, screen.height),
|
|
862
|
+
extractOptions = {
|
|
863
|
+
target: stage,
|
|
864
|
+
frame,
|
|
865
|
+
resolution
|
|
866
|
+
};
|
|
867
|
+
|
|
868
|
+
try {
|
|
869
|
+
if (as === 'canvas') {
|
|
870
|
+
return renderer.extract.canvas(extractOptions);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
return await renderer.extract.base64({
|
|
874
|
+
...extractOptions,
|
|
875
|
+
format,
|
|
876
|
+
quality
|
|
877
|
+
});
|
|
878
|
+
} catch (err) {
|
|
879
|
+
platypus.debug.warn('Game.captureViewport failed.', err);
|
|
880
|
+
|
|
881
|
+
// Fallback: canvas buffer after the render above (viewport-sized).
|
|
882
|
+
try {
|
|
883
|
+
if (as === 'canvas') {
|
|
884
|
+
return this.canvas;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
const mime = format === 'jpg' || format === 'jpeg'
|
|
888
|
+
? 'image/jpeg'
|
|
889
|
+
: format === 'webp'
|
|
890
|
+
? 'image/webp'
|
|
891
|
+
: 'image/png';
|
|
892
|
+
|
|
893
|
+
return typeof quality === 'number'
|
|
894
|
+
? this.canvas.toDataURL(mime, quality)
|
|
895
|
+
: this.canvas.toDataURL(mime);
|
|
896
|
+
} catch (fallbackErr) {
|
|
897
|
+
platypus.debug.warn('Game.captureViewport canvas fallback failed.', fallbackErr);
|
|
898
|
+
return null;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
823
902
|
|
|
824
903
|
/**
|
|
825
904
|
* This method destroys the game.
|
|
@@ -4,6 +4,43 @@ import Gamepad from '../Gamepad.js';
|
|
|
4
4
|
import createComponentClass from '../factory.js';
|
|
5
5
|
|
|
6
6
|
const
|
|
7
|
+
/**
|
|
8
|
+
* Whether the event target is (or is inside) an editable field. Used so
|
|
9
|
+
* capture-phase game key handlers do not call preventDefault / emit game
|
|
10
|
+
* input while the user types in HTML overlays (forms, Notefully, etc.).
|
|
11
|
+
*
|
|
12
|
+
* @param {Event} event
|
|
13
|
+
* @return {Boolean}
|
|
14
|
+
*/
|
|
15
|
+
isEditableKeyboardTarget = function (event) {
|
|
16
|
+
const target = event?.target;
|
|
17
|
+
|
|
18
|
+
if (!target || typeof target.closest !== 'function') {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (target.isContentEditable) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const tag = target.tagName;
|
|
27
|
+
|
|
28
|
+
if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return !!target.closest('input, textarea, select, [contenteditable=""], [contenteditable="true"]');
|
|
33
|
+
},
|
|
34
|
+
/**
|
|
35
|
+
* When the game is paused or focus is in an editable field, let the browser
|
|
36
|
+
* handle keys (no game events, no preventDefault).
|
|
37
|
+
*
|
|
38
|
+
* @param {Event} event
|
|
39
|
+
* @return {Boolean}
|
|
40
|
+
*/
|
|
41
|
+
shouldPassThroughKeyboard = function (event) {
|
|
42
|
+
return !!(platypus.game?.paused || isEditableKeyboardTarget(event));
|
|
43
|
+
},
|
|
7
44
|
broadcastHandleController = function (tick) {
|
|
8
45
|
if (hasGamepads) {
|
|
9
46
|
const
|
|
@@ -170,25 +207,33 @@ export default createComponentClass(/** @lends platypus.components.HandlerContro
|
|
|
170
207
|
*/
|
|
171
208
|
initialize: function () {
|
|
172
209
|
const
|
|
173
|
-
{enableDefaultEvents} = this
|
|
210
|
+
{enableDefaultEvents} = this,
|
|
211
|
+
wrapKeyboard = (handler) => (event) => {
|
|
212
|
+
if (shouldPassThroughKeyboard(event)) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
handler(event);
|
|
217
|
+
};
|
|
174
218
|
|
|
175
219
|
this.upsToBlur = [];
|
|
176
220
|
|
|
177
221
|
if (platypus.game.settings.debug || enableDefaultEvents === 'all') { // If this is a test build, leave in the browser key combinations so debug tools can be opened as expected.
|
|
178
|
-
this.callbackKeyDown = onDown.bind(this, 'keyboard');
|
|
179
|
-
this.callbackKeyUp = onUp.bind(this, 'keyboard');
|
|
222
|
+
this.callbackKeyDown = wrapKeyboard(onDown.bind(this, 'keyboard'));
|
|
223
|
+
this.callbackKeyUp = wrapKeyboard(onUp.bind(this, 'keyboard'));
|
|
180
224
|
} else { // Otherwise remove default browser behavior for key inputs so that they do not interfere with game-play.
|
|
181
225
|
const
|
|
182
226
|
preventDefault = Array.isArray(enableDefaultEvents) ? (event) => enableDefaultEvents.indexOf(event.code) === -1 ? event.preventDefault() : null : (event) => event.preventDefault();
|
|
183
227
|
|
|
184
|
-
this.callbackKeyDown = (event) => {
|
|
228
|
+
this.callbackKeyDown = wrapKeyboard((event) => {
|
|
185
229
|
onDown.call(this, 'keyboard', event);
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
230
|
+
// Still aggressive for gameplay keys, but not while paused or typing in HTML.
|
|
231
|
+
preventDefault(event);
|
|
232
|
+
});
|
|
233
|
+
this.callbackKeyUp = wrapKeyboard((event) => {
|
|
189
234
|
onUp.call(this, 'keyboard', event);
|
|
190
235
|
preventDefault(event);
|
|
191
|
-
};
|
|
236
|
+
});
|
|
192
237
|
}
|
|
193
238
|
|
|
194
239
|
window.addEventListener('keydown', this.callbackKeyDown, true);
|
|
@@ -256,8 +301,8 @@ export default createComponentClass(/** @lends platypus.components.HandlerContro
|
|
|
256
301
|
},
|
|
257
302
|
methods: {
|
|
258
303
|
destroy: function () {
|
|
259
|
-
window.removeEventListener('keydown', this.callbackKeyDown);
|
|
260
|
-
window.removeEventListener('keyup', this.callbackKeyUp);
|
|
304
|
+
window.removeEventListener('keydown', this.callbackKeyDown, true);
|
|
305
|
+
window.removeEventListener('keyup', this.callbackKeyUp, true);
|
|
261
306
|
for (let i = 0; i < this.gamepads.length; i++) {
|
|
262
307
|
if (this.gamepads[i]) {
|
|
263
308
|
this.gamepads[i].recycle();
|