littlejsengine 1.9.6 → 1.9.8
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/LICENSE +22 -0
- package/README.md +13 -8
- package/dist/littlejs.d.ts +85 -84
- package/dist/littlejs.esm.js +370 -390
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +365 -386
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +302 -355
- package/examples/box2d/game.js +138 -0
- package/examples/box2d/index.html +25 -0
- package/examples/box2d/scenes.js +412 -0
- package/examples/box2d/tiles.png +0 -0
- package/examples/breakout/game.js +3 -3
- package/examples/breakout/index.html +1 -0
- package/examples/platformer/gameEffects.js +2 -2
- package/examples/platformer/gameLevel.js +0 -1
- package/package.json +1 -1
- package/plugins/Box2D_v2.3.1_min.wasm.js +630 -0
- package/plugins/Box2D_v2.3.1_min.wasm.wasm +0 -0
- package/plugins/box2d.js +873 -0
- package/plugins/newgrounds.js +169 -0
- package/plugins/postProcess.js +101 -0
- package/src/engine.js +45 -26
- package/src/engineAudio.js +72 -47
- package/src/engineDebug.js +68 -33
- package/src/engineDraw.js +1 -1
- package/src/engineExport.js +5 -4
- package/src/engineMedals.js +39 -171
- package/src/engineObject.js +47 -4
- package/src/engineParticles.js +3 -0
- package/src/engineRelease.js +5 -2
- package/src/engineSettings.js +8 -3
- package/src/engineUtilities.js +81 -7
- package/src/engineWebGL.js +1 -94
package/dist/littlejs.js
CHANGED
|
@@ -80,6 +80,20 @@ function debugRect(pos, size=vec2(), color='#fff', time=0, angle=0, fill=false)
|
|
|
80
80
|
debugPrimitives.push({pos, size:vec2(size), color, time:new Timer(time), angle, fill});
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/** Draw a debug poly in world space
|
|
84
|
+
* @param {Vector2} pos
|
|
85
|
+
* @param {Array} points
|
|
86
|
+
* @param {String} [color]
|
|
87
|
+
* @param {Number} [time]
|
|
88
|
+
* @param {Number} [angle]
|
|
89
|
+
* @param {Boolean} [fill]
|
|
90
|
+
* @memberof Debug */
|
|
91
|
+
function debugPoly(pos, points, color='#fff', time=0, angle=0, fill=false)
|
|
92
|
+
{
|
|
93
|
+
ASSERT(typeof color == 'string', 'pass in css color strings');
|
|
94
|
+
debugPrimitives.push({pos, points, color, time:new Timer(time), angle, fill});
|
|
95
|
+
}
|
|
96
|
+
|
|
83
97
|
/** Draw a debug circle in world space
|
|
84
98
|
* @param {Vector2} pos
|
|
85
99
|
* @param {Number} [radius]
|
|
@@ -89,7 +103,7 @@ function debugRect(pos, size=vec2(), color='#fff', time=0, angle=0, fill=false)
|
|
|
89
103
|
* @memberof Debug */
|
|
90
104
|
function debugCircle(pos, radius=0, color='#fff', time=0, fill=false)
|
|
91
105
|
{
|
|
92
|
-
ASSERT(typeof color == 'string', 'pass in css color strings');
|
|
106
|
+
ASSERT(typeof color == 'string', 'pass in css color strings');
|
|
93
107
|
debugPrimitives.push({pos, size:radius, color, time:new Timer(time), angle:0, fill});
|
|
94
108
|
}
|
|
95
109
|
|
|
@@ -99,7 +113,11 @@ function debugCircle(pos, radius=0, color='#fff', time=0, fill=false)
|
|
|
99
113
|
* @param {Number} [time]
|
|
100
114
|
* @param {Number} [angle]
|
|
101
115
|
* @memberof Debug */
|
|
102
|
-
function debugPoint(pos, color, time, angle)
|
|
116
|
+
function debugPoint(pos, color, time, angle)
|
|
117
|
+
{
|
|
118
|
+
ASSERT(typeof color == 'string', 'pass in css color strings');
|
|
119
|
+
debugRect(pos, undefined, color, time, angle);
|
|
120
|
+
}
|
|
103
121
|
|
|
104
122
|
/** Draw a debug line in world space
|
|
105
123
|
* @param {Vector2} posA
|
|
@@ -110,19 +128,20 @@ function debugPoint(pos, color, time, angle) {debugRect(pos, undefined, color, t
|
|
|
110
128
|
* @memberof Debug */
|
|
111
129
|
function debugLine(posA, posB, color, thickness=.1, time)
|
|
112
130
|
{
|
|
131
|
+
ASSERT(typeof color == 'string', 'pass in css color strings');
|
|
113
132
|
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
114
133
|
const size = vec2(thickness, halfDelta.length()*2);
|
|
115
134
|
debugRect(posA.add(halfDelta), size, color, time, halfDelta.angle(), true);
|
|
116
135
|
}
|
|
117
136
|
|
|
118
|
-
/** Draw a debug axis aligned bounding box in world space
|
|
137
|
+
/** Draw a debug combined axis aligned bounding box in world space
|
|
119
138
|
* @param {Vector2} pA - position A
|
|
120
139
|
* @param {Vector2} sA - size A
|
|
121
140
|
* @param {Vector2} pB - position B
|
|
122
141
|
* @param {Vector2} sB - size B
|
|
123
142
|
* @param {String} [color]
|
|
124
143
|
* @memberof Debug */
|
|
125
|
-
function
|
|
144
|
+
function debugOverlap(pA, sA, pB, sB, color)
|
|
126
145
|
{
|
|
127
146
|
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));
|
|
128
147
|
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));
|
|
@@ -140,7 +159,7 @@ function debugAABB(pA, sA, pB, sB, color)
|
|
|
140
159
|
* @memberof Debug */
|
|
141
160
|
function debugText(text, pos, size=1, color='#fff', time=0, angle=0, font='monospace')
|
|
142
161
|
{
|
|
143
|
-
ASSERT(typeof color == 'string', 'pass in css color strings');
|
|
162
|
+
ASSERT(typeof color == 'string', 'pass in css color strings');
|
|
144
163
|
debugPrimitives.push({text, pos, size, color, time:new Timer(time), angle, font});
|
|
145
164
|
}
|
|
146
165
|
|
|
@@ -239,7 +258,7 @@ function debugRender()
|
|
|
239
258
|
const stickScale = 1;
|
|
240
259
|
const buttonScale = .2;
|
|
241
260
|
const centerPos = cameraPos;
|
|
242
|
-
const sticks =
|
|
261
|
+
const sticks = gamepadStickData[i];
|
|
243
262
|
for (let j = sticks.length; j--;)
|
|
244
263
|
{
|
|
245
264
|
const drawPos = centerPos.add(vec2(j*stickScale*2, i*stickScale*3));
|
|
@@ -259,6 +278,7 @@ function debugRender()
|
|
|
259
278
|
}
|
|
260
279
|
}
|
|
261
280
|
|
|
281
|
+
let debugObject;
|
|
262
282
|
if (debugOverlay)
|
|
263
283
|
{
|
|
264
284
|
const saveContext = mainContext;
|
|
@@ -269,11 +289,13 @@ function debugRender()
|
|
|
269
289
|
debugRect(cameraPos, cameraSize.subtract(vec2(.1)), '#f008');
|
|
270
290
|
|
|
271
291
|
// mouse pick
|
|
272
|
-
let bestDistance = Infinity
|
|
292
|
+
let bestDistance = Infinity;
|
|
273
293
|
for (const o of engineObjects)
|
|
274
294
|
{
|
|
275
295
|
if (o.canvas || o.destroyed)
|
|
276
296
|
continue;
|
|
297
|
+
|
|
298
|
+
o.renderDebugInfo();
|
|
277
299
|
if (!o.size.x || !o.size.y)
|
|
278
300
|
continue;
|
|
279
301
|
|
|
@@ -281,33 +303,15 @@ function debugRender()
|
|
|
281
303
|
if (distance < bestDistance)
|
|
282
304
|
{
|
|
283
305
|
bestDistance = distance;
|
|
284
|
-
|
|
306
|
+
debugObject = o;
|
|
285
307
|
}
|
|
286
|
-
|
|
287
|
-
// show object info
|
|
288
|
-
const size = vec2(max(o.size.x, .2), max(o.size.y, .2));
|
|
289
|
-
const color1 = new Color(o.collideTiles?1:0, o.collideSolidObjects?1:0, o.isSolid?1:0, o.parent?.2:.5);
|
|
290
|
-
const color2 = o.parent ? new Color(1,1,1,.5) : new Color(0,0,0,.8);
|
|
291
|
-
drawRect(o.pos, size, color1, o.angle, false);
|
|
292
|
-
drawRect(o.pos, size.scale(.8), color2, o.angle, false);
|
|
293
|
-
o.parent && drawLine(o.pos, o.parent.pos, .1, new Color(0,0,1,.5), false);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
if (bestObject)
|
|
297
|
-
{
|
|
298
|
-
const raycastHitPos = tileCollisionRaycast(bestObject.pos, mousePos);
|
|
299
|
-
raycastHitPos && drawRect(raycastHitPos.floor().add(vec2(.5)), vec2(1), new Color(0,1,1,.3));
|
|
300
|
-
drawRect(mousePos.floor().add(vec2(.5)), vec2(1), new Color(0,0,1,.5), 0, false);
|
|
301
|
-
drawLine(mousePos, bestObject.pos, .1, raycastHitPos ? new Color(1,0,0,.5) : new Color(0,1,0,.5), false);
|
|
302
|
-
|
|
303
|
-
const debugText = 'mouse pos = ' + mousePos +
|
|
304
|
-
'\nmouse collision = ' + getTileCollisionData(mousePos) +
|
|
305
|
-
'\n\n--- object info ---\n' +
|
|
306
|
-
bestObject.toString();
|
|
307
|
-
drawTextScreen(debugText, mousePosScreen, 24, new Color, .05, undefined, 'center', 'monospace');
|
|
308
308
|
}
|
|
309
309
|
|
|
310
|
-
|
|
310
|
+
if (tileCollisionSize.x > 0 && tileCollisionSize.y > 0)
|
|
311
|
+
drawRect(mousePos.floor().add(vec2(.5)), vec2(1), rgb(0,0,1,.5), 0, false);
|
|
312
|
+
mainContext = saveContext;
|
|
313
|
+
|
|
314
|
+
//glCopyToContext(mainContext = saveContext);
|
|
311
315
|
}
|
|
312
316
|
|
|
313
317
|
{
|
|
@@ -322,6 +326,7 @@ function debugRender()
|
|
|
322
326
|
const pos = worldToScreen(p.pos);
|
|
323
327
|
overlayContext.translate(pos.x|0, pos.y|0);
|
|
324
328
|
overlayContext.rotate(p.angle);
|
|
329
|
+
overlayContext.scale(1, -1);
|
|
325
330
|
overlayContext.fillStyle = overlayContext.strokeStyle = p.color;
|
|
326
331
|
|
|
327
332
|
if (p.text != undefined)
|
|
@@ -331,7 +336,20 @@ function debugRender()
|
|
|
331
336
|
overlayContext.textBaseline = 'middle';
|
|
332
337
|
overlayContext.fillText(p.text, 0, 0);
|
|
333
338
|
}
|
|
334
|
-
else if (p.
|
|
339
|
+
else if (p.points != undefined)
|
|
340
|
+
{
|
|
341
|
+
// poly
|
|
342
|
+
overlayContext.beginPath();
|
|
343
|
+
for (const point of p.points)
|
|
344
|
+
{
|
|
345
|
+
const p2 = point.scale(cameraScale).floor();
|
|
346
|
+
overlayContext.lineTo(p2.x, p2.y);
|
|
347
|
+
}
|
|
348
|
+
overlayContext.closePath();
|
|
349
|
+
p.fill && overlayContext.fill();
|
|
350
|
+
overlayContext.stroke();
|
|
351
|
+
}
|
|
352
|
+
else if (p.size == 0 || p.size.x === 0 && p.size.y === 0)
|
|
335
353
|
{
|
|
336
354
|
// point
|
|
337
355
|
overlayContext.fillRect(-pointSize/2, -1, pointSize, 3);
|
|
@@ -340,7 +358,8 @@ function debugRender()
|
|
|
340
358
|
else if (p.size.x != undefined)
|
|
341
359
|
{
|
|
342
360
|
// rect
|
|
343
|
-
const
|
|
361
|
+
const s = p.size.scale(cameraScale).floor();
|
|
362
|
+
const w = s.x, h = s.y;
|
|
344
363
|
p.fill && overlayContext.fillRect(-w/2|0, -h/2|0, w, h);
|
|
345
364
|
overlayContext.strokeRect(-w/2|0, -h/2|0, w, h);
|
|
346
365
|
}
|
|
@@ -359,6 +378,22 @@ function debugRender()
|
|
|
359
378
|
// remove expired primitives
|
|
360
379
|
debugPrimitives = debugPrimitives.filter(r=>r.time<0);
|
|
361
380
|
}
|
|
381
|
+
|
|
382
|
+
if (debugObject)
|
|
383
|
+
{
|
|
384
|
+
const saveContext = mainContext;
|
|
385
|
+
mainContext = overlayContext;
|
|
386
|
+
const raycastHitPos = tileCollisionRaycast(debugObject.pos, mousePos);
|
|
387
|
+
raycastHitPos && drawRect(raycastHitPos.floor().add(vec2(.5)), vec2(1), rgb(0,1,1,.3));
|
|
388
|
+
drawLine(mousePos, debugObject.pos, .1, raycastHitPos ? rgb(1,0,0,.5) : rgb(0,1,0,.5), false);
|
|
389
|
+
|
|
390
|
+
const debugText = 'mouse pos = ' + mousePos +
|
|
391
|
+
'\nmouse collision = ' + getTileCollisionData(mousePos) +
|
|
392
|
+
'\n\n--- object info ---\n' +
|
|
393
|
+
debugObject.toString();
|
|
394
|
+
drawTextScreen(debugText, mousePosScreen, 24, rgb(), .05, undefined, 'center', 'monospace');
|
|
395
|
+
mainContext = saveContext;
|
|
396
|
+
}
|
|
362
397
|
|
|
363
398
|
{
|
|
364
399
|
// draw debug overlay
|
|
@@ -721,7 +756,7 @@ class RandomGenerator
|
|
|
721
756
|
*/
|
|
722
757
|
function vec2(x=0, y)
|
|
723
758
|
{
|
|
724
|
-
return typeof x
|
|
759
|
+
return typeof x == 'number' ?
|
|
725
760
|
new Vector2(x, y == undefined? x : y) :
|
|
726
761
|
new Vector2(x.x, x.y);
|
|
727
762
|
}
|
|
@@ -750,13 +785,19 @@ class Vector2
|
|
|
750
785
|
* @param {Number} [y] - Y axis location */
|
|
751
786
|
constructor(x=0, y=0)
|
|
752
787
|
{
|
|
753
|
-
ASSERT(typeof x
|
|
788
|
+
ASSERT(typeof x == 'number' && typeof y == 'number');
|
|
754
789
|
/** @property {Number} - X axis location */
|
|
755
790
|
this.x = x;
|
|
756
791
|
/** @property {Number} - Y axis location */
|
|
757
792
|
this.y = y;
|
|
758
793
|
}
|
|
759
794
|
|
|
795
|
+
/** Sets values of this vector and returns self
|
|
796
|
+
* @param {Number} [x] - X axis location
|
|
797
|
+
* @param {Number} [y] - Y axis location
|
|
798
|
+
* @return {Vector2} */
|
|
799
|
+
set(x=0, y=0) { this.x=x; this.y=y; return this; }
|
|
800
|
+
|
|
760
801
|
/** Returns a new vector that is a copy of this
|
|
761
802
|
* @return {Vector2} */
|
|
762
803
|
copy() { return new Vector2(this.x, this.y); }
|
|
@@ -1008,6 +1049,15 @@ class Color
|
|
|
1008
1049
|
this.a = a;
|
|
1009
1050
|
}
|
|
1010
1051
|
|
|
1052
|
+
/** Sets values of this color and returns self
|
|
1053
|
+
* @param {Number} [r] - red
|
|
1054
|
+
* @param {Number} [g] - green
|
|
1055
|
+
* @param {Number} [b] - blue
|
|
1056
|
+
* @param {Number} [a] - alpha
|
|
1057
|
+
* @return {Color} */
|
|
1058
|
+
set(r=1, g=1, b=1, a=1)
|
|
1059
|
+
{ this.r=r; this.g=g; this.b=b; this.a=a; return this; }
|
|
1060
|
+
|
|
1011
1061
|
/** Returns a new color that is a copy of this
|
|
1012
1062
|
* @return {Color} */
|
|
1013
1063
|
copy() { return new Color(this.r, this.g, this.b, this.a); }
|
|
@@ -1135,14 +1185,15 @@ class Color
|
|
|
1135
1185
|
).clamp();
|
|
1136
1186
|
}
|
|
1137
1187
|
|
|
1138
|
-
/** Returns this color expressed as a
|
|
1188
|
+
/** Returns this color expressed as a hex color code
|
|
1139
1189
|
* @param {Boolean} [useAlpha] - if alpha should be included in result
|
|
1140
1190
|
* @return {String} */
|
|
1141
|
-
toString(useAlpha = true)
|
|
1142
|
-
{
|
|
1143
|
-
|
|
1191
|
+
toString(useAlpha = true)
|
|
1192
|
+
{
|
|
1193
|
+
const toHex = (c)=> ((c=c*255|0)<16 ? '0' : '') + c.toString(16);
|
|
1194
|
+
return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
|
|
1144
1195
|
}
|
|
1145
|
-
|
|
1196
|
+
|
|
1146
1197
|
/** Set this color from a hex code
|
|
1147
1198
|
* @param {String} hex - html hex code
|
|
1148
1199
|
* @return {Color} */
|
|
@@ -1168,6 +1219,64 @@ class Color
|
|
|
1168
1219
|
}
|
|
1169
1220
|
}
|
|
1170
1221
|
|
|
1222
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1223
|
+
// default colors
|
|
1224
|
+
|
|
1225
|
+
/** Color - White
|
|
1226
|
+
* @type {Color}
|
|
1227
|
+
* @memberof Utilities */
|
|
1228
|
+
const WHITE = rgb();
|
|
1229
|
+
|
|
1230
|
+
/** Color - Black
|
|
1231
|
+
* @type {Color}
|
|
1232
|
+
* @memberof Utilities */
|
|
1233
|
+
const BLACK = rgb(0,0,0);
|
|
1234
|
+
|
|
1235
|
+
/** Color - Gray
|
|
1236
|
+
* @type {Color}
|
|
1237
|
+
* @memberof Utilities */
|
|
1238
|
+
const GRAY = rgb(.5,.5,.5);
|
|
1239
|
+
|
|
1240
|
+
/** Color - Red
|
|
1241
|
+
* @type {Color}
|
|
1242
|
+
* @memberof Utilities */
|
|
1243
|
+
const RED = rgb(1,0,0);
|
|
1244
|
+
|
|
1245
|
+
/** Color - Orange
|
|
1246
|
+
* @type {Color}
|
|
1247
|
+
* @memberof Utilities */
|
|
1248
|
+
const ORANGE = rgb(1,.5,0);
|
|
1249
|
+
|
|
1250
|
+
/** Color - Yellow
|
|
1251
|
+
* @type {Color}
|
|
1252
|
+
* @memberof Utilities */
|
|
1253
|
+
const YELLOW = rgb(1,1,0);
|
|
1254
|
+
|
|
1255
|
+
/** Color - Green
|
|
1256
|
+
* @type {Color}
|
|
1257
|
+
* @memberof Utilities */
|
|
1258
|
+
const GREEN = rgb(0,1,0);
|
|
1259
|
+
|
|
1260
|
+
/** Color - Cyan
|
|
1261
|
+
* @type {Color}
|
|
1262
|
+
* @memberof Utilities */
|
|
1263
|
+
const CYAN = rgb(0,1,1);
|
|
1264
|
+
|
|
1265
|
+
/** Color - Blue
|
|
1266
|
+
* @type {Color}
|
|
1267
|
+
* @memberof Utilities */
|
|
1268
|
+
const BLUE = rgb(0,0,1);
|
|
1269
|
+
|
|
1270
|
+
/** Color - Purple
|
|
1271
|
+
* @type {Color}
|
|
1272
|
+
* @memberof Utilities */
|
|
1273
|
+
const PURPLE = rgb(.5,0,1);
|
|
1274
|
+
|
|
1275
|
+
/** Color - Magenta
|
|
1276
|
+
* @type {Color}
|
|
1277
|
+
* @memberof Utilities */
|
|
1278
|
+
const MAGENTA = rgb(1,0,1);
|
|
1279
|
+
|
|
1171
1280
|
///////////////////////////////////////////////////////////////////////////////
|
|
1172
1281
|
|
|
1173
1282
|
/**
|
|
@@ -1248,9 +1357,9 @@ let cameraScale = 32;
|
|
|
1248
1357
|
|
|
1249
1358
|
/** The max size of the canvas, centered if window is larger
|
|
1250
1359
|
* @type {Vector2}
|
|
1251
|
-
* @default Vector2(1920,
|
|
1360
|
+
* @default Vector2(1920,1080)
|
|
1252
1361
|
* @memberof Settings */
|
|
1253
|
-
let canvasMaxSize = vec2(1920,
|
|
1362
|
+
let canvasMaxSize = vec2(1920, 1080);
|
|
1254
1363
|
|
|
1255
1364
|
/** Fixed size of the canvas, if enabled canvas size never changes
|
|
1256
1365
|
* - you may also need to set mainCanvasSize if using screen space coords in startup
|
|
@@ -1639,7 +1748,12 @@ function setSoundEnable(enable) { soundEnable = enable; }
|
|
|
1639
1748
|
/** Set volume scale to apply to all sound, music and speech
|
|
1640
1749
|
* @param {Number} volume
|
|
1641
1750
|
* @memberof Settings */
|
|
1642
|
-
function setSoundVolume(volume)
|
|
1751
|
+
function setSoundVolume(volume)
|
|
1752
|
+
{
|
|
1753
|
+
soundVolume = volume;
|
|
1754
|
+
if (soundEnable && !headlessMode && audioGainNode)
|
|
1755
|
+
audioGainNode.gain.value = volume; // update gain immediatly
|
|
1756
|
+
}
|
|
1643
1757
|
|
|
1644
1758
|
/** Set default range where sound no longer plays
|
|
1645
1759
|
* @param {Number} range
|
|
@@ -1772,6 +1886,8 @@ class EngineObject
|
|
|
1772
1886
|
this.spawnTime = time;
|
|
1773
1887
|
/** @property {Array} - List of children of this object */
|
|
1774
1888
|
this.children = [];
|
|
1889
|
+
/** @property {Boolean} - Limit object speed using linear or circular math */
|
|
1890
|
+
this.clampSpeedLinear = true;
|
|
1775
1891
|
|
|
1776
1892
|
// parent child system
|
|
1777
1893
|
/** @property {EngineObject} - Parent of object if in local space */
|
|
@@ -1820,8 +1936,21 @@ class EngineObject
|
|
|
1820
1936
|
return;
|
|
1821
1937
|
|
|
1822
1938
|
// limit max speed to prevent missing collisions
|
|
1823
|
-
|
|
1824
|
-
|
|
1939
|
+
if (this.clampSpeedLinear)
|
|
1940
|
+
{
|
|
1941
|
+
this.velocity.x = clamp(this.velocity.x, -objectMaxSpeed, objectMaxSpeed);
|
|
1942
|
+
this.velocity.y = clamp(this.velocity.y, -objectMaxSpeed, objectMaxSpeed);
|
|
1943
|
+
}
|
|
1944
|
+
else
|
|
1945
|
+
{
|
|
1946
|
+
const length2 = this.velocity.lengthSquared();
|
|
1947
|
+
if (length2 > objectMaxSpeed*objectMaxSpeed)
|
|
1948
|
+
{
|
|
1949
|
+
const s = objectMaxSpeed / length2**.5;
|
|
1950
|
+
this.velocity.x *= s;
|
|
1951
|
+
this.velocity.y *= s;
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1825
1954
|
|
|
1826
1955
|
// apply physics
|
|
1827
1956
|
const oldPos = this.pos.copy();
|
|
@@ -1877,7 +2006,7 @@ class EngineObject
|
|
|
1877
2006
|
if (o.mass) // push away if not fixed
|
|
1878
2007
|
o.velocity = o.velocity.subtract(velocity);
|
|
1879
2008
|
|
|
1880
|
-
debugOverlay && debugPhysics &&
|
|
2009
|
+
debugOverlay && debugPhysics && debugOverlap(this.pos, this.size, o.pos, o.size, '#f00');
|
|
1881
2010
|
continue;
|
|
1882
2011
|
}
|
|
1883
2012
|
|
|
@@ -1939,7 +2068,7 @@ class EngineObject
|
|
|
1939
2068
|
else // bounce if other object is fixed
|
|
1940
2069
|
this.velocity.x *= -elasticity;
|
|
1941
2070
|
}
|
|
1942
|
-
debugOverlay && debugPhysics &&
|
|
2071
|
+
debugOverlay && debugPhysics && debugOverlap(this.pos, this.size, o.pos, o.size, '#f0f');
|
|
1943
2072
|
}
|
|
1944
2073
|
}
|
|
1945
2074
|
if (this.collideTiles)
|
|
@@ -2000,6 +2129,22 @@ class EngineObject
|
|
|
2000
2129
|
for (const child of this.children)
|
|
2001
2130
|
child.destroy(child.parent = 0);
|
|
2002
2131
|
}
|
|
2132
|
+
|
|
2133
|
+
/** Convert from local space to world space
|
|
2134
|
+
* @param {Vector2} pos - local space point */
|
|
2135
|
+
localToWorld(pos) { return this.pos.add(pos.rotate(-this.angle)); }
|
|
2136
|
+
|
|
2137
|
+
/** Convert from world space to local space
|
|
2138
|
+
* @param {Vector2} pos - world space point */
|
|
2139
|
+
worldToLocal(pos) { return pos.subtract(this.pos).rotate(this.angle); }
|
|
2140
|
+
|
|
2141
|
+
/** Convert from local space to world space for a vector (rotation only)
|
|
2142
|
+
* @param {Vector2} vec - local space vector */
|
|
2143
|
+
localToWorldVector(vec) { return vec.rotate(this.angle); }
|
|
2144
|
+
|
|
2145
|
+
/** Convert from world space to local space for a vector (rotation only)
|
|
2146
|
+
* @param {Vector2} vec - world space vector */
|
|
2147
|
+
worldToLocalVector(vec) { return vec.rotate(-this.angle); }
|
|
2003
2148
|
|
|
2004
2149
|
/** Called to check if a tile collision should be resolved
|
|
2005
2150
|
* @param {Number} tileData - the value of the tile at the position
|
|
@@ -2086,6 +2231,18 @@ class EngineObject
|
|
|
2086
2231
|
return text;
|
|
2087
2232
|
}
|
|
2088
2233
|
}
|
|
2234
|
+
|
|
2235
|
+
/** Render debug info for this object */
|
|
2236
|
+
renderDebugInfo()
|
|
2237
|
+
{
|
|
2238
|
+
// show object info for debugging
|
|
2239
|
+
const size = vec2(max(this.size.x, .2), max(this.size.y, .2));
|
|
2240
|
+
const color1 = rgb(this.collideTiles?1:0, this.collideSolidObjects?1:0, this.isSolid?1:0, this.parent?.2:.5);
|
|
2241
|
+
const color2 = this.parent ? rgb(1,1,1,.5) : rgb(0,0,0,.8);
|
|
2242
|
+
drawRect(this.pos, size, color1, this.angle, false);
|
|
2243
|
+
drawRect(this.pos, size.scale(.8), color2, this.angle, false);
|
|
2244
|
+
this.parent && drawLine(this.pos, this.parent.pos, .1, rgb(0,0,1,.5), false);
|
|
2245
|
+
}
|
|
2089
2246
|
}
|
|
2090
2247
|
/**
|
|
2091
2248
|
* LittleJS Drawing System
|
|
@@ -2409,7 +2566,7 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, conte
|
|
|
2409
2566
|
context.save();
|
|
2410
2567
|
context.translate(pos.x+.5, pos.y+.5);
|
|
2411
2568
|
context.rotate(angle);
|
|
2412
|
-
context.scale(mirror ? -size.x : size.x, size.y);
|
|
2569
|
+
context.scale(mirror ? -size.x : size.x, -size.y);
|
|
2413
2570
|
drawFunction(context);
|
|
2414
2571
|
context.restore();
|
|
2415
2572
|
}
|
|
@@ -3104,6 +3261,32 @@ function touchGamepadRender()
|
|
|
3104
3261
|
|
|
3105
3262
|
|
|
3106
3263
|
|
|
3264
|
+
/** Audio context used by the engine
|
|
3265
|
+
* @type {AudioContext}
|
|
3266
|
+
* @memberof Audio */
|
|
3267
|
+
let audioContext;
|
|
3268
|
+
|
|
3269
|
+
/** Master gain node for all audio to pass through
|
|
3270
|
+
* @type {GainNode}
|
|
3271
|
+
* @memberof Audio */
|
|
3272
|
+
let audioGainNode;
|
|
3273
|
+
|
|
3274
|
+
function audioInit()
|
|
3275
|
+
{
|
|
3276
|
+
if (!soundEnable || headlessMode) return;
|
|
3277
|
+
|
|
3278
|
+
// create audio context
|
|
3279
|
+
audioContext = new AudioContext;
|
|
3280
|
+
|
|
3281
|
+
// create and connect gain node
|
|
3282
|
+
// (createGain is more widely spported then GainNode construtor)
|
|
3283
|
+
audioGainNode = audioContext.createGain();
|
|
3284
|
+
audioGainNode.connect(audioContext.destination);
|
|
3285
|
+
setSoundVolume(soundVolume); // update gain volume
|
|
3286
|
+
}
|
|
3287
|
+
|
|
3288
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3289
|
+
|
|
3107
3290
|
/**
|
|
3108
3291
|
* Sound Object - Stores a sound for later use and can be played positionally
|
|
3109
3292
|
*
|
|
@@ -3155,7 +3338,8 @@ class Sound
|
|
|
3155
3338
|
*/
|
|
3156
3339
|
play(pos, volume=1, pitch=1, randomnessScale=1, loop=false)
|
|
3157
3340
|
{
|
|
3158
|
-
if (!soundEnable ||
|
|
3341
|
+
if (!soundEnable || headlessMode) return;
|
|
3342
|
+
if (!this.sampleChannels) return;
|
|
3159
3343
|
|
|
3160
3344
|
let pan;
|
|
3161
3345
|
if (pos)
|
|
@@ -3178,7 +3362,17 @@ class Sound
|
|
|
3178
3362
|
|
|
3179
3363
|
// play the sound
|
|
3180
3364
|
const playbackRate = pitch + pitch * this.randomness*randomnessScale*rand(-1,1);
|
|
3181
|
-
|
|
3365
|
+
this.gainNode = audioContext.createGain();
|
|
3366
|
+
return this.source = playSamples(this.sampleChannels, volume, playbackRate, pan, loop, this.sampleRate, this.gainNode);
|
|
3367
|
+
}
|
|
3368
|
+
|
|
3369
|
+
/** Set the sound volume
|
|
3370
|
+
* @param {Number} [volume] - How much to scale volume by
|
|
3371
|
+
*/
|
|
3372
|
+
setVolume(volume=1)
|
|
3373
|
+
{
|
|
3374
|
+
if (this.gainNode)
|
|
3375
|
+
this.gainNode.gain.value = volume;
|
|
3182
3376
|
}
|
|
3183
3377
|
|
|
3184
3378
|
/** Stop the last instance of this sound that was played */
|
|
@@ -3232,16 +3426,14 @@ class SoundWave extends Sound
|
|
|
3232
3426
|
* @param {Number} [randomness] - How much to randomize frequency each time sound plays
|
|
3233
3427
|
* @param {Number} [range=soundDefaultRange] - World space max range of sound, will not play if camera is farther away
|
|
3234
3428
|
* @param {Number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering off
|
|
3429
|
+
* @param {Function} [onloadCallback] - callback function to call when sound is loaded
|
|
3235
3430
|
*/
|
|
3236
|
-
constructor(filename, randomness=0, range, taper)
|
|
3431
|
+
constructor(filename, randomness=0, range, taper, onloadCallback)
|
|
3237
3432
|
{
|
|
3238
3433
|
super(undefined, range, taper);
|
|
3239
|
-
this.randomness = randomness;
|
|
3240
|
-
|
|
3241
3434
|
if (!soundEnable || headlessMode) return;
|
|
3242
|
-
if (!audioContext)
|
|
3243
|
-
audioContext = new AudioContext; // create audio context
|
|
3244
3435
|
|
|
3436
|
+
this.randomness = randomness;
|
|
3245
3437
|
fetch(filename)
|
|
3246
3438
|
.then(response => response.arrayBuffer())
|
|
3247
3439
|
.then(arrayBuffer => audioContext.decodeAudioData(arrayBuffer))
|
|
@@ -3251,10 +3443,23 @@ class SoundWave extends Sound
|
|
|
3251
3443
|
for (let i = audioBuffer.numberOfChannels; i--;)
|
|
3252
3444
|
this.sampleChannels[i] = Array.from(audioBuffer.getChannelData(i));
|
|
3253
3445
|
this.sampleRate = audioBuffer.sampleRate;
|
|
3254
|
-
});
|
|
3446
|
+
}).then(() => onloadCallback && onloadCallback(this));
|
|
3255
3447
|
}
|
|
3256
3448
|
}
|
|
3257
3449
|
|
|
3450
|
+
/** Play an mp3, ogg, or wav audio from a local file or url
|
|
3451
|
+
* @param {String} filename - Location of sound file to play
|
|
3452
|
+
* @param {Number} [volume] - How much to scale volume by
|
|
3453
|
+
* @param {Boolean} [loop] - True if the music should loop
|
|
3454
|
+
* @return {SoundWave} - The sound object for this file
|
|
3455
|
+
* @memberof Audio */
|
|
3456
|
+
function playAudioFile(filename, volume=1, loop=false)
|
|
3457
|
+
{
|
|
3458
|
+
if (!soundEnable || headlessMode) return;
|
|
3459
|
+
|
|
3460
|
+
return new SoundWave(filename,0,0,0, s=>s.play(undefined, volume, 1, 1, loop));
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3258
3463
|
/**
|
|
3259
3464
|
* Music Object - Stores a zzfx music track for later use
|
|
3260
3465
|
*
|
|
@@ -3309,23 +3514,6 @@ class Music extends Sound
|
|
|
3309
3514
|
{ return super.play(undefined, volume, 1, 1, loop); }
|
|
3310
3515
|
}
|
|
3311
3516
|
|
|
3312
|
-
/** Play an mp3, ogg, or wav audio from a local file or url
|
|
3313
|
-
* @param {String} filename - Location of sound file to play
|
|
3314
|
-
* @param {Number} [volume] - How much to scale volume by
|
|
3315
|
-
* @param {Boolean} [loop] - True if the music should loop
|
|
3316
|
-
* @return {HTMLAudioElement} - The audio element for this sound
|
|
3317
|
-
* @memberof Audio */
|
|
3318
|
-
function playAudioFile(filename, volume=1, loop=false)
|
|
3319
|
-
{
|
|
3320
|
-
if (!soundEnable || headlessMode) return;
|
|
3321
|
-
|
|
3322
|
-
const audio = new Audio(filename);
|
|
3323
|
-
audio.volume = soundVolume * volume;
|
|
3324
|
-
audio.loop = loop;
|
|
3325
|
-
audio.play();
|
|
3326
|
-
return audio;
|
|
3327
|
-
}
|
|
3328
|
-
|
|
3329
3517
|
/** Speak text with passed in settings
|
|
3330
3518
|
* @param {String} text - The text to speak
|
|
3331
3519
|
* @param {String} [language] - The language/accent to use (examples: en, it, ru, ja, zh)
|
|
@@ -3336,7 +3524,8 @@ function playAudioFile(filename, volume=1, loop=false)
|
|
|
3336
3524
|
* @memberof Audio */
|
|
3337
3525
|
function speak(text, language='', volume=1, rate=1, pitch=1)
|
|
3338
3526
|
{
|
|
3339
|
-
if (!soundEnable ||
|
|
3527
|
+
if (!soundEnable || headlessMode) return;
|
|
3528
|
+
if (!speechSynthesis) return;
|
|
3340
3529
|
|
|
3341
3530
|
// common languages (not supported by all browsers)
|
|
3342
3531
|
// en - english, it - italian, fr - french, de - german, es - spanish
|
|
@@ -3366,30 +3555,23 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
|
|
|
3366
3555
|
|
|
3367
3556
|
///////////////////////////////////////////////////////////////////////////////
|
|
3368
3557
|
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
* @memberof Audio */
|
|
3372
|
-
let audioContext;
|
|
3373
|
-
|
|
3374
|
-
/** Keep track if audio was suspended when last sound was played
|
|
3375
|
-
* @type {Boolean}
|
|
3376
|
-
* @memberof Audio */
|
|
3558
|
+
// internal tracking if audio was suspended when last sound was played
|
|
3559
|
+
// allows first suspended sound to play when audio is resumed
|
|
3377
3560
|
let audioSuspended = false;
|
|
3378
3561
|
|
|
3379
3562
|
/** Play cached audio samples with given settings
|
|
3380
|
-
* @param {Array}
|
|
3381
|
-
* @param {Number}
|
|
3382
|
-
* @param {Number}
|
|
3383
|
-
* @param {Number}
|
|
3384
|
-
* @param {Boolean}
|
|
3385
|
-
* @param {Number}
|
|
3563
|
+
* @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback)
|
|
3564
|
+
* @param {Number} [volume] - How much to scale volume by
|
|
3565
|
+
* @param {Number} [rate] - The playback rate to use
|
|
3566
|
+
* @param {Number} [pan] - How much to apply stereo panning
|
|
3567
|
+
* @param {Boolean} [loop] - True if the sound should loop when it reaches the end
|
|
3568
|
+
* @param {Number} [sampleRate=44100] - Sample rate for the sound
|
|
3569
|
+
* @param {GainNode} [gainNode] - Optional gain node for volume control while playing
|
|
3386
3570
|
* @return {AudioBufferSourceNode} - The audio node of the sound played
|
|
3387
3571
|
* @memberof Audio */
|
|
3388
|
-
function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=zzfxR)
|
|
3572
|
+
function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=zzfxR, gainNode)
|
|
3389
3573
|
{
|
|
3390
3574
|
if (!soundEnable || headlessMode) return;
|
|
3391
|
-
if (!audioContext)
|
|
3392
|
-
audioContext = new AudioContext; // create audio context
|
|
3393
3575
|
|
|
3394
3576
|
// prevent sounds from building up if they can't be played
|
|
3395
3577
|
const audioWasSuspended = audioSuspended;
|
|
@@ -3413,10 +3595,10 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
3413
3595
|
source.playbackRate.value = rate;
|
|
3414
3596
|
source.loop = loop;
|
|
3415
3597
|
|
|
3416
|
-
// create and connect gain node
|
|
3417
|
-
|
|
3418
|
-
gainNode.gain.value =
|
|
3419
|
-
gainNode.connect(
|
|
3598
|
+
// create and connect gain node
|
|
3599
|
+
gainNode = gainNode || audioContext.createGain();
|
|
3600
|
+
gainNode.gain.value = volume;
|
|
3601
|
+
gainNode.connect(audioGainNode);
|
|
3420
3602
|
|
|
3421
3603
|
// connect source to stereo panner and gain
|
|
3422
3604
|
source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)})).connect(gainNode);
|
|
@@ -4323,6 +4505,9 @@ class Particle extends EngineObject
|
|
|
4323
4505
|
this.localSpaceEmitter = localSpaceEmitter;
|
|
4324
4506
|
/** @property {Function} - Called when particle dies */
|
|
4325
4507
|
this.destroyCallback = destroyCallback;
|
|
4508
|
+
|
|
4509
|
+
// particles use circular clamped speed
|
|
4510
|
+
this.clampSpeedLinear = false;
|
|
4326
4511
|
}
|
|
4327
4512
|
|
|
4328
4513
|
/** Render the particle, automatically called each frame, sorted by renderOrder */
|
|
@@ -4392,9 +4577,9 @@ class Particle extends EngineObject
|
|
|
4392
4577
|
|
|
4393
4578
|
|
|
4394
4579
|
/** List of all medals
|
|
4395
|
-
* @type {
|
|
4580
|
+
* @type {Object}
|
|
4396
4581
|
* @memberof Medals */
|
|
4397
|
-
const medals =
|
|
4582
|
+
const medals = {};
|
|
4398
4583
|
|
|
4399
4584
|
// Engine internal variables not exposed to documentation
|
|
4400
4585
|
let medalsDisplayQueue = [], medalsSaveName, medalsDisplayTimeLast;
|
|
@@ -4410,9 +4595,45 @@ function medalsInit(saveName)
|
|
|
4410
4595
|
{
|
|
4411
4596
|
// check if medals are unlocked
|
|
4412
4597
|
medalsSaveName = saveName;
|
|
4413
|
-
|
|
4598
|
+
if (!debugMedals)
|
|
4599
|
+
medalsForEach(medal=> medal.unlocked = (localStorage[medal.storageKey()] | 0));
|
|
4600
|
+
|
|
4601
|
+
// engine automatically renders medals
|
|
4602
|
+
addPluginRender(function()
|
|
4603
|
+
{
|
|
4604
|
+
if (!medalsDisplayQueue.length)
|
|
4605
|
+
return;
|
|
4606
|
+
|
|
4607
|
+
// update first medal in queue
|
|
4608
|
+
const medal = medalsDisplayQueue[0];
|
|
4609
|
+
const time = timeReal - medalsDisplayTimeLast;
|
|
4610
|
+
if (!medalsDisplayTimeLast)
|
|
4611
|
+
medalsDisplayTimeLast = timeReal;
|
|
4612
|
+
else if (time > medalDisplayTime)
|
|
4613
|
+
{
|
|
4614
|
+
medalsDisplayTimeLast = 0;
|
|
4615
|
+
medalsDisplayQueue.shift();
|
|
4616
|
+
}
|
|
4617
|
+
else
|
|
4618
|
+
{
|
|
4619
|
+
// slide on/off medals
|
|
4620
|
+
const slideOffTime = medalDisplayTime - medalDisplaySlideTime;
|
|
4621
|
+
const hidePercent =
|
|
4622
|
+
time < medalDisplaySlideTime ? 1 - time / medalDisplaySlideTime :
|
|
4623
|
+
time > slideOffTime ? (time - slideOffTime) / medalDisplaySlideTime : 0;
|
|
4624
|
+
medal.render(hidePercent);
|
|
4625
|
+
}
|
|
4626
|
+
});
|
|
4414
4627
|
}
|
|
4415
4628
|
|
|
4629
|
+
/** Calls a function for each medal
|
|
4630
|
+
* @param {Function} callback
|
|
4631
|
+
* @memberof Medals */
|
|
4632
|
+
function medalsForEach(callback)
|
|
4633
|
+
{ Object.values(medals).forEach(medal=>callback(medal)); }
|
|
4634
|
+
|
|
4635
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
4636
|
+
|
|
4416
4637
|
/**
|
|
4417
4638
|
* Medal - Tracks an unlockable medal
|
|
4418
4639
|
* @example
|
|
@@ -4457,7 +4678,6 @@ class Medal
|
|
|
4457
4678
|
ASSERT(medalsSaveName, 'save name must be set');
|
|
4458
4679
|
localStorage[this.storageKey()] = this.unlocked = 1;
|
|
4459
4680
|
medalsDisplayQueue.push(this);
|
|
4460
|
-
newgrounds && newgrounds.unlockMedal(this.id);
|
|
4461
4681
|
}
|
|
4462
4682
|
|
|
4463
4683
|
/** Render a medal
|
|
@@ -4505,173 +4725,6 @@ class Medal
|
|
|
4505
4725
|
|
|
4506
4726
|
// Get local storage key used by the medal
|
|
4507
4727
|
storageKey() { return medalsSaveName + '_' + this.id; }
|
|
4508
|
-
}
|
|
4509
|
-
|
|
4510
|
-
// engine automatically renders medals
|
|
4511
|
-
function medalsRender()
|
|
4512
|
-
{
|
|
4513
|
-
if (!medalsDisplayQueue.length)
|
|
4514
|
-
return;
|
|
4515
|
-
|
|
4516
|
-
// update first medal in queue
|
|
4517
|
-
const medal = medalsDisplayQueue[0];
|
|
4518
|
-
const time = timeReal - medalsDisplayTimeLast;
|
|
4519
|
-
if (!medalsDisplayTimeLast)
|
|
4520
|
-
medalsDisplayTimeLast = timeReal;
|
|
4521
|
-
else if (time > medalDisplayTime)
|
|
4522
|
-
{
|
|
4523
|
-
medalsDisplayTimeLast = 0;
|
|
4524
|
-
medalsDisplayQueue.shift();
|
|
4525
|
-
}
|
|
4526
|
-
else
|
|
4527
|
-
{
|
|
4528
|
-
// slide on/off medals
|
|
4529
|
-
const slideOffTime = medalDisplayTime - medalDisplaySlideTime;
|
|
4530
|
-
const hidePercent =
|
|
4531
|
-
time < medalDisplaySlideTime ? 1 - time / medalDisplaySlideTime :
|
|
4532
|
-
time > slideOffTime ? (time - slideOffTime) / medalDisplaySlideTime : 0;
|
|
4533
|
-
medal.render(hidePercent);
|
|
4534
|
-
}
|
|
4535
|
-
}
|
|
4536
|
-
|
|
4537
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
4538
|
-
|
|
4539
|
-
// global Newgrounds object
|
|
4540
|
-
let newgrounds;
|
|
4541
|
-
|
|
4542
|
-
/** This can used to enable Newgrounds functionality
|
|
4543
|
-
* @param {Number} app_id - The newgrounds App ID
|
|
4544
|
-
* @param {String} [cipher] - The encryption Key (AES-128/Base64)
|
|
4545
|
-
* @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher
|
|
4546
|
-
* @memberof Medals */
|
|
4547
|
-
function newgroundsInit(app_id, cipher, cryptoJS)
|
|
4548
|
-
{ newgrounds = new Newgrounds(app_id, cipher, cryptoJS); }
|
|
4549
|
-
|
|
4550
|
-
/**
|
|
4551
|
-
* Newgrounds API wrapper object
|
|
4552
|
-
* @example
|
|
4553
|
-
* // create a newgrounds object, replace the app id with your own
|
|
4554
|
-
* const app_id = '53123:1ZuSTQ9l';
|
|
4555
|
-
* newgrounds = new Newgrounds(app_id);
|
|
4556
|
-
*/
|
|
4557
|
-
class Newgrounds
|
|
4558
|
-
{
|
|
4559
|
-
/** Create a newgrounds object
|
|
4560
|
-
* @param {Number} app_id - The newgrounds App ID
|
|
4561
|
-
* @param {String} [cipher] - The encryption Key (AES-128/Base64)
|
|
4562
|
-
* @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher */
|
|
4563
|
-
constructor(app_id, cipher, cryptoJS)
|
|
4564
|
-
{
|
|
4565
|
-
ASSERT(!newgrounds && app_id>0, 'there can only be one newgrounds object');
|
|
4566
|
-
ASSERT(!cipher || cryptoJS, 'must provide cryptojs if there is a cipher');
|
|
4567
|
-
|
|
4568
|
-
this.app_id = app_id;
|
|
4569
|
-
this.cipher = cipher;
|
|
4570
|
-
this.cryptoJS = cryptoJS;
|
|
4571
|
-
this.host = location ? location.hostname : '';
|
|
4572
|
-
|
|
4573
|
-
// get session id from url search params
|
|
4574
|
-
const url = new URL(location.href);
|
|
4575
|
-
this.session_id = url.searchParams.get('ngio_session_id');
|
|
4576
|
-
|
|
4577
|
-
if (!this.session_id)
|
|
4578
|
-
return; // only use newgrounds when logged in
|
|
4579
|
-
|
|
4580
|
-
// get medals
|
|
4581
|
-
const medalsResult = this.call('Medal.getList');
|
|
4582
|
-
this.medals = medalsResult ? medalsResult.result.data['medals'] : [];
|
|
4583
|
-
debugMedals && console.log(this.medals);
|
|
4584
|
-
for (const newgroundsMedal of this.medals)
|
|
4585
|
-
{
|
|
4586
|
-
const medal = medals[newgroundsMedal['id']];
|
|
4587
|
-
if (medal)
|
|
4588
|
-
{
|
|
4589
|
-
// copy newgrounds medal data
|
|
4590
|
-
medal.image = new Image;
|
|
4591
|
-
medal.image.src = newgroundsMedal['icon'];
|
|
4592
|
-
medal.name = newgroundsMedal['name'];
|
|
4593
|
-
medal.description = newgroundsMedal['description'];
|
|
4594
|
-
medal.unlocked = newgroundsMedal['unlocked'];
|
|
4595
|
-
medal.difficulty = newgroundsMedal['difficulty'];
|
|
4596
|
-
medal.value = newgroundsMedal['value'];
|
|
4597
|
-
|
|
4598
|
-
if (medal.value)
|
|
4599
|
-
medal.description = medal.description + ' (' + medal.value + ')';
|
|
4600
|
-
}
|
|
4601
|
-
}
|
|
4602
|
-
|
|
4603
|
-
// get scoreboards
|
|
4604
|
-
const scoreboardResult = this.call('ScoreBoard.getBoards');
|
|
4605
|
-
this.scoreboards = scoreboardResult ? scoreboardResult.result.data.scoreboards : [];
|
|
4606
|
-
debugMedals && console.log(this.scoreboards);
|
|
4607
|
-
|
|
4608
|
-
const keepAliveMS = 5 * 60 * 1e3;
|
|
4609
|
-
setInterval(()=>this.call('Gateway.ping', 0, true), keepAliveMS);
|
|
4610
|
-
}
|
|
4611
|
-
|
|
4612
|
-
/** Send message to unlock a medal by id
|
|
4613
|
-
* @param {Number} id - The medal id */
|
|
4614
|
-
unlockMedal(id) { return this.call('Medal.unlock', {'id':id}, true); }
|
|
4615
|
-
|
|
4616
|
-
/** Send message to post score
|
|
4617
|
-
* @param {Number} id - The scoreboard id
|
|
4618
|
-
* @param {Number} value - The score value */
|
|
4619
|
-
postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, true); }
|
|
4620
|
-
|
|
4621
|
-
/** Get scores from a scoreboard
|
|
4622
|
-
* @param {Number} id - The scoreboard id
|
|
4623
|
-
* @param {String} [user] - A user's id or name
|
|
4624
|
-
* @param {Number} [social] - If true, only social scores will be loaded
|
|
4625
|
-
* @param {Number} [skip] - Number of scores to skip before start
|
|
4626
|
-
* @param {Number} [limit] - Number of scores to include in the list
|
|
4627
|
-
* @return {Object} - The response JSON object
|
|
4628
|
-
*/
|
|
4629
|
-
getScores(id, user, social=0, skip=0, limit=10)
|
|
4630
|
-
{ return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); }
|
|
4631
|
-
|
|
4632
|
-
/** Send message to log a view */
|
|
4633
|
-
logView() { return this.call('App.logView', {'host':this.host}, true); }
|
|
4634
|
-
|
|
4635
|
-
/** Send a message to call a component of the Newgrounds API
|
|
4636
|
-
* @param {String} component - Name of the component
|
|
4637
|
-
* @param {Object} [parameters] - Parameters to use for call
|
|
4638
|
-
* @param {Boolean} [async] - If true, don't wait for response before continuing
|
|
4639
|
-
* @return {Object} - The response JSON object
|
|
4640
|
-
*/
|
|
4641
|
-
call(component, parameters, async=false)
|
|
4642
|
-
{
|
|
4643
|
-
const call = {'component':component, 'parameters':parameters};
|
|
4644
|
-
if (this.cipher)
|
|
4645
|
-
{
|
|
4646
|
-
// encrypt using AES-128 Base64 with cryptoJS
|
|
4647
|
-
const cryptoJS = this.cryptoJS;
|
|
4648
|
-
const aesKey = cryptoJS['enc']['Base64']['parse'](this.cipher);
|
|
4649
|
-
const iv = cryptoJS['lib']['WordArray']['random'](16);
|
|
4650
|
-
const encrypted = cryptoJS['AES']['encrypt'](JSON.stringify(call), aesKey, {'iv':iv});
|
|
4651
|
-
call['secure'] = cryptoJS['enc']['Base64']['stringify'](iv.concat(encrypted['ciphertext']));
|
|
4652
|
-
call['parameters'] = 0;
|
|
4653
|
-
}
|
|
4654
|
-
|
|
4655
|
-
// build the input object
|
|
4656
|
-
const input =
|
|
4657
|
-
{
|
|
4658
|
-
'app_id': this.app_id,
|
|
4659
|
-
'session_id': this.session_id,
|
|
4660
|
-
'call': call
|
|
4661
|
-
};
|
|
4662
|
-
|
|
4663
|
-
// build post data
|
|
4664
|
-
const formData = new FormData();
|
|
4665
|
-
formData.append('input', JSON.stringify(input));
|
|
4666
|
-
|
|
4667
|
-
// send post data
|
|
4668
|
-
const xmlHttp = new XMLHttpRequest();
|
|
4669
|
-
const url = 'https://newgrounds.io/gateway_v3.php';
|
|
4670
|
-
xmlHttp.open('POST', url, !debugMedals && async);
|
|
4671
|
-
xmlHttp.send(formData);
|
|
4672
|
-
debugMedals && console.log(xmlHttp.responseText);
|
|
4673
|
-
return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
|
|
4674
|
-
}
|
|
4675
4728
|
}
|
|
4676
4729
|
/**
|
|
4677
4730
|
* LittleJS WebGL Interface
|
|
@@ -4762,7 +4815,7 @@ function glPreRender()
|
|
|
4762
4815
|
|
|
4763
4816
|
// clear and set to same size as main canvas
|
|
4764
4817
|
glContext.viewport(0, 0, glCanvas.width=mainCanvas.width, glCanvas.height=mainCanvas.height);
|
|
4765
|
-
|
|
4818
|
+
glContext.clear(gl_COLOR_BUFFER_BIT);
|
|
4766
4819
|
|
|
4767
4820
|
// set up the shader
|
|
4768
4821
|
glContext.useProgram(glShader);
|
|
@@ -4945,99 +4998,6 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdd
|
|
|
4945
4998
|
glInstanceCount++;
|
|
4946
4999
|
}
|
|
4947
5000
|
|
|
4948
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
4949
|
-
// post processing - can be enabled to pass other canvases through a final shader
|
|
4950
|
-
|
|
4951
|
-
let glPostShader, glPostTexture, glPostIncludeOverlay;
|
|
4952
|
-
|
|
4953
|
-
/** Set up a post processing shader
|
|
4954
|
-
* @param {String} shaderCode
|
|
4955
|
-
* @param {Boolean} includeOverlay
|
|
4956
|
-
* @memberof WebGL */
|
|
4957
|
-
function glInitPostProcess(shaderCode, includeOverlay=false)
|
|
4958
|
-
{
|
|
4959
|
-
ASSERT(!glPostShader, 'can only have 1 post effects shader');
|
|
4960
|
-
if (headlessMode) return;
|
|
4961
|
-
if (!shaderCode) // default shader pass through
|
|
4962
|
-
shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
|
|
4963
|
-
|
|
4964
|
-
// create the shader
|
|
4965
|
-
glPostShader = glCreateProgram(
|
|
4966
|
-
'#version 300 es\n' + // specify GLSL ES version
|
|
4967
|
-
'precision highp float;'+ // use highp for better accuracy
|
|
4968
|
-
'in vec2 p;'+ // position
|
|
4969
|
-
'void main(){'+ // shader entry point
|
|
4970
|
-
'gl_Position=vec4(p+p-1.,1,1);'+ // set position
|
|
4971
|
-
'}' // end of shader
|
|
4972
|
-
,
|
|
4973
|
-
'#version 300 es\n' + // specify GLSL ES version
|
|
4974
|
-
'precision highp float;'+ // use highp for better accuracy
|
|
4975
|
-
'uniform sampler2D iChannel0;'+ // input texture
|
|
4976
|
-
'uniform vec3 iResolution;'+ // size of output texture
|
|
4977
|
-
'uniform float iTime;'+ // time
|
|
4978
|
-
'out vec4 c;'+ // out color
|
|
4979
|
-
'\n' + shaderCode + '\n'+ // insert custom shader code
|
|
4980
|
-
'void main(){'+ // shader entry point
|
|
4981
|
-
'mainImage(c,gl_FragCoord.xy);'+ // call post process function
|
|
4982
|
-
'c.a=1.;'+ // always use full alpha
|
|
4983
|
-
'}' // end of shader
|
|
4984
|
-
);
|
|
4985
|
-
|
|
4986
|
-
// create buffer and texture
|
|
4987
|
-
glPostTexture = glCreateTexture(undefined);
|
|
4988
|
-
glPostIncludeOverlay = includeOverlay;
|
|
4989
|
-
|
|
4990
|
-
// hide the original 2d canvas
|
|
4991
|
-
mainCanvas.style.visibility = 'hidden';
|
|
4992
|
-
if (glPostIncludeOverlay)
|
|
4993
|
-
overlayCanvas.style.visibility = 'hidden';
|
|
4994
|
-
}
|
|
4995
|
-
|
|
4996
|
-
// Render the post processing shader, called automatically by the engine
|
|
4997
|
-
function glRenderPostProcess()
|
|
4998
|
-
{
|
|
4999
|
-
if (!glPostShader || headlessMode) return;
|
|
5000
|
-
|
|
5001
|
-
// prepare to render post process shader
|
|
5002
|
-
if (glEnable)
|
|
5003
|
-
{
|
|
5004
|
-
glFlush(); // clear out the buffer
|
|
5005
|
-
mainContext.drawImage(glCanvas, 0, 0); // copy to the main canvas
|
|
5006
|
-
}
|
|
5007
|
-
else
|
|
5008
|
-
{
|
|
5009
|
-
// set the viewport
|
|
5010
|
-
glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
|
|
5011
|
-
}
|
|
5012
|
-
|
|
5013
|
-
// copy overlay canvas so it will be included in post processing
|
|
5014
|
-
glPostIncludeOverlay && mainContext.drawImage(overlayCanvas, 0, 0);
|
|
5015
|
-
|
|
5016
|
-
// setup shader program to draw one triangle
|
|
5017
|
-
glContext.useProgram(glPostShader);
|
|
5018
|
-
glContext.bindBuffer(gl_ARRAY_BUFFER, glGeometryBuffer);
|
|
5019
|
-
glContext.pixelStorei(gl_UNPACK_FLIP_Y_WEBGL, 1);
|
|
5020
|
-
glContext.disable(gl_BLEND);
|
|
5021
|
-
|
|
5022
|
-
// set textures, pass in the 2d canvas and gl canvas in separate texture channels
|
|
5023
|
-
glContext.activeTexture(gl_TEXTURE0);
|
|
5024
|
-
glContext.bindTexture(gl_TEXTURE_2D, glPostTexture);
|
|
5025
|
-
glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, mainCanvas);
|
|
5026
|
-
|
|
5027
|
-
// set vertex position attribute
|
|
5028
|
-
const vertexByteStride = 8;
|
|
5029
|
-
const pLocation = glContext.getAttribLocation(glPostShader, 'p');
|
|
5030
|
-
glContext.enableVertexAttribArray(pLocation);
|
|
5031
|
-
glContext.vertexAttribPointer(pLocation, 2, gl_FLOAT, false, vertexByteStride, 0);
|
|
5032
|
-
|
|
5033
|
-
// set uniforms and draw
|
|
5034
|
-
const uniformLocation = (name)=>glContext.getUniformLocation(glPostShader, name);
|
|
5035
|
-
glContext.uniform1i(uniformLocation('iChannel0'), 0);
|
|
5036
|
-
glContext.uniform1f(uniformLocation('iTime'), time);
|
|
5037
|
-
glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
|
|
5038
|
-
glContext.drawArrays(gl_TRIANGLE_STRIP, 0, 4);
|
|
5039
|
-
}
|
|
5040
|
-
|
|
5041
5001
|
///////////////////////////////////////////////////////////////////////////////
|
|
5042
5002
|
// store gl constants as integers so their name doesn't use space in minifed
|
|
5043
5003
|
const
|
|
@@ -5102,7 +5062,7 @@ const engineName = 'LittleJS';
|
|
|
5102
5062
|
* @type {String}
|
|
5103
5063
|
* @default
|
|
5104
5064
|
* @memberof Engine */
|
|
5105
|
-
const engineVersion = '1.9.
|
|
5065
|
+
const engineVersion = '1.9.8';
|
|
5106
5066
|
|
|
5107
5067
|
/** Frames per second to update
|
|
5108
5068
|
* @type {Number}
|
|
@@ -5156,6 +5116,22 @@ function setPaused(isPaused) { paused = isPaused; }
|
|
|
5156
5116
|
let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
|
|
5157
5117
|
|
|
5158
5118
|
///////////////////////////////////////////////////////////////////////////////
|
|
5119
|
+
// plugin hooks
|
|
5120
|
+
|
|
5121
|
+
const pluginUpdateList = [], pluginRenderList = [];
|
|
5122
|
+
|
|
5123
|
+
/** Add a new update function for a plugin
|
|
5124
|
+
* @param {Function} updateFunction
|
|
5125
|
+
* @memberof Engine */
|
|
5126
|
+
function addPluginUpdate(updateFunction) { pluginUpdateList.push(updateFunction); }
|
|
5127
|
+
|
|
5128
|
+
/** Add a new render function for a plugin
|
|
5129
|
+
* @param {Function} renderFunction
|
|
5130
|
+
* @memberof Engine */
|
|
5131
|
+
function addPluginRender(renderFunction) { pluginRenderList.push(renderFunction); }
|
|
5132
|
+
|
|
5133
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5134
|
+
// Main engine functions
|
|
5159
5135
|
|
|
5160
5136
|
/** Startup LittleJS engine with your callback functions
|
|
5161
5137
|
* @param {Function} gameInit - Called once after the engine starts up, setup the game
|
|
@@ -5231,6 +5207,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5231
5207
|
// update game and objects
|
|
5232
5208
|
inputUpdate();
|
|
5233
5209
|
gameUpdate();
|
|
5210
|
+
pluginUpdateList.forEach(f=>f());
|
|
5234
5211
|
engineObjectsUpdate();
|
|
5235
5212
|
|
|
5236
5213
|
// do post update
|
|
@@ -5252,8 +5229,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5252
5229
|
for (const o of engineObjects)
|
|
5253
5230
|
o.destroyed || o.render();
|
|
5254
5231
|
gameRenderPost();
|
|
5255
|
-
|
|
5256
|
-
medalsRender();
|
|
5232
|
+
pluginRenderList.forEach(f=>f());
|
|
5257
5233
|
touchGamepadRender();
|
|
5258
5234
|
debugRender();
|
|
5259
5235
|
glCopyToContext(mainContext);
|
|
@@ -5335,6 +5311,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5335
5311
|
|
|
5336
5312
|
// init stuff and start engine
|
|
5337
5313
|
inputInit();
|
|
5314
|
+
audioInit();
|
|
5338
5315
|
debugInit();
|
|
5339
5316
|
glInit();
|
|
5340
5317
|
|
|
@@ -5542,21 +5519,23 @@ function drawEngineSplashScreen(t)
|
|
|
5542
5519
|
x.setLineDash([99*p2,99]);
|
|
5543
5520
|
|
|
5544
5521
|
// cab top
|
|
5545
|
-
rect(7,
|
|
5546
|
-
rect(7,
|
|
5547
|
-
rect(25,
|
|
5548
|
-
rect(25,
|
|
5549
|
-
rect(25,
|
|
5522
|
+
rect(7,16,18,-8,color(2,2));
|
|
5523
|
+
rect(7,8,18,4,color(2,3));
|
|
5524
|
+
rect(25,8,8,8,color(2,1));
|
|
5525
|
+
rect(25,8,-18,8);
|
|
5526
|
+
rect(25,8,8,8);
|
|
5550
5527
|
|
|
5551
5528
|
// cab
|
|
5552
|
-
rect(25,
|
|
5553
|
-
rect(11,
|
|
5554
|
-
rect(11,
|
|
5555
|
-
rect(11,
|
|
5556
|
-
rect(
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
rect(
|
|
5529
|
+
rect(25,16,7,23,color());
|
|
5530
|
+
rect(11,39,14,-23,color(1,1));
|
|
5531
|
+
rect(11,16,14,18,color(1,2));
|
|
5532
|
+
rect(11,16,14,8,color(1,3));
|
|
5533
|
+
rect(25,16,-14,24);
|
|
5534
|
+
|
|
5535
|
+
// cab window
|
|
5536
|
+
rect(15,29,6,-9,color(2,2));
|
|
5537
|
+
circle(15,21,5,0,PI/2,color(2,4),1);
|
|
5538
|
+
rect(21,21,-6,9);
|
|
5560
5539
|
|
|
5561
5540
|
// little stack
|
|
5562
5541
|
rect(37,14,9,6,color(3,2));
|
|
@@ -5564,16 +5543,16 @@ function drawEngineSplashScreen(t)
|
|
|
5564
5543
|
rect(37,14,9,6);
|
|
5565
5544
|
|
|
5566
5545
|
// big stack
|
|
5567
|
-
rect(50,20,10,-8,color(0,1))
|
|
5568
|
-
rect(50,20,6.5,-8,color(0,2))
|
|
5569
|
-
rect(50,20,3.5,-8,color(0,3))
|
|
5570
|
-
rect(50,20,10,-8)
|
|
5571
|
-
circle(55,2,11.4,.5,PI-.5,color(3,3))
|
|
5572
|
-
circle(55,2,11.4,.5,PI/2,color(3,2),1)
|
|
5573
|
-
circle(55,2,11.4,.5,PI-.5)
|
|
5574
|
-
rect(45,7,20,-7,color(0,2))
|
|
5575
|
-
rect(45,-1,20,4,color(0,3))
|
|
5576
|
-
rect(45,-1,20,8)
|
|
5546
|
+
rect(50,20,10,-8,color(0,1));
|
|
5547
|
+
rect(50,20,6.5,-8,color(0,2));
|
|
5548
|
+
rect(50,20,3.5,-8,color(0,3));
|
|
5549
|
+
rect(50,20,10,-8);
|
|
5550
|
+
circle(55,2,11.4,.5,PI-.5,color(3,3));
|
|
5551
|
+
circle(55,2,11.4,.5,PI/2,color(3,2),1);
|
|
5552
|
+
circle(55,2,11.4,.5,PI-.5);
|
|
5553
|
+
rect(45,7,20,-7,color(0,2));
|
|
5554
|
+
rect(45,-1,20,4,color(0,3));
|
|
5555
|
+
rect(45,-1,20,8);
|
|
5577
5556
|
|
|
5578
5557
|
// engine
|
|
5579
5558
|
for (let i=5; i--;)
|