punter.js 1.0.1 → 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.
Files changed (87) hide show
  1. package/.vscode/launch.json +15 -0
  2. package/README.MD +41 -46
  3. package/games/breakout.html +297 -0
  4. package/games/pong.html +59 -39
  5. package/images/breakout/ball.png +0 -0
  6. package/images/breakout/brick-blue.png +0 -0
  7. package/images/breakout/brick-green.png +0 -0
  8. package/images/breakout/brick-orange.png +0 -0
  9. package/images/breakout/brick-red.png +0 -0
  10. package/images/breakout/brick-yellow.png +0 -0
  11. package/images/breakout/paddle.png +0 -0
  12. package/images/pong/ball.png +0 -0
  13. package/package.json +3 -2
  14. package/punter.js +54 -35
  15. package/sounds/alarm1.mp3 +0 -0
  16. package/sounds/alarm2.mp3 +0 -0
  17. package/sounds/alarm3.mp3 +0 -0
  18. package/sounds/alarm4.mp3 +0 -0
  19. package/sounds/beep1.mp3 +0 -0
  20. package/sounds/beep2.mp3 +0 -0
  21. package/sounds/beep3.mp3 +0 -0
  22. package/sounds/beep4.mp3 +0 -0
  23. package/sounds/beep5.mp3 +0 -0
  24. package/sounds/beep6.mp3 +0 -0
  25. package/sounds/bling1.mp3 +0 -0
  26. package/sounds/bling2.mp3 +0 -0
  27. package/sounds/bling3.mp3 +0 -0
  28. package/sounds/bling4.mp3 +0 -0
  29. package/sounds/bling5.mp3 +0 -0
  30. package/sounds/cannon1.mp3 +0 -0
  31. package/sounds/cannon2.mp3 +0 -0
  32. package/sounds/chime1.mp3 +0 -0
  33. package/sounds/chime2.mp3 +0 -0
  34. package/sounds/chime3.mp3 +0 -0
  35. package/sounds/chime4.mp3 +0 -0
  36. package/sounds/chime5.mp3 +0 -0
  37. package/sounds/click1.mp3 +0 -0
  38. package/sounds/click2.mp3 +0 -0
  39. package/sounds/click3.mp3 +0 -0
  40. package/sounds/click4.mp3 +0 -0
  41. package/sounds/click5.mp3 +0 -0
  42. package/sounds/click6.mp3 +0 -0
  43. package/sounds/click7.mp3 +0 -0
  44. package/sounds/error1.mp3 +0 -0
  45. package/sounds/error2.mp3 +0 -0
  46. package/sounds/error3.mp3 +0 -0
  47. package/sounds/error4.mp3 +0 -0
  48. package/sounds/fanfare1.mp3 +0 -0
  49. package/sounds/fanfare2.mp3 +0 -0
  50. package/sounds/jingle1.mp3 +0 -0
  51. package/sounds/jingle2.mp3 +0 -0
  52. package/sounds/jingle3.mp3 +0 -0
  53. package/sounds/laugh1.mp3 +0 -0
  54. package/sounds/laugh2.mp3 +0 -0
  55. package/sounds/punch1.mp3 +0 -0
  56. package/sounds/punch2.mp3 +0 -0
  57. package/sounds/punch3.mp3 +0 -0
  58. package/sounds/resonance1.mp3 +0 -0
  59. package/sounds/resonance2.mp3 +0 -0
  60. package/sounds/resonance3.mp3 +0 -0
  61. package/sounds/resonance4.mp3 +0 -0
  62. package/sounds/scale1.mp3 +0 -0
  63. package/sounds/scale2.mp3 +0 -0
  64. package/sounds/scale3.mp3 +0 -0
  65. package/sounds/slide1.mp3 +0 -0
  66. package/sounds/slide2.mp3 +0 -0
  67. package/sounds/slide3.mp3 +0 -0
  68. package/sounds/slide4.mp3 +0 -0
  69. package/sounds/slide5.mp3 +0 -0
  70. package/sounds/slide6.mp3 +0 -0
  71. package/sounds/splash1.mp3 +0 -0
  72. package/sounds/splash2.mp3 +0 -0
  73. package/sounds/throw1.mp3 +0 -0
  74. package/sounds/throw2.mp3 +0 -0
  75. package/sounds/throw3.mp3 +0 -0
  76. package/sounds/throw4.mp3 +0 -0
  77. package/sounds/whoosh1.mp3 +0 -0
  78. package/sounds/whoosh2.mp3 +0 -0
  79. package/sounds/whoosh3.mp3 +0 -0
  80. package/sounds/whoosh4.mp3 +0 -0
  81. package/sounds/whoosh5.mp3 +0 -0
  82. package/tests/fixtures/index.html +2 -2
  83. package/tests/interface-spec.js +31 -1
  84. package/tests/scenes-spec.js +55 -0
  85. package/tests/sprites-spec.js +29 -29
  86. package/images/pong/sprites/ball.png +0 -0
  87. /package/images/pong/{sprites/paddle.png → paddle.png} +0 -0
package/games/pong.html CHANGED
@@ -1,13 +1,28 @@
1
1
  <!doctype html>
2
2
  <html>
3
3
  <head>
4
+ <title>Pong</title>
4
5
  <meta charset="utf-8">
5
6
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>Pong</title>
7
7
  <style>
8
- * { margin: 0; padding: 0; box-sizing: border-box; }
9
- html, body { width: 100%; height: 100%; background: #1e2230; overflow: hidden; }
10
- canvas { display: block; }
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ html, body {
15
+ width: 100%;
16
+ height: 100%;
17
+ background: #1e2230;
18
+ overflow: hidden;
19
+ }
20
+
21
+ canvas {
22
+ display: block;
23
+ cursor: none;
24
+ background: #1b2125;
25
+ }
11
26
  </style>
12
27
  <script src="../punter.js"></script>
13
28
  </head>
@@ -54,10 +69,11 @@
54
69
  // =========================================================
55
70
  // GAME SETTINGS
56
71
  // =========================================================
57
- var MARGIN = px(40); // gap between paddle and screen edge
58
- var PADDLE_W = px(14);
59
- var PADDLE_H = px(90);
60
- var BALL_SIZE = px(14);
72
+ var MARGIN = px(20); // gap between paddle and screen edge
73
+ var PADDLE_W = px(14); // width of paddle
74
+ var PADDLE_H = px(90); // height of paddle
75
+ var AI_PADDLE_H = px(90);
76
+ var BALL_SIZE = px(24);
61
77
  var PADDLE_SPEED = px(7);
62
78
  var AI_SPEED = px(5);
63
79
  var BALL_SPEED = px(5);
@@ -80,11 +96,15 @@
80
96
  // SETUP
81
97
  // Load the two sprite images before the game starts.
82
98
  // =========================================================
83
- punter.init({
99
+ punter.setup({
84
100
  canvas: '#game',
85
- sprites: {
86
- paddle: '../images/pong/sprites/paddle.png',
87
- ball: '../images/pong/sprites/ball.png'
101
+ images: {
102
+ paddle: '../images/pong/paddle.png',
103
+ ball: '../images/pong/ball.png'
104
+ },
105
+ sounds: {
106
+ bounce: '../sounds/beep6.mp3',
107
+ die: '../sounds/error2.mp3'
88
108
  }
89
109
  });
90
110
 
@@ -93,11 +113,13 @@
93
113
  // =========================================================
94
114
  punter.scene('play', function () {
95
115
 
116
+ score = { player: 0, ai: 0 };
117
+
96
118
  // Create the three sprites punter.js will draw each frame.
97
119
  // w/h set the draw size; the image is scaled to fit automatically.
98
- playerSprite = punter.createSprite({ id: 'player', key: 'paddle', x: 0, y: 0, w: PADDLE_W, h: PADDLE_H });
99
- aiSprite = punter.createSprite({ id: 'ai', key: 'paddle', x: 0, y: 0, w: PADDLE_W, h: PADDLE_H });
100
- ballSprite = punter.createSprite({ id: 'ball', key: 'ball', x: 0, y: 0, w: BALL_SIZE, h: BALL_SIZE });
120
+ playerSprite = punter.createSprite({ id: 'player', image: 'paddle', x: 0, y: 0, w: PADDLE_W, h: PADDLE_H });
121
+ aiSprite = punter.createSprite({ id: 'ai', image: 'paddle', x: 0, y: 0, w: PADDLE_W, h: AI_PADDLE_H });
122
+ ballSprite = punter.createSprite({ id: 'ball', image: 'ball', x: 0, y: 0, w: BALL_SIZE, h: BALL_SIZE });
101
123
 
102
124
  // Place everything in the correct starting position
103
125
  resetPositions();
@@ -113,8 +135,8 @@
113
135
  aiSprite.x = w - MARGIN - PADDLE_W;
114
136
 
115
137
  // --- Player: keyboard ---
116
- if (punter.keys['ArrowUp'] || punter.keys['w']) playerSprite.y -= PADDLE_SPEED;
117
- if (punter.keys['ArrowDown'] || punter.keys['s']) playerSprite.y += PADDLE_SPEED;
138
+ if (punter.keys['ArrowUp'] || punter.keys['w']) playerSprite.moveY(-PADDLE_SPEED);
139
+ if (punter.keys['ArrowDown'] || punter.keys['s']) playerSprite.moveY(PADDLE_SPEED);
118
140
 
119
141
  // --- Player: tap / click ---
120
142
  if (punter.mouse.clicked) {
@@ -126,13 +148,13 @@
126
148
 
127
149
  // --- AI: follow the ball ---
128
150
  var aiCenterY = aiSprite.y + PADDLE_H / 2;
129
- if (ballSprite.y < aiCenterY - 10) aiSprite.y -= AI_SPEED;
130
- if (ballSprite.y > aiCenterY + 10) aiSprite.y += AI_SPEED;
151
+ if (ballSprite.y < aiCenterY - 10) aiSprite.moveY(-AI_SPEED);
152
+ if (ballSprite.y > aiCenterY + 10) aiSprite.moveY(AI_SPEED);
131
153
  aiSprite.y = clamp(aiSprite.y, 0, h - PADDLE_H);
132
154
 
133
155
  // --- Move the ball ---
134
- ballSprite.x += ballVel.x;
135
- ballSprite.y += ballVel.y;
156
+ ballSprite.moveX(ballVel.x);
157
+ ballSprite.moveY(ballVel.y);
136
158
 
137
159
  // Bounce off top and bottom walls
138
160
  if (ballSprite.y <= 0) { ballVel.y = Math.abs(ballVel.y); ballSprite.y = 0; }
@@ -146,6 +168,8 @@
146
168
  // Where on the paddle the ball hit: 0 = top edge, 1 = bottom edge, 0.5 = centre (flat return)
147
169
  var playerHit = (ballSprite.y + BALL_SIZE / 2 - playerSprite.y) / PADDLE_H;
148
170
  ballVel.y = (playerHit - 0.5) * BALL_SPEED * 2;
171
+
172
+ punter.playSound('bounce');
149
173
  }
150
174
 
151
175
  // --- Collision: ball hits AI paddle ---
@@ -156,6 +180,8 @@
156
180
  // Where on the paddle the ball hit: 0 = top edge, 1 = bottom edge, 0.5 = centre (flat return)
157
181
  var aiHit = (ballSprite.y + BALL_SIZE / 2 - aiSprite.y) / PADDLE_H;
158
182
  ballVel.y = (aiHit - 0.5) * BALL_SPEED * 2;
183
+
184
+ punter.playSound('bounce');
159
185
  }
160
186
 
161
187
  // Cap ball speed so the game stays playable
@@ -163,8 +189,17 @@
163
189
  if (ballVel.x < -MAX_BALL_SPEED) ballVel.x = -MAX_BALL_SPEED;
164
190
 
165
191
  // --- Scoring ---
166
- if (ballSprite.x + BALL_SIZE < 0) { score.ai++; resetBall(SERVE_RIGHT); }
167
- if (ballSprite.x > w) { score.player++; resetBall(SERVE_LEFT); }
192
+ if (ballSprite.x + BALL_SIZE < 0) {
193
+ score.ai++;
194
+ punter.playSound('die');
195
+ resetBall(SERVE_RIGHT);
196
+ }
197
+
198
+ if (ballSprite.x > w) {
199
+ score.player++;
200
+ punter.playSound('die');
201
+ resetBall(SERVE_LEFT);
202
+ }
168
203
  });
169
204
 
170
205
  // -------------------------------------------------------
@@ -175,10 +210,6 @@
175
210
  var w = punter.width;
176
211
  var h = punter.height;
177
212
 
178
- // Background
179
- ctx.fillStyle = COLOR_BG;
180
- ctx.fillRect(0, 0, w, h);
181
-
182
213
  // Dashed centre line
183
214
  ctx.setLineDash([px(8), px(10)]);
184
215
  ctx.strokeStyle = COLOR_NET;
@@ -189,11 +220,6 @@
189
220
  ctx.stroke();
190
221
  ctx.setLineDash([]);
191
222
 
192
- // Sprites — punter.js scales and draws the PNG images
193
- playerSprite.draw(ctx);
194
- aiSprite.draw(ctx);
195
- ballSprite.draw(ctx);
196
-
197
223
  // Scores
198
224
  ctx.fillStyle = COLOR_SCORE;
199
225
  ctx.font = 'bold ' + px(48) + 'px monospace';
@@ -247,13 +273,7 @@
247
273
  resetPositions();
248
274
  });
249
275
 
250
- // =========================================================
251
- // START — go to the play scene once everything is loaded
252
- // =========================================================
253
- punter.on('ready', function () {
254
- score = { player: 0, ai: 0 };
255
- punter.go('play');
256
- });
276
+ punter.go('play');
257
277
 
258
278
  </script>
259
279
  </body>
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "punter.js",
3
- "version": "1.0.1",
4
- "description": "A lightweight, dependency-free JavaScript 2D game engine for the browser. Drop in a script tag and start building.",
3
+ "version": "1.2.0",
4
+ "description": "A simple, dependency-free 2D game engine for the browser. No build step, no frameworks - just one script tag.",
5
5
  "main": "punter.js",
6
6
  "directories": {
7
7
  "test": "tests"
8
8
  },
9
9
  "scripts": {
10
10
  "start": "npx serve .",
11
+ "start:pong": "npx serve . -l 4000 & npx open-cli http://localhost:4000/games/pong.html",
11
12
  "test": ". \"$NVM_DIR/nvm.sh\" && nvm use && NODE_ENV=test TZ=UTC node_modules/.bin/jasmine --config=tests/jasmine/config.json"
12
13
  },
13
14
  "repository": {
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: function() {},
43
+ draw: null,
43
44
  resize: function() {},
44
45
  go: function() {}
45
46
  };
@@ -106,11 +107,11 @@
106
107
  })();
107
108
 
108
109
  /**
109
- * Initialises the engine with canvas, sprites, sounds, and buttons
110
+ * Initialises the engine with canvas, images, sounds, and buttons
110
111
  * @param {Object} config - configuration object
111
112
  * @returns {void}
112
113
  */
113
- function init(config) {
114
+ function setup(config) {
114
115
 
115
116
  config = config || {};
116
117
 
@@ -151,11 +152,11 @@
151
152
  _boundsCanvas = document.createElement('canvas');
152
153
  _boundsCtx = _boundsCanvas.getContext('2d', { willReadFrequently: true });
153
154
 
154
- loadSprites(config.sprites || {}).then(function() {
155
+ loadImages(config.images || {}).then(function() {
155
156
 
156
157
  // precompute bounds for all sprite frames
157
- if (config.sprites) {
158
- for (var key in config.sprites) {
158
+ if (config.images) {
159
+ for (var key in config.images) {
159
160
  var img = images[key];
160
161
  if (img && img.complete && img.naturalWidth) {
161
162
  // this warms up the boundingCache
@@ -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;
@@ -182,13 +184,13 @@
182
184
  }
183
185
 
184
186
  /**
185
- * Loads and decodes all sprite images
186
- * @param {Object} sprites - Key-value map of sprite keys to image URLs
187
+ * Loads and decodes all images
188
+ * @param {Object} images - Key-value map of image keys to image URLs
187
189
  * @returns {Promise} - Resolves when all images are loaded and decoded
188
190
  */
189
- function loadSprites(sprites) {
191
+ function loadImages(imageMap) {
190
192
 
191
- var keys = Object.keys(sprites);
193
+ var keys = Object.keys(imageMap);
192
194
  var total = keys.length;
193
195
 
194
196
  if (!total) return Promise.resolve();
@@ -234,7 +236,7 @@
234
236
 
235
237
  for (var i = 0; i < total; i++) {
236
238
  var key = keys[i];
237
- var url = sprites[key];
239
+ var url = imageMap[key];
238
240
  var img = new Image();
239
241
  img.key = key; // for debugging
240
242
  img.onload = handleLoad.bind(img, key);
@@ -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.key - sprite key used when loading sprite (use array for animations)
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.key) throw new Error('Sprite: missing key');
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.key = opts.key;
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.key);
526
+ this._animated = Array.isArray(this.image);
525
527
 
526
- var initialDrawKey = Array.isArray(this.key) ? this.key[0] : this.key;
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.getFrameKey = function () {
544
- if (!this._animated) return this.key;
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.key[index % this.key.length];
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.getFrameKey(); // frame key to draw (single or animated)
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.getFrameKey();
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.getFrameKey();
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.getFrameKey();
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.getFrameKey();
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.key.length;
795
+ this._frameIndex = (this._frameIndex + 1) % this.image.length;
794
796
  }
795
797
  };
796
798
  Sprite.prototype.moveX = function (dx) {
@@ -1003,7 +1005,7 @@
1003
1005
  */
1004
1006
  function startLoop() {
1005
1007
 
1006
- if (!_initilised) throw new Error('punter.init must be called first');
1008
+ if (!_initilised) throw new Error('punter.setup must be called first');
1007
1009
 
1008
1010
  _canvasCtx = _canvas.getContext('2d', { alpha: true, desynchronized: true });
1009
1011
  _canvasCtx.imageSmoothingEnabled = false;
@@ -1037,8 +1039,17 @@
1037
1039
  // clear screen
1038
1040
  _canvasCtx.clearRect(0, 0, engine.width, engine.height);
1039
1041
 
1040
- // draw everything
1041
- eventHandlers.draw.call(_canvasCtx);
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;
@@ -1067,7 +1078,7 @@
1067
1078
 
1068
1079
  function pauseLoop() {
1069
1080
 
1070
- if (!_initilised) throw new Error('punter.init must be called first');
1081
+ if (!_initilised) throw new Error('punter.setup must be called first');
1071
1082
 
1072
1083
  if (_loopId !== null) {
1073
1084
  cancelAnimationFrame(_loopId);
@@ -1090,7 +1101,7 @@
1090
1101
  */
1091
1102
  function playSound(name, options) {
1092
1103
 
1093
- if (!_initilised) throw new Error('punter.init must be called first');
1104
+ if (!_initilised) throw new Error('punter.setup must be called first');
1094
1105
 
1095
1106
  var buffer = sounds[name];
1096
1107
  if (!buffer) return;
@@ -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) {
@@ -1131,7 +1143,7 @@
1131
1143
 
1132
1144
  function stopSound(name) {
1133
1145
 
1134
- if (!_initilised) throw new Error('punter.init must be called first');
1146
+ if (!_initilised) throw new Error('punter.setup must be called first');
1135
1147
 
1136
1148
  var arr = playingSounds[name];
1137
1149
  if (!arr || !arr.length) return;
@@ -1296,7 +1308,7 @@
1296
1308
  var api = {
1297
1309
 
1298
1310
  // initialization
1299
- init: init,
1311
+ setup: setup,
1300
1312
 
1301
1313
  // scene lifecycle
1302
1314
  scene: function (name, handler) {
@@ -1304,12 +1316,19 @@
1304
1316
  },
1305
1317
  go: function (name) {
1306
1318
 
1307
- if (!_initilised) throw new Error('punter.init 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 = function () {};
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();
@@ -1355,7 +1374,7 @@
1355
1374
 
1356
1375
  // sprite factory
1357
1376
  createSprite: function(opts) {
1358
- if (!_initilised) throw new Error('createSprite: punter.init must be called first');
1377
+ if (!_initilised) throw new Error('createSprite: punter.setup must be called first');
1359
1378
  return new Sprite(opts);
1360
1379
  },
1361
1380
  getSprite: function(id) {
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