littlejsengine 1.14.7 → 1.14.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.
@@ -33,7 +33,7 @@ const engineName = 'LittleJS';
33
33
  * @type {string}
34
34
  * @default
35
35
  * @memberof Engine */
36
- const engineVersion = '1.14.7';
36
+ const engineVersion = '1.14.9';
37
37
 
38
38
  /** Frames per second to update
39
39
  * @type {number}
@@ -865,8 +865,11 @@ function nearestPowerOfTwo(value) { return 2**Math.ceil(Math.log2(value)); }
865
865
  * @memberof Utilities */
866
866
  function isOverlapping(posA, sizeA, posB, sizeB=vec2())
867
867
  {
868
- return abs(posA.x - posB.x)*2 < sizeA.x + sizeB.x
869
- && abs(posA.y - posB.y)*2 < sizeA.y + sizeB.y;
868
+ const dx = (posA.x - posB.x)*2;
869
+ const dy = (posA.y - posB.y)*2;
870
+ const sx = sizeA.x + sizeB.x;
871
+ const sy = sizeA.y + sizeB.y;
872
+ return dx >= -sx && dx < sx && dy >= -sy && dy < sy;
870
873
  }
871
874
 
872
875
  /** Returns true if a line segment is intersecting an axis aligned box
@@ -2097,7 +2100,7 @@ function setGLEnable(enable)
2097
2100
  glCanvas.style.visibility = enable ? 'visible' : 'hidden';
2098
2101
  }
2099
2102
 
2100
- /** Set how many sided polygons to use when drawing circles and elipses with WebGL
2103
+ /** Set how many sided polygons to use when drawing circles and ellipses with WebGL
2101
2104
  * @param {number} sides
2102
2105
  * @memberof Settings */
2103
2106
  function setGLCircleSides(sides) { glCircleSides = sides; }
@@ -2308,7 +2311,7 @@ class EngineObject
2308
2311
  /** @property {Vector2} - World space position of the object */
2309
2312
  this.pos = pos.copy();
2310
2313
  /** @property {Vector2} - World space width and height of the object */
2311
- this.size = size;
2314
+ this.size = size.copy();
2312
2315
  /** @property {Vector2} - Size of object used for drawing, uses size if not set */
2313
2316
  this.drawSize = undefined;
2314
2317
  /** @property {TileInfo} - Tile info to render object (undefined is untextured) */
@@ -2316,7 +2319,7 @@ class EngineObject
2316
2319
  /** @property {number} - Angle to rotate the object */
2317
2320
  this.angle = angle;
2318
2321
  /** @property {Color} - Color to apply when rendered */
2319
- this.color = color;
2322
+ this.color = color.copy();
2320
2323
  /** @property {Color} - Additive color to apply when rendered */
2321
2324
  this.additiveColor = undefined;
2322
2325
  /** @property {boolean} - Should it flip along y axis when rendered */
@@ -2440,7 +2443,7 @@ class EngineObject
2440
2443
  for (const o of engineObjectsCollide)
2441
2444
  {
2442
2445
  // non solid objects don't collide with each other
2443
- if (!this.isSolid && !o.isSolid || o.destroyed || o.parent || o === this)
2446
+ if ((!this.isSolid && !o.isSolid) || o.destroyed || o.parent || o === this)
2444
2447
  continue;
2445
2448
 
2446
2449
  // check collision
@@ -2479,7 +2482,7 @@ class EngineObject
2479
2482
  {
2480
2483
  // push outside object collision
2481
2484
  this.pos.y = o.pos.y + (sizeBoth.y/2 + epsilon) * sign(oldPos.y - o.pos.y);
2482
- if (o.groundObject && wasMovingDown || !o.mass)
2485
+ if ((o.groundObject && wasMovingDown) || !o.mass)
2483
2486
  {
2484
2487
  // set ground object if landed on something
2485
2488
  if (wasMovingDown)
@@ -3630,7 +3633,7 @@ class FontImage
3630
3633
  * @param {Vector2} pos
3631
3634
  * @param {number} [scale=.25]
3632
3635
  * @param {boolean} [center]
3633
- * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D}[context=drawContext]
3636
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
3634
3637
  */
3635
3638
  drawText(text, pos, scale=1, center, context=drawContext)
3636
3639
  {
@@ -3897,6 +3900,7 @@ function inputInit()
3897
3900
  document.addEventListener('wheel', onMouseWheel);
3898
3901
  document.addEventListener('contextmenu', onContextMenu);
3899
3902
  document.addEventListener('blur', onBlur);
3903
+ document.addEventListener('mouseleave', onMouseLeave);
3900
3904
 
3901
3905
  // init touch input
3902
3906
  if (isTouchDevice && touchInputEnable)
@@ -3955,6 +3959,12 @@ function inputInit()
3955
3959
  function onMouseWheel(e) { mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY); }
3956
3960
  function onContextMenu(e) { e.preventDefault(); } // prevent right click menu
3957
3961
  function onBlur() { inputClear(); } // reset input when focus is lost
3962
+ function onMouseLeave()
3963
+ {
3964
+ // set mouse position and delta when leaving canvas
3965
+ mousePosScreen = vec2(Infinity);
3966
+ mouseDeltaScreen = vec2(0);
3967
+ }
3958
3968
  }
3959
3969
 
3960
3970
  // convert a mouse or touch event position to screen space
@@ -5076,7 +5086,7 @@ class TileLayerData
5076
5086
  /** @property {boolean} - If the tile should be mirrored along the x axis */
5077
5087
  this.mirror = mirror;
5078
5088
  /** @property {Color} - Color of the tile */
5079
- this.color = color;
5089
+ this.color = color.copy();
5080
5090
  }
5081
5091
 
5082
5092
  /** Set this tile to clear, it will not be rendered */
@@ -5175,7 +5185,7 @@ class CanvasLayer extends EngineObject
5175
5185
  * @param {TileInfo} [tileInfo]
5176
5186
  * @param {Color} [color=(1,1,1,1)]
5177
5187
  * @param {number} [angle=0]
5178
- * @param {boolean} [mirror=0] */
5188
+ * @param {boolean} [mirror=false] */
5179
5189
  drawTile(pos, size=vec2(1), tileInfo, color=new Color, angle, mirror)
5180
5190
  {
5181
5191
  this.drawCanvas2D(pos, size, angle, mirror, (context)=>
@@ -6773,7 +6783,7 @@ function glPolyStrip(points)
6773
6783
  while (indices.length > 3 && attempts++ < maxAttempts)
6774
6784
  {
6775
6785
  let foundEar = false;
6776
- for (let i = indices.length; --i;)
6786
+ for (let i = 0; i < indices.length; i++)
6777
6787
  {
6778
6788
  const i0 = indices[(i + indices.length - 1) % indices.length];
6779
6789
  const i1 = indices[i];
@@ -6810,7 +6820,7 @@ function glPolyStrip(points)
6810
6820
  if (!foundEar)
6811
6821
  {
6812
6822
  let worstIndex = -1, worstValue = Infinity;
6813
- for (let i = indices.length; --i;)
6823
+ for (let i = 0; i < indices.length; i++)
6814
6824
  {
6815
6825
  const i0 = indices[(i + indices.length - 1) % indices.length];
6816
6826
  const i1 = indices[i];
@@ -7373,6 +7383,10 @@ class UISystemPlugin
7373
7383
  this.uiObjects = [];
7374
7384
  /** @property {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} - Context to render UI elements to */
7375
7385
  this.uiContext = context;
7386
+ /** @property {UIObject} - Top most object user is over */
7387
+ this.hoverObject = undefined;
7388
+ /** @property {UIObject} - Object user is currently interacting with */
7389
+ this.activeObject = undefined;
7376
7390
 
7377
7391
  engineAddPlugin(uiUpdate, uiRender);
7378
7392
 
@@ -7402,6 +7416,8 @@ class UISystemPlugin
7402
7416
  else
7403
7417
  updateInvisibleObject(o);
7404
7418
  }
7419
+ // reset hover object at start of update
7420
+ uiSystem.hoverObject = undefined;
7405
7421
  for (let i = uiSystem.uiObjects.length; i--;)
7406
7422
  {
7407
7423
  const o = uiSystem.uiObjects[i];
@@ -7513,6 +7529,8 @@ class UIObject
7513
7529
  this.size = size.copy();
7514
7530
  /** @property {Color} - Color of the object */
7515
7531
  this.color = uiSystem.defaultColor;
7532
+ /** @property {Color} - Color of the object when active, uses color if undefined */
7533
+ this.activeColor = undefined;
7516
7534
  /** @property {string} - Text for this ui object */
7517
7535
  this.text = undefined;
7518
7536
  /** @property {Color} - Color when disabled */
@@ -7543,7 +7561,7 @@ class UIObject
7543
7561
  this.children = [];
7544
7562
  /** @property {UIObject} - This object's parent, position is in parent space */
7545
7563
  this.parent = undefined;
7546
- /** @property {number} - Extra size added to make small buttons easier to touch on mobile devices */
7564
+ /** @property {number} - Added size to make small buttons easier to touch on mobile devices */
7547
7565
  this.extraTouchSize = 0;
7548
7566
  /** @property {Sound} - Sound when interactive element is pressed */
7549
7567
  this.soundPress = uiSystem.defaultSoundPress;
@@ -7551,12 +7569,10 @@ class UIObject
7551
7569
  this.soundRelease = uiSystem.defaultSoundRelease;
7552
7570
  /** @property {Sound} - Sound when interactive element is clicked */
7553
7571
  this.soundClick = uiSystem.defaultSoundClick;
7554
- /** @property {boolean} - Is the mouse over this element */
7555
- this.mouseIsOver = false;
7556
7572
  /** @property {boolean} - Is this element interactive */
7557
7573
  this.interactive = false;
7558
- /** @property {boolean} - Is the mouse held on this element (was pressed while over and hasn't been released) */
7559
- this.mouseIsHeld = false;
7574
+ /** @property {boolean} - Activate when dragged over with mouse held down */
7575
+ this.dragActivate = false;
7560
7576
  uiSystem.uiObjects.push(this);
7561
7577
  }
7562
7578
 
@@ -7583,15 +7599,18 @@ class UIObject
7583
7599
  /** Update the object, called automatically by plugin once each frame */
7584
7600
  update()
7585
7601
  {
7586
- const mouseWasOver = this.mouseIsOver;
7587
- const mousePress = mouseWasPressed(0);
7602
+ const wasHover = this.isHoverObject();
7603
+ const isActive = this.isActiveObject();
7588
7604
  const mouseDown = mouseIsDown(0);
7589
- if (mousePress || !mouseDown || this.mouseIsHeld)
7605
+ const mousePress = this.dragActivate ? mouseDown : mouseWasPressed(0);
7606
+ if (!uiSystem.hoverObject)
7607
+ if (mousePress || !mouseDown || isActive)
7590
7608
  {
7591
7609
  const size = this.size.add(vec2(isTouchDevice && this.extraTouchSize || 0));
7592
- this.mouseIsOver = isOverlapping(this.pos, size, mousePosScreen);
7610
+ if (isOverlapping(this.pos, size, mousePosScreen))
7611
+ uiSystem.hoverObject = this;
7593
7612
  }
7594
- if (this.mouseIsOver)
7613
+ if (this.isHoverObject())
7595
7614
  {
7596
7615
  if (mousePress)
7597
7616
  inputClearKey(0,0,0,1,0); // clear mouse was pressed state
@@ -7604,10 +7623,12 @@ class UIObject
7604
7623
  this.onPress();
7605
7624
  if (this.soundPress)
7606
7625
  this.soundPress.play();
7626
+ if (uiSystem.activeObject && !isActive)
7627
+ uiSystem.activeObject.onRelease();
7628
+ uiSystem.activeObject = this;
7607
7629
  }
7608
- this.mouseIsHeld = true;
7609
7630
  }
7610
- if (!mouseDown && this.mouseIsHeld && this.interactive)
7631
+ if (!mouseDown && uiSystem.activeObject === this && this.interactive)
7611
7632
  {
7612
7633
  this.onClick();
7613
7634
  if (this.soundClick)
@@ -7615,35 +7636,35 @@ class UIObject
7615
7636
  }
7616
7637
  }
7617
7638
  }
7618
- if (!mouseDown)
7639
+ if (isActive)
7640
+ if (!mouseDown || (this.dragActivate && !this.isHoverObject()))
7619
7641
  {
7620
- if (this.mouseIsHeld && this.interactive)
7621
- {
7622
- this.onRelease();
7623
- if (this.soundRelease)
7624
- this.soundRelease.play();
7625
- }
7626
- if (isTouchDevice)
7627
- this.mouseIsOver = false;
7628
- this.mouseIsHeld = false;
7642
+ this.onRelease();
7643
+ if (this.soundRelease)
7644
+ this.soundRelease.play();
7645
+ uiSystem.activeObject = undefined;
7629
7646
  }
7630
7647
 
7631
- if (this.mouseIsOver !== mouseWasOver)
7632
- this.mouseIsOver ? this.onEnter() : this.onLeave();
7648
+ if (this.isHoverObject() !== wasHover)
7649
+ this.isHoverObject() ? this.onEnter() : this.onLeave();
7633
7650
  }
7634
7651
 
7635
7652
  /** Render the object, called automatically by plugin once each frame */
7636
7653
  render()
7637
7654
  {
7638
- if (this.size.x && this.size.y)
7639
- uiSystem.drawRect(this.pos, this.size, this.color, this.lineWidth, this.lineColor, this.cornerRadius);
7655
+ if (!this.size.x || !this.size.y) return;
7656
+
7657
+ const lineColor = this.interactive && this.isActiveObject() && !this.disabled ? this.color : this.lineColor;
7658
+ const color = this.interactive ? this.disabled ? this.disabledColor : this.isActiveObject() ? this.activeColor || this.color : this.isHoverObject() ? this.hoverColor : this.color : this.color;
7659
+ uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor, this.cornerRadius);
7640
7660
  }
7641
7661
 
7642
7662
  /** Special update when object is not visible */
7643
7663
  updateInvisible()
7644
7664
  {
7645
7665
  // reset input state when not visible
7646
- this.mouseIsOver = this.mouseIsHeld = false;
7666
+ if (this.isActiveObject())
7667
+ uiSystem.activeObject = undefined;
7647
7668
  }
7648
7669
 
7649
7670
  /** Get the size for text with overrides and scale
@@ -7656,6 +7677,12 @@ class UIObject
7656
7677
  this.textHeight || this.textScale * this.size.y);
7657
7678
  }
7658
7679
 
7680
+ /** @return {boolean} - Is the mouse hovering over this element */
7681
+ isHoverObject() { return uiSystem.hoverObject === this; }
7682
+
7683
+ /** @return {boolean} - Is the mouse held onto this element */
7684
+ isActiveObject() { return uiSystem.activeObject === this; }
7685
+
7659
7686
  /** Called when the mouse enters the object */
7660
7687
  onEnter() {}
7661
7688
 
@@ -7766,10 +7793,7 @@ class UIButton extends UIObject
7766
7793
  }
7767
7794
  render()
7768
7795
  {
7769
- // draw the button
7770
- const lineColor = this.mouseIsHeld && !this.disabled ? this.color : this.lineColor;
7771
- const color = this.disabled ? this.disabledColor : this.mouseIsOver ? this.hoverColor : this.color;
7772
- uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor, this.cornerRadius);
7796
+ super.render();
7773
7797
 
7774
7798
  // draw the text scaled to fit
7775
7799
  const textSize = this.getTextSize();
@@ -7810,8 +7834,7 @@ class UICheckbox extends UIObject
7810
7834
  }
7811
7835
  render()
7812
7836
  {
7813
- const color = this.disabled ? this.disabledColor : this.mouseIsOver ? this.hoverColor : this.color;
7814
- uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, this.lineColor, this.cornerRadius);
7837
+ super.render();
7815
7838
  if (this.checked)
7816
7839
  {
7817
7840
  const p = this.cornerRadius / min(this.size.x, this.size.y) * 2;
@@ -7861,7 +7884,7 @@ class UIScrollbar extends UIObject
7861
7884
  update()
7862
7885
  {
7863
7886
  super.update();
7864
- if (this.mouseIsHeld && this.interactive)
7887
+ if (this.isActiveObject() && this.interactive)
7865
7888
  {
7866
7889
  // check if value changed
7867
7890
  const handleSize = vec2(this.size.y);
@@ -7875,12 +7898,7 @@ class UIScrollbar extends UIObject
7875
7898
  }
7876
7899
  render()
7877
7900
  {
7878
- // draw the scrollbar background
7879
- const lineColor = this.interactive && this.mouseIsHeld && !this.disabled ?
7880
- this.color : this.lineColor;
7881
- const color = this.disabled ? this.disabledColor :
7882
- this.interactive && this.mouseIsHeld ? this.hoverColor : this.color;
7883
- uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor, this.cornerRadius);
7901
+ super.render();
7884
7902
 
7885
7903
  // draw the scrollbar handle
7886
7904
  const handleSize = vec2(this.size.y);
@@ -7889,7 +7907,7 @@ class UIScrollbar extends UIObject
7889
7907
  const p2 = this.pos.x + handleWidth/2;
7890
7908
  const handlePos = vec2(lerp(p1, p2, this.value), this.pos.y);
7891
7909
  const handleColor = this.disabled ? this.disabledColor :
7892
- this.interactive && this.mouseIsHeld ? this.color : this.handleColor;
7910
+ this.interactive && this.isActiveObject() ? this.color : this.handleColor;
7893
7911
  uiSystem.drawRect(handlePos, handleSize, handleColor, this.lineWidth, this.lineColor, this.cornerRadius);
7894
7912
 
7895
7913
  // draw the text scaled to fit on the scrollbar
@@ -72,12 +72,12 @@ const exampleList =
72
72
  [
73
73
  new ExampleInfo('--- SHORT EXAMPLES ---'),
74
74
  new ExampleInfo('Hello World', 'helloWorld.js', 'Minimal starter example'),
75
- new ExampleInfo('Starfield', 'starfield.js', 'Animated parallax starfield'),
75
+ new ExampleInfo('Texture', 'texture.js', 'Texture display and manipulation'),
76
+ new ExampleInfo('Animation', 'animation.js', 'Sprite animation system'),
76
77
  new ExampleInfo('Shapes', 'shapes.js', 'Draw geometric shapes and primitives'),
77
78
  new ExampleInfo('Colors', 'colors.js', 'Color manipulation and HSL'),
78
- new ExampleInfo('Texture', 'texture.js', 'Texture display and manipulation'),
79
+ new ExampleInfo('Starfield', 'starfield.js', 'Animated parallax starfield'),
79
80
  new ExampleInfo('Sprite Atlas', 'spriteAtlas.js', 'Sprite atlas and tile rendering'),
80
- new ExampleInfo('Animation', 'animation.js', 'Sprite animation system'),
81
81
  new ExampleInfo('Blending', 'blending.js', 'Additive blending and transparency'),
82
82
  new ExampleInfo('Tile Layer', 'tileLayer.js', 'Tile layer rendering system'),
83
83
  new ExampleInfo('Particles', 'particles.js', 'Particle system'),
@@ -423,7 +423,7 @@ function setCode(code, filename)
423
423
  {
424
424
  // try to extract line number from stack trace
425
425
  // look for <anonymous> or injectedScript to find user code
426
- const anonymousMatch = stack.match(/(<anonymous>|injectedScript):(\d+)/);
426
+ const anonymousMatch = stack?.match(/(<anonymous>|injectedScript):(\d+)/);
427
427
  return anonymousMatch ? parseInt(anonymousMatch[2]) : -1;
428
428
  }
429
429
  iframeContent.onerror = (message, source, lineno, colno, error) =>
@@ -520,7 +520,12 @@ function setCode(code, filename)
520
520
  const errorLine = getErrorLine(error.stack);
521
521
  if (errorLine >= 0)
522
522
  setErrorLine(errorLine);
523
- setErrorMessage(error.message + '\n' + error.stack);
523
+ let message = error;
524
+ if (error.message)
525
+ message = error.message;
526
+ if (error.stack)
527
+ message += '\n' + error.stack;
528
+ setErrorMessage(message);
524
529
  throw error;
525
530
  })
526
531
  .then(()=>
@@ -36,7 +36,11 @@ export const persistentParticleDestroyCallback = (particle)=>
36
36
  // copy particle to tile layer on death
37
37
  LJS.ASSERT(!particle.tileInfo, 'quick draw to tile layer uses canvas 2d so must be untextured');
38
38
  if (particle.groundObject)
39
+ {
39
40
  GameLevel.foregroundTileLayer.drawTile(particle.pos, particle.size, particle.tileInfo, particle.color, particle.angle, particle.mirror);
41
+ // update WebGL texture
42
+ GameLevel.foregroundTileLayer.useWebGL();
43
+ }
40
44
  }
41
45
 
42
46
  export function makeBlood(pos, amount) { makeDebris(pos, hsl(0,1,.5), amount, .1, 0); }
@@ -83,7 +87,7 @@ export function explosion(pos, radius=3)
83
87
  }
84
88
 
85
89
  // update WebGL texture
86
- GameLevel.updateWebGL(GameLevel.foregroundTileLayer);
90
+ GameLevel.foregroundTileLayer.useWebGL();
87
91
 
88
92
  // kill/push objects
89
93
  LJS.engineObjectsCallback(pos, radius*3, (o)=>
@@ -159,7 +163,7 @@ export function destroyTile(pos, makeSound = 1, cleanup = 1)
159
163
  for (let i=-1;i<=1;++i)
160
164
  for (let j=-1;j<=1;++j)
161
165
  GameLevel.decorateTile(pos.add(vec2(i,j)));
162
- GameLevel.updateWebGL(layer);
166
+ layer.useWebGL();
163
167
  }
164
168
 
165
169
  return true;
@@ -14,7 +14,6 @@ import * as GameEffects from './gameEffects.js';
14
14
  import * as Game from './game.js';
15
15
  const {vec2, hsl, tile} = LJS;
16
16
 
17
- export const useWebGLTileLayers = true;
18
17
  export const tileType_ladder = -1;
19
18
  export const tileType_empty = 0;
20
19
  export const tileType_solid = 1;
@@ -138,7 +137,7 @@ function loadLevelData()
138
137
  for (pos.x=levelSize.x; pos.x--;)
139
138
  for (pos.y=levelSize.y; pos.y--;)
140
139
  decorateTile(pos, layer);
141
- updateWebGL(tileLayers[layer]);
140
+ tileLayers[layer].useWebGL();
142
141
  }
143
142
  }
144
143
 
@@ -204,9 +203,4 @@ export function getCameraTarget()
204
203
  // camera is above player
205
204
  const offset = 100/LJS.cameraScale*LJS.percent(LJS.mainCanvasSize.y, 300, 600);
206
205
  return Game.player.pos.add(vec2(0, offset));
207
- }
208
-
209
- export function updateWebGL(layer)
210
- {
211
- layer.useWebGL(useWebGLTileLayers);
212
206
  }
@@ -38,7 +38,8 @@ class CarObject extends Box2dObject
38
38
 
39
39
  // car controls - use mouse, arrow keys, or A/D to drive
40
40
  const maxSpeed = 40;
41
- const input = keyDirection().x || mouseIsDown(0);
41
+ const input = mouseIsDown(0) ? 1 :
42
+ mouseIsDown(2) ? -1 : keyDirection().x ;
42
43
  let s = this.wheels[0].motorJoint.getMotorSpeed();
43
44
  s = input ? clamp(s - input, -maxSpeed, maxSpeed) : 0;
44
45
  this.wheels[0].motorJoint.setMotorSpeed(s);
@@ -1,51 +1,50 @@
1
- const pianoSound = new Sound([,0,220,,9]), keys = [];
1
+ const pianoSound = new Sound([,0,220,,9]);
2
2
 
3
- class PianoKey extends EngineObject
3
+ class PianoKey extends UIButton
4
4
  {
5
5
  constructor(pos, size, semitone, isWhite)
6
6
  {
7
- const keySize = 2;
7
+ const keySize = 65;
8
8
  size = size.scale(keySize);
9
- pos = pos.scale(keySize).add(vec2(0,4-size.y/2));
10
- super(pos, size, 0, 0, hsl(0,0,isWhite ? 1 : .3));
11
- this.drawSize = size.subtract(vec2(.1,0));
9
+ pos = pos.scale(keySize).add(vec2(0,size.y/2-keySize*2));
10
+ pos = pos.add(mainCanvasSize.scale(.5));
11
+ super(pos, size, '', hsl(0,0,isWhite ? 1 : .3));
12
12
  this.semitone = semitone;
13
+ this.dragActivate = true;
14
+ this.hoverColor = hsl(0,1,isWhite ? .9 : .3);
15
+ this.activeColor = RED;
13
16
  }
14
- press()
17
+ onPress()
15
18
  {
16
19
  if (!this.soundInstance)
17
20
  this.soundInstance = pianoSound.playNote(this.semitone);
18
21
  }
19
- release()
22
+ onRelease()
20
23
  {
21
24
  if (this.soundInstance)
22
25
  this.soundInstance.stop(.2);
23
26
  this.soundInstance = 0;
24
27
  }
25
- render()
26
- {
27
- const color = this.soundInstance ? RED : this.color;
28
- drawRect(this.pos, this.drawSize, color);
29
- }
30
28
  }
31
29
 
32
30
  function gameInit()
33
31
  {
32
+ // initialize UI system
33
+ new UISystemPlugin;
34
+
34
35
  // create piano keyboard
35
36
  for (let i=15; i--;)
36
- keys.unshift(new PianoKey(vec2(i-7,0), vec2(1,4), [0,2,4,5,7,9,11][i%7]+(i/7|0)*12, 1));
37
+ {
38
+ const pos = vec2(i-7,0);
39
+ const size = vec2(1,4);
40
+ const semitone = [0,2,4,5,7,9,11][i%7]+(i/7|0)*12;
41
+ new PianoKey(pos, size, semitone, true);
42
+ }
37
43
  for (let i=10; i--;)
38
- keys.unshift(new PianoKey(vec2([1,2,4,5,6][i%5]+(i/5|0)*7-7.5,0), vec2(1,2), [1,3,6,8,10][i%5]+(i/5|0)*12));
39
- }
40
-
41
- function gameUpdate()
42
- {
43
- // update state of piano keys
44
- const pressedKey = keys.find(k=>k.soundInstance);
45
- const newPressedKey = mouseIsDown(0) && keys.find(k=>isOverlapping(k.pos, k.size, mousePos));
46
- if (newPressedKey != pressedKey)
47
44
  {
48
- pressedKey && pressedKey.release();
49
- newPressedKey && newPressedKey.press();
45
+ const pos = vec2([1,2,4,5,6][i%5]+(i/5|0)*7-7.5,0)
46
+ const size = vec2(1,2);
47
+ const semitone = [1,3,6,8,10][i%5]+(i/5|0)*12;
48
+ new PianoKey(pos, size, semitone);
50
49
  }
51
50
  }
@@ -1,38 +1,26 @@
1
- class SoundButton extends EngineObject
1
+ function gameInit()
2
2
  {
3
- constructor(pos, icon, sound)
4
- {
5
- super(pos, vec2(5,4));
6
- this.icon = icon;
7
- this.sound = sound;
8
- }
9
-
10
- update()
11
- {
12
- // play sound when clicked
13
- if (mouseWasPressed(0) && isOverlapping(this.pos, this.size, mousePos))
14
- this.sound.play(this.pos);
15
- }
3
+ // initialize UI system
4
+ new UISystemPlugin;
5
+ canvasClearColor = hsl(.6,.3,.2);
16
6
 
17
- render()
7
+ // create a grid of buttons with different sounds
8
+ const w = 200, h = 150, gap = 20;
9
+ function makeSoundButton(pos, icon, sound)
18
10
  {
19
- // draw the button and icon
20
- drawRect(this.pos, this.size, hsl(0,0,.8));
21
- drawTextOverlay(this.icon, this.pos, 3);
11
+ pos = pos.multiply(vec2(w+gap, h+gap));
12
+ pos = pos.add(mainCanvasSize.scale(.5));
13
+ const button = new UIButton(pos, vec2(w, h), icon);
14
+ button.textHeight = 100;
15
+ button.onClick = ()=> sound.play();
22
16
  }
23
- }
24
-
25
- function gameInit()
26
- {
27
- // create a grid of buttons with different sounds
28
- new SoundButton(vec2(-6, 5),'💰', new Sound([,,1675,,.06,.24,1,1.82,,,837,.06]));
29
- new SoundButton(vec2( 0, 5),'🥊', new Sound([,,925,.04,.3,.6,1,.3,,6.27,-184,.09,.17]));
30
- new SoundButton(vec2( 6, 5),'', new Sound([,,539,0,.04,.29,1,1.92,,,567,.02,.02,,,,.04]));
31
- new SoundButton(vec2(-6, 0),'🐁', new Sound([,.2,1e3,.02,,.01,2,,18,,475,.01,.01]));
32
- new SoundButton(vec2( 0, 0),'🎹', new Sound([1.5,.5,270,,.1,,1,1.5,,,,,,,,.1,.01]));
33
- new SoundButton(vec2( 6, 0),'🏌️', new Sound([,,150,.05,,.05,,1.3,,,,,,3]));
34
- new SoundButton(vec2(-6,-5),'🌊', new Sound([,.2,40,.5,,1.5,,11,,,,,,199]));
35
- new SoundButton(vec2( 0,-5),'🛰️', new Sound([,.5,847,.02,.3,.9,1,1.67,,,-294,.04,.13,,,,.1]));
36
- new SoundButton(vec2( 6,-5),'⚡', new Sound([,,471,,.09,.47,4,1.06,-6.7,,,,,.9,61,.1,,.82,.1]));
37
- canvasClearColor = hsl(.6,.3,.2); // background color
17
+ makeSoundButton(vec2(-1, 1),'💰', new Sound([,,1675,,.06,.24,1,1.82,,,837,.06]));
18
+ makeSoundButton(vec2( 0, 1),'🥊', new Sound([,,925,.04,.3,.6,1,.3,,6.27,-184,.09,.17]));
19
+ makeSoundButton(vec2( 1, 1),'✨', new Sound([,,539,0,.04,.29,1,1.92,,,567,.02,.02,,,,.04]));
20
+ makeSoundButton(vec2(-1, 0),'🐁', new Sound([,.2,1e3,.02,,.01,2,,18,,475,.01,.01]));
21
+ makeSoundButton(vec2( 0, 0),'🎹', new Sound([1.5,.5,270,,.1,,1,1.5,,,,,,,,.1,.01]));
22
+ makeSoundButton(vec2( 1, 0),'🏌️', new Sound([,,150,.05,,.05,,1.3,,,,,,3]));
23
+ makeSoundButton(vec2(-1,-1),'🌊', new Sound([,.2,40,.5,,1.5,,11,,,,,,199]));
24
+ makeSoundButton(vec2( 0,-1),'🛰️', new Sound([,.5,847,.02,.3,.9,1,1.67,,,-294,.04,.13,,,,.1]));
25
+ makeSoundButton(vec2( 1,-1),'', new Sound([,,471,,.09,.47,4,1.06,-6.7,,,,,.9,61,.1,,.82,.1]));
38
26
  }
@@ -10,22 +10,29 @@ function gameInit()
10
10
  uiSystem.defaultSoundClick = new Sound([1,0,440]);
11
11
  uiSystem.defaultCornerRadius = 8;
12
12
 
13
+ // example button that returns to menu
14
+ const buttonBack = new UIButton(mainCanvasSize.scale(.5), vec2(200), 'Back\nto\nMenu');
15
+ buttonBack.textHeight = 60;
16
+ buttonBack.onClick = ()=> uiMenu.visible = true;
17
+
13
18
  // setup example menu
14
- const uiMenu = new UIObject(mainCanvasSize.scale(.5), vec2(450));
19
+ const uiMenu = new UIObject(mainCanvasSize.scale(.5), vec2(600,450));
15
20
  canvasClearColor = hsl(0,0,.8);
16
21
 
17
22
  // example text
18
- const textTitle = new UIText(vec2(0,-120), vec2(400, 60), 'LittleJS UI\nSystem Demo');
19
- uiMenu.addChild(textTitle);
23
+ uiMenu.addChild(new UIText(vec2(-80,-120), vec2(500, 80), 'LittleJS UI\nSystem Demo'));
24
+
25
+ // example image
26
+ uiMenu.addChild(new UITile(vec2(200,-140), vec2(128), tile(3, 128)));
20
27
 
21
28
  // example button
22
- const button1 = new UIButton(vec2(70,0), vec2(140, 80), 'Test');
29
+ const button1 = new UIButton(vec2(80,0), vec2(140, 80), 'Test');
23
30
  button1.textHeight = 60;
24
31
  uiMenu.addChild(button1);
25
- button1.onClick = ()=> uiBackground.color = randColor();
32
+ button1.onClick = ()=> canvasClearColor = randColor();
26
33
 
27
34
  // example checkbox
28
- const checkbox = new UICheckbox(vec2(-70,0), vec2(50));
35
+ const checkbox = new UICheckbox(vec2(-80,0), vec2(50));
29
36
  checkbox.onChange = ()=> button1.disabled = checkbox.checked;
30
37
  uiMenu.addChild(checkbox);
31
38
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "littlejsengine",
3
- "version": "1.14.7",
3
+ "version": "1.14.9",
4
4
  "description": "LittleJS - Tiny and Fast HTML5 Game Engine",
5
5
  "main": "dist/littlejs.esm.js",
6
6
  "types": "dist/littlejs.d.ts",