littlejsengine 1.17.11 → 1.17.15

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 (48) hide show
  1. package/AI.md +162 -0
  2. package/README.md +50 -63
  3. package/dist/littlejs.d.ts +150 -68
  4. package/dist/littlejs.esm.js +323 -105
  5. package/dist/littlejs.esm.min.js +1 -1
  6. package/dist/littlejs.js +320 -104
  7. package/dist/littlejs.min.js +1 -1
  8. package/dist/littlejs.release.js +318 -101
  9. package/examples/electron/{build.js → build.mjs} +26 -23
  10. package/examples/electron/game.js +5 -50
  11. package/examples/electron/index.html +2 -2
  12. package/examples/index.html +1 -0
  13. package/examples/module/{build.js → build.mjs} +22 -19
  14. package/examples/shorts/base.html +1 -1
  15. package/examples/shorts/hillGlideGame.js +1 -1
  16. package/examples/shorts/nineSlice.js +3 -3
  17. package/examples/shorts/shapes.js +1 -1
  18. package/examples/shorts/uiSystem.js +17 -16
  19. package/examples/starter/build.bat +1 -1
  20. package/examples/starter/{build.js → build.mjs} +25 -22
  21. package/examples/starter/index.html +2 -2
  22. package/examples/typescript/build.mjs +60 -0
  23. package/examples/typescript/game.js +2 -2
  24. package/examples/typescript/tsconfig.json +11 -2
  25. package/package.json +2 -2
  26. package/plugins/pluginExport.js +1 -0
  27. package/plugins/uiSystem.js +143 -20
  28. package/reference.md +0 -1
  29. package/src/engine.js +16 -14
  30. package/src/engineAudio.js +9 -6
  31. package/src/{engineBuild.js → engineBuild.mjs} +95 -78
  32. package/src/engineDebug.js +8 -5
  33. package/src/engineDraw.js +16 -14
  34. package/src/engineExport.js +2 -1
  35. package/src/engineInput.js +29 -8
  36. package/src/engineMath.js +46 -14
  37. package/src/engineMedals.js +6 -3
  38. package/src/engineObject.js +14 -3
  39. package/src/engineParticles.js +8 -3
  40. package/src/engineRelease.js +6 -2
  41. package/src/engineTileLayer.js +8 -4
  42. package/src/engineUtilities.js +6 -2
  43. package/src/engineWebGL.js +11 -8
  44. package/src/jsconfig.json +2 -1
  45. package/examples/electron/build.bat +0 -7
  46. package/examples/module/build.bat +0 -7
  47. package/examples/typescript/build.bat +0 -5
  48. package/examples/typescript/build.js +0 -31
package/dist/littlejs.js CHANGED
@@ -8,18 +8,20 @@
8
8
  * MIT License - Copyright 2021 Frank Force
9
9
  *
10
10
  * Engine Features
11
- * - Object oriented system with base class engine object
12
- * - Base class object handles update, physics, collision, rendering, etc
13
- * - Engine helper classes and functions like Vector2, Color, and Timer
14
- * - Super fast rendering system for tile sheets
15
- * - Sound effects audio with zzfx and music with zzfxm
16
- * - Input processing system with gamepad and touchscreen support
17
- * - Tile layer rendering and collision system
18
- * - Particle effect system
19
- * - Medal system tracks and displays achievements
20
- * - Debug tools and debug rendering system
21
- * - Post processing effects
22
- * - Call engineInit() to start it up!
11
+ * - Object oriented system with EngineObject base class
12
+ * - Automatic object lifecycle (update, physics, collision, rendering)
13
+ * - Engine helper classes: Vector2, Color, Timer, RandomGenerator
14
+ * - Hybrid rendering with WebGL batching and Canvas2D fallback
15
+ * - Audio system with wave, mp3, or ZzFX sound effects
16
+ * - Input system with keyboard, mouse, gamepad, and touch support
17
+ * - Tile layer rendering and collision detection
18
+ * - Particle effect system with emitters
19
+ * - Medal/achievement system with local storage
20
+ * - Comprehensive debug tools and visualizations
21
+ * - Fixed 60 FPS timestep with configurable time scale
22
+ * - Raycast and spatial query utilities
23
+ * - Plugin system for extending engine functionality
24
+ * - Start with engineInit() and provide your game callbacks
23
25
  * @namespace Engine
24
26
  */
25
27
 
@@ -33,7 +35,7 @@ const engineName = 'LittleJS';
33
35
  * @type {string}
34
36
  * @default
35
37
  * @memberof Engine */
36
- const engineVersion = '1.17.11';
38
+ const engineVersion = '1.17.15';
37
39
 
38
40
  /** Frames per second to update
39
41
  * @type {number}
@@ -607,7 +609,7 @@ function drawEngineLogo(t)
607
609
  const color = (c,l)=> l?`hsl(${[.95,.56,.13][c%3]*360} 99%${[0,50,75][l]}%`:'#000';
608
610
 
609
611
  // center and fit tos screen
610
- const alpha = wave(1,1,t);
612
+ const alpha = oscillate(1,1,t);
611
613
  const p = percent(alpha, .1, .5);
612
614
  const size = min(6, min(w,h)/99);
613
615
  x.translate(w/2,h/2);
@@ -685,11 +687,14 @@ function drawEngineLogo(t)
685
687
  }
686
688
  /**
687
689
  * LittleJS Debug System
688
- * - Press Esc to show debug overlay with mouse pick
689
- * - Number keys toggle debug functions
690
- * - +/- apply time scale
691
- * - Debug primitive rendering
692
- * - Save a 2d canvas as a png image
690
+ * - Press Esc to toggle debug overlay with object picking
691
+ * - Number keys toggle debug visualizations (physics, particles, etc.)
692
+ * - +/- keys control time scale for slow motion/fast forward
693
+ * - ASSERT and LOG macros for development (removed in release builds)
694
+ * - Debug primitive rendering (rectangles, circles, lines, points, text)
695
+ * - Screenshot and video capture support
696
+ * - FPS counter and performance watermark
697
+ * - Debug overlay shows mouse position and picked objects
693
698
  * @namespace Debug
694
699
  */
695
700
 
@@ -1404,10 +1409,17 @@ function debugProtectConstant(obj)
1404
1409
  }
1405
1410
  /**
1406
1411
  * LittleJS Math Classes and Functions
1407
- * - General purpose math library
1408
- * - RandomGenerator - seeded random number generator
1409
- * - Vector2 - fast, simple, easy 2D vector class
1410
- * - Color - holds a rgba color with math functions
1412
+ * - Comprehensive math utilities for game development
1413
+ * - Vector2 class for 2D positions, directions, and math operations
1414
+ * - Color class for RGBA colors with interpolation and manipulation
1415
+ * - RandomGenerator for seeded pseudo-random number generation
1416
+ * - Math shortcuts (PI, abs, floor, ceil, min, max, sin, cos, etc.)
1417
+ * - Interpolation functions (lerp, smoothStep, percent)
1418
+ * - Clamping, wrapping, and modulo operations
1419
+ * - Angle utilities with wrap-around support
1420
+ * - Collision detection (overlapping, intersection, line tests)
1421
+ * - Random number generation and seeding
1422
+ * - Type checking utilities
1411
1423
  * @namespace Math
1412
1424
  */
1413
1425
 
@@ -1656,10 +1668,24 @@ function isIntersecting(start, end, pos, size)
1656
1668
  * @param {number} [amplitude] - Amplitude (max height) of the wave
1657
1669
  * @param {number} [t=time] - Value to use for time of the wave
1658
1670
  * @param {number} [offset] - Value to use for time offset of the wave
1671
+ * @param {number} [type] - Wave type: 0=sine, 1=triangle, 2=square, 3=sawtooth
1659
1672
  * @return {number} - Value waving between 0 and amplitude
1660
1673
  * @memberof Math */
1661
- function wave(frequency=1, amplitude=1, t=time, offset=0)
1662
- { return amplitude/2 * (1 - cos(offset + t*frequency*2*PI)); }
1674
+ function oscillate(frequency=1, amplitude=1, t=time, offset=0, type=0)
1675
+ {
1676
+ const phase = (offset + t*frequency) % 1;
1677
+ let value;
1678
+
1679
+ if (type === 1) // triangle
1680
+ value = 2 * abs(2 * phase - 1) - 1;
1681
+ else if (type === 2) // square
1682
+ value = phase < .5 ? -1 : 1;
1683
+ else if (type === 3) // sawtooth
1684
+ value = 2 * phase - 1;
1685
+ else // sine
1686
+ value = -cos(phase * 2*PI);
1687
+ return amplitude/2 * (value + 1);
1688
+ }
1663
1689
 
1664
1690
  /**
1665
1691
  * Check if object is a valid number, not NaN or undefined, but it may be infinite
@@ -1741,15 +1767,17 @@ function lineTest(posStart, posEnd, testFunction, normal)
1741
1767
  // set hit point
1742
1768
  const hitPos = vec2(posStart.x + dirX*t, posStart.y + dirY*t);
1743
1769
 
1744
- // move inside of tile if on positive edge
1770
+ // ensure result is inside the tile
1745
1771
  const e = 1e-9;
1746
- if (wasX)
1747
- {
1748
- if (stepX < 0)
1749
- hitPos.x -= e;
1750
- }
1751
- else if (stepY < 0)
1752
- hitPos.y -= e;
1772
+ const hitPosFloor = hitPos.floor();
1773
+ if (hitPosFloor.x < pos.x)
1774
+ hitPos.x = pos.x;
1775
+ else if (hitPosFloor.x > pos.x)
1776
+ hitPos.x = pos.x + 1 - e;
1777
+ if (hitPosFloor.y < pos.y)
1778
+ hitPos.y = pos.y;
1779
+ else if (hitPosFloor.y > pos.y)
1780
+ hitPos.y = pos.y + 1 - e;
1753
1781
 
1754
1782
  // set normal
1755
1783
  if (normal)
@@ -2142,6 +2170,15 @@ class Vector2
2142
2170
  * @return {Vector2} */
2143
2171
  floor() { return new Vector2(floor(this.x), floor(this.y)); }
2144
2172
 
2173
+ /** Returns a copy of this vector snapped to a grid
2174
+ * @param {number} grid - grid size to snap to
2175
+ * @return {Vector2} */
2176
+ snap(grid)
2177
+ {
2178
+ ASSERT_NUMBER_VALID(grid);
2179
+ return new Vector2(floor(this.x*grid)/grid, floor(this.y*grid)/grid);
2180
+ }
2181
+
2145
2182
  /** Returns new vec2 with modded values
2146
2183
  * @param {number} [divisor]
2147
2184
  * @return {Vector2} */
@@ -2516,8 +2553,12 @@ const PURPLE = debugProtectConstant(rgb(.5,0,1));
2516
2553
  const MAGENTA = debugProtectConstant(rgb(1,0,1));
2517
2554
  /**
2518
2555
  * LittleJS Utility Classes and Functions
2519
- * - General purpose utilities
2520
- * - Timer - tracks time automatically
2556
+ * - Timer - tracks time automatically with support for pause and real-time modes
2557
+ * - Time formatting helper
2558
+ * - JSON file fetching
2559
+ * - File saving (text, canvas, data URLs)
2560
+ * - Native share dialog support
2561
+ * - Local storage save data management
2521
2562
  * @namespace Utilities
2522
2563
  */
2523
2564
 
@@ -3310,6 +3351,13 @@ function setDebugWatermark(show) { debugWatermark = show; }
3310
3351
  function setDebugKey(key) { debugKey = key; }
3311
3352
  /**
3312
3353
  * LittleJS Object System
3354
+ * - EngineObject is the base class for all game objects
3355
+ * - Handles automatic updating, rendering, physics, and collision
3356
+ * - Supports parent-child hierarchies with transform inheritance
3357
+ * - 2D physics with velocity, acceleration, damping, and gravity
3358
+ * - Collision system with tiles and other objects
3359
+ * - Renders sprites from tile sheets with color and rotation
3360
+ * - Objects sorted by renderOrder for layered rendering
3313
3361
  */
3314
3362
 
3315
3363
  /**
@@ -3822,33 +3870,38 @@ class EngineObject
3822
3870
  {
3823
3871
  if (!debug) return;
3824
3872
 
3873
+ // check if there is anything to show
3874
+ const hasPhysics = this.collideTiles || this.collideSolidObjects || this.isSolid;
3875
+ if (!hasPhysics && !this.parent) return;
3876
+
3825
3877
  // show object info for debugging
3826
3878
  const size = vec2(max(this.size.x, .2), max(this.size.y, .2));
3827
3879
  const color = rgb(this.collideTiles?1:0, this.collideSolidObjects?1:0, this.isSolid?1:0, .5);
3828
- drawRect(this.pos, size, color, this.angle);
3880
+ debugRect(this.pos, size, color, 0, this.angle, hasPhysics);
3829
3881
  if (this.parent)
3830
- drawRect(this.pos, size.scale(.8), rgb(1,1,1,.5), this.angle);
3831
- this.parent && drawLine(this.pos, this.parent.pos, .1, rgb(1,1,1,.5));
3882
+ debugRect(this.pos, size.scale(.8), rgb(1,1,1,.5), 0, this.angle);
3883
+ this.parent && debugLine(this.pos, this.parent.pos, rgb(1,1,1,.5), .5);
3832
3884
  }
3833
3885
  }
3834
3886
  /**
3835
3887
  * LittleJS Drawing System
3836
- * - Hybrid system with both Canvas2D and WebGL available
3837
- * - Super fast tile sheet rendering with WebGL
3838
- * - Can apply rotation, mirror, color and additive color
3839
- * - Font rendering system with built in engine font
3840
- * - Many useful utility functions
3888
+ * - Hybrid rendering with both Canvas2D and WebGL support
3889
+ * - Optimized tile sheet sprite rendering using WebGL batching
3890
+ * - Primitive drawing for polygons, ellipses, and lines
3891
+ * - Tile-based rendering with TileInfo and TextureInfo classes
3892
+ * - Text rendering with custom fonts and FontImage support
3893
+ * - Color and additive color blending for effects
3894
+ * - Rotation, mirroring, and scaling transformations
3895
+ * - Camera system with position, scale, and rotation
3896
+ * - Multiple canvas support (main, WebGL, work canvases)
3897
+ * - Gradient fills and outlined shapes
3898
+ * - Image manipulation and color tinting
3841
3899
  *
3842
- * LittleJS uses a hybrid rendering solution with the best of both Canvas2D and WebGL.
3843
- * There are 3 canvas/contexts available to draw to...
3844
- * mainCanvas - 2D background canvas, non WebGL stuff like tile layers are drawn here.
3845
- * glCanvas - Used by the accelerated WebGL batch rendering system.
3900
+ * Rendering Architecture:
3901
+ * - glCanvas: WebGL canvas for accelerated sprite batch rendering
3902
+ * - mainCanvas: Canvas2D overlay for text, UI, and custom drawing
3903
+ * - All draw functions default to WebGL when enabled, can force Canvas2D with useWebGL parameter
3846
3904
  *
3847
- * The WebGL rendering system is very fast with some caveats...
3848
- * - Switching blend modes (additive) or textures causes another draw call which is expensive in excess
3849
- * - Group additive rendering together using renderOrder to mitigate this issue
3850
- *
3851
- * The LittleJS rendering solution is intentionally simple, feel free to adjust it for your needs!
3852
3905
  * @namespace Draw
3853
3906
  */
3854
3907
 
@@ -3939,6 +3992,7 @@ function tile(index=new Vector2, size=tileDefaultSize, texture=0, padding=tileDe
3939
3992
  // create tile info object
3940
3993
  const textureInfo = typeof texture === 'number' ?
3941
3994
  textureInfos[texture] : texture;
3995
+ ASSERT(textureInfo instanceof TextureInfo, 'tile texture is not loaded');
3942
3996
 
3943
3997
  // get the position of the tile
3944
3998
  const sizePaddedX = size.x + padding*2;
@@ -4982,11 +5036,14 @@ async function fontImageInit()
4982
5036
  }
4983
5037
  /**
4984
5038
  * LittleJS Input System
4985
- * - Tracks keyboard down, pressed, and released
4986
- * - Tracks mouse buttons, position, and wheel
4987
- * - Tracks multiple analog gamepads
4988
- * - Touch input is handled as mouse input
4989
- * - Virtual gamepad for touch devices
5039
+ * - Keyboard input with key down, pressed, and released states
5040
+ * - Mouse input with position (world and screen space), buttons, and wheel
5041
+ * - Gamepad support for multiple controllers with analog sticks and buttons
5042
+ * - Touch input mapped to mouse position and buttons
5043
+ * - Virtual on-screen gamepad for mobile devices
5044
+ * - Automatic gamepad vs keyboard/mouse detection
5045
+ * - Input event prevention for canvas focus
5046
+ * - Clipboard copy/paste support
4990
5047
  * @namespace Input
4991
5048
  */
4992
5049
 
@@ -5316,7 +5373,19 @@ function inputInit()
5316
5373
  inputData[0][remapKey(e.code)] = 3;
5317
5374
  }
5318
5375
 
5319
- // prevent arrow key from moving the page
5376
+ // try to prevent default browser handling of input
5377
+ if (!inputPreventDefault || !document.hasFocus() || !e.cancelable) return;
5378
+
5379
+ // don't break browser shortcuts
5380
+ if (e.ctrlKey || e.metaKey || e.altKey) return;
5381
+
5382
+ // don't interfere with user typing into UI fields
5383
+ if (isTextInput(e.target) || isTextInput(document.activeElement)) return;
5384
+
5385
+ // fix browser setting "Search for text when you start typing"
5386
+ const printable = typeof e.key === 'string' && e.key.length === 1;
5387
+
5388
+ // prevent arrow key and other default keys from messing with stuff
5320
5389
  const preventDefaultKeys =
5321
5390
  [
5322
5391
  'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', // scrolling
@@ -5324,9 +5393,15 @@ function inputInit()
5324
5393
  'Tab', // focus navigation
5325
5394
  'Backspace', // browser back
5326
5395
  ];
5327
- if (preventDefaultKeys.includes(e.code))
5328
- if (inputPreventDefault && document.hasFocus() && e.cancelable)
5396
+ if (preventDefaultKeys.includes(e.code) || printable)
5329
5397
  e.preventDefault();
5398
+
5399
+ function isTextInput(element)
5400
+ {
5401
+ const tag = element?.tagName;
5402
+ const editable = element?.isContentEditable;
5403
+ return editable || ['INPUT','TEXTAREA','SELECT'].includes(tag);
5404
+ }
5330
5405
  }
5331
5406
  function onKeyUp(e)
5332
5407
  {
@@ -5759,12 +5834,15 @@ function touchGamepadButtonCenter()
5759
5834
  }
5760
5835
  /**
5761
5836
  * LittleJS Audio System
5762
- * - <a href=https://killedbyapixel.github.io/ZzFX/>ZzFX Sound Effects</a> - ZzFX Sound Effect Generator
5763
- * - <a href=https://keithclark.github.io/ZzFXM/>ZzFXM Music</a> - ZzFXM Music System
5764
- * - Caches sounds and music for fast playback
5765
- * - Can attenuate and apply stereo panning to sounds
5766
- * - Ability to play mp3, ogg, and wave files
5767
- * - Speech synthesis functions
5837
+ * - Play audio files (mp3, ogg, wave) and generate sounds with ZzFX
5838
+ * - ZzFX sound generator integration: <a href=https://killedbyapixel.github.io/ZzFX/>ZzFX</a>
5839
+ * - Sound caching for fast playback and memory efficiency
5840
+ * - Volume control with attenuation and stereo panning
5841
+ * - 2D spatial audio based on camera position with distance-based falloff
5842
+ * - Sound instance management (pause, resume, stop)
5843
+ * - Speech synthesis for text-to-speech
5844
+ * - Music playback with ZzFXM support
5845
+ * - Web Audio API integration with master gain control
5768
5846
  * @namespace Audio
5769
5847
  */
5770
5848
 
@@ -6433,10 +6511,14 @@ function zzfxG
6433
6511
  }
6434
6512
  /**
6435
6513
  * LittleJS Tile Layer System
6436
- * - Caches arrays of tiles to off screen canvas for fast rendering
6437
- * - Unlimited numbers of layers, allocates canvases as needed
6438
- * - Tile layers can be drawn to using their context with canvas2d
6439
- * - Tile layers can also have collision with EngineObjects
6514
+ * - Renders large tile-based levels efficiently using cached canvases
6515
+ * - Unlimited tile layers with automatic canvas allocation
6516
+ * - Layers support both rendering and collision detection
6517
+ * - Direct canvas2d drawing access for custom tile rendering
6518
+ * - TileLayer for rendering, TileCollisionLayer for physics
6519
+ * - Collision callbacks for tile interactions with objects
6520
+ * - Optimized raycast support for tile-based physics
6521
+ * - Integration with Box2D physics via Box2DTileLayer plugin
6440
6522
  * @namespace TileLayers
6441
6523
  */
6442
6524
 
@@ -7141,9 +7223,14 @@ class TileCollisionLayer extends TileLayer
7141
7223
  }
7142
7224
  /**
7143
7225
  * LittleJS Particle System
7144
- * - A simple but fast and flexible particle system
7145
- * - Lightweight Particles are created and managed by ParticleEmitters
7146
- * - The particle design tool can be used to help create emitters
7226
+ * - Fast and flexible particle effects system
7227
+ * - ParticleEmitter spawns and manages lightweight Particle objects
7228
+ * - Particles support color gradients, fading, rotation, and scaling
7229
+ * - Physics simulation with velocity, gravity, and damping
7230
+ * - Collision detection with tile layers
7231
+ * - Additive blending for glowing effects
7232
+ * - Cone-based emission with randomization
7233
+ * - Particle design tool available for easy emitter creation
7147
7234
  * @namespace Particles
7148
7235
  */
7149
7236
 
@@ -7662,9 +7749,12 @@ class Particle
7662
7749
  }
7663
7750
  /**
7664
7751
  * LittleJS Medal System
7665
- * - Tracks and displays medals
7666
- * - Saves medals to local storage
7667
- * - Newgrounds integration
7752
+ * - Achievement/trophy system for games
7753
+ * - Medal class with name, description, icon, and unlock tracking
7754
+ * - Automatic saving to local storage
7755
+ * - Visual display queue with slide-in notifications
7756
+ * - Newgrounds API integration for online achievements
7757
+ * - Debug mode to unlock/reset medals during development
7668
7758
  * @namespace Medals
7669
7759
  */
7670
7760
 
@@ -7853,14 +7943,17 @@ class Medal
7853
7943
  }
7854
7944
  /**
7855
7945
  * LittleJS WebGL Interface
7856
- * - All WebGL used by the engine is wrapped up here
7857
- * - Will fall back to 2D canvas rendering if WebGL is not supported
7858
- * - For normal stuff you won't need to see or call anything in this file
7859
- * - For advanced stuff there are helper functions to create shaders, textures, etc
7860
- * - Can be disabled with glEnable to revert to 2D canvas rendering
7861
- * - Batches sprite rendering on GPU for incredibly fast performance
7862
- * - Sprite transform math is done in the shader where possible
7863
- * - Supports shadertoy style post processing shaders via plugin
7946
+ * - WebGL2 rendering engine for high-performance graphics
7947
+ * - Batched sprite rendering for drawing thousands of sprites efficiently
7948
+ * - Instanced rendering using vertex array objects (VAOs)
7949
+ * - Polygon rendering with triangle strip support
7950
+ * - Shader system with custom vertex and fragment shaders
7951
+ * - Texture management with automatic atlas support
7952
+ * - Post-processing effects via framebuffer and shader plugins
7953
+ * - Automatic fallback to Canvas2D if WebGL is unavailable
7954
+ * - Context loss and restoration handling
7955
+ * - Can be disabled with glEnable setting
7956
+ * - Advanced users can create custom shaders and render targets
7864
7957
  * @namespace WebGL
7865
7958
  */
7866
7959
 
@@ -9351,9 +9444,16 @@ class UISystemPlugin
9351
9444
  this.lastHoverObject = undefined;
9352
9445
  /** @property {UIObject} - Current confirm menu being shown */
9353
9446
  this.confirmDialog = undefined;
9447
+ /** @property {UIObject} - Object to send keyboard input to */
9448
+ this.keyInputObject = undefined;
9354
9449
 
9355
9450
  engineAddPlugin(uiUpdate, uiRender);
9356
9451
 
9452
+ // key down handler
9453
+ function onKeyDown(e)
9454
+ { uiSystem.keyInputObject?.onKeyDown(e); }
9455
+ document.addEventListener('keydown', onKeyDown);
9456
+
9357
9457
  // set object position in parent space
9358
9458
  function updateTransforms(o)
9359
9459
  {
@@ -9372,9 +9472,18 @@ class UISystemPlugin
9372
9472
  // reset hover object at start of update
9373
9473
  uiSystem.lastHoverObject = uiSystem.hoverObject;
9374
9474
  uiSystem.hoverObject = undefined;
9375
-
9475
+
9376
9476
  if (mouseWasPressed(0))
9377
9477
  {
9478
+ // exit navigation mode on mouse press
9479
+ uiSystem.navigationMode = false;
9480
+ uiSystem.navigationObject = undefined;
9481
+ }
9482
+ if (uiSystem.keyInputObject)
9483
+ {
9484
+ // handle text input
9485
+ uiSystem.activeObject = uiSystem.keyInputObject;
9486
+ uiSystem.hoverObject = uiSystem.keyInputObject;
9378
9487
  uiSystem.navigationMode = false;
9379
9488
  uiSystem.navigationObject = undefined;
9380
9489
  }
@@ -9383,7 +9492,7 @@ class UISystemPlugin
9383
9492
  const navigableObjects = uiSystem.getNavigableObjects();
9384
9493
  if (!navigableObjects.length)
9385
9494
  uiSystem.navigationObject = undefined;
9386
- else
9495
+ else if (!uiSystem.keyInputObject)
9387
9496
  {
9388
9497
  // unselect object if it is no longer navigable
9389
9498
  if (!navigableObjects.includes(uiSystem.navigationObject))
@@ -9696,6 +9805,7 @@ class UISystemPlugin
9696
9805
  this.activeObject = undefined;
9697
9806
  this.hoverObject = undefined;
9698
9807
  this.lastHoverObject = undefined;
9808
+ this.keyInputObject = undefined;
9699
9809
  }
9700
9810
 
9701
9811
  /** Get all navigable UI objects sorted by navigationIndex
@@ -9976,7 +10086,7 @@ class UIObject
9976
10086
  }
9977
10087
  }
9978
10088
 
9979
- /** Check if the mouse is overlapping a box in screen space
10089
+ /** Check if the mouse is overlapping this ui object
9980
10090
  * @return {boolean} - True if overlapping */
9981
10091
  isMouseOverlapping()
9982
10092
  {
@@ -9995,8 +10105,16 @@ class UIObject
9995
10105
  this.onUpdate();
9996
10106
 
9997
10107
  // unset active if disabled
9998
- if (this.disabled && this === uiSystem.activeObject)
9999
- uiSystem.activeObject = undefined;
10108
+ if (this.disabled)
10109
+ {
10110
+ if (this === uiSystem.activeObject)
10111
+ uiSystem.activeObject = undefined;
10112
+ if (this === uiSystem.keyInputObject)
10113
+ uiSystem.keyInputObject = undefined;
10114
+ }
10115
+
10116
+ if (uiSystem.keyInputObject)
10117
+ return;
10000
10118
 
10001
10119
  const wasHover = uiSystem.lastHoverObject === this;
10002
10120
  const isActive = this.isActiveObject();
@@ -10023,19 +10141,12 @@ class UIObject
10023
10141
  uiSystem.activeObject = this;
10024
10142
 
10025
10143
  if (uiSystem.activateOnPress)
10026
- {
10027
- this.onClick();
10028
- if (!this.soundPress && this.soundClick)
10029
- this.soundClick.play();
10030
- }
10144
+ this.click(!this.soundPress);
10031
10145
  }
10032
10146
  }
10033
10147
  if (!uiSystem.activateOnPress)
10034
10148
  if (!mouseDown && this.isActiveObject() && this.interactive)
10035
- {
10036
- this.onClick();
10037
- this.soundClick && this.soundClick.play();
10038
- }
10149
+ this.click();
10039
10150
  }
10040
10151
 
10041
10152
  // clear mouse was pressed state even when disabled
@@ -10087,11 +10198,7 @@ class UIObject
10087
10198
  }
10088
10199
 
10089
10200
  /** Called when the navigation button is pressed on this object */
10090
- navigatePressed()
10091
- {
10092
- this.onClick();
10093
- this.soundClick && this.soundClick.play();
10094
- }
10201
+ navigatePressed() { this.click(); }
10095
10202
 
10096
10203
  /** @return {boolean} - Is the mouse hovering over this element */
10097
10204
  isHoverObject() { return uiSystem.hoverObject === this; }
@@ -10102,6 +10209,9 @@ class UIObject
10102
10209
  /** @return {boolean} - Is the gamepad or keyboard navigation object */
10103
10210
  isNavigationObject() { return uiSystem.navigationObject === this; }
10104
10211
 
10212
+ /** @return {boolean} - Is this object in keyboard input mode */
10213
+ isKeyInputObject() { return uiSystem.keyInputObject === this; }
10214
+
10105
10215
  /** @return {boolean} - Can it be interacted with */
10106
10216
  isInteractive() { return this.interactive && this.visible && !this.disabled;}
10107
10217
 
@@ -10138,6 +10248,15 @@ class UIObject
10138
10248
  uiSystem.drawRect(this.pos, this.size, CLEAR_BLACK, 4, color);
10139
10249
  }
10140
10250
 
10251
+ /** Internal function called when object is clicked
10252
+ * @param {boolean} [playSound] */
10253
+ click(playSound)
10254
+ {
10255
+ this.onClick();
10256
+ if (playSound && this.soundClick)
10257
+ this.soundClick.play();
10258
+ }
10259
+
10141
10260
  /** Called each frame before object updates */
10142
10261
  onUpdate() {}
10143
10262
 
@@ -10213,6 +10332,102 @@ class UIText extends UIObject
10213
10332
  }
10214
10333
  }
10215
10334
 
10335
+ ///////////////////////////////////////////////////////////////////////////////
10336
+ /**
10337
+ * UITextInput - An editable text input field
10338
+ * - A simple text entry field that supports basic editing
10339
+ * - Suitable for short text input like names or numbers
10340
+ * @extends UIObject
10341
+ * @memberof UISystem
10342
+ */
10343
+ class UITextInput extends UIObject
10344
+ {
10345
+ /** Create a UITextInput object
10346
+ * @param {Vector2} [pos]
10347
+ * @param {Vector2} [size]
10348
+ * @param {string} [text]
10349
+ */
10350
+ constructor(pos, size, text='')
10351
+ {
10352
+ super(pos, size);
10353
+
10354
+ ASSERT(isString(text), 'ui text must be a string');
10355
+
10356
+ /** @property {number} - Max length of input (0 = no limit) */
10357
+ this.maxLength = 0;
10358
+
10359
+ // set properties
10360
+ this.text = text;
10361
+ this.interactive = true;
10362
+ this.canBeHover = true;
10363
+ }
10364
+
10365
+ click()
10366
+ {
10367
+ // start editing the text
10368
+ uiSystem.keyInputObject = this;
10369
+ this.onClick();
10370
+ }
10371
+
10372
+ /** Stop editing the text edited */
10373
+ stopEditing()
10374
+ {
10375
+ if (!this.isKeyInputObject())
10376
+ return;
10377
+
10378
+ if (this.soundRelease)
10379
+ this.soundRelease.play();
10380
+ uiSystem.activeObject = undefined;
10381
+ uiSystem.keyInputObject = undefined;
10382
+ this.onChange();
10383
+ }
10384
+
10385
+ /** Key down event handler if this object is being edited
10386
+ * @param {KeyboardEvent} [e] */
10387
+ onKeyDown(e)
10388
+ {
10389
+ const code = e.code, key = e.key
10390
+ if (code === 'Backspace')
10391
+ this.text = this.text.slice(0, -1);
10392
+ else if (code === 'Enter' || code === 'Escape')
10393
+ this.stopEditing();
10394
+ else if (key.length === 1) // printable characters
10395
+ {
10396
+ if (!this.maxLength || this.text.length < this.maxLength)
10397
+ this.text += key;
10398
+ }
10399
+ }
10400
+
10401
+ update()
10402
+ {
10403
+ super.update();
10404
+
10405
+ if (!this.isKeyInputObject())
10406
+ return;
10407
+
10408
+ // click off object to stop editing
10409
+ if (mouseWasPressed(0) && !this.isMouseOverlapping() ||
10410
+ gamepadWasPressed(0, gamepadPrimary))
10411
+ {
10412
+ this.stopEditing();
10413
+ inputClearKey(0,0);
10414
+ }
10415
+ }
10416
+
10417
+ render()
10418
+ {
10419
+ super.render();
10420
+
10421
+ // draw the text scaled to fit
10422
+ const textSize = this.getTextSize();
10423
+ let text = this.text;
10424
+ if (this.isKeyInputObject()) // add a cursor to end of text
10425
+ text += timeReal%1 < .5 ? '█' : '░';
10426
+ uiSystem.drawText(text, this.pos, textSize,
10427
+ this.textColor, this.textLineWidth, this.textLineColor, this.align, this.font, this.fontStyle, true, this.textShadow);
10428
+ }
10429
+ }
10430
+
10216
10431
  ///////////////////////////////////////////////////////////////////////////////
10217
10432
  /**
10218
10433
  * UITile - A UI object that displays a tile image
@@ -10324,9 +10539,10 @@ class UICheckbox extends UIObject
10324
10539
  this.color = color.copy();
10325
10540
  this.interactive = true;
10326
10541
  }
10327
- onClick()
10542
+ click()
10328
10543
  {
10329
10544
  this.checked = !this.checked;
10545
+ this.onClick();
10330
10546
  this.onChange();
10331
10547
  }
10332
10548
  render()