littlejsengine 1.9.9 → 1.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +6 -0
  2. package/dist/littlejs.d.ts +107 -14
  3. package/dist/littlejs.esm.js +250 -102
  4. package/dist/littlejs.esm.min.js +1 -1
  5. package/dist/littlejs.js +226 -98
  6. package/dist/littlejs.min.js +1 -1
  7. package/dist/littlejs.release.js +208 -95
  8. package/examples/box2d/game.js +29 -10
  9. package/examples/box2d/gameObjects.js +485 -0
  10. package/examples/box2d/index.html +7 -5
  11. package/examples/box2d/scenes.js +28 -247
  12. package/examples/box2d/tiles.png +0 -0
  13. package/examples/breakout/game.js +1 -1
  14. package/examples/breakout/index.html +5 -4
  15. package/examples/breakoutTutorial/index.html +4 -3
  16. package/examples/electron/game.js +1 -1
  17. package/examples/electron/index.html +4 -3
  18. package/examples/empty/game.js +1 -1
  19. package/examples/empty/index.html +2 -1
  20. package/examples/htmlMenu/game.js +67 -0
  21. package/examples/htmlMenu/index.html +56 -0
  22. package/examples/htmlMenu/tiles.png +0 -0
  23. package/examples/index.html +33 -0
  24. package/examples/js13k/game.js +1 -1
  25. package/examples/js13k/index.html +17 -14
  26. package/examples/module/game.js +1 -1
  27. package/examples/module/index.html +3 -2
  28. package/examples/particles/index.html +4 -3
  29. package/examples/platformer/game.js +1 -1
  30. package/examples/platformer/gameCharacter.js +1 -0
  31. package/examples/platformer/gameEffects.js +0 -2
  32. package/examples/platformer/gameLevel.js +2 -2
  33. package/examples/platformer/gameObjects.js +1 -2
  34. package/examples/platformer/index.html +10 -9
  35. package/examples/puzzle/game.js +1 -1
  36. package/examples/puzzle/index.html +4 -3
  37. package/examples/starter/game.js +5 -5
  38. package/examples/starter/index.html +15 -14
  39. package/examples/stress/index.html +3 -2
  40. package/examples/typescript/game.js +1 -1
  41. package/examples/typescript/index.html +3 -2
  42. package/examples/uiSystem/game.js +134 -0
  43. package/examples/uiSystem/index.html +25 -0
  44. package/examples/uiSystem/tiles.png +0 -0
  45. package/jsconfig.json +11 -0
  46. package/package.json +1 -1
  47. package/plugins/box2d.js +116 -42
  48. package/plugins/postProcess.js +6 -7
  49. package/plugins/uiSystem.js +243 -0
  50. package/src/engine.js +48 -20
  51. package/src/engineAudio.js +22 -25
  52. package/src/engineDebug.js +18 -3
  53. package/src/engineDraw.js +101 -27
  54. package/src/engineExport.js +23 -4
  55. package/src/engineInput.js +9 -5
  56. package/src/engineObject.js +15 -12
  57. package/src/engineSettings.js +1 -1
  58. package/src/engineUtilities.js +1 -1
  59. package/src/engineWebGL.js +11 -4
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 fixtures
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 fixtureList = [];
184
+ const fixtures = [];
174
185
  for (let fixture=this.body.GetFixtureList(); !box2dIsNull(fixture); )
175
186
  {
176
- fixtureList.push(fixture);
187
+ fixtures.push(fixture);
177
188
  fixture = fixture.GetNext();
178
189
  }
179
- return fixtureList;
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 kinematics
267
+ // physics force and torque functions
232
268
 
233
269
  applyForce(force, pos)
234
270
  {
235
- pos = pos || this.getCenterOfMass();
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 = pos || this.getCenterOfMass();
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
- return raycastResults.length ?
297
- raycastResults.reduce((a,b)=>a.fraction < b.fraction ? a : b) : undefined;
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 all results
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 result
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 all results
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 result
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
- results = results.filter(o=>o.pos.distanceSquared(pos) < radius2);
358
- results = results.sort((a,b)=>a.pos.distanceSquared(pos)-b.pos.distanceSquared(pos));
359
- return results[0];
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 result
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(1e3 * object.getMass());
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 = anchorA || vec2(objectA.body.GetPosition());
404
- anchorB = anchorB || vec2(objectB.body.GetPosition());
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 = anchor || vec2(objectB.body.GetPosition());
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 = anchor || vec2(objectB.body.GetPosition());
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 = anchor || vec2(objectB.body.GetPosition());
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 = anchor || vec2(objectB.body.GetPosition());
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 = anchor || vec2(objectB.body.GetPosition());
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 = anchorA || vec2(objectA.body.GetPosition());
497
- anchorB = anchorB || vec2(objectB.body.GetPosition());
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 = anchorA || vec2(objectA.body.GetPosition());
513
- anchorB = anchorB || vec2(objectB.body.GetPosition());
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
 
@@ -802,7 +875,8 @@ function box2dEngineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameR
802
875
  engineAddPlugin(box2dUpdate, box2dRender);
803
876
  function box2dUpdate()
804
877
  {
805
- box2dWorld.Step(timeDelta, box2dStepIterations, box2dStepIterations);
878
+ if (!paused)
879
+ box2dWorld.Step(timeDelta, box2dStepIterations, box2dStepIterations);
806
880
  }
807
881
  function box2dRender()
808
882
  {
@@ -49,11 +49,6 @@ function initPostProcess(shaderCode, includeOverlay=false)
49
49
  glPostTexture = glCreateTexture(undefined);
50
50
  glPostIncludeOverlay = includeOverlay;
51
51
 
52
- // hide the original 2d canvas
53
- mainCanvas.style.visibility = 'hidden';
54
- if (glPostIncludeOverlay)
55
- overlayCanvas.style.visibility = 'hidden';
56
-
57
52
  // Render the post processing shader, called automatically by the engine
58
53
  engineAddPlugin(undefined, postProcessRender);
59
54
  function postProcessRender()
@@ -72,8 +67,12 @@ function initPostProcess(shaderCode, includeOverlay=false)
72
67
  glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
73
68
  }
74
69
 
75
- // copy overlay canvas so it will be included in post processing
76
- glPostIncludeOverlay && mainContext.drawImage(overlayCanvas, 0, 0);
70
+ if (glPostIncludeOverlay)
71
+ {
72
+ // copy overlay canvas so it will be included in post processing
73
+ mainContext.drawImage(overlayCanvas, 0, 0);
74
+ overlayCanvas.width |= 0;
75
+ }
77
76
 
78
77
  // setup shader program to draw one triangle
79
78
  glContext.useProgram(glPostShader);
@@ -0,0 +1,243 @@
1
+ /**
2
+ * LittleJS User Interface Plugin
3
+ * - Nested Menus
4
+ * - Text
5
+ * - Buttons
6
+ * - Checkboxes
7
+ * - Images
8
+ */
9
+
10
+ 'use strict';
11
+
12
+ ///////////////////////////////////////////////////////////////////////////////
13
+
14
+ // ui defaults
15
+ let uiDefaultColor = WHITE;
16
+ let uiDefaultLineColor = BLACK;
17
+ let uiDefaultTextColor = BLACK;
18
+ let uiDefaultButtonColor = hsl(0,0,.5);
19
+ let uiDefaultHoverColor = hsl(0,0,.7);
20
+ let uiDefaultLineWidth = 4;
21
+ let uiDefaultFont = 'arial';
22
+
23
+ // ui system
24
+ let uiObjects = [];
25
+ let uiContext;
26
+
27
+ function initUISystem(context=overlayContext)
28
+ {
29
+ uiContext = context;
30
+ engineAddPlugin(uiUpdate, uiRender);
31
+
32
+ // setup recursive update and render
33
+ function uiUpdate()
34
+ {
35
+ function updateObject(o)
36
+ {
37
+ if (!o.visible)
38
+ return;
39
+ if (o.parent)
40
+ o.pos = o.localPos.add(o.parent.pos);
41
+ o.update();
42
+ for(const c of o.children)
43
+ updateObject(c)
44
+ }
45
+ uiObjects.forEach(o=> o.parent || updateObject(o));
46
+ }
47
+ function uiRender()
48
+ {
49
+ function renderObject(o)
50
+ {
51
+ if (!o.visible)
52
+ return;
53
+ o.render();
54
+ for(const c of o.children)
55
+ renderObject(c)
56
+ }
57
+ uiObjects.forEach(o=> o.parent || renderObject(o));
58
+ }
59
+ }
60
+
61
+ function drawUIRect(pos, size, color=uiDefaultColor, lineWidth=uiDefaultLineWidth, lineColor=uiDefaultLineColor)
62
+ {
63
+ uiContext.fillStyle = color.toString();
64
+ uiContext.beginPath();
65
+ uiContext.rect(pos.x-size.x/2, pos.y-size.y/2, size.x, size.y);
66
+ uiContext.fill();
67
+ if (lineWidth)
68
+ {
69
+ uiContext.strokeStyle = lineColor.toString();
70
+ uiContext.lineWidth = lineWidth;
71
+ uiContext.stroke();
72
+ }
73
+ }
74
+
75
+ function drawUILine(posA, posB, thickness=uiDefaultLineWidth, color=uiDefaultLineColor)
76
+ {
77
+ uiContext.strokeStyle = color.toString();
78
+ uiContext.lineWidth = thickness;
79
+ uiContext.beginPath();
80
+ uiContext.lineTo(posA.x, posA.y);
81
+ uiContext.lineTo(posB.x, posB.y);
82
+ uiContext.stroke();
83
+ }
84
+
85
+ function drawUITile(pos, size, tileInfo, color=uiDefaultColor, angle=0, mirror=false, additiveColor=BLACK)
86
+ {
87
+ drawTile(pos, size, tileInfo, color, angle, mirror, additiveColor, false, true, uiContext);
88
+ }
89
+
90
+ function drawUIText(text, pos, size, color=uiDefaultColor, lineWidth=uiDefaultLineWidth, lineColor=uiDefaultLineColor, align='center', font=uiDefaultFont)
91
+ {
92
+ drawTextScreen(text, pos, size.y, color, lineWidth, lineColor, align, font, uiContext, size.x);
93
+ }
94
+
95
+ ///////////////////////////////////////////////////////////////////////////////
96
+
97
+ class UIObject
98
+ {
99
+ constructor(localPos, size=vec2())
100
+ {
101
+ this.localPos = localPos.copy();
102
+ this.pos = localPos.copy();
103
+ this.size = size.copy();
104
+ this.color = uiDefaultColor;
105
+ this.lineColor = uiDefaultLineColor;
106
+ this.textColor = uiDefaultTextColor;
107
+ this.hoverColor = uiDefaultHoverColor;
108
+ this.lineWidth = uiDefaultLineWidth;
109
+ this.font = uiDefaultFont;
110
+ this.visible = true;
111
+ this.children = [];
112
+ this.parent = null;
113
+ uiObjects.push(this);
114
+ }
115
+
116
+ addChild(child)
117
+ {
118
+ ASSERT(!child.parent && !this.children.includes(child));
119
+ this.children.push(child);
120
+ child.parent = this;
121
+ }
122
+
123
+ removeChild(child)
124
+ {
125
+ ASSERT(child.parent == this && this.children.includes(child));
126
+ this.children.splice(this.children.indexOf(child), 1);
127
+ child.parent = 0;
128
+ }
129
+
130
+ update()
131
+ {
132
+ // track mouse input
133
+ const mouseWasOver = this.mouseIsOver;
134
+ this.mouseIsOver = isOverlapping(this.pos, this.size, mousePosScreen);
135
+ if (this.mouseIsOver && !mouseWasOver)
136
+ this.onEnter();
137
+ if (!this.mouseIsOver && mouseWasOver)
138
+ this.onLeave();
139
+ if (mouseWasPressed(0) && this.mouseIsOver)
140
+ {
141
+ this.mouseIsHeld = true;
142
+ this.onPress();
143
+ }
144
+ else if (this.mouseIsHeld && !mouseIsDown(0))
145
+ {
146
+ this.mouseIsHeld = false;
147
+ if (this.mouseIsOver)
148
+ this.onClick();
149
+ }
150
+ }
151
+ render()
152
+ {
153
+ if (this.size.x && this.size.y)
154
+ drawUIRect(this.pos, this.size, this.color, this.lineWidth, this.lineColor);
155
+ }
156
+
157
+ // callback functions
158
+ onEnter() {}
159
+ onLeave() {}
160
+ onPress() {}
161
+ onClick() {}
162
+ }
163
+
164
+ ///////////////////////////////////////////////////////////////////////////////
165
+
166
+ class UIText extends UIObject
167
+ {
168
+ constructor(pos, size, text, align='center', font=fontDefault)
169
+ {
170
+ super(pos, size);
171
+
172
+ this.text = text;
173
+ this.align = align;
174
+ this.font = font;
175
+ this.lineWidth = 0;
176
+ }
177
+ render()
178
+ {
179
+ drawUIText(this.text, this.pos, this.size, this.textColor, this.lineWidth, this.lineColor, this.align, this.font);
180
+ }
181
+ }
182
+
183
+ ///////////////////////////////////////////////////////////////////////////////
184
+
185
+ class UITile extends UIObject
186
+ {
187
+ constructor(pos, size, tileInfo, color=WHITE, angle=0, mirror=false, additiveColor=BLACK)
188
+ {
189
+ super(pos, size);
190
+
191
+ this.tileInfo = tileInfo;
192
+ this.color = color;
193
+ this.angle = angle;
194
+ this.mirror = mirror;
195
+ this.additiveColor = additiveColor;
196
+ }
197
+ render()
198
+ {
199
+ drawUITile(this.pos, this.size, this.tileInfo, this.color, this.angle, this.mirror, this.additiveColor);
200
+ }
201
+ }
202
+
203
+ ///////////////////////////////////////////////////////////////////////////////
204
+
205
+ class UIButton extends UIObject
206
+ {
207
+ constructor(pos, size, text)
208
+ {
209
+ super(pos, size);
210
+ this.text = text;
211
+ this.color = uiDefaultButtonColor;
212
+ }
213
+ render()
214
+ {
215
+ const lineColor = this.mouseIsHeld ? this.color : this.lineColor;
216
+ const color = this.mouseIsOver? this.hoverColor : this.color;
217
+ drawUIRect(this.pos, this.size, color, this.lineWidth, lineColor);
218
+ drawTextScreen(this.text, this.pos, this.size.y*.8,
219
+ this.textColor, undefined, undefined, this.align, this.font, uiContext);
220
+ }
221
+ }
222
+
223
+ ///////////////////////////////////////////////////////////////////////////////
224
+
225
+ class UICheckbox extends UIObject
226
+ {
227
+ constructor(pos, size, checked=false)
228
+ {
229
+ super(pos, size);
230
+ this.checked = checked;
231
+ }
232
+ onClick() { this.checked = !this.checked; }
233
+ render()
234
+ {
235
+ drawUIRect(this.pos, this.size, this.color, this.lineWidth, this.lineColor);
236
+ if (this.checked)
237
+ {
238
+ // draw an X if checked
239
+ drawUILine(this.pos.add(this.size.multiply(vec2(-.5,-.5))), this.pos.add(this.size.multiply(vec2(.5,.5))), this.lineWidth, this.lineColor);
240
+ drawUILine(this.pos.add(this.size.multiply(vec2(-.5,.5))), this.pos.add(this.size.multiply(vec2(.5,-.5))), this.lineWidth, this.lineColor);
241
+ }
242
+ }
243
+ }