littlejsengine 1.9.9 → 1.9.11
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/README.md +2 -0
- package/dist/littlejs.d.ts +27 -5
- package/dist/littlejs.esm.js +62 -36
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +56 -34
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +39 -32
- package/examples/box2d/game.js +28 -9
- package/examples/box2d/gameObjects.js +485 -0
- package/examples/box2d/index.html +5 -4
- package/examples/box2d/scenes.js +28 -247
- package/examples/box2d/tiles.png +0 -0
- package/examples/breakout/index.html +3 -3
- package/examples/breakoutTutorial/index.html +2 -2
- package/examples/electron/index.html +2 -2
- package/examples/js13k/index.html +13 -13
- package/examples/module/index.html +1 -1
- package/examples/particles/index.html +1 -1
- package/examples/platformer/gameCharacter.js +1 -0
- package/examples/platformer/gameEffects.js +0 -2
- package/examples/platformer/gameObjects.js +1 -1
- package/examples/platformer/index.html +8 -8
- package/examples/puzzle/index.html +2 -2
- package/examples/starter/game.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.js +114 -41
- package/src/engine.js +1 -1
- package/src/engineAudio.js +16 -16
- package/src/engineDebug.js +17 -2
- package/src/engineExport.js +5 -2
- package/src/engineInput.js +7 -3
- package/src/engineObject.js +15 -12
package/plugins/box2d.js
CHANGED
|
@@ -43,8 +43,9 @@ class Box2dObject extends EngineObject
|
|
|
43
43
|
|
|
44
44
|
destroy()
|
|
45
45
|
{
|
|
46
|
-
// destroy physics body and
|
|
47
|
-
box2dWorld.DestroyBody(this.body);
|
|
46
|
+
// destroy physics body, fixtures, and joints
|
|
47
|
+
this.body && box2dWorld.DestroyBody(this.body);
|
|
48
|
+
this.body = 0;
|
|
48
49
|
super.destroy();
|
|
49
50
|
}
|
|
50
51
|
|
|
@@ -91,20 +92,20 @@ class Box2dObject extends EngineObject
|
|
|
91
92
|
addShape(shape, density, friction, restitution, isSensor)
|
|
92
93
|
{
|
|
93
94
|
const fd = box2dCreateFixtureDef(shape, density, friction, restitution, isSensor);
|
|
94
|
-
this.addFixture(fd);
|
|
95
|
+
return this.addFixture(fd);
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
addBox(size=vec2(1), offset=vec2(), angle=0, density, friction, restitution, isSensor)
|
|
98
99
|
{
|
|
99
100
|
const shape = new box2d.b2PolygonShape();
|
|
100
101
|
shape.SetAsBox(size.x/2, size.y/2, offset.getBox2d(), angle);
|
|
101
|
-
this.addShape(shape, density, friction, restitution, isSensor);
|
|
102
|
+
return this.addShape(shape, density, friction, restitution, isSensor);
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
addPoly(points, density, friction, restitution, isSensor)
|
|
105
106
|
{
|
|
106
107
|
const shape = box2dCreatePolygonShape(points);
|
|
107
|
-
this.addShape(shape, density, friction, restitution, isSensor);
|
|
108
|
+
return this.addShape(shape, density, friction, restitution, isSensor);
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
addRegularPoly(diameter=1, sides=8, density, friction, restitution, isSensor)
|
|
@@ -113,7 +114,7 @@ class Box2dObject extends EngineObject
|
|
|
113
114
|
const radius = diameter/2;
|
|
114
115
|
for (let i=sides; i--;)
|
|
115
116
|
points.push(vec2(radius,0).rotate((i+.5)/sides*PI*2));
|
|
116
|
-
this.addPoly(points, density, friction, restitution, isSensor);
|
|
117
|
+
return this.addPoly(points, density, friction, restitution, isSensor);
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
addRandomPoly(diameter=1, density, friction, restitution, isSensor)
|
|
@@ -123,7 +124,7 @@ class Box2dObject extends EngineObject
|
|
|
123
124
|
const radius = diameter/2;
|
|
124
125
|
for (let i=sides; i--;)
|
|
125
126
|
points.push(vec2(rand(radius/2,radius*1.5),0).rotate(i/sides*PI*2));
|
|
126
|
-
this.addPoly(points, density, friction, restitution, isSensor);
|
|
127
|
+
return this.addPoly(points, density, friction, restitution, isSensor);
|
|
127
128
|
}
|
|
128
129
|
|
|
129
130
|
addCircle(diameter=1, offset=vec2(), density, friction, restitution, isSensor)
|
|
@@ -131,18 +132,19 @@ class Box2dObject extends EngineObject
|
|
|
131
132
|
const shape = new box2d.b2CircleShape();
|
|
132
133
|
shape.set_m_p(offset.getBox2d());
|
|
133
134
|
shape.set_m_radius(diameter/2);
|
|
134
|
-
this.addShape(shape, density, friction, restitution, isSensor);
|
|
135
|
+
return this.addShape(shape, density, friction, restitution, isSensor);
|
|
135
136
|
}
|
|
136
137
|
|
|
137
138
|
addEdge(point1, point2, density, friction, restitution, isSensor)
|
|
138
139
|
{
|
|
139
140
|
const shape = new box2d.b2EdgeShape();
|
|
140
141
|
shape.Set(point1.getBox2d(), point2.getBox2d());
|
|
141
|
-
this.addShape(shape, density, friction, restitution, isSensor);
|
|
142
|
+
return this.addShape(shape, density, friction, restitution, isSensor);
|
|
142
143
|
}
|
|
143
144
|
|
|
144
145
|
addEdgeLoop(points, density, friction, restitution, isSensor)
|
|
145
146
|
{
|
|
147
|
+
const fixtures = [];
|
|
146
148
|
const getPoint = i=> points[mod(i,points.length)];
|
|
147
149
|
for (let i=0; i<points.length; ++i)
|
|
148
150
|
{
|
|
@@ -151,12 +153,15 @@ class Box2dObject extends EngineObject
|
|
|
151
153
|
shape.set_m_vertex1(getPoint(i+0).getBox2d());
|
|
152
154
|
shape.set_m_vertex2(getPoint(i+1).getBox2d());
|
|
153
155
|
shape.set_m_vertex3(getPoint(i+2).getBox2d());
|
|
154
|
-
this.addShape(shape, density, friction, restitution, isSensor);
|
|
156
|
+
const f = this.addShape(shape, density, friction, restitution, isSensor);
|
|
157
|
+
fixtures.push(f);
|
|
155
158
|
}
|
|
159
|
+
return fixtures;
|
|
156
160
|
}
|
|
157
161
|
|
|
158
162
|
addEdgeList(points, density, friction, restitution, isSensor)
|
|
159
163
|
{
|
|
164
|
+
const fixtures = [];
|
|
160
165
|
for (let i=0; i<points.length-1; ++i)
|
|
161
166
|
{
|
|
162
167
|
const shape = new box2d.b2EdgeShape();
|
|
@@ -164,19 +169,37 @@ class Box2dObject extends EngineObject
|
|
|
164
169
|
points[i+0] && shape.set_m_vertex1(points[i+0].getBox2d());
|
|
165
170
|
points[i+1] && shape.set_m_vertex2(points[i+1].getBox2d());
|
|
166
171
|
points[i+2] && shape.set_m_vertex3(points[i+2].getBox2d());
|
|
167
|
-
this.addShape(shape, density, friction, restitution, isSensor);
|
|
172
|
+
const f = this.addShape(shape, density, friction, restitution, isSensor);
|
|
173
|
+
fixtures.push(f);
|
|
168
174
|
}
|
|
175
|
+
return fixtures;
|
|
169
176
|
}
|
|
170
177
|
|
|
178
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
179
|
+
// lists of fixtures and joints
|
|
180
|
+
|
|
181
|
+
hasFixtures() { return !box2dIsNull(this.body.GetFixtureList()); }
|
|
171
182
|
getFixtureList()
|
|
172
183
|
{
|
|
173
|
-
const
|
|
184
|
+
const fixtures = [];
|
|
174
185
|
for (let fixture=this.body.GetFixtureList(); !box2dIsNull(fixture); )
|
|
175
186
|
{
|
|
176
|
-
|
|
187
|
+
fixtures.push(fixture);
|
|
177
188
|
fixture = fixture.GetNext();
|
|
178
189
|
}
|
|
179
|
-
return
|
|
190
|
+
return fixtures;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
hasJoints() { return !box2dIsNull(this.body.GetJointList()); }
|
|
194
|
+
getJointList()
|
|
195
|
+
{
|
|
196
|
+
const joints = [];
|
|
197
|
+
for (let joint=this.body.GetJointList(); !box2dIsNull(joint); )
|
|
198
|
+
{
|
|
199
|
+
joints.push(joint);
|
|
200
|
+
joint = joint.get_next();
|
|
201
|
+
}
|
|
202
|
+
return joints;
|
|
180
203
|
}
|
|
181
204
|
|
|
182
205
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -214,6 +237,19 @@ class Box2dObject extends EngineObject
|
|
|
214
237
|
setBodyType(type) { this.body.SetType(type); }
|
|
215
238
|
setSleepingAllowed(isAllowed=true) { this.body.SetSleepingAllowed(isAllowed); }
|
|
216
239
|
setFixedRotation(isFixed=true) { this.body.SetFixedRotation(isFixed); }
|
|
240
|
+
setCenterOfMass(center) { this.setMassData(center) }
|
|
241
|
+
setMass(mass) { this.setMassData(undefined, mass) }
|
|
242
|
+
setMomentOfInertia(I) { this.setMassData(undefined, undefined, I) }
|
|
243
|
+
resetMassData() { this.body.ResetMassData(); }
|
|
244
|
+
setMassData(localCenter, mass, momentOfInertia)
|
|
245
|
+
{
|
|
246
|
+
const data = new box2d.b2MassData();
|
|
247
|
+
this.body.GetMassData(data);
|
|
248
|
+
localCenter && data.set_center(localCenter.getBox2d());
|
|
249
|
+
mass && data.set_mass(mass);
|
|
250
|
+
momentOfInertia && data.set_I(momentOfInertia);
|
|
251
|
+
this.body.SetMassData(data);
|
|
252
|
+
}
|
|
217
253
|
setFilterData(categoryBits=0, ignoreCategoryBits=0, groupIndex=0)
|
|
218
254
|
{
|
|
219
255
|
this.getFixtureList().forEach(fixture=>
|
|
@@ -228,17 +264,17 @@ class Box2dObject extends EngineObject
|
|
|
228
264
|
{ this.getFixtureList().forEach(f=>f.SetSensor(isSensor)); }
|
|
229
265
|
|
|
230
266
|
///////////////////////////////////////////////////////////////////////////////
|
|
231
|
-
// physics
|
|
267
|
+
// physics force and torque functions
|
|
232
268
|
|
|
233
269
|
applyForce(force, pos)
|
|
234
270
|
{
|
|
235
|
-
pos
|
|
271
|
+
pos ||= this.getCenterOfMass();
|
|
236
272
|
this.setAwake();
|
|
237
273
|
this.body.ApplyForce(force.getBox2d(), pos.getBox2d());
|
|
238
274
|
}
|
|
239
275
|
applyAcceleration(acceleration, pos)
|
|
240
276
|
{
|
|
241
|
-
pos
|
|
277
|
+
pos ||= this.getCenterOfMass();
|
|
242
278
|
this.setAwake();
|
|
243
279
|
this.body.ApplyLinearImpulse(acceleration.getBox2d(), pos.getBox2d());
|
|
244
280
|
}
|
|
@@ -270,7 +306,7 @@ class Box2dRaycastResult
|
|
|
270
306
|
}
|
|
271
307
|
}
|
|
272
308
|
|
|
273
|
-
// raycast and return a list of all results
|
|
309
|
+
// raycast and return a list of all the results
|
|
274
310
|
function box2dRaycastAll(start, end)
|
|
275
311
|
{
|
|
276
312
|
const raycastCallback = new box2d.JSRayCastCallback();
|
|
@@ -293,11 +329,12 @@ function box2dRaycastAll(start, end)
|
|
|
293
329
|
function box2dRaycast(start, end)
|
|
294
330
|
{
|
|
295
331
|
const raycastResults = box2dRaycastAll(start, end);
|
|
296
|
-
|
|
297
|
-
|
|
332
|
+
if (!raycastResults.length)
|
|
333
|
+
return undefined;
|
|
334
|
+
return raycastResults.reduce((a,b)=>a.fraction < b.fraction ? a : b);
|
|
298
335
|
}
|
|
299
336
|
|
|
300
|
-
// box aabb cast and return the
|
|
337
|
+
// box aabb cast and return all the objects
|
|
301
338
|
function box2dBoxCastAll(pos, size)
|
|
302
339
|
{
|
|
303
340
|
const queryCallback = new box2d.JSQueryCallback();
|
|
@@ -320,7 +357,7 @@ function box2dBoxCastAll(pos, size)
|
|
|
320
357
|
return queryObjects;
|
|
321
358
|
}
|
|
322
359
|
|
|
323
|
-
// box aabb cast and return the first
|
|
360
|
+
// box aabb cast and return the first object
|
|
324
361
|
function box2dBoxCast(pos, size)
|
|
325
362
|
{
|
|
326
363
|
const queryCallback = new box2d.JSQueryCallback();
|
|
@@ -341,7 +378,7 @@ function box2dBoxCast(pos, size)
|
|
|
341
378
|
return queryObject;
|
|
342
379
|
}
|
|
343
380
|
|
|
344
|
-
// circle cast and return the
|
|
381
|
+
// circle cast and return all the objects
|
|
345
382
|
function box2dCircleCastAll(pos, diameter)
|
|
346
383
|
{
|
|
347
384
|
const radius2 = (diameter/2)**2;
|
|
@@ -349,17 +386,26 @@ function box2dCircleCastAll(pos, diameter)
|
|
|
349
386
|
return results.filter(o=>o.pos.distanceSquared(pos) < radius2);
|
|
350
387
|
}
|
|
351
388
|
|
|
352
|
-
// circle cast and return the first
|
|
389
|
+
// circle cast and return the first object
|
|
353
390
|
function box2dCircleCast(pos, diameter)
|
|
354
391
|
{
|
|
355
392
|
const radius2 = (diameter/2)**2;
|
|
356
393
|
let results = box2dBoxCastAll(pos, vec2(diameter));
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
394
|
+
|
|
395
|
+
let bestResult, bestDistance2;
|
|
396
|
+
for (const result of results)
|
|
397
|
+
{
|
|
398
|
+
const distance2 = result.pos.distanceSquared(pos);
|
|
399
|
+
if (distance2 < radius2 && (!bestResult || distance2 < bestDistance2))
|
|
400
|
+
{
|
|
401
|
+
bestResult = result;
|
|
402
|
+
bestDistance2 = distance2;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return bestResult;
|
|
360
406
|
}
|
|
361
407
|
|
|
362
|
-
// point cast and return the first
|
|
408
|
+
// point cast and return the first object
|
|
363
409
|
function box2dPointCast(pos, dynamicOnly=true)
|
|
364
410
|
{
|
|
365
411
|
const queryCallback = new box2d.JSQueryCallback();
|
|
@@ -384,6 +430,28 @@ function box2dPointCast(pos, dynamicOnly=true)
|
|
|
384
430
|
return queryResult;
|
|
385
431
|
}
|
|
386
432
|
|
|
433
|
+
// box aabb cast and return all the fixtures
|
|
434
|
+
function box2dBoxCastAllFixtures(pos, size)
|
|
435
|
+
{
|
|
436
|
+
const queryCallback = new box2d.JSQueryCallback();
|
|
437
|
+
queryCallback.ReportFixture = function(fixturePointer)
|
|
438
|
+
{
|
|
439
|
+
const fixture = box2d.wrapPointer(fixturePointer, box2d.b2Fixture);
|
|
440
|
+
if (!queryObjects.includes(fixture))
|
|
441
|
+
queryObjects.push(fixture); // add if not already in list
|
|
442
|
+
return true; // continue getting results
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
const aabb = new box2d.b2AABB();
|
|
446
|
+
aabb.set_lowerBound(pos.subtract(size.scale(.5)).getBox2d());
|
|
447
|
+
aabb.set_upperBound(pos.add(size.scale(.5)).getBox2d());
|
|
448
|
+
|
|
449
|
+
let queryFixtures = [];
|
|
450
|
+
box2dWorld.QueryAABB(queryCallback, aabb);
|
|
451
|
+
debugRaycast && debugRect(pos, size, raycstResult ? '#f00' : '#00f', .02);
|
|
452
|
+
return queryFixtures;
|
|
453
|
+
}
|
|
454
|
+
|
|
387
455
|
///////////////////////////////////////////////////////////////////////////////
|
|
388
456
|
// Box2D Joints
|
|
389
457
|
|
|
@@ -394,14 +462,19 @@ function box2dCreateMouseJoint(object, fixedObject, worldPos)
|
|
|
394
462
|
jointDef.set_bodyA(fixedObject.body);
|
|
395
463
|
jointDef.set_bodyB(object.body);
|
|
396
464
|
jointDef.set_target(worldPos.getBox2d());
|
|
397
|
-
jointDef.set_maxForce(
|
|
465
|
+
jointDef.set_maxForce(2e3 * object.getMass());
|
|
398
466
|
return box2dCastObject(box2dWorld.CreateJoint(jointDef));
|
|
399
467
|
}
|
|
400
468
|
|
|
469
|
+
function box2dCreatePinJoint(objectA, objectB, collide=false)
|
|
470
|
+
{
|
|
471
|
+
return box2dCreateDistanceJoint(objectA, objectB, objectB.pos, undefined, collide);
|
|
472
|
+
}
|
|
473
|
+
|
|
401
474
|
function box2dCreateDistanceJoint(objectA, objectB, anchorA, anchorB, collide=false)
|
|
402
475
|
{
|
|
403
|
-
anchorA
|
|
404
|
-
anchorB
|
|
476
|
+
anchorA ||= vec2(objectA.body.GetPosition());
|
|
477
|
+
anchorB ||= vec2(objectB.body.GetPosition());
|
|
405
478
|
const localAnchorA = objectA.worldToLocal(anchorA);
|
|
406
479
|
const localAnchorB = objectB.worldToLocal(anchorB);
|
|
407
480
|
const jointDef = new box2d.b2DistanceJointDef();
|
|
@@ -416,7 +489,7 @@ function box2dCreateDistanceJoint(objectA, objectB, anchorA, anchorB, collide=fa
|
|
|
416
489
|
|
|
417
490
|
function box2dCreateRevoluteJoint(objectA, objectB, anchor, collide=false)
|
|
418
491
|
{
|
|
419
|
-
anchor
|
|
492
|
+
anchor ||= vec2(objectB.body.GetPosition());
|
|
420
493
|
const localAnchorA = objectA.worldToLocal(anchor);
|
|
421
494
|
const localAnchorB = objectB.worldToLocal(anchor);
|
|
422
495
|
const jointDef = new box2d.b2RevoluteJointDef();
|
|
@@ -431,7 +504,7 @@ function box2dCreateRevoluteJoint(objectA, objectB, anchor, collide=false)
|
|
|
431
504
|
|
|
432
505
|
function box2dCreatePrismaticJoint(objectA, objectB, anchor, worldAxis=vec2(0,1), collide=false)
|
|
433
506
|
{
|
|
434
|
-
anchor
|
|
507
|
+
anchor ||= vec2(objectB.body.GetPosition());
|
|
435
508
|
const localAnchorA = objectA.worldToLocal(anchor);
|
|
436
509
|
const localAnchorB = objectB.worldToLocal(anchor);
|
|
437
510
|
const localAxisA = objectB.worldToLocalVector(worldAxis);
|
|
@@ -448,7 +521,7 @@ function box2dCreatePrismaticJoint(objectA, objectB, anchor, worldAxis=vec2(0,1)
|
|
|
448
521
|
|
|
449
522
|
function box2dCreateWheelJoint(objectA, objectB, anchor, worldAxis=vec2(0,1), collide=false)
|
|
450
523
|
{
|
|
451
|
-
anchor
|
|
524
|
+
anchor ||= vec2(objectB.body.GetPosition());
|
|
452
525
|
const localAnchorA = objectA.worldToLocal(anchor);
|
|
453
526
|
const localAnchorB = objectB.worldToLocal(anchor);
|
|
454
527
|
const localAxisA = objectB.worldToLocalVector(worldAxis);
|
|
@@ -464,7 +537,7 @@ function box2dCreateWheelJoint(objectA, objectB, anchor, worldAxis=vec2(0,1), co
|
|
|
464
537
|
|
|
465
538
|
function box2dCreateWeldJoint(objectA, objectB, anchor, collide=false)
|
|
466
539
|
{
|
|
467
|
-
anchor
|
|
540
|
+
anchor ||= vec2(objectB.body.GetPosition());
|
|
468
541
|
const localAnchorA = objectA.worldToLocal(anchor);
|
|
469
542
|
const localAnchorB = objectB.worldToLocal(anchor);
|
|
470
543
|
const jointDef = new box2d.b2WeldJointDef();
|
|
@@ -479,7 +552,7 @@ function box2dCreateWeldJoint(objectA, objectB, anchor, collide=false)
|
|
|
479
552
|
|
|
480
553
|
function box2dCreateFrictionJoint(objectA, objectB, anchor, collide=false)
|
|
481
554
|
{
|
|
482
|
-
anchor
|
|
555
|
+
anchor ||= vec2(objectB.body.GetPosition());
|
|
483
556
|
const localAnchorA = objectA.worldToLocal(anchor);
|
|
484
557
|
const localAnchorB = objectB.worldToLocal(anchor);
|
|
485
558
|
const jointDef = new box2d.b2FrictionJointDef();
|
|
@@ -493,8 +566,8 @@ function box2dCreateFrictionJoint(objectA, objectB, anchor, collide=false)
|
|
|
493
566
|
|
|
494
567
|
function box2dCreateRopeJoint(objectA, objectB, anchorA, anchorB, extraLength=0, collide=false)
|
|
495
568
|
{
|
|
496
|
-
anchorA
|
|
497
|
-
anchorB
|
|
569
|
+
anchorA ||= vec2(objectA.body.GetPosition());
|
|
570
|
+
anchorB ||= vec2(objectB.body.GetPosition());
|
|
498
571
|
const localAnchorA = objectA.worldToLocal(anchorA);
|
|
499
572
|
const localAnchorB = objectB.worldToLocal(anchorB);
|
|
500
573
|
const jointDef = new box2d.b2RopeJointDef();
|
|
@@ -509,8 +582,8 @@ function box2dCreateRopeJoint(objectA, objectB, anchorA, anchorB, extraLength=0,
|
|
|
509
582
|
|
|
510
583
|
function box2dCreatePulleyJoint(objectA, objectB, groundAnchorA, groundAnchorB, anchorA, anchorB, ratio=1, collide=false)
|
|
511
584
|
{
|
|
512
|
-
anchorA
|
|
513
|
-
anchorB
|
|
585
|
+
anchorA ||= vec2(objectA.body.GetPosition());
|
|
586
|
+
anchorB ||= vec2(objectB.body.GetPosition());
|
|
514
587
|
const localAnchorA = objectA.worldToLocal(anchorA);
|
|
515
588
|
const localAnchorB = objectB.worldToLocal(anchorB);
|
|
516
589
|
const jointDef = new box2d.b2PulleyJointDef();
|
|
@@ -641,7 +714,7 @@ function box2dCastObject(object)
|
|
|
641
714
|
function box2dWarmup(frames=100)
|
|
642
715
|
{
|
|
643
716
|
// run the sim for a few frames to let objects settle
|
|
644
|
-
for(let i=frames; i--;)
|
|
717
|
+
for (let i=frames; i--;)
|
|
645
718
|
box2dWorld.Step(timeDelta, box2dStepIterations, box2dStepIterations);
|
|
646
719
|
}
|
|
647
720
|
|
package/src/engine.js
CHANGED
package/src/engineAudio.js
CHANGED
|
@@ -73,6 +73,7 @@ class Sound
|
|
|
73
73
|
// generate zzfx sound now for fast playback
|
|
74
74
|
const defaultRandomness = .05;
|
|
75
75
|
this.randomness = zzfxSound[1] || defaultRandomness;
|
|
76
|
+
zzfxSound[1] = 0; // generate without randomness
|
|
76
77
|
this.sampleChannels = [zzfxG(...zzfxSound)];
|
|
77
78
|
this.sampleRate = zzfxR;
|
|
78
79
|
}
|
|
@@ -305,10 +306,6 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
|
|
|
305
306
|
|
|
306
307
|
///////////////////////////////////////////////////////////////////////////////
|
|
307
308
|
|
|
308
|
-
// internal tracking if audio was suspended when last sound was played
|
|
309
|
-
// allows first suspended sound to play when audio is resumed
|
|
310
|
-
let audioSuspended = false;
|
|
311
|
-
|
|
312
309
|
/** Play cached audio samples with given settings
|
|
313
310
|
* @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback)
|
|
314
311
|
* @param {Number} [volume] - How much to scale volume by
|
|
@@ -324,20 +321,21 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
324
321
|
if (!soundEnable || headlessMode) return;
|
|
325
322
|
|
|
326
323
|
// prevent sounds from building up if they can't be played
|
|
327
|
-
|
|
328
|
-
if (audioSuspended = audioContext.state != 'running')
|
|
324
|
+
if (audioContext.state != 'running')
|
|
329
325
|
{
|
|
330
326
|
// fix stalled audio
|
|
331
|
-
audioContext.resume()
|
|
327
|
+
audioContext.resume().then(()=>
|
|
328
|
+
playSamples(sampleChannels, volume, rate, pan, loop, sampleRate, gainNode));
|
|
332
329
|
|
|
333
330
|
// prevent suspended sounds from building up
|
|
334
|
-
|
|
335
|
-
return;
|
|
331
|
+
return;
|
|
336
332
|
}
|
|
337
333
|
|
|
338
334
|
// create buffer and source
|
|
339
|
-
const
|
|
340
|
-
|
|
335
|
+
const channelCount = sampleChannels.length;
|
|
336
|
+
const sampleLength = sampleChannels[0].length;
|
|
337
|
+
const buffer = audioContext.createBuffer(channelCount, sampleLength, sampleRate);
|
|
338
|
+
const source = audioContext.createBufferSource();
|
|
341
339
|
|
|
342
340
|
// copy samples to buffer and setup source
|
|
343
341
|
sampleChannels.forEach((c,i)=> buffer.getChannelData(i).set(c));
|
|
@@ -351,7 +349,8 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
351
349
|
gainNode.connect(audioGainNode);
|
|
352
350
|
|
|
353
351
|
// connect source to stereo panner and gain
|
|
354
|
-
|
|
352
|
+
const pannerNode = new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)});
|
|
353
|
+
source.connect(pannerNode).connect(gainNode);
|
|
355
354
|
|
|
356
355
|
// play and return sound
|
|
357
356
|
source.start();
|
|
@@ -367,7 +366,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
367
366
|
* @param {Array} zzfxSound - Array of ZzFX parameters, ex. [.5,.5]
|
|
368
367
|
* @return {AudioBufferSourceNode} - The audio node of the sound played
|
|
369
368
|
* @memberof Audio */
|
|
370
|
-
function zzfx(...zzfxSound) { return
|
|
369
|
+
function zzfx(...zzfxSound) { return playSamples([zzfxG(...zzfxSound)]); }
|
|
371
370
|
|
|
372
371
|
/** Sample rate used for all ZzFX sounds
|
|
373
372
|
* @default 44100
|
|
@@ -376,7 +375,7 @@ const zzfxR = 44100;
|
|
|
376
375
|
|
|
377
376
|
/** Generate samples for a ZzFX sound
|
|
378
377
|
* @param {Number} [volume] - Volume scale (percent)
|
|
379
|
-
* @param {Number} [randomness] -
|
|
378
|
+
* @param {Number} [randomness] - How much to randomize frequency (percent Hz)
|
|
380
379
|
* @param {Number} [frequency] - Frequency of sound (Hz)
|
|
381
380
|
* @param {Number} [attack] - Attack time, how fast sound starts (seconds)
|
|
382
381
|
* @param {Number} [sustain] - Sustain time, how long sound holds (seconds)
|
|
@@ -402,7 +401,7 @@ const zzfxR = 44100;
|
|
|
402
401
|
function zzfxG
|
|
403
402
|
(
|
|
404
403
|
// parameters
|
|
405
|
-
volume = 1, randomness =
|
|
404
|
+
volume = 1, randomness = .05, frequency = 220, attack = 0, sustain = 0,
|
|
406
405
|
release = .1, shape = 0, shapeCurve = 1, slide = 0, deltaSlide = 0,
|
|
407
406
|
pitchJump = 0, pitchJumpTime = 0, repeatTime = 0, noise = 0, modulation = 0,
|
|
408
407
|
bitCrush = 0, delay = 0, sustainVolume = 1, decay = 0, tremolo = 0, filter = 0
|
|
@@ -413,7 +412,8 @@ function zzfxG
|
|
|
413
412
|
// init parameters
|
|
414
413
|
let PI2 = PI*2, sampleRate = zzfxR,
|
|
415
414
|
startSlide = slide *= 500 * PI2 / sampleRate / sampleRate,
|
|
416
|
-
startFrequency = frequency *=
|
|
415
|
+
startFrequency = frequency *=
|
|
416
|
+
rand(1 + randomness, 1-randomness) * PI2 / sampleRate,
|
|
417
417
|
b = [], t = 0, tm = 0, i = 0, j = 1, r = 0, c = 0, s = 0, f, length,
|
|
418
418
|
|
|
419
419
|
// biquad LP/HP filter
|
package/src/engineDebug.js
CHANGED
|
@@ -190,8 +190,23 @@ function debugSaveDataURL(dataURL, filename)
|
|
|
190
190
|
downloadLink.click();
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
+
/** Show error as full page of red text
|
|
194
|
+
* @memberof Debug */
|
|
195
|
+
function debugShowErrors()
|
|
196
|
+
{
|
|
197
|
+
onunhandledrejection = (event)=>showError(event.reason);
|
|
198
|
+
onerror = (event, source, lineno, colno)=>
|
|
199
|
+
showError(`${event}\n${source}\nLn ${lineno}, Col ${colno}`);
|
|
200
|
+
|
|
201
|
+
const showError = (message)=>
|
|
202
|
+
{
|
|
203
|
+
document.body.style.backgroundColor = '#111';
|
|
204
|
+
document.body.innerHTML = `<pre style=color:#f00;font-size:50px>` + message;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
193
208
|
///////////////////////////////////////////////////////////////////////////////
|
|
194
|
-
// Engine debug
|
|
209
|
+
// Engine debug functions (called automatically)
|
|
195
210
|
|
|
196
211
|
function debugInit()
|
|
197
212
|
{
|
|
@@ -322,7 +337,7 @@ function debugRender()
|
|
|
322
337
|
const pos = worldToScreen(p.pos);
|
|
323
338
|
overlayContext.translate(pos.x|0, pos.y|0);
|
|
324
339
|
overlayContext.rotate(p.angle);
|
|
325
|
-
overlayContext.scale(1, -1);
|
|
340
|
+
overlayContext.scale(1, p.text ? 1 : -1);
|
|
326
341
|
overlayContext.fillStyle = overlayContext.strokeStyle = p.color;
|
|
327
342
|
|
|
328
343
|
if (p.text != undefined)
|
package/src/engineExport.js
CHANGED
|
@@ -188,10 +188,13 @@ export {
|
|
|
188
188
|
// WebGL
|
|
189
189
|
glCanvas,
|
|
190
190
|
glContext,
|
|
191
|
-
glSetTexture,
|
|
192
191
|
glCompileShader,
|
|
192
|
+
glCopyToContext,
|
|
193
193
|
glCreateProgram,
|
|
194
194
|
glCreateTexture,
|
|
195
|
+
glDraw,
|
|
196
|
+
glFlush,
|
|
197
|
+
glSetTexture,
|
|
195
198
|
|
|
196
199
|
// Input
|
|
197
200
|
keyIsDown,
|
|
@@ -251,4 +254,4 @@ export {
|
|
|
251
254
|
medalsPreventUnlock,
|
|
252
255
|
medalsInit,
|
|
253
256
|
Medal,
|
|
254
|
-
};
|
|
257
|
+
};
|
package/src/engineInput.js
CHANGED
|
@@ -199,6 +199,10 @@ function inputInit()
|
|
|
199
199
|
// mouse event handlers
|
|
200
200
|
onmousedown = (e)=>
|
|
201
201
|
{
|
|
202
|
+
// fix stalled audio requiring user interaction
|
|
203
|
+
if (soundEnable && !headlessMode && audioContext && audioContext.state != 'running')
|
|
204
|
+
audioContext.resume();
|
|
205
|
+
|
|
202
206
|
isUsingGamepad = false;
|
|
203
207
|
inputData[0][e.button] = 3;
|
|
204
208
|
mousePosScreen = mouseToScreen(e);
|
|
@@ -210,7 +214,7 @@ function inputInit()
|
|
|
210
214
|
oncontextmenu = (e)=> false; // prevent right click menu
|
|
211
215
|
|
|
212
216
|
// init touch input
|
|
213
|
-
if (isTouchDevice && touchInputEnable
|
|
217
|
+
if (isTouchDevice && touchInputEnable)
|
|
214
218
|
touchInputInit();
|
|
215
219
|
}
|
|
216
220
|
|
|
@@ -367,8 +371,8 @@ function touchInputInit()
|
|
|
367
371
|
function handleTouchDefault(e)
|
|
368
372
|
{
|
|
369
373
|
// fix stalled audio requiring user interaction
|
|
370
|
-
if (soundEnable && audioContext && audioContext.state != 'running')
|
|
371
|
-
|
|
374
|
+
if (soundEnable && !headlessMode && audioContext && audioContext.state != 'running')
|
|
375
|
+
audioContext.resume();
|
|
372
376
|
|
|
373
377
|
// check if touching and pass to mouse events
|
|
374
378
|
const touching = e.touches.length;
|
package/src/engineObject.js
CHANGED
|
@@ -153,9 +153,9 @@ class EngineObject
|
|
|
153
153
|
|
|
154
154
|
// apply physics
|
|
155
155
|
const oldPos = this.pos.copy();
|
|
156
|
-
this.velocity.y += gravity * this.gravityScale;
|
|
157
156
|
this.pos.x += this.velocity.x *= this.damping;
|
|
158
|
-
this.pos.y += this.velocity.y
|
|
157
|
+
this.pos.y += this.velocity.y = this.damping * this.velocity.y
|
|
158
|
+
+ gravity * this.gravityScale;
|
|
159
159
|
this.angle += this.angleVelocity *= this.angleDamping;
|
|
160
160
|
|
|
161
161
|
// physics sanity checks
|
|
@@ -284,19 +284,22 @@ class EngineObject
|
|
|
284
284
|
const isBlockedX = tileCollisionTest(vec2(this.pos.x, oldPos.y), this.size, this);
|
|
285
285
|
if (isBlockedY || !isBlockedX)
|
|
286
286
|
{
|
|
287
|
-
// set if landed on ground
|
|
288
|
-
this.groundObject = wasMovingDown;
|
|
289
|
-
|
|
290
287
|
// bounce velocity
|
|
291
288
|
this.velocity.y *= -this.elasticity;
|
|
292
289
|
|
|
293
|
-
//
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
290
|
+
// set if landed on ground
|
|
291
|
+
if (this.groundObject = wasMovingDown)
|
|
292
|
+
{
|
|
293
|
+
// adjust position to slightly above nearest tile boundary
|
|
294
|
+
// this prevents gap between object and ground
|
|
295
|
+
const epsilon = .0001;
|
|
296
|
+
this.pos.y = (oldPos.y-this.size.y/2|0)+this.size.y/2+epsilon;
|
|
297
|
+
}
|
|
298
|
+
else
|
|
299
|
+
{
|
|
300
|
+
// move to previous position
|
|
301
|
+
this.pos.y = oldPos.y;
|
|
302
|
+
}
|
|
300
303
|
}
|
|
301
304
|
if (isBlockedX)
|
|
302
305
|
{
|