littlejsengine 1.9.7 → 1.9.9

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 (48) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +13 -8
  3. package/dist/littlejs.d.ts +75 -69
  4. package/dist/littlejs.esm.js +313 -354
  5. package/dist/littlejs.esm.min.js +1 -1
  6. package/dist/littlejs.js +307 -350
  7. package/dist/littlejs.min.js +1 -1
  8. package/dist/littlejs.release.js +244 -319
  9. package/examples/box2d/game.js +137 -0
  10. package/examples/box2d/index.html +12 -0
  11. package/examples/box2d/scenes.js +412 -0
  12. package/examples/box2d/tiles.png +0 -0
  13. package/examples/breakout/game.js +3 -3
  14. package/examples/breakout/index.html +4 -3
  15. package/examples/breakoutTutorial/index.html +2 -2
  16. package/examples/electron/build.js +1 -1
  17. package/examples/electron/index.html +2 -2
  18. package/examples/js13k/build.js +19 -1
  19. package/examples/js13k/index.html +13 -13
  20. package/examples/module/index.html +1 -1
  21. package/examples/particles/index.html +1 -1
  22. package/examples/platformer/gameEffects.js +2 -2
  23. package/examples/platformer/gameLevel.js +0 -1
  24. package/examples/platformer/index.html +8 -8
  25. package/examples/puzzle/index.html +2 -2
  26. package/examples/starter/build.js +1 -1
  27. package/examples/starter/index.html +13 -13
  28. package/examples/stress/index.html +1 -1
  29. package/examples/typescript/index.html +1 -1
  30. package/package.json +1 -1
  31. package/plugins/Box2D_v2.3.1_min.wasm.js +630 -0
  32. package/plugins/Box2D_v2.3.1_min.wasm.wasm +0 -0
  33. package/plugins/box2d.js +879 -0
  34. package/plugins/newgrounds.js +169 -0
  35. package/plugins/postProcess.js +102 -0
  36. package/src/engine.js +48 -29
  37. package/src/engineAudio.js +20 -12
  38. package/src/engineBuild.js +1 -1
  39. package/src/engineDebug.js +68 -33
  40. package/src/engineDraw.js +1 -1
  41. package/src/engineExport.js +6 -4
  42. package/src/engineInput.js +7 -4
  43. package/src/engineMedals.js +40 -171
  44. package/src/engineObject.js +33 -2
  45. package/src/engineRelease.js +5 -2
  46. package/src/engineSettings.js +14 -2
  47. package/src/engineUtilities.js +75 -2
  48. package/src/engineWebGL.js +1 -94
@@ -0,0 +1,169 @@
1
+ /**
2
+ * LittleJS Newgrounds API
3
+ * - NewgroundsMedal extends Medal with Newgrounds API functionality
4
+ * - Call newgroundsInit to enable Newgrounds functionality
5
+ * - Uses CryptoJS for encryption if optional cipher is provided
6
+ * - Keeps connection alive and logs views
7
+ * - Functions to interact with scoreboards
8
+ * - Functions to unlock medals
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ ///////////////////////////////////////////////////////////////////////////////
14
+
15
+ /** Newgrounds medal auto unlocks in newgronds API */
16
+ class NewgroundsMedal extends Medal
17
+ {
18
+ /** Create a medal object and adds it to the list of medals */
19
+ constructor(id, name, description, icon, src)
20
+ { super(id, name, description, icon, src); }
21
+
22
+ /** Unlocks a medal if not already unlocked */
23
+ unlock()
24
+ {
25
+ super.unlock();
26
+ newgrounds && newgrounds.unlockMedal(this.id);
27
+ }
28
+ }
29
+
30
+ ///////////////////////////////////////////////////////////////////////////////
31
+
32
+ /** Global Newgrounds object */
33
+ let newgrounds;
34
+
35
+ /** This can used to enable Newgrounds functionality
36
+ * @param {Number} app_id - The newgrounds App ID
37
+ * @param {String} [cipher] - The encryption Key (AES-128/Base64)
38
+ * @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher
39
+ * @memberof Medals */
40
+ function newgroundsInit(app_id, cipher, cryptoJS)
41
+ { newgrounds = new Newgrounds(app_id, cipher, cryptoJS); }
42
+
43
+ /**
44
+ * Newgrounds API wrapper object
45
+ * @example
46
+ * // create a newgrounds object, replace the app id with your own
47
+ * const app_id = '52123:1ZuSTQ7l';
48
+ * newgrounds = new Newgrounds(app_id);
49
+ */
50
+ class Newgrounds
51
+ {
52
+ /** Create a newgrounds object
53
+ * @param {String} app_id - The newgrounds App ID
54
+ * @param {String} [cipher] - The encryption Key (AES-128/Base64)
55
+ * @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher */
56
+ constructor(app_id, cipher, cryptoJS)
57
+ {
58
+ ASSERT(!newgrounds, 'there can only be one newgrounds object');
59
+ ASSERT(!cipher || cryptoJS, 'must provide cryptojs if there is a cipher');
60
+
61
+ this.app_id = app_id;
62
+ this.cipher = cipher;
63
+ this.cryptoJS = cryptoJS;
64
+ this.host = location ? location.hostname : '';
65
+
66
+ // get session id from url search params
67
+ const url = new URL(location.href);
68
+ this.session_id = url.searchParams.get('ngio_session_id');
69
+
70
+ if (!this.session_id)
71
+ return; // only use newgrounds when logged in
72
+
73
+ // get medals
74
+ const medalsResult = this.call('Medal.getList');
75
+ this.medals = medalsResult ? medalsResult.result.data['medals'] : [];
76
+ debugMedals && console.log(this.medals);
77
+ for (const newgroundsMedal of this.medals)
78
+ {
79
+ const medal = medals[newgroundsMedal['id']];
80
+ if (medal)
81
+ {
82
+ // copy newgrounds medal data
83
+ medal.image = new Image;
84
+ medal.image.src = newgroundsMedal['icon'];
85
+ medal.name = newgroundsMedal['name'];
86
+ medal.description = newgroundsMedal['description'];
87
+ medal.unlocked = newgroundsMedal['unlocked'];
88
+ medal.difficulty = newgroundsMedal['difficulty'];
89
+ medal.value = newgroundsMedal['value'];
90
+
91
+ if (medal.value) // add value to description
92
+ medal.description = medal.description + ` (${ medal.value })`;
93
+ }
94
+ }
95
+
96
+ // get scoreboards
97
+ const scoreboardResult = this.call('ScoreBoard.getBoards');
98
+ this.scoreboards = scoreboardResult ? scoreboardResult.result.data.scoreboards : [];
99
+ debugMedals && console.log(this.scoreboards);
100
+
101
+ // keep the session alive with a ping every 5 minutes
102
+ const keepAliveMS = 5 * 60 * 1e3;
103
+ setInterval(()=>this.call('Gateway.ping', 0, true), keepAliveMS);
104
+ }
105
+
106
+ /** Send message to unlock a medal by id
107
+ * @param {Number} id - The medal id */
108
+ unlockMedal(id) { return this.call('Medal.unlock', {'id':id}, true); }
109
+
110
+ /** Send message to post score
111
+ * @param {Number} id - The scoreboard id
112
+ * @param {Number} value - The score value */
113
+ postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, true); }
114
+
115
+ /** Get scores from a scoreboard
116
+ * @param {Number} id - The scoreboard id
117
+ * @param {String} [user] - A user's id or name
118
+ * @param {Number} [social] - If true, only social scores will be loaded
119
+ * @param {Number} [skip] - Number of scores to skip before start
120
+ * @param {Number} [limit] - Number of scores to include in the list
121
+ * @return {Object} - The response JSON object
122
+ */
123
+ getScores(id, user, social=0, skip=0, limit=10)
124
+ { return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); }
125
+
126
+ /** Send message to log a view */
127
+ logView() { return this.call('App.logView', {'host':this.host}, true); }
128
+
129
+ /** Send a message to call a component of the Newgrounds API
130
+ * @param {String} component - Name of the component
131
+ * @param {Object} [parameters] - Parameters to use for call
132
+ * @param {Boolean} [async] - If true, don't wait for response before continuing
133
+ * @return {Object} - The response JSON object
134
+ */
135
+ call(component, parameters, async=false)
136
+ {
137
+ const call = {'component':component, 'parameters':parameters};
138
+ if (this.cipher)
139
+ {
140
+ // encrypt using AES-128 Base64 with cryptoJS
141
+ const cryptoJS = this.cryptoJS;
142
+ const aesKey = cryptoJS['enc']['Base64']['parse'](this.cipher);
143
+ const iv = cryptoJS['lib']['WordArray']['random'](16);
144
+ const encrypted = cryptoJS['AES']['encrypt'](JSON.stringify(call), aesKey, {'iv':iv});
145
+ call['secure'] = cryptoJS['enc']['Base64']['stringify'](iv.concat(encrypted['ciphertext']));
146
+ call['parameters'] = 0;
147
+ }
148
+
149
+ // build the input object
150
+ const input =
151
+ {
152
+ 'app_id': this.app_id,
153
+ 'session_id': this.session_id,
154
+ 'call': call
155
+ };
156
+
157
+ // build post data
158
+ const formData = new FormData();
159
+ formData.append('input', JSON.stringify(input));
160
+
161
+ // send post data
162
+ const xmlHttp = new XMLHttpRequest();
163
+ const url = 'https://newgrounds.io/gateway_v3.php';
164
+ xmlHttp.open('POST', url, !debugMedals && async);
165
+ xmlHttp.send(formData);
166
+ debugMedals && console.log(xmlHttp.responseText);
167
+ return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
168
+ }
169
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * LittleJS Post Processing Plugin
3
+ * - Supports shadertoy style post processing shaders
4
+ * - call glInitPostProcess
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ ///////////////////////////////////////////////////////////////////////////////
10
+ // post processing - can be enabled to pass other canvases through a final shader
11
+
12
+ let glPostShader, glPostTexture, glPostIncludeOverlay;
13
+
14
+ /** Set up a post processing shader
15
+ * @param {String} shaderCode
16
+ * @param {Boolean} includeOverlay
17
+ * @memberof WebGL */
18
+ function initPostProcess(shaderCode, includeOverlay=false)
19
+ {
20
+ ASSERT(!glPostShader, 'can only have 1 post effects shader');
21
+ if (headlessMode) return;
22
+
23
+ if (!shaderCode) // default shader pass through
24
+ shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
25
+
26
+ // create the shader
27
+ glPostShader = glCreateProgram(
28
+ '#version 300 es\n' + // specify GLSL ES version
29
+ 'precision highp float;'+ // use highp for better accuracy
30
+ 'in vec2 p;'+ // position
31
+ 'void main(){'+ // shader entry point
32
+ 'gl_Position=vec4(p+p-1.,1,1);'+ // set position
33
+ '}' // end of shader
34
+ ,
35
+ '#version 300 es\n' + // specify GLSL ES version
36
+ 'precision highp float;'+ // use highp for better accuracy
37
+ 'uniform sampler2D iChannel0;'+ // input texture
38
+ 'uniform vec3 iResolution;'+ // size of output texture
39
+ 'uniform float iTime;'+ // time
40
+ 'out vec4 c;'+ // out color
41
+ '\n' + shaderCode + '\n'+ // insert custom shader code
42
+ 'void main(){'+ // shader entry point
43
+ 'mainImage(c,gl_FragCoord.xy);'+ // call post process function
44
+ 'c.a=1.;'+ // always use full alpha
45
+ '}' // end of shader
46
+ );
47
+
48
+ // create buffer and texture
49
+ glPostTexture = glCreateTexture(undefined);
50
+ glPostIncludeOverlay = includeOverlay;
51
+
52
+ // hide the original 2d canvas
53
+ mainCanvas.style.visibility = 'hidden';
54
+ if (glPostIncludeOverlay)
55
+ overlayCanvas.style.visibility = 'hidden';
56
+
57
+ // Render the post processing shader, called automatically by the engine
58
+ engineAddPlugin(undefined, postProcessRender);
59
+ function postProcessRender()
60
+ {
61
+ if (headlessMode) return;
62
+
63
+ // prepare to render post process shader
64
+ if (glEnable)
65
+ {
66
+ glFlush(); // clear out the buffer
67
+ mainContext.drawImage(glCanvas, 0, 0); // copy to the main canvas
68
+ }
69
+ else
70
+ {
71
+ // set the viewport
72
+ glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
73
+ }
74
+
75
+ // copy overlay canvas so it will be included in post processing
76
+ glPostIncludeOverlay && mainContext.drawImage(overlayCanvas, 0, 0);
77
+
78
+ // setup shader program to draw one triangle
79
+ glContext.useProgram(glPostShader);
80
+ glContext.bindBuffer(gl_ARRAY_BUFFER, glGeometryBuffer);
81
+ glContext.pixelStorei(gl_UNPACK_FLIP_Y_WEBGL, 1);
82
+ glContext.disable(gl_BLEND);
83
+
84
+ // set textures, pass in the 2d canvas and gl canvas in separate texture channels
85
+ glContext.activeTexture(gl_TEXTURE0);
86
+ glContext.bindTexture(gl_TEXTURE_2D, glPostTexture);
87
+ glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, mainCanvas);
88
+
89
+ // set vertex position attribute
90
+ const vertexByteStride = 8;
91
+ const pLocation = glContext.getAttribLocation(glPostShader, 'p');
92
+ glContext.enableVertexAttribArray(pLocation);
93
+ glContext.vertexAttribPointer(pLocation, 2, gl_FLOAT, false, vertexByteStride, 0);
94
+
95
+ // set uniforms and draw
96
+ const uniformLocation = (name)=>glContext.getUniformLocation(glPostShader, name);
97
+ glContext.uniform1i(uniformLocation('iChannel0'), 0);
98
+ glContext.uniform1f(uniformLocation('iTime'), time);
99
+ glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
100
+ glContext.drawArrays(gl_TRIANGLE_STRIP, 0, 4);
101
+ }
102
+ }
package/src/engine.js CHANGED
@@ -30,7 +30,7 @@ const engineName = 'LittleJS';
30
30
  * @type {String}
31
31
  * @default
32
32
  * @memberof Engine */
33
- const engineVersion = '1.9.7';
33
+ const engineVersion = '1.9.9';
34
34
 
35
35
  /** Frames per second to update
36
36
  * @type {Number}
@@ -84,6 +84,22 @@ function setPaused(isPaused) { paused = isPaused; }
84
84
  let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
85
85
 
86
86
  ///////////////////////////////////////////////////////////////////////////////
87
+ // plugin hooks
88
+
89
+ const pluginUpdateList = [], pluginRenderList = [];
90
+
91
+ /** Add a new update function for a plugin
92
+ * @param {Function} [updateFunction]
93
+ * @param {Function} [renderFunction]
94
+ * @memberof Engine */
95
+ function engineAddPlugin(updateFunction, renderFunction)
96
+ {
97
+ updateFunction && pluginUpdateList.push(updateFunction);
98
+ renderFunction && pluginRenderList.push(renderFunction);
99
+ }
100
+
101
+ ///////////////////////////////////////////////////////////////////////////////
102
+ // Main engine functions
87
103
 
88
104
  /** Startup LittleJS engine with your callback functions
89
105
  * @param {Function} gameInit - Called once after the engine starts up, setup the game
@@ -159,6 +175,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
159
175
  // update game and objects
160
176
  inputUpdate();
161
177
  gameUpdate();
178
+ pluginUpdateList.forEach(f=>f());
162
179
  engineObjectsUpdate();
163
180
 
164
181
  // do post update
@@ -180,8 +197,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
180
197
  for (const o of engineObjects)
181
198
  o.destroyed || o.render();
182
199
  gameRenderPost();
183
- glRenderPostProcess();
184
- medalsRender();
200
+ pluginRenderList.forEach(f=>f());
185
201
  touchGamepadRender();
186
202
  debugRender();
187
203
  glCopyToContext(mainContext);
@@ -253,10 +269,11 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
253
269
  const styleBody =
254
270
  'margin:0;overflow:hidden;' + // fill the window
255
271
  'background:#000;' + // set background color
256
- 'touch-action:none;' + // prevent mobile pinch to resize
257
- 'user-select:none;' + // prevent mobile hold to select
272
+ 'user-select:none;' + // prevent hold to select
258
273
  '-webkit-user-select:none;' + // compatibility for ios
259
- '-webkit-touch-callout:none'; // compatibility for ios
274
+ (!touchInputEnable ? '' : // no touch css setttings
275
+ 'touch-action:none;' + // prevent mobile pinch to resize
276
+ '-webkit-touch-callout:none');// compatibility for ios
260
277
  document.body.style.cssText = styleBody;
261
278
  document.body.appendChild(mainCanvas = document.createElement('canvas'));
262
279
  mainContext = mainCanvas.getContext('2d');
@@ -471,21 +488,23 @@ function drawEngineSplashScreen(t)
471
488
  x.setLineDash([99*p2,99]);
472
489
 
473
490
  // cab top
474
- rect(7,17,18,-8,color(2,2));
475
- rect(7,9,18,4,color(2,3));
476
- rect(25,9,8,8,color(2,1));
477
- rect(25,9,-18,8);
478
- rect(25,9,8,8);
491
+ rect(7,16,18,-8,color(2,2));
492
+ rect(7,8,18,4,color(2,3));
493
+ rect(25,8,8,8,color(2,1));
494
+ rect(25,8,-18,8);
495
+ rect(25,8,8,8);
479
496
 
480
497
  // cab
481
- rect(25,17,7,22,color());
482
- rect(11,40,14,-23,color(1,1));
483
- rect(11,17,14,17,color(1,2));
484
- rect(11,17,14,9,color(1,3));
485
- rect(15,31,6,-9,color(2,2));
486
- circle(15,23,5,0,PI/2,color(2,4),1);
487
- rect(25,17,-14,23);
488
- rect(21,22,-6,9);
498
+ rect(25,16,7,23,color());
499
+ rect(11,39,14,-23,color(1,1));
500
+ rect(11,16,14,18,color(1,2));
501
+ rect(11,16,14,8,color(1,3));
502
+ rect(25,16,-14,24);
503
+
504
+ // cab window
505
+ rect(15,29,6,-9,color(2,2));
506
+ circle(15,21,5,0,PI/2,color(2,4),1);
507
+ rect(21,21,-6,9);
489
508
 
490
509
  // little stack
491
510
  rect(37,14,9,6,color(3,2));
@@ -493,16 +512,16 @@ function drawEngineSplashScreen(t)
493
512
  rect(37,14,9,6);
494
513
 
495
514
  // big stack
496
- rect(50,20,10,-8,color(0,1))
497
- rect(50,20,6.5,-8,color(0,2))
498
- rect(50,20,3.5,-8,color(0,3))
499
- rect(50,20,10,-8)
500
- circle(55,2,11.4,.5,PI-.5,color(3,3))
501
- circle(55,2,11.4,.5,PI/2,color(3,2),1)
502
- circle(55,2,11.4,.5,PI-.5)
503
- rect(45,7,20,-7,color(0,2))
504
- rect(45,-1,20,4,color(0,3))
505
- rect(45,-1,20,8)
515
+ rect(50,20,10,-8,color(0,1));
516
+ rect(50,20,6.5,-8,color(0,2));
517
+ rect(50,20,3.5,-8,color(0,3));
518
+ rect(50,20,10,-8);
519
+ circle(55,2,11.4,.5,PI-.5,color(3,3));
520
+ circle(55,2,11.4,.5,PI/2,color(3,2),1);
521
+ circle(55,2,11.4,.5,PI-.5);
522
+ rect(45,7,20,-7,color(0,2));
523
+ rect(45,-1,20,4,color(0,3));
524
+ rect(45,-1,20,8);
506
525
 
507
526
  // engine
508
527
  for (let i=5; i--;)
@@ -112,7 +112,17 @@ class Sound
112
112
 
113
113
  // play the sound
114
114
  const playbackRate = pitch + pitch * this.randomness*randomnessScale*rand(-1,1);
115
- return this.source = playSamples(this.sampleChannels, volume, playbackRate, pan, loop, this.sampleRate);
115
+ this.gainNode = audioContext.createGain();
116
+ return this.source = playSamples(this.sampleChannels, volume, playbackRate, pan, loop, this.sampleRate, this.gainNode);
117
+ }
118
+
119
+ /** Set the sound volume
120
+ * @param {Number} [volume] - How much to scale volume by
121
+ */
122
+ setVolume(volume=1)
123
+ {
124
+ if (this.gainNode)
125
+ this.gainNode.gain.value = volume;
116
126
  }
117
127
 
118
128
  /** Stop the last instance of this sound that was played */
@@ -300,15 +310,16 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
300
310
  let audioSuspended = false;
301
311
 
302
312
  /** Play cached audio samples with given settings
303
- * @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback)
304
- * @param {Number} [volume] - How much to scale volume by
305
- * @param {Number} [rate] - The playback rate to use
306
- * @param {Number} [pan] - How much to apply stereo panning
307
- * @param {Boolean} [loop] - True if the sound should loop when it reaches the end
308
- * @param {Number} [sampleRate=44100] - Sample rate for the sound
313
+ * @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback)
314
+ * @param {Number} [volume] - How much to scale volume by
315
+ * @param {Number} [rate] - The playback rate to use
316
+ * @param {Number} [pan] - How much to apply stereo panning
317
+ * @param {Boolean} [loop] - True if the sound should loop when it reaches the end
318
+ * @param {Number} [sampleRate=44100] - Sample rate for the sound
319
+ * @param {GainNode} [gainNode] - Optional gain node for volume control while playing
309
320
  * @return {AudioBufferSourceNode} - The audio node of the sound played
310
321
  * @memberof Audio */
311
- function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=zzfxR)
322
+ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=zzfxR, gainNode)
312
323
  {
313
324
  if (!soundEnable || headlessMode) return;
314
325
 
@@ -334,11 +345,8 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
334
345
  source.playbackRate.value = rate;
335
346
  source.loop = loop;
336
347
 
337
- // set master gain volume
338
- setSoundVolume(soundVolume);
339
-
340
348
  // create and connect gain node
341
- const gainNode = audioContext.createGain();
349
+ gainNode = gainNode || audioContext.createGain();
342
350
  gainNode.gain.value = volume;
343
351
  gainNode.connect(audioGainNode);
344
352
 
@@ -137,7 +137,7 @@ function closureCompilerStep(filename)
137
137
  fs.copyFileSync(filename, filenameTemp);
138
138
  try
139
139
  {
140
- child_process.execSync(`npx google-closure-compiler --js=${filenameTemp} --js_output_file=${filename} --language_out=ECMASCRIPT_2021 --warning_level=VERBOSE --jscomp_off=*`);
140
+ child_process.execSync(`npx google-closure-compiler --js=${filenameTemp} --js_output_file=${filename} --warning_level=VERBOSE --jscomp_off=*`);
141
141
  fs.rmSync(filenameTemp);
142
142
  }
143
143
  catch (e) { handleError(e, 'Failed to run Closure Compiler step!'); }
@@ -76,6 +76,20 @@ function debugRect(pos, size=vec2(), color='#fff', time=0, angle=0, fill=false)
76
76
  debugPrimitives.push({pos, size:vec2(size), color, time:new Timer(time), angle, fill});
77
77
  }
78
78
 
79
+ /** Draw a debug poly in world space
80
+ * @param {Vector2} pos
81
+ * @param {Array} points
82
+ * @param {String} [color]
83
+ * @param {Number} [time]
84
+ * @param {Number} [angle]
85
+ * @param {Boolean} [fill]
86
+ * @memberof Debug */
87
+ function debugPoly(pos, points, color='#fff', time=0, angle=0, fill=false)
88
+ {
89
+ ASSERT(typeof color == 'string', 'pass in css color strings');
90
+ debugPrimitives.push({pos, points, color, time:new Timer(time), angle, fill});
91
+ }
92
+
79
93
  /** Draw a debug circle in world space
80
94
  * @param {Vector2} pos
81
95
  * @param {Number} [radius]
@@ -85,7 +99,7 @@ function debugRect(pos, size=vec2(), color='#fff', time=0, angle=0, fill=false)
85
99
  * @memberof Debug */
86
100
  function debugCircle(pos, radius=0, color='#fff', time=0, fill=false)
87
101
  {
88
- ASSERT(typeof color == 'string', 'pass in css color strings');
102
+ ASSERT(typeof color == 'string', 'pass in css color strings');
89
103
  debugPrimitives.push({pos, size:radius, color, time:new Timer(time), angle:0, fill});
90
104
  }
91
105
 
@@ -95,7 +109,11 @@ function debugCircle(pos, radius=0, color='#fff', time=0, fill=false)
95
109
  * @param {Number} [time]
96
110
  * @param {Number} [angle]
97
111
  * @memberof Debug */
98
- function debugPoint(pos, color, time, angle) {debugRect(pos, undefined, color, time, angle);}
112
+ function debugPoint(pos, color, time, angle)
113
+ {
114
+ ASSERT(typeof color == 'string', 'pass in css color strings');
115
+ debugRect(pos, undefined, color, time, angle);
116
+ }
99
117
 
100
118
  /** Draw a debug line in world space
101
119
  * @param {Vector2} posA
@@ -106,19 +124,20 @@ function debugPoint(pos, color, time, angle) {debugRect(pos, undefined, color, t
106
124
  * @memberof Debug */
107
125
  function debugLine(posA, posB, color, thickness=.1, time)
108
126
  {
127
+ ASSERT(typeof color == 'string', 'pass in css color strings');
109
128
  const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
110
129
  const size = vec2(thickness, halfDelta.length()*2);
111
130
  debugRect(posA.add(halfDelta), size, color, time, halfDelta.angle(), true);
112
131
  }
113
132
 
114
- /** Draw a debug axis aligned bounding box in world space
133
+ /** Draw a debug combined axis aligned bounding box in world space
115
134
  * @param {Vector2} pA - position A
116
135
  * @param {Vector2} sA - size A
117
136
  * @param {Vector2} pB - position B
118
137
  * @param {Vector2} sB - size B
119
138
  * @param {String} [color]
120
139
  * @memberof Debug */
121
- function debugAABB(pA, sA, pB, sB, color)
140
+ function debugOverlap(pA, sA, pB, sB, color)
122
141
  {
123
142
  const minPos = vec2(min(pA.x - sA.x/2, pB.x - sB.x/2), min(pA.y - sA.y/2, pB.y - sB.y/2));
124
143
  const maxPos = vec2(max(pA.x + sA.x/2, pB.x + sB.x/2), max(pA.y + sA.y/2, pB.y + sB.y/2));
@@ -136,7 +155,7 @@ function debugAABB(pA, sA, pB, sB, color)
136
155
  * @memberof Debug */
137
156
  function debugText(text, pos, size=1, color='#fff', time=0, angle=0, font='monospace')
138
157
  {
139
- ASSERT(typeof color == 'string', 'pass in css color strings');
158
+ ASSERT(typeof color == 'string', 'pass in css color strings');
140
159
  debugPrimitives.push({text, pos, size, color, time:new Timer(time), angle, font});
141
160
  }
142
161
 
@@ -235,7 +254,7 @@ function debugRender()
235
254
  const stickScale = 1;
236
255
  const buttonScale = .2;
237
256
  const centerPos = cameraPos;
238
- const sticks = stickData[i];
257
+ const sticks = gamepadStickData[i];
239
258
  for (let j = sticks.length; j--;)
240
259
  {
241
260
  const drawPos = centerPos.add(vec2(j*stickScale*2, i*stickScale*3));
@@ -255,6 +274,7 @@ function debugRender()
255
274
  }
256
275
  }
257
276
 
277
+ let debugObject;
258
278
  if (debugOverlay)
259
279
  {
260
280
  const saveContext = mainContext;
@@ -265,11 +285,13 @@ function debugRender()
265
285
  debugRect(cameraPos, cameraSize.subtract(vec2(.1)), '#f008');
266
286
 
267
287
  // mouse pick
268
- let bestDistance = Infinity, bestObject;
288
+ let bestDistance = Infinity;
269
289
  for (const o of engineObjects)
270
290
  {
271
291
  if (o.canvas || o.destroyed)
272
292
  continue;
293
+
294
+ o.renderDebugInfo();
273
295
  if (!o.size.x || !o.size.y)
274
296
  continue;
275
297
 
@@ -277,33 +299,15 @@ function debugRender()
277
299
  if (distance < bestDistance)
278
300
  {
279
301
  bestDistance = distance;
280
- bestObject = o;
302
+ debugObject = o;
281
303
  }
282
-
283
- // show object info
284
- const size = vec2(max(o.size.x, .2), max(o.size.y, .2));
285
- const color1 = new Color(o.collideTiles?1:0, o.collideSolidObjects?1:0, o.isSolid?1:0, o.parent?.2:.5);
286
- const color2 = o.parent ? new Color(1,1,1,.5) : new Color(0,0,0,.8);
287
- drawRect(o.pos, size, color1, o.angle, false);
288
- drawRect(o.pos, size.scale(.8), color2, o.angle, false);
289
- o.parent && drawLine(o.pos, o.parent.pos, .1, new Color(0,0,1,.5), false);
290
- }
291
-
292
- if (bestObject)
293
- {
294
- const raycastHitPos = tileCollisionRaycast(bestObject.pos, mousePos);
295
- raycastHitPos && drawRect(raycastHitPos.floor().add(vec2(.5)), vec2(1), new Color(0,1,1,.3));
296
- drawRect(mousePos.floor().add(vec2(.5)), vec2(1), new Color(0,0,1,.5), 0, false);
297
- drawLine(mousePos, bestObject.pos, .1, raycastHitPos ? new Color(1,0,0,.5) : new Color(0,1,0,.5), false);
298
-
299
- const debugText = 'mouse pos = ' + mousePos +
300
- '\nmouse collision = ' + getTileCollisionData(mousePos) +
301
- '\n\n--- object info ---\n' +
302
- bestObject.toString();
303
- drawTextScreen(debugText, mousePosScreen, 24, new Color, .05, undefined, 'center', 'monospace');
304
304
  }
305
305
 
306
- glCopyToContext(mainContext = saveContext);
306
+ if (tileCollisionSize.x > 0 && tileCollisionSize.y > 0)
307
+ drawRect(mousePos.floor().add(vec2(.5)), vec2(1), rgb(0,0,1,.5), 0, false);
308
+ mainContext = saveContext;
309
+
310
+ //glCopyToContext(mainContext = saveContext);
307
311
  }
308
312
 
309
313
  {
@@ -318,6 +322,7 @@ function debugRender()
318
322
  const pos = worldToScreen(p.pos);
319
323
  overlayContext.translate(pos.x|0, pos.y|0);
320
324
  overlayContext.rotate(p.angle);
325
+ overlayContext.scale(1, -1);
321
326
  overlayContext.fillStyle = overlayContext.strokeStyle = p.color;
322
327
 
323
328
  if (p.text != undefined)
@@ -327,7 +332,20 @@ function debugRender()
327
332
  overlayContext.textBaseline = 'middle';
328
333
  overlayContext.fillText(p.text, 0, 0);
329
334
  }
330
- else if (p.size == 0 || p.size.x === 0 && p.size.y === 0 )
335
+ else if (p.points != undefined)
336
+ {
337
+ // poly
338
+ overlayContext.beginPath();
339
+ for (const point of p.points)
340
+ {
341
+ const p2 = point.scale(cameraScale).floor();
342
+ overlayContext.lineTo(p2.x, p2.y);
343
+ }
344
+ overlayContext.closePath();
345
+ p.fill && overlayContext.fill();
346
+ overlayContext.stroke();
347
+ }
348
+ else if (p.size == 0 || p.size.x === 0 && p.size.y === 0)
331
349
  {
332
350
  // point
333
351
  overlayContext.fillRect(-pointSize/2, -1, pointSize, 3);
@@ -336,7 +354,8 @@ function debugRender()
336
354
  else if (p.size.x != undefined)
337
355
  {
338
356
  // rect
339
- const w = p.size.x*cameraScale|0, h = p.size.y*cameraScale|0;
357
+ const s = p.size.scale(cameraScale).floor();
358
+ const w = s.x, h = s.y;
340
359
  p.fill && overlayContext.fillRect(-w/2|0, -h/2|0, w, h);
341
360
  overlayContext.strokeRect(-w/2|0, -h/2|0, w, h);
342
361
  }
@@ -355,6 +374,22 @@ function debugRender()
355
374
  // remove expired primitives
356
375
  debugPrimitives = debugPrimitives.filter(r=>r.time<0);
357
376
  }
377
+
378
+ if (debugObject)
379
+ {
380
+ const saveContext = mainContext;
381
+ mainContext = overlayContext;
382
+ const raycastHitPos = tileCollisionRaycast(debugObject.pos, mousePos);
383
+ raycastHitPos && drawRect(raycastHitPos.floor().add(vec2(.5)), vec2(1), rgb(0,1,1,.3));
384
+ drawLine(mousePos, debugObject.pos, .1, raycastHitPos ? rgb(1,0,0,.5) : rgb(0,1,0,.5), false);
385
+
386
+ const debugText = 'mouse pos = ' + mousePos +
387
+ '\nmouse collision = ' + getTileCollisionData(mousePos) +
388
+ '\n\n--- object info ---\n' +
389
+ debugObject.toString();
390
+ drawTextScreen(debugText, mousePosScreen, 24, rgb(), .05, undefined, 'center', 'monospace');
391
+ mainContext = saveContext;
392
+ }
358
393
 
359
394
  {
360
395
  // draw debug overlay