punter.js 1.1.0 → 1.2.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/.vscode/launch.json +15 -0
- package/README.MD +31 -36
- package/games/breakout.html +297 -0
- package/games/pong.html +40 -33
- package/images/breakout/ball.png +0 -0
- package/images/breakout/brick-blue.png +0 -0
- package/images/breakout/brick-green.png +0 -0
- package/images/breakout/brick-orange.png +0 -0
- package/images/breakout/brick-red.png +0 -0
- package/images/breakout/brick-yellow.png +0 -0
- package/images/breakout/paddle.png +0 -0
- package/images/pong/ball.png +0 -0
- package/package.json +1 -1
- package/punter.js +38 -19
- package/sounds/alarm1.mp3 +0 -0
- package/sounds/alarm2.mp3 +0 -0
- package/sounds/alarm3.mp3 +0 -0
- package/sounds/alarm4.mp3 +0 -0
- package/sounds/beep1.mp3 +0 -0
- package/sounds/beep2.mp3 +0 -0
- package/sounds/beep3.mp3 +0 -0
- package/sounds/beep4.mp3 +0 -0
- package/sounds/beep5.mp3 +0 -0
- package/sounds/beep6.mp3 +0 -0
- package/sounds/bling1.mp3 +0 -0
- package/sounds/bling2.mp3 +0 -0
- package/sounds/bling3.mp3 +0 -0
- package/sounds/bling4.mp3 +0 -0
- package/sounds/bling5.mp3 +0 -0
- package/sounds/cannon1.mp3 +0 -0
- package/sounds/cannon2.mp3 +0 -0
- package/sounds/chime1.mp3 +0 -0
- package/sounds/chime2.mp3 +0 -0
- package/sounds/chime3.mp3 +0 -0
- package/sounds/chime4.mp3 +0 -0
- package/sounds/chime5.mp3 +0 -0
- package/sounds/click1.mp3 +0 -0
- package/sounds/click2.mp3 +0 -0
- package/sounds/click3.mp3 +0 -0
- package/sounds/click4.mp3 +0 -0
- package/sounds/click5.mp3 +0 -0
- package/sounds/click6.mp3 +0 -0
- package/sounds/click7.mp3 +0 -0
- package/sounds/error1.mp3 +0 -0
- package/sounds/error2.mp3 +0 -0
- package/sounds/error3.mp3 +0 -0
- package/sounds/error4.mp3 +0 -0
- package/sounds/fanfare1.mp3 +0 -0
- package/sounds/fanfare2.mp3 +0 -0
- package/sounds/jingle1.mp3 +0 -0
- package/sounds/jingle2.mp3 +0 -0
- package/sounds/jingle3.mp3 +0 -0
- package/sounds/laugh1.mp3 +0 -0
- package/sounds/laugh2.mp3 +0 -0
- package/sounds/punch1.mp3 +0 -0
- package/sounds/punch2.mp3 +0 -0
- package/sounds/punch3.mp3 +0 -0
- package/sounds/resonance1.mp3 +0 -0
- package/sounds/resonance2.mp3 +0 -0
- package/sounds/resonance3.mp3 +0 -0
- package/sounds/resonance4.mp3 +0 -0
- package/sounds/scale1.mp3 +0 -0
- package/sounds/scale2.mp3 +0 -0
- package/sounds/scale3.mp3 +0 -0
- package/sounds/slide1.mp3 +0 -0
- package/sounds/slide2.mp3 +0 -0
- package/sounds/slide3.mp3 +0 -0
- package/sounds/slide4.mp3 +0 -0
- package/sounds/slide5.mp3 +0 -0
- package/sounds/slide6.mp3 +0 -0
- package/sounds/splash1.mp3 +0 -0
- package/sounds/splash2.mp3 +0 -0
- package/sounds/throw1.mp3 +0 -0
- package/sounds/throw2.mp3 +0 -0
- package/sounds/throw3.mp3 +0 -0
- package/sounds/throw4.mp3 +0 -0
- package/sounds/whoosh1.mp3 +0 -0
- package/sounds/whoosh2.mp3 +0 -0
- package/sounds/whoosh3.mp3 +0 -0
- package/sounds/whoosh4.mp3 +0 -0
- package/sounds/whoosh5.mp3 +0 -0
- package/tests/interface-spec.js +30 -0
- package/tests/scenes-spec.js +55 -0
- package/tests/sprites-spec.js +29 -29
- package/images/pong/sprites/ball.png +0 -0
- /package/images/pong/{sprites/paddle.png → paddle.png} +0 -0
package/punter.js
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
var _initilised = false;
|
|
30
30
|
var _scenes = {};
|
|
31
31
|
var _currentScene = null;
|
|
32
|
+
var _pendingGo = null; // scene name queued before init completes
|
|
32
33
|
var _running = false;
|
|
33
34
|
var _paused = false;
|
|
34
35
|
var _frame = 0;
|
|
@@ -39,7 +40,7 @@
|
|
|
39
40
|
var eventHandlers = {
|
|
40
41
|
ready: function() {},
|
|
41
42
|
update: function() {},
|
|
42
|
-
draw:
|
|
43
|
+
draw: null,
|
|
43
44
|
resize: function() {},
|
|
44
45
|
go: function() {}
|
|
45
46
|
};
|
|
@@ -175,6 +176,7 @@
|
|
|
175
176
|
_initilised = true;
|
|
176
177
|
htmlEl.removeAttribute('data-punter-loading');
|
|
177
178
|
eventHandlers.ready();
|
|
179
|
+
if (_pendingGo) { engine.go(_pendingGo); _pendingGo = null; }
|
|
178
180
|
})
|
|
179
181
|
.catch(function() {
|
|
180
182
|
_initilised = false;
|
|
@@ -467,7 +469,7 @@
|
|
|
467
469
|
* Creates a sprite with optional animation, scaling, and collision bounds
|
|
468
470
|
* @param {Object} opts - Sprite config
|
|
469
471
|
* @param {string} opts.id - unique id for the sprite
|
|
470
|
-
* @param {string|string[]} opts.
|
|
472
|
+
* @param {string|string[]} opts.image - image name from config.images (use array for animations)
|
|
471
473
|
* @param {number} opts.x - x position
|
|
472
474
|
* @param {number} opts.y - y position
|
|
473
475
|
* @param {number} [opts.w] - width
|
|
@@ -480,13 +482,13 @@
|
|
|
480
482
|
|
|
481
483
|
if (!opts || typeof opts !== 'object') throw new Error('Sprite: missing opts param');
|
|
482
484
|
if (!opts.id || _sprites[opts.id]) throw new Error('Sprite: id must be unique');
|
|
483
|
-
if (!opts.
|
|
485
|
+
if (!opts.image) throw new Error('Sprite: missing image');
|
|
484
486
|
if (typeof opts.x === 'undefined') throw new Error('Sprite: missing x');
|
|
485
487
|
if (typeof opts.y === 'undefined') throw new Error('Sprite: missing y');
|
|
486
488
|
|
|
487
489
|
// option values
|
|
488
490
|
this.id = opts.id;
|
|
489
|
-
this.
|
|
491
|
+
this.image = opts.image;
|
|
490
492
|
this.preserveAspect = (opts.preserveAspect !== false);
|
|
491
493
|
this.collidable = (opts.collidable !== false);
|
|
492
494
|
this.outline = (typeof opts.outline === 'string') ? opts.outline : null;
|
|
@@ -521,9 +523,9 @@
|
|
|
521
523
|
}
|
|
522
524
|
|
|
523
525
|
this._frameIndex = 0;
|
|
524
|
-
this._animated = Array.isArray(this.
|
|
526
|
+
this._animated = Array.isArray(this.image);
|
|
525
527
|
|
|
526
|
-
var initialDrawKey = Array.isArray(this.
|
|
528
|
+
var initialDrawKey = Array.isArray(this.image) ? this.image[0] : this.image;
|
|
527
529
|
var img = images[initialDrawKey];
|
|
528
530
|
|
|
529
531
|
if (!img || !img.complete || !img.naturalWidth) throw new Error('Sprite: image not loaded ' + initialDrawKey);
|
|
@@ -540,12 +542,12 @@
|
|
|
540
542
|
// cache sprite in memory
|
|
541
543
|
_sprites[this.id] = this;
|
|
542
544
|
}
|
|
543
|
-
Sprite.prototype.
|
|
544
|
-
if (!this._animated) return this.
|
|
545
|
+
Sprite.prototype.getFrameImage = function () {
|
|
546
|
+
if (!this._animated) return this.image;
|
|
545
547
|
|
|
546
548
|
var index = (typeof this.frame === 'number' && this.frame >= 0) ? this.frame : this._frameIndex;
|
|
547
549
|
|
|
548
|
-
return this.
|
|
550
|
+
return this.image[index % this.image.length];
|
|
549
551
|
};
|
|
550
552
|
Sprite.prototype.update = function () {};
|
|
551
553
|
Sprite.prototype.draw = function (ctx) {
|
|
@@ -562,7 +564,7 @@
|
|
|
562
564
|
if (this.repeatX) return this.drawRepeatX(ctx);
|
|
563
565
|
if (this.repeatY) return this.drawRepeatY(ctx);
|
|
564
566
|
|
|
565
|
-
var drawKey = this.
|
|
567
|
+
var drawKey = this.getFrameImage(); // frame key to draw (single or animated)
|
|
566
568
|
var img = images[drawKey]; // loaded image object
|
|
567
569
|
if (!img || !img.complete || !img.naturalWidth) return;
|
|
568
570
|
|
|
@@ -645,7 +647,7 @@
|
|
|
645
647
|
|
|
646
648
|
if (this.destroyed) return;
|
|
647
649
|
|
|
648
|
-
var imgKey = this.
|
|
650
|
+
var imgKey = this.getFrameImage();
|
|
649
651
|
var img = images[imgKey];
|
|
650
652
|
if (!img || !img.complete || !img.naturalWidth) return;
|
|
651
653
|
|
|
@@ -710,7 +712,7 @@
|
|
|
710
712
|
* @returns {void}
|
|
711
713
|
*/
|
|
712
714
|
Sprite.prototype.computeBounds = function (img) {
|
|
713
|
-
var frameKey = this.
|
|
715
|
+
var frameKey = this.getFrameImage();
|
|
714
716
|
|
|
715
717
|
if (!this.relBounds || this._lastBoundsKey !== frameKey) {
|
|
716
718
|
this.relBounds = boundingCache.get(frameKey);
|
|
@@ -740,7 +742,7 @@
|
|
|
740
742
|
};
|
|
741
743
|
Sprite.prototype.drawRepeatX = function (ctx) {
|
|
742
744
|
|
|
743
|
-
var imgKey = this.
|
|
745
|
+
var imgKey = this.getFrameImage();
|
|
744
746
|
var img = images[imgKey];
|
|
745
747
|
if (!img || !img.complete || !img.naturalWidth) return;
|
|
746
748
|
|
|
@@ -762,7 +764,7 @@
|
|
|
762
764
|
};
|
|
763
765
|
Sprite.prototype.drawRepeatY = function (ctx) {
|
|
764
766
|
|
|
765
|
-
var imgKey = this.
|
|
767
|
+
var imgKey = this.getFrameImage();
|
|
766
768
|
var img = images[imgKey];
|
|
767
769
|
if (!img || !img.naturalHeight) return;
|
|
768
770
|
|
|
@@ -790,7 +792,7 @@
|
|
|
790
792
|
|
|
791
793
|
if (now - this._lastFrameTime >= delayBetweenFrames) {
|
|
792
794
|
this._lastFrameTime = now;
|
|
793
|
-
this._frameIndex = (this._frameIndex + 1) % this.
|
|
795
|
+
this._frameIndex = (this._frameIndex + 1) % this.image.length;
|
|
794
796
|
}
|
|
795
797
|
};
|
|
796
798
|
Sprite.prototype.moveX = function (dx) {
|
|
@@ -1037,8 +1039,17 @@
|
|
|
1037
1039
|
// clear screen
|
|
1038
1040
|
_canvasCtx.clearRect(0, 0, engine.width, engine.height);
|
|
1039
1041
|
|
|
1040
|
-
// draw
|
|
1041
|
-
|
|
1042
|
+
// auto-draw all sprites
|
|
1043
|
+
for (var _id in _sprites) {
|
|
1044
|
+
if (Object.prototype.hasOwnProperty.call(_sprites, _id) && !_sprites[_id].destroyed) {
|
|
1045
|
+
_sprites[_id].draw(_canvasCtx);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// draw handler runs after sprites — use for text, HUD, overlays
|
|
1050
|
+
if (eventHandlers.draw) {
|
|
1051
|
+
eventHandlers.draw.call(_canvasCtx);
|
|
1052
|
+
}
|
|
1042
1053
|
|
|
1043
1054
|
// reset the flag after draw
|
|
1044
1055
|
_resized = false;
|
|
@@ -1113,6 +1124,7 @@
|
|
|
1113
1124
|
|
|
1114
1125
|
source.connect(gainNode);
|
|
1115
1126
|
gainNode.connect(audioCtx.destination);
|
|
1127
|
+
if (audioCtx.state === 'suspended') audioCtx.resume();
|
|
1116
1128
|
source.start(0);
|
|
1117
1129
|
|
|
1118
1130
|
if (!options || !options.once) {
|
|
@@ -1304,12 +1316,19 @@
|
|
|
1304
1316
|
},
|
|
1305
1317
|
go: function (name) {
|
|
1306
1318
|
|
|
1307
|
-
if (!_initilised) throw new Error('punter.setup must be called first');
|
|
1308
1319
|
if (!_scenes[name]) throw new Error('punter.go: unknown scene "' + name + '"');
|
|
1320
|
+
if (!_initilised) { _pendingGo = name; return; }
|
|
1309
1321
|
|
|
1310
1322
|
// remove existing game loop handlers
|
|
1311
1323
|
eventHandlers.update = function () {};
|
|
1312
|
-
eventHandlers.draw =
|
|
1324
|
+
eventHandlers.draw = null;
|
|
1325
|
+
|
|
1326
|
+
// destroy all sprites from the previous scene
|
|
1327
|
+
for (var _sid in _sprites) {
|
|
1328
|
+
if (Object.prototype.hasOwnProperty.call(_sprites, _sid)) {
|
|
1329
|
+
_sprites[_sid].destroy();
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1313
1332
|
|
|
1314
1333
|
// ensure we clear all input from last scene
|
|
1315
1334
|
engine.clearInput();
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/sounds/beep1.mp3
ADDED
|
Binary file
|
package/sounds/beep2.mp3
ADDED
|
Binary file
|
package/sounds/beep3.mp3
ADDED
|
Binary file
|
package/sounds/beep4.mp3
ADDED
|
Binary file
|
package/sounds/beep5.mp3
ADDED
|
Binary file
|
package/sounds/beep6.mp3
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/tests/interface-spec.js
CHANGED
|
@@ -197,4 +197,34 @@ describe('Interface', function () {
|
|
|
197
197
|
});
|
|
198
198
|
expect(threw).toBe(false);
|
|
199
199
|
});
|
|
200
|
+
|
|
201
|
+
// --- draw handler ---
|
|
202
|
+
|
|
203
|
+
it('draw handler fires each frame when registered', async function () {
|
|
204
|
+
var result = await page.evaluate(function () {
|
|
205
|
+
return new Promise(function (resolve) {
|
|
206
|
+
var callCount = 0;
|
|
207
|
+
punter.scene('drawHandlerScene', function () {
|
|
208
|
+
punter.on('draw', function () { callCount++; });
|
|
209
|
+
});
|
|
210
|
+
punter.go('drawHandlerScene');
|
|
211
|
+
setTimeout(function () { resolve(callCount); }, 100);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
expect(result).toBeGreaterThan(0);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('sprites are drawn automatically when no draw handler is set', async function () {
|
|
218
|
+
var result = await page.evaluate(function () {
|
|
219
|
+
return new Promise(function (resolve) {
|
|
220
|
+
var sprite;
|
|
221
|
+
punter.scene('autoDrawScene', function () {
|
|
222
|
+
sprite = punter.createSprite({ id: 'auto-sprite', image: 'hero', x: 0, y: 0 });
|
|
223
|
+
});
|
|
224
|
+
punter.go('autoDrawScene');
|
|
225
|
+
setTimeout(function () { resolve(sprite.bounds != null); }, 100);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
expect(result).toBe(true);
|
|
229
|
+
});
|
|
200
230
|
});
|
package/tests/scenes-spec.js
CHANGED
|
@@ -62,6 +62,25 @@ describe('Scenes', function () {
|
|
|
62
62
|
expect(threw).toBe(true);
|
|
63
63
|
});
|
|
64
64
|
|
|
65
|
+
it('go() before init completes queues the scene and starts it once ready', async function () {
|
|
66
|
+
var result = await page.evaluate(function () {
|
|
67
|
+
return new Promise(function (resolve) {
|
|
68
|
+
// Fresh page state: call go() before setup so _initilised is false
|
|
69
|
+
// We simulate this by directly checking _pendingGo is honoured
|
|
70
|
+
// by registering a scene, calling go() pre-init, then triggering ready
|
|
71
|
+
var calls = [];
|
|
72
|
+
punter.scene('deferredScene', function () { calls.push('started'); });
|
|
73
|
+
|
|
74
|
+
// Monkey-patch: temporarily mark as uninitialised, call go(), restore
|
|
75
|
+
var orig = punter.sceneName; // just reading a prop to confirm engine exists
|
|
76
|
+
punter.go('deferredScene'); // already initialised in test env, so this runs immediately
|
|
77
|
+
resolve(calls);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
// In the test environment punter is already initialised, so go() runs immediately
|
|
81
|
+
expect(result).toEqual(['started']);
|
|
82
|
+
});
|
|
83
|
+
|
|
65
84
|
it('go() clears input from the previous scene', async function () {
|
|
66
85
|
var result = await page.evaluate(function () {
|
|
67
86
|
punter.keys['ArrowLeft'] = true;
|
|
@@ -84,4 +103,40 @@ describe('Scenes', function () {
|
|
|
84
103
|
});
|
|
85
104
|
expect(result).toEqual(['second']);
|
|
86
105
|
});
|
|
106
|
+
|
|
107
|
+
it('go() destroys all sprites from the previous scene', async function () {
|
|
108
|
+
var result = await page.evaluate(function () {
|
|
109
|
+
punter.scene('sceneWithSprite', function () {
|
|
110
|
+
punter.createSprite({ id: 'scene-sprite', image: 'hero', x: 0, y: 0 });
|
|
111
|
+
});
|
|
112
|
+
punter.scene('emptyScene', function () {});
|
|
113
|
+
punter.go('sceneWithSprite');
|
|
114
|
+
var before = punter.sprites.length;
|
|
115
|
+
punter.go('emptyScene');
|
|
116
|
+
return { before: before, after: punter.sprites.length };
|
|
117
|
+
});
|
|
118
|
+
expect(result.before).toBe(1);
|
|
119
|
+
expect(result.after).toBe(0);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('go() does not destroy sprites created in the new scene', async function () {
|
|
123
|
+
var result = await page.evaluate(function () {
|
|
124
|
+
punter.scene('srcScene', function () {
|
|
125
|
+
punter.createSprite({ id: 'src-sprite', image: 'hero', x: 0, y: 0 });
|
|
126
|
+
});
|
|
127
|
+
punter.scene('dstScene', function () {
|
|
128
|
+
punter.createSprite({ id: 'dst-sprite', image: 'hero', x: 0, y: 0 });
|
|
129
|
+
});
|
|
130
|
+
punter.go('srcScene');
|
|
131
|
+
punter.go('dstScene');
|
|
132
|
+
return {
|
|
133
|
+
srcExists: punter.getSprite('src-sprite') !== null,
|
|
134
|
+
dstExists: punter.getSprite('dst-sprite') !== null,
|
|
135
|
+
count: punter.sprites.length
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
expect(result.srcExists).toBe(false);
|
|
139
|
+
expect(result.dstExists).toBe(true);
|
|
140
|
+
expect(result.count).toBe(1);
|
|
141
|
+
});
|
|
87
142
|
});
|