littlejsengine 1.15.3 → 1.15.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/src/engine.js CHANGED
@@ -30,7 +30,7 @@ const engineName = 'LittleJS';
30
30
  * @type {string}
31
31
  * @default
32
32
  * @memberof Engine */
33
- const engineVersion = '1.15.3';
33
+ const engineVersion = '1.15.9';
34
34
 
35
35
  /** Frames per second to update
36
36
  * @type {number}
@@ -276,7 +276,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
276
276
  o.destroyed || o.render();
277
277
  gameRenderPost();
278
278
  pluginList.forEach(plugin=>plugin.render?.());
279
- touchGamepadRender();
279
+ inputRender();
280
280
  debugRender();
281
281
  glFlush();
282
282
  debugVideoCaptureUpdate();
@@ -305,23 +305,48 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
305
305
 
306
306
  if (canvasFixedSize.x)
307
307
  {
308
- // clear canvas and set fixed size
309
- mainCanvas.width = canvasFixedSize.x;
310
- mainCanvas.height = canvasFixedSize.y;
311
-
312
- // fit to window by adding space on top or bottom if necessary
313
- const aspect = innerWidth / innerHeight;
314
- const fixedAspect = mainCanvas.width / mainCanvas.height;
315
- (glCanvas||mainCanvas).style.width = mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
316
- (glCanvas||mainCanvas).style.height = mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
308
+ // set canvas fixed size
309
+ mainCanvasSize = canvasFixedSize.copy();
310
+
311
+ // fit to window using css width and height
312
+ const innerAspect = innerWidth / innerHeight;
313
+ const fixedAspect = canvasFixedSize.x / canvasFixedSize.y;
314
+ const w = innerAspect < fixedAspect ? '100%' : '';
315
+ const h = innerAspect < fixedAspect ? '' : '100%';
316
+ overlayCanvas.style.width = mainCanvas.style.width = w;
317
+ overlayCanvas.style.height = mainCanvas.style.height = h;
318
+ if (glCanvas)
319
+ {
320
+ glCanvas.style.width = w;
321
+ glCanvas.style.height = h;
322
+ }
317
323
  }
318
324
  else
319
325
  {
320
- // clear canvas and set size to same as window
321
- mainCanvas.width = min(innerWidth, canvasMaxSize.x);
322
- mainCanvas.height = min(innerHeight, canvasMaxSize.y);
326
+ // get main canvas size based on window size
327
+ mainCanvasSize.x = min(innerWidth, canvasMaxSize.x);
328
+ mainCanvasSize.y = min(innerHeight, canvasMaxSize.y);
329
+
330
+ // responsive aspect ratio with native resolution
331
+ const innerAspect = innerWidth / innerHeight;
332
+ if (canvasMaxAspect && innerAspect > canvasMaxAspect)
333
+ {
334
+ // full height
335
+ const w = mainCanvasSize.y * canvasMaxAspect | 0;
336
+ mainCanvasSize.x = min(w, canvasMaxSize.x);
337
+ }
338
+ else if (innerAspect < canvasMinAspect)
339
+ {
340
+ // full width
341
+ const h = mainCanvasSize.x / canvasMinAspect | 0;
342
+ mainCanvasSize.y = min(h, canvasMaxSize.y);
343
+ }
323
344
  }
324
345
 
346
+ // clear main and overlay canvas and set size
347
+ overlayCanvas.width = mainCanvas.width = mainCanvasSize.x;
348
+ overlayCanvas.height = mainCanvas.height = mainCanvasSize.y;
349
+
325
350
  // apply the clear color to main canvas
326
351
  if (canvasClearColor.a > 0)
327
352
  {
@@ -330,13 +355,6 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
330
355
  mainContext.fillStyle = BLACK.toString();
331
356
  }
332
357
 
333
- // clear overlay canvas and set size
334
- overlayCanvas.width = mainCanvas.width;
335
- overlayCanvas.height = mainCanvas.height;
336
-
337
- // save canvas size
338
- mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
339
-
340
358
  // set default line join and cap
341
359
  const lineJoin = 'round', lineCap = 'round';
342
360
  mainContext.lineJoin = overlayContext.lineJoin = lineJoin;
@@ -360,7 +378,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
360
378
  'user-select:none;' + // prevent hold to select
361
379
  '-webkit-user-select:none;' + // compatibility for ios
362
380
  'touch-action:none;' + // prevent mobile pinch to resize
363
- '-webkit-touch-callout:none';// compatibility for ios
381
+ '-webkit-touch-callout:none'; // compatibility for ios
364
382
  rootElement.style.cssText = styleRoot;
365
383
  drawCanvas = mainCanvas = document.createElement('canvas');
366
384
  rootElement.appendChild(mainCanvas);
@@ -377,7 +395,8 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
377
395
  rootElement.appendChild(overlayCanvas);
378
396
  overlayContext = overlayCanvas.getContext('2d');
379
397
 
380
- // set canvases
398
+ // setup canvases
399
+ // transform way is still more reliable then flexbox or grid
381
400
  const styleCanvas = 'position:absolute;'+ // allow canvases to overlap
382
401
  'top:50%;left:50%;transform:translate(-50%,-50%)'; // center on screen
383
402
  mainCanvas.style.cssText = overlayCanvas.style.cssText = styleCanvas;
@@ -441,9 +460,176 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
441
460
  // wait for all the promises to finish
442
461
  await Promise.all(promises);
443
462
  return startEngine();
463
+
464
+ ///////////////////////////////////////////////////////////////////////////
465
+ // LittleJS Splash Screen
466
+ function drawEngineSplashScreen(t)
467
+ {
468
+ const x = overlayContext;
469
+ const w = overlayCanvas.width = innerWidth;
470
+ const h = overlayCanvas.height = innerHeight;
471
+
472
+ {
473
+ // background
474
+ const p3 = percent(t, 1, .8);
475
+ const p4 = percent(t, 0, .5);
476
+ const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.7);
477
+ g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString());
478
+ g.addColorStop(1,hsl(0,0,0,p3).toString());
479
+ x.save();
480
+ x.fillStyle = g;
481
+ x.fillRect(0,0,w,h);
482
+ }
483
+
484
+ // draw LittleJS logo...
485
+ const rect = (X, Y, W, H, C)=>
486
+ {
487
+ x.beginPath();
488
+ x.rect(X,Y,W,C?H*p:H);
489
+ x.fillStyle = C;
490
+ C ? x.fill() : x.stroke();
491
+ };
492
+ const line = (X, Y, Z, W)=>
493
+ {
494
+ x.beginPath();
495
+ x.lineTo(X,Y);
496
+ x.lineTo(Z,W);
497
+ x.stroke();
498
+ };
499
+ const circle = (X, Y, R, A=0, B=2*PI, C, F)=>
500
+ {
501
+ const D = (A+B)/2, E = p*(B-A)/2;
502
+ x.beginPath();
503
+ F && x.lineTo(X,Y);
504
+ x.arc(X,Y,R,D-E,D+E);
505
+ x.fillStyle = C;
506
+ C ? x.fill() : x.stroke();
507
+ };
508
+ const color = (c=0, l=0) =>
509
+ hsl([.98,.3,.57,.14][c%4],.8,[0,.3,.5,.8,.9][l]).toString();
510
+ const alpha = wave(1,1,t);
511
+ const p = percent(alpha, .1, .5);
512
+
513
+ // setup
514
+ x.translate(w/2,h/2);
515
+ const size = min(6, min(w,h)/99); // fit to screen
516
+ x.scale(size,size);
517
+ x.translate(-40,-35);
518
+ x.lineJoin = x.lineCap = 'round';
519
+ x.lineWidth = .1 + p*1.9;
520
+
521
+ // drawing effect
522
+ const p2 = percent(alpha,.1,1);
523
+ x.setLineDash([99*p2,99]);
524
+
525
+ // cab top
526
+ rect(7,16,18,-8,color(2,2));
527
+ rect(7,8,18,4,color(2,3));
528
+ rect(25,8,8,8,color(2,1));
529
+ rect(25,8,-18,8);
530
+ rect(25,8,8,8);
531
+
532
+ // cab
533
+ rect(25,16,7,23,color());
534
+ rect(11,39,14,-23,color(1,1));
535
+ rect(11,16,14,18,color(1,2));
536
+ rect(11,16,14,8,color(1,3));
537
+ rect(25,16,-14,24);
538
+
539
+ // cab window
540
+ rect(15,29,6,-9,color(2,2));
541
+ circle(15,21,5,0,PI/2,color(2,4),1);
542
+ rect(21,21,-6,9);
543
+
544
+ // little stack
545
+ rect(37,14,9,6,color(3,2));
546
+ rect(37,14,4.5,6,color(3,3));
547
+ rect(37,14,9,6);
548
+
549
+ // big stack
550
+ rect(50,20,10,-8,color(0,1));
551
+ rect(50,20,6.5,-8,color(0,2));
552
+ rect(50,20,3.5,-8,color(0,3));
553
+ rect(50,20,10,-8);
554
+ circle(55,2,11.4,.5,PI-.5,color(3,3));
555
+ circle(55,2,11.4,.5,PI/2,color(3,2),1);
556
+ circle(55,2,11.4,.5,PI-.5);
557
+ rect(45,7,20,-7,color(0,2));
558
+ rect(45,-1,20,4,color(0,3));
559
+ rect(45,-1,20,8);
560
+
561
+ // engine
562
+ for (let i=5; i--;)
563
+ {
564
+ // stagger radius to fix slight seam
565
+ circle(60-i*6,30, 9.9,0,2*PI,color(i+2,3));
566
+ circle(60-i*6,30,10.0,-.5,PI+.5,color(i+2,2));
567
+ circle(60-i*6,30,10.1,.5,PI-.5,color(i+2,1));
568
+ }
569
+
570
+ // engine outline
571
+ circle(36,30,10,PI/2,PI*3/2);
572
+ circle(48,30,10,PI/2,PI*3/2);
573
+ circle(60,30,10);
574
+ line(36,20,60,20);
575
+
576
+ // engine front light
577
+ circle(60,30,4,PI,3*PI,color(3,2));
578
+ circle(60,30,4,PI,2*PI,color(3,3));
579
+ circle(60,30,4,PI,3*PI);
580
+
581
+ // front brush
582
+ for (let i=6; i--;)
583
+ {
584
+ x.beginPath();
585
+ x.lineTo(53,54);
586
+ x.lineTo(53,40);
587
+ x.lineTo(53+(1+i*2.9)*p,40);
588
+ x.lineTo(53+(4+i*3.5)*p,54);
589
+ x.fillStyle = color(0,i%2+2);
590
+ x.fill();
591
+ i%2 && x.stroke();
592
+ }
593
+
594
+ // wheels
595
+ rect(6,40,5,5);
596
+ rect(6,40,5,5,color());
597
+ rect(15,54,38,-14,color());
598
+ for (let i=3; i--;)
599
+ for (let j=2; j--;)
600
+ {
601
+ circle(15*i+15,47,j?7:1,PI,3*PI,color(i,3));
602
+ x.stroke();
603
+ circle(15*i+15,47,j?7:1,0,PI,color(i,2));
604
+ x.stroke();
605
+ }
606
+ line(6,40,68,40); // center
607
+ line(77,54,4,54); // bottom
608
+
609
+ // draw engine name
610
+ const s = engineName;
611
+ x.font = '900 16px arial';
612
+ x.textAlign = 'center';
613
+ x.textBaseline = 'top';
614
+ x.lineWidth = .1+p*3.9;
615
+ let w2 = 0;
616
+ for (let i=0; i<s.length; ++i)
617
+ w2 += x.measureText(s[i]).width;
618
+ for (let j=2; j--;)
619
+ for (let i=0, X=41-w2/2; i<s.length; ++i)
620
+ {
621
+ x.fillStyle = color(i,2);
622
+ const w = x.measureText(s[i]).width;
623
+ x[j?'strokeText':'fillText'](s[i],X+w/2,55.5,17*p);
624
+ X += w;
625
+ }
626
+ x.restore();
627
+ }
628
+ ///////////////////////////////////////////////////////////////////////////
444
629
  }
445
630
 
446
631
  /** Update each engine object, remove destroyed objects, and update time
632
+ * can be called manually if objects need to be updated outside of main loop
447
633
  * @memberof Engine */
448
634
  function engineObjectsUpdate()
449
635
  {
@@ -495,18 +681,21 @@ function engineObjectsDestroy()
495
681
  function engineObjectsCollect(pos, size, objects=engineObjects)
496
682
  {
497
683
  const collectedObjects = [];
498
- if (!pos) // all objects
684
+ if (!pos)
499
685
  {
686
+ // all objects
500
687
  for (const o of objects)
501
688
  collectedObjects.push(o);
502
689
  }
503
- else if (size instanceof Vector2) // bounding box test
690
+ else if (size instanceof Vector2)
504
691
  {
692
+ // bounding box test
505
693
  for (const o of objects)
506
694
  o.isOverlapping(pos, size) && collectedObjects.push(o);
507
695
  }
508
- else // circle test
696
+ else
509
697
  {
698
+ // circle test
510
699
  const sizeSquared = size*size;
511
700
  for (const o of objects)
512
701
  pos.distanceSquared(o.pos) < sizeSquared && collectedObjects.push(o);
@@ -549,171 +738,4 @@ function engineObjectsRaycast(start, end, objects=engineObjects)
549
738
 
550
739
  debugRaycast && debugLine(start, end, hitObjects.length ? '#f00' : '#00f', .02);
551
740
  return hitObjects;
552
- }
553
-
554
- ///////////////////////////////////////////////////////////////////////////////
555
- // LittleJS splash screen and logo
556
-
557
- function drawEngineSplashScreen(t)
558
- {
559
- const x = overlayContext;
560
- const w = overlayCanvas.width = innerWidth;
561
- const h = overlayCanvas.height = innerHeight;
562
-
563
- {
564
- // background
565
- const p3 = percent(t, 1, .8);
566
- const p4 = percent(t, 0, .5);
567
- const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.7);
568
- g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString());
569
- g.addColorStop(1,hsl(0,0,0,p3).toString());
570
- x.save();
571
- x.fillStyle = g;
572
- x.fillRect(0,0,w,h);
573
- }
574
-
575
- // draw LittleJS logo...
576
- const rect = (X, Y, W, H, C)=>
577
- {
578
- x.beginPath();
579
- x.rect(X,Y,W,C?H*p:H);
580
- x.fillStyle = C;
581
- C ? x.fill() : x.stroke();
582
- };
583
- const line = (X, Y, Z, W)=>
584
- {
585
- x.beginPath();
586
- x.lineTo(X,Y);
587
- x.lineTo(Z,W);
588
- x.stroke();
589
- };
590
- const circle = (X, Y, R, A=0, B=2*PI, C, F)=>
591
- {
592
- const D = (A+B)/2, E = p*(B-A)/2;
593
- x.beginPath();
594
- F && x.lineTo(X,Y);
595
- x.arc(X,Y,R,D-E,D+E);
596
- x.fillStyle = C;
597
- C ? x.fill() : x.stroke();
598
- };
599
- const color = (c=0, l=0) =>
600
- hsl([.98,.3,.57,.14][c%4]-10,.8,[0,.3,.5,.8,.9][l]).toString();
601
- const alpha = wave(1,1,t);
602
- const p = percent(alpha, .1, .5);
603
-
604
- // setup
605
- x.translate(w/2,h/2);
606
- const size = min(6, min(w,h)/99); // fit to screen
607
- x.scale(size,size);
608
- x.translate(-40,-35);
609
- x.lineJoin = x.lineCap = 'round';
610
- x.lineWidth = .1 + p*1.9;
611
-
612
- // drawing effect
613
- const p2 = percent(alpha,.1,1);
614
- x.setLineDash([99*p2,99]);
615
-
616
- // cab top
617
- rect(7,16,18,-8,color(2,2));
618
- rect(7,8,18,4,color(2,3));
619
- rect(25,8,8,8,color(2,1));
620
- rect(25,8,-18,8);
621
- rect(25,8,8,8);
622
-
623
- // cab
624
- rect(25,16,7,23,color());
625
- rect(11,39,14,-23,color(1,1));
626
- rect(11,16,14,18,color(1,2));
627
- rect(11,16,14,8,color(1,3));
628
- rect(25,16,-14,24);
629
-
630
- // cab window
631
- rect(15,29,6,-9,color(2,2));
632
- circle(15,21,5,0,PI/2,color(2,4),1);
633
- rect(21,21,-6,9);
634
-
635
- // little stack
636
- rect(37,14,9,6,color(3,2));
637
- rect(37,14,4.5,6,color(3,3));
638
- rect(37,14,9,6);
639
-
640
- // big stack
641
- rect(50,20,10,-8,color(0,1));
642
- rect(50,20,6.5,-8,color(0,2));
643
- rect(50,20,3.5,-8,color(0,3));
644
- rect(50,20,10,-8);
645
- circle(55,2,11.4,.5,PI-.5,color(3,3));
646
- circle(55,2,11.4,.5,PI/2,color(3,2),1);
647
- circle(55,2,11.4,.5,PI-.5);
648
- rect(45,7,20,-7,color(0,2));
649
- rect(45,-1,20,4,color(0,3));
650
- rect(45,-1,20,8);
651
-
652
- // engine
653
- for (let i=5; i--;)
654
- {
655
- // stagger radius to fix slight seam
656
- circle(60-i*6,30, 9.9,0,2*PI,color(i+2,3));
657
- circle(60-i*6,30,10.0,-.5,PI+.5,color(i+2,2));
658
- circle(60-i*6,30,10.1,.5,PI-.5,color(i+2,1));
659
- }
660
-
661
- // engine outline
662
- circle(36,30,10,PI/2,PI*3/2);
663
- circle(48,30,10,PI/2,PI*3/2);
664
- circle(60,30,10);
665
- line(36,20,60,20);
666
-
667
- // engine front light
668
- circle(60,30,4,PI,3*PI,color(3,2));
669
- circle(60,30,4,PI,2*PI,color(3,3));
670
- circle(60,30,4,PI,3*PI);
671
-
672
- // front brush
673
- for (let i=6; i--;)
674
- {
675
- x.beginPath();
676
- x.lineTo(53,54);
677
- x.lineTo(53,40);
678
- x.lineTo(53+(1+i*2.9)*p,40);
679
- x.lineTo(53+(4+i*3.5)*p,54);
680
- x.fillStyle = color(0,i%2+2);
681
- x.fill();
682
- i%2 && x.stroke();
683
- }
684
-
685
- // wheels
686
- rect(6,40,5,5);
687
- rect(6,40,5,5,color());
688
- rect(15,54,38,-14,color());
689
- for (let i=3; i--;)
690
- for (let j=2; j--;)
691
- {
692
- circle(15*i+15,47,j?7:1,PI,3*PI,color(i,3));
693
- x.stroke();
694
- circle(15*i+15,47,j?7:1,0,PI,color(i,2));
695
- x.stroke();
696
- }
697
- line(6,40,68,40); // center
698
- line(77,54,4,54); // bottom
699
-
700
- // draw engine name
701
- const s = engineName;
702
- x.font = '900 16px arial';
703
- x.textAlign = 'center';
704
- x.textBaseline = 'top';
705
- x.lineWidth = .1+p*3.9;
706
- let w2 = 0;
707
- for (let i=0; i<s.length; ++i)
708
- w2 += x.measureText(s[i]).width;
709
- for (let j=2; j--;)
710
- for (let i=0, X=41-w2/2; i<s.length; ++i)
711
- {
712
- x.fillStyle = color(i,2);
713
- const w = x.measureText(s[i]).width;
714
- x[j?'strokeText':'fillText'](s[i],X+w/2,55.5,17*p);
715
- X += w;
716
- }
717
-
718
- x.restore();
719
741
  }
@@ -194,7 +194,7 @@ function debugOverlap(posA, sizeA, posB, sizeB, color, time, screenSpace=false)
194
194
  }
195
195
 
196
196
  /** Draw a debug axis aligned bounding box in world space
197
- * @param {string} text
197
+ * @param {string|number} text
198
198
  * @param {Vector2} pos
199
199
  * @param {number} [size]
200
200
  * @param {Color|string} [color]
@@ -352,34 +352,46 @@ function debugRender()
352
352
  debugTakeScreenshot = 0;
353
353
  }
354
354
 
355
- if (debugGamepads && gamepadsEnable && navigator.getGamepads)
355
+ if (debugGamepads && gamepadsEnable)
356
356
  {
357
- // gamepad debug display
358
- const gamepads = navigator.getGamepads();
359
- for (let i = gamepads.length; i--;)
357
+ // draw gamepads
358
+ const maxGamepads = 8;
359
+ let gamepadConnectedCount = 0;
360
+ for (let i = 0; i < maxGamepads; i++)
361
+ gamepadConnected(i) && gamepadConnectedCount++;
362
+
363
+ for (let i = 0; i < maxGamepads; i++)
360
364
  {
361
- const gamepad = gamepads[i];
362
- if (gamepad)
365
+ if (!gamepadConnected(i))
366
+ continue;
367
+
368
+ const stickScale = 1;
369
+ const buttonScale = .2;
370
+ const cornerPos = cameraPos.add(vec2(-stickScale*2, ((gamepadConnectedCount-1)/2-i)*stickScale*3));
371
+ debugText(i, cornerPos.add(vec2(-stickScale, stickScale)), 1);
372
+ if (i === gamepadPrimary)
373
+ debugText('Main', cornerPos.add(vec2(-stickScale*2, 0)),1, '#0f0');
374
+
375
+ // read analog sticks
376
+ const stickCount = gamepadStickData[i].length;
377
+ for (let j = 0; j < stickCount; j++)
363
378
  {
364
- const stickScale = 1;
365
- const buttonScale = .2;
366
- const centerPos = cameraPos;
367
- const sticks = gamepadStickData[i];
368
- for (let j = sticks.length; j--;)
369
- {
370
- const drawPos = centerPos.add(vec2(j*stickScale*2, i*stickScale*3));
371
- const stickPos = drawPos.add(sticks[j].scale(stickScale));
372
- debugCircle(drawPos, stickScale*2, '#fff7',0,true);
373
- debugLine(drawPos, stickPos, '#f00');
374
- debugPoint(stickPos, '#f00');
375
- }
376
- for (let j = gamepad.buttons.length; j--;)
377
- {
378
- const drawPos = centerPos.add(vec2(j*buttonScale*2, i*stickScale*3-stickScale-buttonScale));
379
- const pressed = gamepad.buttons[j].pressed;
380
- debugCircle(drawPos, buttonScale*2, pressed ? '#f00' : '#fff7', 0, true);
381
- debugText(''+j, drawPos, .2);
382
- }
379
+ const stick = gamepadStick(j, i);
380
+ const drawPos = cornerPos.add(vec2(j*stickScale*2, 0));
381
+ const stickPos = drawPos.add(stick.scale(stickScale));
382
+ debugCircle(drawPos, stickScale*2, '#fff7',0,true);
383
+ debugLine(drawPos, stickPos, '#f00');
384
+ debugText(j, drawPos, .3);
385
+ debugPoint(stickPos, '#f00');
386
+ }
387
+
388
+ const buttonCount = inputData[i+1].length;
389
+ for (let j = 0; j < buttonCount; j++)
390
+ {
391
+ const drawPos = cornerPos.add(vec2(j*buttonScale*2, -stickScale-buttonScale*2));
392
+ const pressed = gamepadIsDown(j, i);
393
+ debugCircle(drawPos, buttonScale*2, pressed ? '#f00' : '#fff7', 0, true);
394
+ debugText(j, drawPos, .3);
383
395
  }
384
396
  }
385
397
  }
@@ -561,14 +573,18 @@ function debugRender()
561
573
  mousePressed && overlayContext.fillText('Mouse: ' + mousePressed, x, y += h);
562
574
  keysPressed && overlayContext.fillText('Keys: ' + keysPressed, x, y += h);
563
575
 
564
- let buttonsPressed = '';
565
- if (inputData[1])
566
- for (const i in inputData[1])
576
+ // show gamepad buttons
577
+ for (let i = 1; i < inputData.length; i++)
567
578
  {
568
- if (keyIsDown(i, 1))
569
- buttonsPressed += i + ' ' ;
579
+ let buttonsPressed = '';
580
+ if (inputData[i])
581
+ for (const j in inputData[i])
582
+ {
583
+ if (keyIsDown(j, i))
584
+ buttonsPressed += j + ' ' ;
585
+ }
586
+ buttonsPressed && overlayContext.fillText(`Gamepad ${i-1}: ` + buttonsPressed, x, y += h);
570
587
  }
571
- buttonsPressed && overlayContext.fillText('Gamepad: ' + buttonsPressed, x, y += h);
572
588
  }
573
589
  else
574
590
  {
package/src/engineDraw.js CHANGED
@@ -151,7 +151,7 @@ class TileInfo
151
151
  this.padding = padding;
152
152
  /** @property {TextureInfo} - The texture info for this tile */
153
153
  this.textureInfo = textureInfos[this.textureIndex];
154
- /** @property {float} - Shrinks tile by this many pixels to prevent neighbors bleeding */
154
+ /** @property {number} - Shrinks tile by this many pixels to prevent neighbors bleeding */
155
155
  this.bleedScale = bleedScale;
156
156
  }
157
157
 
@@ -638,7 +638,7 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
638
638
 
639
639
  /** Draw text on main canvas in world space
640
640
  * Automatically splits new lines into rows
641
- * @param {string} text
641
+ * @param {string|number} text
642
642
  * @param {Vector2} pos
643
643
  * @param {number} [size]
644
644
  * @param {Color} [color=(1,1,1,1)]
@@ -665,7 +665,7 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
665
665
 
666
666
  /** Draw text on overlay canvas in world space
667
667
  * Automatically splits new lines into rows
668
- * @param {string} text
668
+ * @param {string|number} text
669
669
  * @param {Vector2} pos
670
670
  * @param {number} [size]
671
671
  * @param {Color} [color=(1,1,1,1)]
@@ -684,7 +684,7 @@ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textA
684
684
 
685
685
  /** Draw text on overlay canvas in screen space
686
686
  * Automatically splits new lines into rows
687
- * @param {string} text
687
+ * @param {string|number} text
688
688
  * @param {Vector2} pos
689
689
  * @param {number} [size]
690
690
  * @param {Color} [color=(1,1,1,1)]
@@ -849,11 +849,28 @@ function worldToScreenTransform(worldPos, worldSize, worldAngle=0)
849
849
  ];
850
850
  }
851
851
 
852
- /** Get the camera's visible area in world space
852
+ /** Get the size of the camera window in world space
853
853
  * @return {Vector2}
854
854
  * @memberof Draw */
855
855
  function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
856
856
 
857
+ /** Check if a point or circle is on screen
858
+ * If size is a Vector2, uses the largest dimension as diameter
859
+ * This can be used to cull offscreen objects from render or update
860
+ * @param {Vector2} pos - world space position
861
+ * @param {Vector2|number} size - world space size or diameter
862
+ * @return {boolean}
863
+ * @memberof Draw */
864
+ function isOnScreen(pos, size=0)
865
+ {
866
+ pos = worldToScreen(pos);
867
+ if (size instanceof Vector2)
868
+ size = max(size.x, size.y); // use largest dimension
869
+ size *= cameraScale/2;
870
+ return pos.x + size > 0 && pos.x - size < mainCanvasSize.x &&
871
+ pos.y + size > 0 && pos.y - size < mainCanvasSize.y;
872
+ }
873
+
857
874
  /** Enable normal or additive blend mode
858
875
  * @param {boolean} [additive]
859
876
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
@@ -1017,7 +1034,7 @@ class FontImage
1017
1034
  }
1018
1035
 
1019
1036
  /** Draw text in world space using the image font
1020
- * @param {string} text
1037
+ * @param {string|number} text
1021
1038
  * @param {Vector2} pos
1022
1039
  * @param {number} [scale=.25]
1023
1040
  * @param {boolean} [center]
@@ -1029,7 +1046,7 @@ class FontImage
1029
1046
  }
1030
1047
 
1031
1048
  /** Draw text on overlay canvas in world space using the image font
1032
- * @param {string} text
1049
+ * @param {string|number} text
1033
1050
  * @param {Vector2} pos
1034
1051
  * @param {number} [scale]
1035
1052
  * @param {boolean} [center]
@@ -1038,7 +1055,7 @@ class FontImage
1038
1055
  { this.drawText(text, pos, scale, center, overlayContext); }
1039
1056
 
1040
1057
  /** Draw text on overlay canvas in screen space using the image font
1041
- * @param {string} text
1058
+ * @param {string|number} text
1042
1059
  * @param {Vector2} pos
1043
1060
  * @param {number} [scale]
1044
1061
  * @param {boolean} [center]