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.
- package/LICENSE +22 -0
- package/README.md +13 -8
- package/dist/littlejs.d.ts +75 -69
- package/dist/littlejs.esm.js +313 -354
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +307 -350
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +244 -319
- package/examples/box2d/game.js +137 -0
- package/examples/box2d/index.html +12 -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 +4 -3
- package/examples/breakoutTutorial/index.html +2 -2
- package/examples/electron/build.js +1 -1
- package/examples/electron/index.html +2 -2
- package/examples/js13k/build.js +19 -1
- package/examples/js13k/index.html +13 -13
- package/examples/module/index.html +1 -1
- package/examples/particles/index.html +1 -1
- package/examples/platformer/gameEffects.js +2 -2
- package/examples/platformer/gameLevel.js +0 -1
- package/examples/platformer/index.html +8 -8
- package/examples/puzzle/index.html +2 -2
- package/examples/starter/build.js +1 -1
- package/examples/starter/index.html +13 -13
- package/examples/stress/index.html +1 -1
- package/examples/typescript/index.html +1 -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 +879 -0
- package/plugins/newgrounds.js +169 -0
- package/plugins/postProcess.js +102 -0
- package/src/engine.js +48 -29
- package/src/engineAudio.js +20 -12
- package/src/engineBuild.js +1 -1
- package/src/engineDebug.js +68 -33
- package/src/engineDraw.js +1 -1
- package/src/engineExport.js +6 -4
- package/src/engineInput.js +7 -4
- package/src/engineMedals.js +40 -171
- package/src/engineObject.js +33 -2
- package/src/engineRelease.js +5 -2
- package/src/engineSettings.js +14 -2
- package/src/engineUtilities.js +75 -2
- 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); }
|
|
@@ -1169,6 +1219,64 @@ class Color
|
|
|
1169
1219
|
}
|
|
1170
1220
|
}
|
|
1171
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
|
+
|
|
1172
1280
|
///////////////////////////////////////////////////////////////////////////////
|
|
1173
1281
|
|
|
1174
1282
|
/**
|
|
@@ -1249,9 +1357,9 @@ let cameraScale = 32;
|
|
|
1249
1357
|
|
|
1250
1358
|
/** The max size of the canvas, centered if window is larger
|
|
1251
1359
|
* @type {Vector2}
|
|
1252
|
-
* @default Vector2(1920,
|
|
1360
|
+
* @default Vector2(1920,1080)
|
|
1253
1361
|
* @memberof Settings */
|
|
1254
|
-
let canvasMaxSize = vec2(1920,
|
|
1362
|
+
let canvasMaxSize = vec2(1920, 1080);
|
|
1255
1363
|
|
|
1256
1364
|
/** Fixed size of the canvas, if enabled canvas size never changes
|
|
1257
1365
|
* - you may also need to set mainCanvasSize if using screen space coords in startup
|
|
@@ -1392,6 +1500,13 @@ let gamepadDirectionEmulateStick = true;
|
|
|
1392
1500
|
* @memberof Settings */
|
|
1393
1501
|
let inputWASDEmulateDirection = true;
|
|
1394
1502
|
|
|
1503
|
+
/** True if touch input is enabled for mobile devices
|
|
1504
|
+
* - Touch events will be routed to mouse events
|
|
1505
|
+
* @type {Boolean}
|
|
1506
|
+
* @default
|
|
1507
|
+
* @memberof Settings */
|
|
1508
|
+
let touchInputEnable = true;
|
|
1509
|
+
|
|
1395
1510
|
/** True if touch gamepad should appear on mobile devices
|
|
1396
1511
|
* - Supports left analog stick, 4 face buttons and start button (button 9)
|
|
1397
1512
|
* - Must be set by end of gameInit to be activated
|
|
@@ -1607,6 +1722,11 @@ function setGamepadDirectionEmulateStick(enable) { gamepadDirectionEmulateStick
|
|
|
1607
1722
|
* @memberof Settings */
|
|
1608
1723
|
function setInputWASDEmulateDirection(enable) { inputWASDEmulateDirection = enable; }
|
|
1609
1724
|
|
|
1725
|
+
/** Set if touch input is allowed
|
|
1726
|
+
* @param {Boolean} enable
|
|
1727
|
+
* @memberof Settings */
|
|
1728
|
+
function setTouchInputEnable(enable) { touchInputEnable = enable; }
|
|
1729
|
+
|
|
1610
1730
|
/** Set if touch gamepad should appear on mobile devices
|
|
1611
1731
|
* @param {Boolean} enable
|
|
1612
1732
|
* @memberof Settings */
|
|
@@ -1898,7 +2018,7 @@ class EngineObject
|
|
|
1898
2018
|
if (o.mass) // push away if not fixed
|
|
1899
2019
|
o.velocity = o.velocity.subtract(velocity);
|
|
1900
2020
|
|
|
1901
|
-
debugOverlay && debugPhysics &&
|
|
2021
|
+
debugOverlay && debugPhysics && debugOverlap(this.pos, this.size, o.pos, o.size, '#f00');
|
|
1902
2022
|
continue;
|
|
1903
2023
|
}
|
|
1904
2024
|
|
|
@@ -1960,7 +2080,7 @@ class EngineObject
|
|
|
1960
2080
|
else // bounce if other object is fixed
|
|
1961
2081
|
this.velocity.x *= -elasticity;
|
|
1962
2082
|
}
|
|
1963
|
-
debugOverlay && debugPhysics &&
|
|
2083
|
+
debugOverlay && debugPhysics && debugOverlap(this.pos, this.size, o.pos, o.size, '#f0f');
|
|
1964
2084
|
}
|
|
1965
2085
|
}
|
|
1966
2086
|
if (this.collideTiles)
|
|
@@ -2021,6 +2141,22 @@ class EngineObject
|
|
|
2021
2141
|
for (const child of this.children)
|
|
2022
2142
|
child.destroy(child.parent = 0);
|
|
2023
2143
|
}
|
|
2144
|
+
|
|
2145
|
+
/** Convert from local space to world space
|
|
2146
|
+
* @param {Vector2} pos - local space point */
|
|
2147
|
+
localToWorld(pos) { return this.pos.add(pos.rotate(-this.angle)); }
|
|
2148
|
+
|
|
2149
|
+
/** Convert from world space to local space
|
|
2150
|
+
* @param {Vector2} pos - world space point */
|
|
2151
|
+
worldToLocal(pos) { return pos.subtract(this.pos).rotate(this.angle); }
|
|
2152
|
+
|
|
2153
|
+
/** Convert from local space to world space for a vector (rotation only)
|
|
2154
|
+
* @param {Vector2} vec - local space vector */
|
|
2155
|
+
localToWorldVector(vec) { return vec.rotate(this.angle); }
|
|
2156
|
+
|
|
2157
|
+
/** Convert from world space to local space for a vector (rotation only)
|
|
2158
|
+
* @param {Vector2} vec - world space vector */
|
|
2159
|
+
worldToLocalVector(vec) { return vec.rotate(-this.angle); }
|
|
2024
2160
|
|
|
2025
2161
|
/** Called to check if a tile collision should be resolved
|
|
2026
2162
|
* @param {Number} tileData - the value of the tile at the position
|
|
@@ -2107,6 +2243,21 @@ class EngineObject
|
|
|
2107
2243
|
return text;
|
|
2108
2244
|
}
|
|
2109
2245
|
}
|
|
2246
|
+
|
|
2247
|
+
/** Render debug info for this object */
|
|
2248
|
+
renderDebugInfo()
|
|
2249
|
+
{
|
|
2250
|
+
if (debug)
|
|
2251
|
+
{
|
|
2252
|
+
// show object info for debugging
|
|
2253
|
+
const size = vec2(max(this.size.x, .2), max(this.size.y, .2));
|
|
2254
|
+
const color1 = rgb(this.collideTiles?1:0, this.collideSolidObjects?1:0, this.isSolid?1:0, this.parent?.2:.5);
|
|
2255
|
+
const color2 = this.parent ? rgb(1,1,1,.5) : rgb(0,0,0,.8);
|
|
2256
|
+
drawRect(this.pos, size, color1, this.angle, false);
|
|
2257
|
+
drawRect(this.pos, size.scale(.8), color2, this.angle, false);
|
|
2258
|
+
this.parent && drawLine(this.pos, this.parent.pos, .1, rgb(0,0,1,.5), false);
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2110
2261
|
}
|
|
2111
2262
|
/**
|
|
2112
2263
|
* LittleJS Drawing System
|
|
@@ -2430,7 +2581,7 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, conte
|
|
|
2430
2581
|
context.save();
|
|
2431
2582
|
context.translate(pos.x+.5, pos.y+.5);
|
|
2432
2583
|
context.rotate(angle);
|
|
2433
|
-
context.scale(mirror ? -size.x : size.x, size.y);
|
|
2584
|
+
context.scale(mirror ? -size.x : size.x, -size.y);
|
|
2434
2585
|
drawFunction(context);
|
|
2435
2586
|
context.restore();
|
|
2436
2587
|
}
|
|
@@ -2612,6 +2763,7 @@ function toggleFullscreen()
|
|
|
2612
2763
|
* - Tracks keyboard down, pressed, and released
|
|
2613
2764
|
* - Tracks mouse buttons, position, and wheel
|
|
2614
2765
|
* - Tracks multiple analog gamepads
|
|
2766
|
+
* - Touch input is handled as mouse input
|
|
2615
2767
|
* - Virtual gamepad for touch devices
|
|
2616
2768
|
* @namespace Input
|
|
2617
2769
|
*/
|
|
@@ -2745,7 +2897,8 @@ function inputUpdate()
|
|
|
2745
2897
|
if (headlessMode) return;
|
|
2746
2898
|
|
|
2747
2899
|
// clear input when lost focus (prevent stuck keys)
|
|
2748
|
-
isTouchDevice
|
|
2900
|
+
if(!(touchInputEnable && isTouchDevice) && !document.hasFocus())
|
|
2901
|
+
clearInput();
|
|
2749
2902
|
|
|
2750
2903
|
// update mouse world space position
|
|
2751
2904
|
mousePos = screenToWorld(mousePosScreen);
|
|
@@ -2817,7 +2970,7 @@ function inputInit()
|
|
|
2817
2970
|
oncontextmenu = (e)=> false; // prevent right click menu
|
|
2818
2971
|
|
|
2819
2972
|
// init touch input
|
|
2820
|
-
if (isTouchDevice)
|
|
2973
|
+
if (isTouchDevice && touchInputEnable && !headlessMode)
|
|
2821
2974
|
touchInputInit();
|
|
2822
2975
|
}
|
|
2823
2976
|
|
|
@@ -2945,12 +3098,12 @@ function vibrateStop() { vibrate(0); }
|
|
|
2945
3098
|
|
|
2946
3099
|
/** True if a touch device has been detected
|
|
2947
3100
|
* @memberof Input */
|
|
2948
|
-
const isTouchDevice =
|
|
3101
|
+
const isTouchDevice = window.ontouchstart !== undefined;
|
|
2949
3102
|
|
|
2950
3103
|
// touch gamepad internal variables
|
|
2951
3104
|
let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
|
|
2952
3105
|
|
|
2953
|
-
//
|
|
3106
|
+
// enable touch input mouse passthrough
|
|
2954
3107
|
function touchInputInit()
|
|
2955
3108
|
{
|
|
2956
3109
|
// add non passive touch event listeners
|
|
@@ -3059,6 +3212,7 @@ function touchInputInit()
|
|
|
3059
3212
|
// render the touch gamepad, called automatically by the engine
|
|
3060
3213
|
function touchGamepadRender()
|
|
3061
3214
|
{
|
|
3215
|
+
if (!touchInputEnable || !isTouchDevice || headlessMode) return;
|
|
3062
3216
|
if (!touchGamepadEnable || !touchGamepadTimer.isSet())
|
|
3063
3217
|
return;
|
|
3064
3218
|
|
|
@@ -3226,7 +3380,17 @@ class Sound
|
|
|
3226
3380
|
|
|
3227
3381
|
// play the sound
|
|
3228
3382
|
const playbackRate = pitch + pitch * this.randomness*randomnessScale*rand(-1,1);
|
|
3229
|
-
|
|
3383
|
+
this.gainNode = audioContext.createGain();
|
|
3384
|
+
return this.source = playSamples(this.sampleChannels, volume, playbackRate, pan, loop, this.sampleRate, this.gainNode);
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3387
|
+
/** Set the sound volume
|
|
3388
|
+
* @param {Number} [volume] - How much to scale volume by
|
|
3389
|
+
*/
|
|
3390
|
+
setVolume(volume=1)
|
|
3391
|
+
{
|
|
3392
|
+
if (this.gainNode)
|
|
3393
|
+
this.gainNode.gain.value = volume;
|
|
3230
3394
|
}
|
|
3231
3395
|
|
|
3232
3396
|
/** Stop the last instance of this sound that was played */
|
|
@@ -3414,15 +3578,16 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
|
|
|
3414
3578
|
let audioSuspended = false;
|
|
3415
3579
|
|
|
3416
3580
|
/** Play cached audio samples with given settings
|
|
3417
|
-
* @param {Array}
|
|
3418
|
-
* @param {Number}
|
|
3419
|
-
* @param {Number}
|
|
3420
|
-
* @param {Number}
|
|
3421
|
-
* @param {Boolean}
|
|
3422
|
-
* @param {Number}
|
|
3581
|
+
* @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback)
|
|
3582
|
+
* @param {Number} [volume] - How much to scale volume by
|
|
3583
|
+
* @param {Number} [rate] - The playback rate to use
|
|
3584
|
+
* @param {Number} [pan] - How much to apply stereo panning
|
|
3585
|
+
* @param {Boolean} [loop] - True if the sound should loop when it reaches the end
|
|
3586
|
+
* @param {Number} [sampleRate=44100] - Sample rate for the sound
|
|
3587
|
+
* @param {GainNode} [gainNode] - Optional gain node for volume control while playing
|
|
3423
3588
|
* @return {AudioBufferSourceNode} - The audio node of the sound played
|
|
3424
3589
|
* @memberof Audio */
|
|
3425
|
-
function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=zzfxR)
|
|
3590
|
+
function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=zzfxR, gainNode)
|
|
3426
3591
|
{
|
|
3427
3592
|
if (!soundEnable || headlessMode) return;
|
|
3428
3593
|
|
|
@@ -3448,11 +3613,8 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
3448
3613
|
source.playbackRate.value = rate;
|
|
3449
3614
|
source.loop = loop;
|
|
3450
3615
|
|
|
3451
|
-
// set master gain volume
|
|
3452
|
-
setSoundVolume(soundVolume);
|
|
3453
|
-
|
|
3454
3616
|
// create and connect gain node
|
|
3455
|
-
|
|
3617
|
+
gainNode = gainNode || audioContext.createGain();
|
|
3456
3618
|
gainNode.gain.value = volume;
|
|
3457
3619
|
gainNode.connect(audioGainNode);
|
|
3458
3620
|
|
|
@@ -4433,9 +4595,9 @@ class Particle extends EngineObject
|
|
|
4433
4595
|
|
|
4434
4596
|
|
|
4435
4597
|
/** List of all medals
|
|
4436
|
-
* @type {
|
|
4598
|
+
* @type {Object}
|
|
4437
4599
|
* @memberof Medals */
|
|
4438
|
-
const medals =
|
|
4600
|
+
const medals = {};
|
|
4439
4601
|
|
|
4440
4602
|
// Engine internal variables not exposed to documentation
|
|
4441
4603
|
let medalsDisplayQueue = [], medalsSaveName, medalsDisplayTimeLast;
|
|
@@ -4451,9 +4613,46 @@ function medalsInit(saveName)
|
|
|
4451
4613
|
{
|
|
4452
4614
|
// check if medals are unlocked
|
|
4453
4615
|
medalsSaveName = saveName;
|
|
4454
|
-
|
|
4616
|
+
if (!debugMedals)
|
|
4617
|
+
medalsForEach(medal=> medal.unlocked = (localStorage[medal.storageKey()] | 0));
|
|
4618
|
+
|
|
4619
|
+
// engine automatically renders medals
|
|
4620
|
+
engineAddPlugin(undefined, medalsRender);
|
|
4621
|
+
function medalsRender()
|
|
4622
|
+
{
|
|
4623
|
+
if (!medalsDisplayQueue.length)
|
|
4624
|
+
return;
|
|
4625
|
+
|
|
4626
|
+
// update first medal in queue
|
|
4627
|
+
const medal = medalsDisplayQueue[0];
|
|
4628
|
+
const time = timeReal - medalsDisplayTimeLast;
|
|
4629
|
+
if (!medalsDisplayTimeLast)
|
|
4630
|
+
medalsDisplayTimeLast = timeReal;
|
|
4631
|
+
else if (time > medalDisplayTime)
|
|
4632
|
+
{
|
|
4633
|
+
medalsDisplayTimeLast = 0;
|
|
4634
|
+
medalsDisplayQueue.shift();
|
|
4635
|
+
}
|
|
4636
|
+
else
|
|
4637
|
+
{
|
|
4638
|
+
// slide on/off medals
|
|
4639
|
+
const slideOffTime = medalDisplayTime - medalDisplaySlideTime;
|
|
4640
|
+
const hidePercent =
|
|
4641
|
+
time < medalDisplaySlideTime ? 1 - time / medalDisplaySlideTime :
|
|
4642
|
+
time > slideOffTime ? (time - slideOffTime) / medalDisplaySlideTime : 0;
|
|
4643
|
+
medal.render(hidePercent);
|
|
4644
|
+
}
|
|
4645
|
+
}
|
|
4455
4646
|
}
|
|
4456
4647
|
|
|
4648
|
+
/** Calls a function for each medal
|
|
4649
|
+
* @param {Function} callback
|
|
4650
|
+
* @memberof Medals */
|
|
4651
|
+
function medalsForEach(callback)
|
|
4652
|
+
{ Object.values(medals).forEach(medal=>callback(medal)); }
|
|
4653
|
+
|
|
4654
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
4655
|
+
|
|
4457
4656
|
/**
|
|
4458
4657
|
* Medal - Tracks an unlockable medal
|
|
4459
4658
|
* @example
|
|
@@ -4498,7 +4697,6 @@ class Medal
|
|
|
4498
4697
|
ASSERT(medalsSaveName, 'save name must be set');
|
|
4499
4698
|
localStorage[this.storageKey()] = this.unlocked = 1;
|
|
4500
4699
|
medalsDisplayQueue.push(this);
|
|
4501
|
-
newgrounds && newgrounds.unlockMedal(this.id);
|
|
4502
4700
|
}
|
|
4503
4701
|
|
|
4504
4702
|
/** Render a medal
|
|
@@ -4546,173 +4744,6 @@ class Medal
|
|
|
4546
4744
|
|
|
4547
4745
|
// Get local storage key used by the medal
|
|
4548
4746
|
storageKey() { return medalsSaveName + '_' + this.id; }
|
|
4549
|
-
}
|
|
4550
|
-
|
|
4551
|
-
// engine automatically renders medals
|
|
4552
|
-
function medalsRender()
|
|
4553
|
-
{
|
|
4554
|
-
if (!medalsDisplayQueue.length)
|
|
4555
|
-
return;
|
|
4556
|
-
|
|
4557
|
-
// update first medal in queue
|
|
4558
|
-
const medal = medalsDisplayQueue[0];
|
|
4559
|
-
const time = timeReal - medalsDisplayTimeLast;
|
|
4560
|
-
if (!medalsDisplayTimeLast)
|
|
4561
|
-
medalsDisplayTimeLast = timeReal;
|
|
4562
|
-
else if (time > medalDisplayTime)
|
|
4563
|
-
{
|
|
4564
|
-
medalsDisplayTimeLast = 0;
|
|
4565
|
-
medalsDisplayQueue.shift();
|
|
4566
|
-
}
|
|
4567
|
-
else
|
|
4568
|
-
{
|
|
4569
|
-
// slide on/off medals
|
|
4570
|
-
const slideOffTime = medalDisplayTime - medalDisplaySlideTime;
|
|
4571
|
-
const hidePercent =
|
|
4572
|
-
time < medalDisplaySlideTime ? 1 - time / medalDisplaySlideTime :
|
|
4573
|
-
time > slideOffTime ? (time - slideOffTime) / medalDisplaySlideTime : 0;
|
|
4574
|
-
medal.render(hidePercent);
|
|
4575
|
-
}
|
|
4576
|
-
}
|
|
4577
|
-
|
|
4578
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
4579
|
-
|
|
4580
|
-
// global Newgrounds object
|
|
4581
|
-
let newgrounds;
|
|
4582
|
-
|
|
4583
|
-
/** This can used to enable Newgrounds functionality
|
|
4584
|
-
* @param {Number} app_id - The newgrounds App ID
|
|
4585
|
-
* @param {String} [cipher] - The encryption Key (AES-128/Base64)
|
|
4586
|
-
* @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher
|
|
4587
|
-
* @memberof Medals */
|
|
4588
|
-
function newgroundsInit(app_id, cipher, cryptoJS)
|
|
4589
|
-
{ newgrounds = new Newgrounds(app_id, cipher, cryptoJS); }
|
|
4590
|
-
|
|
4591
|
-
/**
|
|
4592
|
-
* Newgrounds API wrapper object
|
|
4593
|
-
* @example
|
|
4594
|
-
* // create a newgrounds object, replace the app id with your own
|
|
4595
|
-
* const app_id = '53123:1ZuSTQ9l';
|
|
4596
|
-
* newgrounds = new Newgrounds(app_id);
|
|
4597
|
-
*/
|
|
4598
|
-
class Newgrounds
|
|
4599
|
-
{
|
|
4600
|
-
/** Create a newgrounds object
|
|
4601
|
-
* @param {Number} app_id - The newgrounds App ID
|
|
4602
|
-
* @param {String} [cipher] - The encryption Key (AES-128/Base64)
|
|
4603
|
-
* @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher */
|
|
4604
|
-
constructor(app_id, cipher, cryptoJS)
|
|
4605
|
-
{
|
|
4606
|
-
ASSERT(!newgrounds && app_id>0, 'there can only be one newgrounds object');
|
|
4607
|
-
ASSERT(!cipher || cryptoJS, 'must provide cryptojs if there is a cipher');
|
|
4608
|
-
|
|
4609
|
-
this.app_id = app_id;
|
|
4610
|
-
this.cipher = cipher;
|
|
4611
|
-
this.cryptoJS = cryptoJS;
|
|
4612
|
-
this.host = location ? location.hostname : '';
|
|
4613
|
-
|
|
4614
|
-
// get session id from url search params
|
|
4615
|
-
const url = new URL(location.href);
|
|
4616
|
-
this.session_id = url.searchParams.get('ngio_session_id');
|
|
4617
|
-
|
|
4618
|
-
if (!this.session_id)
|
|
4619
|
-
return; // only use newgrounds when logged in
|
|
4620
|
-
|
|
4621
|
-
// get medals
|
|
4622
|
-
const medalsResult = this.call('Medal.getList');
|
|
4623
|
-
this.medals = medalsResult ? medalsResult.result.data['medals'] : [];
|
|
4624
|
-
debugMedals && console.log(this.medals);
|
|
4625
|
-
for (const newgroundsMedal of this.medals)
|
|
4626
|
-
{
|
|
4627
|
-
const medal = medals[newgroundsMedal['id']];
|
|
4628
|
-
if (medal)
|
|
4629
|
-
{
|
|
4630
|
-
// copy newgrounds medal data
|
|
4631
|
-
medal.image = new Image;
|
|
4632
|
-
medal.image.src = newgroundsMedal['icon'];
|
|
4633
|
-
medal.name = newgroundsMedal['name'];
|
|
4634
|
-
medal.description = newgroundsMedal['description'];
|
|
4635
|
-
medal.unlocked = newgroundsMedal['unlocked'];
|
|
4636
|
-
medal.difficulty = newgroundsMedal['difficulty'];
|
|
4637
|
-
medal.value = newgroundsMedal['value'];
|
|
4638
|
-
|
|
4639
|
-
if (medal.value)
|
|
4640
|
-
medal.description = medal.description + ' (' + medal.value + ')';
|
|
4641
|
-
}
|
|
4642
|
-
}
|
|
4643
|
-
|
|
4644
|
-
// get scoreboards
|
|
4645
|
-
const scoreboardResult = this.call('ScoreBoard.getBoards');
|
|
4646
|
-
this.scoreboards = scoreboardResult ? scoreboardResult.result.data.scoreboards : [];
|
|
4647
|
-
debugMedals && console.log(this.scoreboards);
|
|
4648
|
-
|
|
4649
|
-
const keepAliveMS = 5 * 60 * 1e3;
|
|
4650
|
-
setInterval(()=>this.call('Gateway.ping', 0, true), keepAliveMS);
|
|
4651
|
-
}
|
|
4652
|
-
|
|
4653
|
-
/** Send message to unlock a medal by id
|
|
4654
|
-
* @param {Number} id - The medal id */
|
|
4655
|
-
unlockMedal(id) { return this.call('Medal.unlock', {'id':id}, true); }
|
|
4656
|
-
|
|
4657
|
-
/** Send message to post score
|
|
4658
|
-
* @param {Number} id - The scoreboard id
|
|
4659
|
-
* @param {Number} value - The score value */
|
|
4660
|
-
postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, true); }
|
|
4661
|
-
|
|
4662
|
-
/** Get scores from a scoreboard
|
|
4663
|
-
* @param {Number} id - The scoreboard id
|
|
4664
|
-
* @param {String} [user] - A user's id or name
|
|
4665
|
-
* @param {Number} [social] - If true, only social scores will be loaded
|
|
4666
|
-
* @param {Number} [skip] - Number of scores to skip before start
|
|
4667
|
-
* @param {Number} [limit] - Number of scores to include in the list
|
|
4668
|
-
* @return {Object} - The response JSON object
|
|
4669
|
-
*/
|
|
4670
|
-
getScores(id, user, social=0, skip=0, limit=10)
|
|
4671
|
-
{ return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); }
|
|
4672
|
-
|
|
4673
|
-
/** Send message to log a view */
|
|
4674
|
-
logView() { return this.call('App.logView', {'host':this.host}, true); }
|
|
4675
|
-
|
|
4676
|
-
/** Send a message to call a component of the Newgrounds API
|
|
4677
|
-
* @param {String} component - Name of the component
|
|
4678
|
-
* @param {Object} [parameters] - Parameters to use for call
|
|
4679
|
-
* @param {Boolean} [async] - If true, don't wait for response before continuing
|
|
4680
|
-
* @return {Object} - The response JSON object
|
|
4681
|
-
*/
|
|
4682
|
-
call(component, parameters, async=false)
|
|
4683
|
-
{
|
|
4684
|
-
const call = {'component':component, 'parameters':parameters};
|
|
4685
|
-
if (this.cipher)
|
|
4686
|
-
{
|
|
4687
|
-
// encrypt using AES-128 Base64 with cryptoJS
|
|
4688
|
-
const cryptoJS = this.cryptoJS;
|
|
4689
|
-
const aesKey = cryptoJS['enc']['Base64']['parse'](this.cipher);
|
|
4690
|
-
const iv = cryptoJS['lib']['WordArray']['random'](16);
|
|
4691
|
-
const encrypted = cryptoJS['AES']['encrypt'](JSON.stringify(call), aesKey, {'iv':iv});
|
|
4692
|
-
call['secure'] = cryptoJS['enc']['Base64']['stringify'](iv.concat(encrypted['ciphertext']));
|
|
4693
|
-
call['parameters'] = 0;
|
|
4694
|
-
}
|
|
4695
|
-
|
|
4696
|
-
// build the input object
|
|
4697
|
-
const input =
|
|
4698
|
-
{
|
|
4699
|
-
'app_id': this.app_id,
|
|
4700
|
-
'session_id': this.session_id,
|
|
4701
|
-
'call': call
|
|
4702
|
-
};
|
|
4703
|
-
|
|
4704
|
-
// build post data
|
|
4705
|
-
const formData = new FormData();
|
|
4706
|
-
formData.append('input', JSON.stringify(input));
|
|
4707
|
-
|
|
4708
|
-
// send post data
|
|
4709
|
-
const xmlHttp = new XMLHttpRequest();
|
|
4710
|
-
const url = 'https://newgrounds.io/gateway_v3.php';
|
|
4711
|
-
xmlHttp.open('POST', url, !debugMedals && async);
|
|
4712
|
-
xmlHttp.send(formData);
|
|
4713
|
-
debugMedals && console.log(xmlHttp.responseText);
|
|
4714
|
-
return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
|
|
4715
|
-
}
|
|
4716
4747
|
}
|
|
4717
4748
|
/**
|
|
4718
4749
|
* LittleJS WebGL Interface
|
|
@@ -4803,7 +4834,7 @@ function glPreRender()
|
|
|
4803
4834
|
|
|
4804
4835
|
// clear and set to same size as main canvas
|
|
4805
4836
|
glContext.viewport(0, 0, glCanvas.width=mainCanvas.width, glCanvas.height=mainCanvas.height);
|
|
4806
|
-
|
|
4837
|
+
glContext.clear(gl_COLOR_BUFFER_BIT);
|
|
4807
4838
|
|
|
4808
4839
|
// set up the shader
|
|
4809
4840
|
glContext.useProgram(glShader);
|
|
@@ -4986,99 +5017,6 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdd
|
|
|
4986
5017
|
glInstanceCount++;
|
|
4987
5018
|
}
|
|
4988
5019
|
|
|
4989
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
4990
|
-
// post processing - can be enabled to pass other canvases through a final shader
|
|
4991
|
-
|
|
4992
|
-
let glPostShader, glPostTexture, glPostIncludeOverlay;
|
|
4993
|
-
|
|
4994
|
-
/** Set up a post processing shader
|
|
4995
|
-
* @param {String} shaderCode
|
|
4996
|
-
* @param {Boolean} includeOverlay
|
|
4997
|
-
* @memberof WebGL */
|
|
4998
|
-
function glInitPostProcess(shaderCode, includeOverlay=false)
|
|
4999
|
-
{
|
|
5000
|
-
ASSERT(!glPostShader, 'can only have 1 post effects shader');
|
|
5001
|
-
if (headlessMode) return;
|
|
5002
|
-
if (!shaderCode) // default shader pass through
|
|
5003
|
-
shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
|
|
5004
|
-
|
|
5005
|
-
// create the shader
|
|
5006
|
-
glPostShader = glCreateProgram(
|
|
5007
|
-
'#version 300 es\n' + // specify GLSL ES version
|
|
5008
|
-
'precision highp float;'+ // use highp for better accuracy
|
|
5009
|
-
'in vec2 p;'+ // position
|
|
5010
|
-
'void main(){'+ // shader entry point
|
|
5011
|
-
'gl_Position=vec4(p+p-1.,1,1);'+ // set position
|
|
5012
|
-
'}' // end of shader
|
|
5013
|
-
,
|
|
5014
|
-
'#version 300 es\n' + // specify GLSL ES version
|
|
5015
|
-
'precision highp float;'+ // use highp for better accuracy
|
|
5016
|
-
'uniform sampler2D iChannel0;'+ // input texture
|
|
5017
|
-
'uniform vec3 iResolution;'+ // size of output texture
|
|
5018
|
-
'uniform float iTime;'+ // time
|
|
5019
|
-
'out vec4 c;'+ // out color
|
|
5020
|
-
'\n' + shaderCode + '\n'+ // insert custom shader code
|
|
5021
|
-
'void main(){'+ // shader entry point
|
|
5022
|
-
'mainImage(c,gl_FragCoord.xy);'+ // call post process function
|
|
5023
|
-
'c.a=1.;'+ // always use full alpha
|
|
5024
|
-
'}' // end of shader
|
|
5025
|
-
);
|
|
5026
|
-
|
|
5027
|
-
// create buffer and texture
|
|
5028
|
-
glPostTexture = glCreateTexture(undefined);
|
|
5029
|
-
glPostIncludeOverlay = includeOverlay;
|
|
5030
|
-
|
|
5031
|
-
// hide the original 2d canvas
|
|
5032
|
-
mainCanvas.style.visibility = 'hidden';
|
|
5033
|
-
if (glPostIncludeOverlay)
|
|
5034
|
-
overlayCanvas.style.visibility = 'hidden';
|
|
5035
|
-
}
|
|
5036
|
-
|
|
5037
|
-
// Render the post processing shader, called automatically by the engine
|
|
5038
|
-
function glRenderPostProcess()
|
|
5039
|
-
{
|
|
5040
|
-
if (!glPostShader || headlessMode) return;
|
|
5041
|
-
|
|
5042
|
-
// prepare to render post process shader
|
|
5043
|
-
if (glEnable)
|
|
5044
|
-
{
|
|
5045
|
-
glFlush(); // clear out the buffer
|
|
5046
|
-
mainContext.drawImage(glCanvas, 0, 0); // copy to the main canvas
|
|
5047
|
-
}
|
|
5048
|
-
else
|
|
5049
|
-
{
|
|
5050
|
-
// set the viewport
|
|
5051
|
-
glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
|
|
5052
|
-
}
|
|
5053
|
-
|
|
5054
|
-
// copy overlay canvas so it will be included in post processing
|
|
5055
|
-
glPostIncludeOverlay && mainContext.drawImage(overlayCanvas, 0, 0);
|
|
5056
|
-
|
|
5057
|
-
// setup shader program to draw one triangle
|
|
5058
|
-
glContext.useProgram(glPostShader);
|
|
5059
|
-
glContext.bindBuffer(gl_ARRAY_BUFFER, glGeometryBuffer);
|
|
5060
|
-
glContext.pixelStorei(gl_UNPACK_FLIP_Y_WEBGL, 1);
|
|
5061
|
-
glContext.disable(gl_BLEND);
|
|
5062
|
-
|
|
5063
|
-
// set textures, pass in the 2d canvas and gl canvas in separate texture channels
|
|
5064
|
-
glContext.activeTexture(gl_TEXTURE0);
|
|
5065
|
-
glContext.bindTexture(gl_TEXTURE_2D, glPostTexture);
|
|
5066
|
-
glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, mainCanvas);
|
|
5067
|
-
|
|
5068
|
-
// set vertex position attribute
|
|
5069
|
-
const vertexByteStride = 8;
|
|
5070
|
-
const pLocation = glContext.getAttribLocation(glPostShader, 'p');
|
|
5071
|
-
glContext.enableVertexAttribArray(pLocation);
|
|
5072
|
-
glContext.vertexAttribPointer(pLocation, 2, gl_FLOAT, false, vertexByteStride, 0);
|
|
5073
|
-
|
|
5074
|
-
// set uniforms and draw
|
|
5075
|
-
const uniformLocation = (name)=>glContext.getUniformLocation(glPostShader, name);
|
|
5076
|
-
glContext.uniform1i(uniformLocation('iChannel0'), 0);
|
|
5077
|
-
glContext.uniform1f(uniformLocation('iTime'), time);
|
|
5078
|
-
glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
|
|
5079
|
-
glContext.drawArrays(gl_TRIANGLE_STRIP, 0, 4);
|
|
5080
|
-
}
|
|
5081
|
-
|
|
5082
5020
|
///////////////////////////////////////////////////////////////////////////////
|
|
5083
5021
|
// store gl constants as integers so their name doesn't use space in minifed
|
|
5084
5022
|
const
|
|
@@ -5143,7 +5081,7 @@ const engineName = 'LittleJS';
|
|
|
5143
5081
|
* @type {String}
|
|
5144
5082
|
* @default
|
|
5145
5083
|
* @memberof Engine */
|
|
5146
|
-
const engineVersion = '1.9.
|
|
5084
|
+
const engineVersion = '1.9.9';
|
|
5147
5085
|
|
|
5148
5086
|
/** Frames per second to update
|
|
5149
5087
|
* @type {Number}
|
|
@@ -5197,6 +5135,22 @@ function setPaused(isPaused) { paused = isPaused; }
|
|
|
5197
5135
|
let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
|
|
5198
5136
|
|
|
5199
5137
|
///////////////////////////////////////////////////////////////////////////////
|
|
5138
|
+
// plugin hooks
|
|
5139
|
+
|
|
5140
|
+
const pluginUpdateList = [], pluginRenderList = [];
|
|
5141
|
+
|
|
5142
|
+
/** Add a new update function for a plugin
|
|
5143
|
+
* @param {Function} [updateFunction]
|
|
5144
|
+
* @param {Function} [renderFunction]
|
|
5145
|
+
* @memberof Engine */
|
|
5146
|
+
function engineAddPlugin(updateFunction, renderFunction)
|
|
5147
|
+
{
|
|
5148
|
+
updateFunction && pluginUpdateList.push(updateFunction);
|
|
5149
|
+
renderFunction && pluginRenderList.push(renderFunction);
|
|
5150
|
+
}
|
|
5151
|
+
|
|
5152
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5153
|
+
// Main engine functions
|
|
5200
5154
|
|
|
5201
5155
|
/** Startup LittleJS engine with your callback functions
|
|
5202
5156
|
* @param {Function} gameInit - Called once after the engine starts up, setup the game
|
|
@@ -5272,6 +5226,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5272
5226
|
// update game and objects
|
|
5273
5227
|
inputUpdate();
|
|
5274
5228
|
gameUpdate();
|
|
5229
|
+
pluginUpdateList.forEach(f=>f());
|
|
5275
5230
|
engineObjectsUpdate();
|
|
5276
5231
|
|
|
5277
5232
|
// do post update
|
|
@@ -5293,8 +5248,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5293
5248
|
for (const o of engineObjects)
|
|
5294
5249
|
o.destroyed || o.render();
|
|
5295
5250
|
gameRenderPost();
|
|
5296
|
-
|
|
5297
|
-
medalsRender();
|
|
5251
|
+
pluginRenderList.forEach(f=>f());
|
|
5298
5252
|
touchGamepadRender();
|
|
5299
5253
|
debugRender();
|
|
5300
5254
|
glCopyToContext(mainContext);
|
|
@@ -5366,10 +5320,11 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5366
5320
|
const styleBody =
|
|
5367
5321
|
'margin:0;overflow:hidden;' + // fill the window
|
|
5368
5322
|
'background:#000;' + // set background color
|
|
5369
|
-
'
|
|
5370
|
-
'user-select:none;' + // prevent mobile hold to select
|
|
5323
|
+
'user-select:none;' + // prevent hold to select
|
|
5371
5324
|
'-webkit-user-select:none;' + // compatibility for ios
|
|
5372
|
-
'
|
|
5325
|
+
(!touchInputEnable ? '' : // no touch css setttings
|
|
5326
|
+
'touch-action:none;' + // prevent mobile pinch to resize
|
|
5327
|
+
'-webkit-touch-callout:none');// compatibility for ios
|
|
5373
5328
|
document.body.style.cssText = styleBody;
|
|
5374
5329
|
document.body.appendChild(mainCanvas = document.createElement('canvas'));
|
|
5375
5330
|
mainContext = mainCanvas.getContext('2d');
|
|
@@ -5584,21 +5539,23 @@ function drawEngineSplashScreen(t)
|
|
|
5584
5539
|
x.setLineDash([99*p2,99]);
|
|
5585
5540
|
|
|
5586
5541
|
// cab top
|
|
5587
|
-
rect(7,
|
|
5588
|
-
rect(7,
|
|
5589
|
-
rect(25,
|
|
5590
|
-
rect(25,
|
|
5591
|
-
rect(25,
|
|
5542
|
+
rect(7,16,18,-8,color(2,2));
|
|
5543
|
+
rect(7,8,18,4,color(2,3));
|
|
5544
|
+
rect(25,8,8,8,color(2,1));
|
|
5545
|
+
rect(25,8,-18,8);
|
|
5546
|
+
rect(25,8,8,8);
|
|
5592
5547
|
|
|
5593
5548
|
// cab
|
|
5594
|
-
rect(25,
|
|
5595
|
-
rect(11,
|
|
5596
|
-
rect(11,
|
|
5597
|
-
rect(11,
|
|
5598
|
-
rect(
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
rect(
|
|
5549
|
+
rect(25,16,7,23,color());
|
|
5550
|
+
rect(11,39,14,-23,color(1,1));
|
|
5551
|
+
rect(11,16,14,18,color(1,2));
|
|
5552
|
+
rect(11,16,14,8,color(1,3));
|
|
5553
|
+
rect(25,16,-14,24);
|
|
5554
|
+
|
|
5555
|
+
// cab window
|
|
5556
|
+
rect(15,29,6,-9,color(2,2));
|
|
5557
|
+
circle(15,21,5,0,PI/2,color(2,4),1);
|
|
5558
|
+
rect(21,21,-6,9);
|
|
5602
5559
|
|
|
5603
5560
|
// little stack
|
|
5604
5561
|
rect(37,14,9,6,color(3,2));
|
|
@@ -5606,16 +5563,16 @@ function drawEngineSplashScreen(t)
|
|
|
5606
5563
|
rect(37,14,9,6);
|
|
5607
5564
|
|
|
5608
5565
|
// big stack
|
|
5609
|
-
rect(50,20,10,-8,color(0,1))
|
|
5610
|
-
rect(50,20,6.5,-8,color(0,2))
|
|
5611
|
-
rect(50,20,3.5,-8,color(0,3))
|
|
5612
|
-
rect(50,20,10,-8)
|
|
5613
|
-
circle(55,2,11.4,.5,PI-.5,color(3,3))
|
|
5614
|
-
circle(55,2,11.4,.5,PI/2,color(3,2),1)
|
|
5615
|
-
circle(55,2,11.4,.5,PI-.5)
|
|
5616
|
-
rect(45,7,20,-7,color(0,2))
|
|
5617
|
-
rect(45,-1,20,4,color(0,3))
|
|
5618
|
-
rect(45,-1,20,8)
|
|
5566
|
+
rect(50,20,10,-8,color(0,1));
|
|
5567
|
+
rect(50,20,6.5,-8,color(0,2));
|
|
5568
|
+
rect(50,20,3.5,-8,color(0,3));
|
|
5569
|
+
rect(50,20,10,-8);
|
|
5570
|
+
circle(55,2,11.4,.5,PI-.5,color(3,3));
|
|
5571
|
+
circle(55,2,11.4,.5,PI/2,color(3,2),1);
|
|
5572
|
+
circle(55,2,11.4,.5,PI-.5);
|
|
5573
|
+
rect(45,7,20,-7,color(0,2));
|
|
5574
|
+
rect(45,-1,20,4,color(0,3));
|
|
5575
|
+
rect(45,-1,20,8);
|
|
5619
5576
|
|
|
5620
5577
|
// engine
|
|
5621
5578
|
for (let i=5; i--;)
|