@melonjs/debug-plugin 14.4.2

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/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@melonjs/debug-plugin",
3
+ "version": "14.4.2",
4
+ "description": "melonJS debug plugin",
5
+ "type": "module",
6
+ "keywords": [
7
+ "2D",
8
+ "HTML5",
9
+ "javascript",
10
+ "TypeScript",
11
+ "es6",
12
+ "Canvas",
13
+ "WebGL",
14
+ "WebGL2",
15
+ "WebAudio",
16
+ "game",
17
+ "engine",
18
+ "tiled",
19
+ "tileset",
20
+ "mapeditor",
21
+ "browser",
22
+ "electron",
23
+ "mobile",
24
+ "cordova"
25
+ ],
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/melonjs/debug-plugin.git"
29
+ },
30
+ "bugs": {
31
+ "url": "https://github.com/melonjs/debug-plugin/issues"
32
+ },
33
+ "license": "MIT",
34
+ "author": "Olivier Biot (AltByte Pte Ltd)",
35
+ "funding": "https://github.com/sponsors/melonjs",
36
+ "engines": {
37
+ "node": ">= 19"
38
+ },
39
+ "main": "dist/melonjs-debug-plugin.js",
40
+ "module": "dist/melonjs-debug-plugin.js",
41
+ "sideEffects": false,
42
+ "files": [
43
+ "dist/melonjs-debug-plugin.js",
44
+ "src/",
45
+ "package.json",
46
+ "README.md",
47
+ "LICENSE"
48
+ ],
49
+ "peerDependencies": {
50
+ "melonjs": "^15.0.0"
51
+ },
52
+ "devDependencies": {
53
+ "@babel/eslint-parser": "^7.21.3",
54
+ "@babel/plugin-syntax-import-assertions": "^7.20.0",
55
+ "@rollup/plugin-commonjs": "^24.0.1",
56
+ "@rollup/plugin-image": "^3.0.2",
57
+ "@rollup/plugin-node-resolve": "^15.0.1",
58
+ "@rollup/plugin-replace": "^5.0.2",
59
+ "del-cli": "^5.0.0",
60
+ "eslint": "^8.36.0",
61
+ "eslint-plugin-jsdoc": "^40.0.3",
62
+ "rollup": "^3.19.1",
63
+ "rollup-plugin-bundle-size": "^1.0.3",
64
+ "rollup-plugin-string": "^3.0.0"
65
+ },
66
+ "scripts": {
67
+ "build": "npm run lint && rollup -c --silent",
68
+ "lint": "eslint src/**.js rollup.config.mjs",
69
+ "test": "",
70
+ "prepublishOnly": "npm run build && npm run test",
71
+ "clean": "del-cli --force build/*.*"
72
+ }
73
+ }
@@ -0,0 +1,17 @@
1
+ class Counters {
2
+ constructor() {
3
+ this.stats = [];
4
+ }
5
+ reset() {
6
+ Object.keys(this.stats).forEach((stat) => {
7
+ this.stats[stat] = 0;
8
+ });
9
+ }
10
+ inc(stat, value) {
11
+ this.stats[stat] += (value || 1);
12
+ }
13
+ get(stat) {
14
+ return this.stats[stat] || 0;
15
+ }
16
+ }
17
+ export default Counters;
@@ -0,0 +1,558 @@
1
+ import {
2
+ video,
3
+ Renderable,
4
+ utils,
5
+ BitmapText,
6
+ Rect,
7
+ event,
8
+ plugin,
9
+ plugins,
10
+ Entity,
11
+ Container,
12
+ Camera2d,
13
+ ImageLayer,
14
+ Text,
15
+ game,
16
+ input,
17
+ timer,
18
+ Math,
19
+ pool,
20
+ collision
21
+ } from "melonjs";
22
+
23
+ import Counters from "./counters";
24
+ import fontImageSource from "./font/PressStart2P.png";
25
+ import fontDataSource from "./font/PressStart2P.fnt";
26
+
27
+ const DEBUG_HEIGHT = 50;
28
+
29
+ class DebugPanel extends Renderable {
30
+ constructor(debugToggle = input.KEY.S) {
31
+ // call the super constructor
32
+ super(0, 0, video.renderer.getWidth(), DEBUG_HEIGHT );
33
+
34
+ // enable collision and event detection
35
+ this.isKinematic = false;
36
+
37
+ // to hold the debug CheckBox
38
+ // zone and status
39
+ this.checkbox = {};
40
+
41
+ // Useful counters
42
+ this.counters = new Counters([
43
+ "shapes",
44
+ "sprites",
45
+ "velocity",
46
+ "bounds",
47
+ "children"
48
+ ]);
49
+
50
+ // for z ordering
51
+ // make it ridiculously high
52
+ this.pos.z = Infinity;
53
+
54
+ // visibility flag
55
+ this.visible = false;
56
+
57
+ // frame update time in ms
58
+ this.frameUpdateTime = 0;
59
+
60
+ // frame draw time in ms
61
+ this.frameDrawTime = 0;
62
+
63
+ // set the object GUID value
64
+ this.GUID = "debug-" + utils.createGUID();
65
+
66
+ // set the object entity name
67
+ this.name = "debugPanel";
68
+
69
+ // the debug panel version
70
+ this.version = "__VERSION__";
71
+
72
+ // persistent
73
+ this.isPersistent = true;
74
+
75
+ // a floating object
76
+ this.floating = true;
77
+
78
+ // renderable
79
+ this.isRenderable = true;
80
+
81
+ // always update, even when not visible
82
+ this.alwaysUpdate = true;
83
+
84
+ // WebGL/Canvas compatibility
85
+ this.canvas = video.createCanvas(this.width, this.height, true);
86
+
87
+ // create a default font, with fixed char width
88
+ this.font_size = 10;
89
+ this.mod = 2;
90
+ if (this.width < 500) {
91
+ this.font_size = 7;
92
+ this.mod = this.mod * (this.font_size / 10);
93
+ }
94
+
95
+ // create the bitmapfont
96
+ var fontImage = new Image();
97
+ fontImage.src = fontImageSource;
98
+
99
+ this.font = new BitmapText(0, 0, {
100
+ fontData: fontDataSource,
101
+ font: fontImage
102
+ });
103
+ this.font.name = "debugPanelFont";
104
+
105
+ // clickable areas
106
+ var hash = utils.getUriFragment();
107
+ var size = 10 * this.mod;
108
+ this.checkbox.renderHitBox = new Rect(250, 2, size, size);
109
+ this.checkbox.renderHitBox.selected = hash.hitbox || false;
110
+ this.checkbox.renderVelocity = new Rect(250, 17, size, size);
111
+ this.checkbox.renderVelocity.selected = hash.velocity || false;
112
+ this.checkbox.renderQuadTree = new Rect(410, 2, size, size);
113
+ this.checkbox.renderVelocity.selected = hash.quadtree || false;
114
+
115
+ // add some keyboard shortcuts
116
+ this.debugToggle = debugToggle;
117
+ this.keyHandler = event.on(event.KEYDOWN, (action, keyCode) => {
118
+ if (keyCode === this.debugToggle) {
119
+ plugins.debugPanel.toggle();
120
+ }
121
+ });
122
+
123
+ // some internal string/length
124
+ this.help_str = "["+String.fromCharCode(32 + this.debugToggle)+"]show/hide";
125
+ this.help_str_len = this.font.measureText(this.help_str).width;
126
+ this.fps_str_len = this.font.measureText("00/00 fps").width;
127
+ this.memoryPositionX = 325 * this.mod;
128
+
129
+ // resize the panel if the browser is resized
130
+ event.on(event.CANVAS_ONRESIZE, (w) => {
131
+ this.resize(w, DEBUG_HEIGHT);
132
+ });
133
+
134
+ // few variables to keep track of time
135
+ this.frameUpdateStartTime = 0;
136
+ this.frameDrawStartTime = 0;
137
+ this.frameUpdateTime = 0;
138
+ this.frameDrawTime = 0;
139
+
140
+ event.on(event.GAME_BEFORE_UPDATE, (time) => {
141
+ this.frameUpdateStartTime = time;
142
+ });
143
+ event.on(event.GAME_AFTER_UPDATE, (time) => {
144
+ this.frameUpdateTime = time - this.frameUpdateStartTime;
145
+ });
146
+
147
+ event.on(event.GAME_BEFORE_DRAW, (time) => {
148
+ this.frameDrawStartTime = time;
149
+ this.counters.reset();
150
+ });
151
+ event.on(event.GAME_AFTER_DRAW, (time) => {
152
+ this.frameDrawTime = time - this.frameDrawStartTime;
153
+ });
154
+
155
+
156
+ this.anchorPoint.set(0, 0);
157
+
158
+ //patch patch patch !
159
+ this.patchSystemFn();
160
+ }
161
+
162
+ /**
163
+ * patch system fn to draw debug information
164
+ */
165
+ patchSystemFn() {
166
+ var _this = this;
167
+
168
+ // patch renderable.js
169
+ plugin.patch(Renderable, "postDraw", function (renderer) {
170
+
171
+ // call the original Renderable.postDraw function
172
+ this._patched.apply(this, arguments);
173
+
174
+ // increment the sprites counter
175
+ if (typeof this.image !== "undefined") {
176
+ _this.counters.inc("sprites");
177
+ }
178
+
179
+ // increment the bound counter
180
+ _this.counters.inc("bounds");
181
+
182
+ // increment the children counter
183
+ if (this instanceof Container) {
184
+ _this.counters.inc("children");
185
+ }
186
+
187
+ // don't do anything else if the panel is hidden
188
+ if (_this.visible) {
189
+
190
+ // omit following object as they are patched later through different methods
191
+ // XXX TODO: make this patched method more generic at Renderable level
192
+ if (!(this instanceof Entity) && !(this.ancestor instanceof Entity) && !(this instanceof Text) &&
193
+ !(this instanceof BitmapText) && !(this instanceof Camera2d)
194
+ && !(this instanceof ImageLayer)) {
195
+
196
+ // draw the renderable bounding box
197
+ if (_this.checkbox.renderHitBox.selected && this.getBounds().isFinite()) {
198
+
199
+ if (typeof this.ancestor !== "undefined") {
200
+ var absolutePosition = this.ancestor.getAbsolutePosition();
201
+
202
+ renderer.save();
203
+
204
+ // if this object of this renderable parent is not the root container
205
+ if (!this.root && !this.ancestor.root && this.ancestor.floating) {
206
+ renderer.translate(
207
+ -absolutePosition.x,
208
+ -absolutePosition.y
209
+ );
210
+ }
211
+ }
212
+
213
+ renderer.setColor("green");
214
+ renderer.stroke(this.getBounds());
215
+
216
+ // the sprite mask if defined
217
+ if (typeof this.mask !== "undefined") {
218
+ renderer.setColor("orange");
219
+ renderer.stroke(this.mask);
220
+ }
221
+
222
+ if (typeof this.body !== "undefined") {
223
+ var bounds = this.getBounds();
224
+ renderer.translate(bounds.x, bounds.y);
225
+
226
+ renderer.setColor("orange");
227
+ renderer.stroke(this.body.getBounds());
228
+
229
+ // draw all defined shapes
230
+ renderer.setColor("red");
231
+ for (var i = this.body.shapes.length, shape; i--, (shape = this.body.shapes[i]);) {
232
+ renderer.stroke(shape);
233
+ _this.counters.inc("shapes");
234
+ }
235
+ renderer.translate(-bounds.x, -bounds.y);
236
+ }
237
+
238
+ if (typeof this.ancestor !== "undefined") {
239
+ renderer.restore();
240
+ }
241
+ }
242
+ }
243
+ }
244
+ });
245
+
246
+ plugin.patch(BitmapText, "draw", function (renderer) {
247
+ // call the original Sprite.draw function
248
+ this._patched.apply(this, arguments);
249
+
250
+ // draw the font rectangle
251
+ if (_this.visible && _this.checkbox.renderHitBox.selected && this.name !== "debugPanelFont") {
252
+ var bounds = this.getBounds();
253
+
254
+ if (typeof this.ancestor !== "undefined") {
255
+ var ax = this.anchorPoint.x * bounds.width,
256
+ ay = this.anchorPoint.y * bounds.height;
257
+ // translate back as the bounds position
258
+ // is already adjusted to the anchor Point
259
+ renderer.save();
260
+ renderer.translate(ax, ay);
261
+ }
262
+
263
+ renderer.setColor("green");
264
+ renderer.stroke(bounds);
265
+
266
+ if (typeof this.ancestor !== "undefined") {
267
+ renderer.restore();
268
+ }
269
+ }
270
+ });
271
+
272
+ // patch text.js
273
+ plugin.patch(Text, "draw", function (renderer) {
274
+ // call the original Text.draw function
275
+ this._patched.apply(this, arguments);
276
+
277
+ if (_this.visible && _this.checkbox.renderHitBox.selected) {
278
+ var bounds = this.getBounds();
279
+
280
+ if (typeof this.ancestor !== "undefined") {
281
+ var absolutePosition = this.ancestor.getAbsolutePosition();
282
+
283
+ renderer.save();
284
+
285
+ // if this object of this renderable parent is not the root container
286
+ if (!this.root && !this.ancestor.root && this.ancestor.floating) {
287
+ renderer.translate(
288
+ -absolutePosition.x,
289
+ -absolutePosition.y
290
+ );
291
+ }
292
+ }
293
+
294
+ renderer.setColor("green");
295
+ renderer.stroke(bounds);
296
+ }
297
+ });
298
+
299
+ // patch entities.js
300
+ plugin.patch(Entity, "postDraw", function (renderer) {
301
+ // don't do anything else if the panel is hidden
302
+ if (_this.visible) {
303
+
304
+ // check if debug mode is enabled
305
+ if (_this.checkbox.renderHitBox.selected) {
306
+
307
+ renderer.save();
308
+
309
+
310
+ if (typeof this.ancestor !== "undefined") {
311
+ var absolutePosition = this.ancestor.getAbsolutePosition();
312
+
313
+ // if this object of this renderable parent is not the root container
314
+ if (!this.root && !this.ancestor.root && this.ancestor.floating) {
315
+ renderer.translate(
316
+ -absolutePosition.x,
317
+ -absolutePosition.y
318
+ );
319
+ }
320
+ }
321
+
322
+ if (this.renderable instanceof Renderable) {
323
+ renderer.setColor("green");
324
+ renderer.stroke(this.renderable.getBounds());
325
+ }
326
+
327
+ renderer.translate(
328
+ this.body.getBounds().x,
329
+ this.body.getBounds().y
330
+ );
331
+
332
+ renderer.translate(
333
+ -this.anchorPoint.x * this.body.getBounds().width,
334
+ -this.anchorPoint.y * this.body.getBounds().height
335
+ );
336
+
337
+ // draw the bounding rect shape
338
+ renderer.setColor("orange");
339
+ renderer.stroke(this.body.getBounds());
340
+
341
+ // draw all defined shapes
342
+ renderer.setColor("red");
343
+ for (var i = this.body.shapes.length, shape; i--, (shape = this.body.shapes[i]);) {
344
+ renderer.stroke(shape);
345
+ _this.counters.inc("shapes");
346
+ }
347
+
348
+ renderer.restore();
349
+
350
+ }
351
+
352
+ if (_this.checkbox.renderVelocity.selected && (this.body.vel.x || this.body.vel.y)) {
353
+ var bounds = this.body.getBounds();
354
+ var hWidth = bounds.width / 2;
355
+ var hHeight = bounds.height / 2;
356
+
357
+ renderer.save();
358
+ renderer.setLineWidth(1);
359
+
360
+ renderer.setColor("blue");
361
+ renderer.translate(0, -hHeight);
362
+ renderer.strokeLine(0, 0, ~~(this.body.vel.x * hWidth), ~~(this.body.vel.y * hHeight));
363
+ _this.counters.inc("velocity");
364
+
365
+ renderer.restore();
366
+ }
367
+ }
368
+ // call the original Entity.postDraw function
369
+ this._patched.apply(this, arguments);
370
+ });
371
+ }
372
+
373
+ /**
374
+ * show the debug panel
375
+ */
376
+ show() {
377
+ if (!this.visible) {
378
+ // add the debug panel to the game world
379
+ game.world.addChild(this, Infinity);
380
+ // register a mouse event for the checkboxes
381
+ input.registerPointerEvent("pointerdown", this, this.onClick.bind(this));
382
+ // mark it as visible
383
+ this.visible = true;
384
+ // force repaint
385
+ game.repaint();
386
+ }
387
+ }
388
+
389
+ /**
390
+ * hide the debug panel
391
+ */
392
+ hide() {
393
+ if (this.visible) {
394
+ // release the mouse event for the checkboxes
395
+ input.releasePointerEvent("pointerdown", this);
396
+ // remove the debug panel from the game world
397
+ game.world.removeChild(this, true);
398
+ // mark it as invisible
399
+ this.visible = false;
400
+ // force repaint
401
+ game.repaint();
402
+ }
403
+ }
404
+
405
+ update() {
406
+ // update the FPS counter
407
+ timer.countFPS();
408
+
409
+ return this.visible;
410
+ }
411
+
412
+ onClick(e) {
413
+ // check the clickable areas
414
+ if (this.checkbox.renderHitBox.contains(e.gameX, e.gameY)) {
415
+ this.checkbox.renderHitBox.selected = !this.checkbox.renderHitBox.selected;
416
+ } else if (this.checkbox.renderVelocity.contains(e.gameX, e.gameY)) {
417
+ // does nothing for now, since velocity is
418
+ // rendered together with hitboxes (is a global debug flag required?)
419
+ this.checkbox.renderVelocity.selected = !this.checkbox.renderVelocity.selected;
420
+ } else if (this.checkbox.renderQuadTree.contains(e.gameX, e.gameY)) {
421
+ this.checkbox.renderQuadTree.selected = !this.checkbox.renderQuadTree.selected;
422
+ }
423
+ // force repaint
424
+ game.repaint();
425
+ }
426
+
427
+ drawQuadTreeNode(renderer, node) {
428
+ var bounds = node.bounds;
429
+
430
+ // draw the current bounds
431
+ if (node.nodes.length === 0) {
432
+ // cap the alpha value to 0.4 maximum
433
+ var _alpha = (node.objects.length * 0.4) / collision.maxChildren;
434
+ if (_alpha > 0.0) {
435
+ renderer.save();
436
+ renderer.setColor("rgba(255,0,0," + _alpha + ")");
437
+ renderer.fillRect(bounds.left, bounds.top, bounds.width, bounds.height);
438
+ renderer.restore();
439
+ }
440
+ } else {
441
+ //has subnodes? drawQuadtree them!
442
+ for (var i = 0; i < node.nodes.length; i++) {
443
+ this.drawQuadTreeNode(renderer, node.nodes[i]);
444
+ }
445
+ }
446
+ }
447
+
448
+ drawQuadTree(renderer) {
449
+ var x = game.viewport.pos.x;
450
+ var y = game.viewport.pos.y;
451
+
452
+ renderer.translate(-x, -y);
453
+
454
+ this.drawQuadTreeNode(renderer, game.world.broadphase);
455
+
456
+ renderer.translate(x, y);
457
+ }
458
+
459
+ /** @private */
460
+ drawMemoryGraph(renderer, endX) {
461
+ if (window && window.performance && window.performance.memory) {
462
+ var usedHeap = Math.round(window.performance.memory.usedJSHeapSize / 1048576, 2);
463
+ var totalHeap = Math.round(window.performance.memory.totalJSHeapSize / 1048576, 2);
464
+ var maxLen = ~~(endX - this.memoryPositionX - 5);
465
+ var len = maxLen * (usedHeap / totalHeap);
466
+
467
+ renderer.setColor("#0065AD");
468
+ renderer.fillRect(this.memoryPositionX, 0, maxLen, 20);
469
+ renderer.setColor("#3AA4F0");
470
+ renderer.fillRect(this.memoryPositionX + 1, 1, len - 1, 17);
471
+
472
+ this.font.draw(renderer, "Heap : " + usedHeap + "/" + totalHeap + " MB", this.memoryPositionX + 5, 2 * this.mod);
473
+ } else {
474
+ // Heap Memory information not available
475
+ this.font.draw(renderer, "Heap : ??/?? MB", this.memoryPositionX, 2 * this.mod);
476
+ }
477
+ this.font.draw(renderer, "Pool : " + pool.getInstanceCount(), this.memoryPositionX, 10 * this.mod);
478
+ }
479
+
480
+ draw(renderer) {
481
+ renderer.save();
482
+
483
+ // draw the QuadTree (before the panel)
484
+ if (this.checkbox.renderQuadTree.selected === true) {
485
+ this.drawQuadTree(renderer);
486
+ }
487
+
488
+ // draw the panel
489
+ renderer.setGlobalAlpha(0.5);
490
+ renderer.setColor("black");
491
+ renderer.fillRect(
492
+ this.left, this.top,
493
+ this.width, this.height
494
+ );
495
+ renderer.setGlobalAlpha(1.0);
496
+ renderer.setColor("white");
497
+
498
+ this.font.textAlign = "left";
499
+
500
+ this.font.draw(renderer, "#objects : " + game.world.children.length, 5 * this.mod, 2 * this.mod);
501
+ this.font.draw(renderer, "#draws : " + game.world.drawCount, 5 * this.mod, 10 * this.mod);
502
+
503
+ // debug checkboxes
504
+ this.font.draw(renderer, "?hitbox [" + (this.checkbox.renderHitBox.selected ? "x" : " ") + "]", 75 * this.mod, 2 * this.mod);
505
+ this.font.draw(renderer, "?velocity [" + (this.checkbox.renderVelocity.selected ? "x" : " ") + "]", 75 * this.mod, 10 * this.mod);
506
+
507
+ this.font.draw(renderer, "?QuadTree [" + (this.checkbox.renderQuadTree.selected ? "x" : " ") + "]", 150 * this.mod, 2 * this.mod);
508
+
509
+ // draw the update duration
510
+ this.font.draw(renderer, "Update : " + this.frameUpdateTime.toFixed(2) + " ms", 225 * this.mod, 2 * this.mod);
511
+ // draw the draw duration
512
+ this.font.draw(renderer, "Draw : " + this.frameDrawTime.toFixed(2) + " ms", 225 * this.mod, 10 * this.mod);
513
+
514
+
515
+ // Draw color code hints (not supported with bitmapfont)
516
+ //this.font.fillStyle.copy("red");
517
+ this.font.draw(renderer, "Shapes : " + this.counters.get("shapes"), 5 * this.mod, 17 * this.mod);
518
+
519
+ //this.font.fillStyle.copy("green");
520
+ this.font.draw(renderer, "Sprites : " + this.counters.get("sprites"), 75 * this.mod, 17 * this.mod);
521
+
522
+ //this.font.fillStyle.copy("blue");
523
+ this.font.draw(renderer, "Velocity : " + this.counters.get("velocity"), 150 * this.mod, 17 * this.mod);
524
+
525
+ //this.font.fillStyle.copy("orange");
526
+ this.font.draw(renderer, "Bounds : " + this.counters.get("bounds"), 225 * this.mod, 17 * this.mod);
527
+
528
+ //this.font.fillStyle.copy("purple");
529
+ this.font.draw(renderer, "Children : " + this.counters.get("children"), 325 * this.mod, 17 * this.mod);
530
+
531
+ // Reset font style
532
+ //this.font.setFont("courier", this.font_size, "white");
533
+
534
+ // draw the memory heap usage
535
+ var endX = this.width - 5;
536
+ this.drawMemoryGraph(renderer, endX - this.help_str_len);
537
+
538
+ this.font.textAlign = "right";
539
+
540
+ // some help string
541
+ this.font.draw(renderer, this.help_str, endX, 17 * this.mod);
542
+
543
+ //fps counter
544
+ var fps_str = timer.fps + "/" + timer.maxfps + " fps";
545
+ this.font.draw(renderer, fps_str, endX, 2 * this.mod);
546
+
547
+ renderer.restore();
548
+ }
549
+
550
+ onDestroyEvent() {
551
+ // hide the panel
552
+ this.hide();
553
+ // unbind keys event
554
+ input.unbindKey(this.toggleKey);
555
+ }
556
+ }
557
+
558
+ export default DebugPanel;