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
@@ -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,8 +687,12 @@ function drawEngineLogo(t)
685
687
  }
686
688
  /**
687
689
  * LittleJS - Release Mode
688
- * - This file is used for release builds in place of engineDebug.js
689
- * - Debug functionality is disabled to reduce size and increase performance
690
+ * - Replaces engineDebug.js in production builds
691
+ * - All debug functions are stubbed out as no-ops
692
+ * - Removes ASSERT and LOG calls to reduce file size
693
+ * - Disables debug overlay, watermark, and visualizations
694
+ * - Improves performance by eliminating debug overhead
695
+ * - Significantly reduces final bundle size
690
696
  */
691
697
 
692
698
  let debugWatermark = 0;
@@ -723,10 +729,17 @@ function debugVideoCaptureUpdate(){}
723
729
  function debugProtectConstant(o){ return o; }
724
730
  /**
725
731
  * LittleJS Math Classes and Functions
726
- * - General purpose math library
727
- * - RandomGenerator - seeded random number generator
728
- * - Vector2 - fast, simple, easy 2D vector class
729
- * - Color - holds a rgba color with math functions
732
+ * - Comprehensive math utilities for game development
733
+ * - Vector2 class for 2D positions, directions, and math operations
734
+ * - Color class for RGBA colors with interpolation and manipulation
735
+ * - RandomGenerator for seeded pseudo-random number generation
736
+ * - Math shortcuts (PI, abs, floor, ceil, min, max, sin, cos, etc.)
737
+ * - Interpolation functions (lerp, smoothStep, percent)
738
+ * - Clamping, wrapping, and modulo operations
739
+ * - Angle utilities with wrap-around support
740
+ * - Collision detection (overlapping, intersection, line tests)
741
+ * - Random number generation and seeding
742
+ * - Type checking utilities
730
743
  * @namespace Math
731
744
  */
732
745
 
@@ -975,10 +988,24 @@ function isIntersecting(start, end, pos, size)
975
988
  * @param {number} [amplitude] - Amplitude (max height) of the wave
976
989
  * @param {number} [t=time] - Value to use for time of the wave
977
990
  * @param {number} [offset] - Value to use for time offset of the wave
991
+ * @param {number} [type] - Wave type: 0=sine, 1=triangle, 2=square, 3=sawtooth
978
992
  * @return {number} - Value waving between 0 and amplitude
979
993
  * @memberof Math */
980
- function wave(frequency=1, amplitude=1, t=time, offset=0)
981
- { return amplitude/2 * (1 - cos(offset + t*frequency*2*PI)); }
994
+ function oscillate(frequency=1, amplitude=1, t=time, offset=0, type=0)
995
+ {
996
+ const phase = (offset + t*frequency) % 1;
997
+ let value;
998
+
999
+ if (type === 1) // triangle
1000
+ value = 2 * abs(2 * phase - 1) - 1;
1001
+ else if (type === 2) // square
1002
+ value = phase < .5 ? -1 : 1;
1003
+ else if (type === 3) // sawtooth
1004
+ value = 2 * phase - 1;
1005
+ else // sine
1006
+ value = -cos(phase * 2*PI);
1007
+ return amplitude/2 * (value + 1);
1008
+ }
982
1009
 
983
1010
  /**
984
1011
  * Check if object is a valid number, not NaN or undefined, but it may be infinite
@@ -1060,15 +1087,17 @@ function lineTest(posStart, posEnd, testFunction, normal)
1060
1087
  // set hit point
1061
1088
  const hitPos = vec2(posStart.x + dirX*t, posStart.y + dirY*t);
1062
1089
 
1063
- // move inside of tile if on positive edge
1090
+ // ensure result is inside the tile
1064
1091
  const e = 1e-9;
1065
- if (wasX)
1066
- {
1067
- if (stepX < 0)
1068
- hitPos.x -= e;
1069
- }
1070
- else if (stepY < 0)
1071
- hitPos.y -= e;
1092
+ const hitPosFloor = hitPos.floor();
1093
+ if (hitPosFloor.x < pos.x)
1094
+ hitPos.x = pos.x;
1095
+ else if (hitPosFloor.x > pos.x)
1096
+ hitPos.x = pos.x + 1 - e;
1097
+ if (hitPosFloor.y < pos.y)
1098
+ hitPos.y = pos.y;
1099
+ else if (hitPosFloor.y > pos.y)
1100
+ hitPos.y = pos.y + 1 - e;
1072
1101
 
1073
1102
  // set normal
1074
1103
  if (normal)
@@ -1461,6 +1490,15 @@ class Vector2
1461
1490
  * @return {Vector2} */
1462
1491
  floor() { return new Vector2(floor(this.x), floor(this.y)); }
1463
1492
 
1493
+ /** Returns a copy of this vector snapped to a grid
1494
+ * @param {number} grid - grid size to snap to
1495
+ * @return {Vector2} */
1496
+ snap(grid)
1497
+ {
1498
+ ASSERT_NUMBER_VALID(grid);
1499
+ return new Vector2(floor(this.x*grid)/grid, floor(this.y*grid)/grid);
1500
+ }
1501
+
1464
1502
  /** Returns new vec2 with modded values
1465
1503
  * @param {number} [divisor]
1466
1504
  * @return {Vector2} */
@@ -1835,8 +1873,12 @@ const PURPLE = debugProtectConstant(rgb(.5,0,1));
1835
1873
  const MAGENTA = debugProtectConstant(rgb(1,0,1));
1836
1874
  /**
1837
1875
  * LittleJS Utility Classes and Functions
1838
- * - General purpose utilities
1839
- * - Timer - tracks time automatically
1876
+ * - Timer - tracks time automatically with support for pause and real-time modes
1877
+ * - Time formatting helper
1878
+ * - JSON file fetching
1879
+ * - File saving (text, canvas, data URLs)
1880
+ * - Native share dialog support
1881
+ * - Local storage save data management
1840
1882
  * @namespace Utilities
1841
1883
  */
1842
1884
 
@@ -2629,6 +2671,13 @@ function setDebugWatermark(show) { debugWatermark = show; }
2629
2671
  function setDebugKey(key) { debugKey = key; }
2630
2672
  /**
2631
2673
  * LittleJS Object System
2674
+ * - EngineObject is the base class for all game objects
2675
+ * - Handles automatic updating, rendering, physics, and collision
2676
+ * - Supports parent-child hierarchies with transform inheritance
2677
+ * - 2D physics with velocity, acceleration, damping, and gravity
2678
+ * - Collision system with tiles and other objects
2679
+ * - Renders sprites from tile sheets with color and rotation
2680
+ * - Objects sorted by renderOrder for layered rendering
2632
2681
  */
2633
2682
 
2634
2683
  /**
@@ -3141,33 +3190,38 @@ class EngineObject
3141
3190
  {
3142
3191
  if (!debug) return;
3143
3192
 
3193
+ // check if there is anything to show
3194
+ const hasPhysics = this.collideTiles || this.collideSolidObjects || this.isSolid;
3195
+ if (!hasPhysics && !this.parent) return;
3196
+
3144
3197
  // show object info for debugging
3145
3198
  const size = vec2(max(this.size.x, .2), max(this.size.y, .2));
3146
3199
  const color = rgb(this.collideTiles?1:0, this.collideSolidObjects?1:0, this.isSolid?1:0, .5);
3147
- drawRect(this.pos, size, color, this.angle);
3200
+ debugRect(this.pos, size, color, 0, this.angle, hasPhysics);
3148
3201
  if (this.parent)
3149
- drawRect(this.pos, size.scale(.8), rgb(1,1,1,.5), this.angle);
3150
- this.parent && drawLine(this.pos, this.parent.pos, .1, rgb(1,1,1,.5));
3202
+ debugRect(this.pos, size.scale(.8), rgb(1,1,1,.5), 0, this.angle);
3203
+ this.parent && debugLine(this.pos, this.parent.pos, rgb(1,1,1,.5), .5);
3151
3204
  }
3152
3205
  }
3153
3206
  /**
3154
3207
  * LittleJS Drawing System
3155
- * - Hybrid system with both Canvas2D and WebGL available
3156
- * - Super fast tile sheet rendering with WebGL
3157
- * - Can apply rotation, mirror, color and additive color
3158
- * - Font rendering system with built in engine font
3159
- * - Many useful utility functions
3208
+ * - Hybrid rendering with both Canvas2D and WebGL support
3209
+ * - Optimized tile sheet sprite rendering using WebGL batching
3210
+ * - Primitive drawing for polygons, ellipses, and lines
3211
+ * - Tile-based rendering with TileInfo and TextureInfo classes
3212
+ * - Text rendering with custom fonts and FontImage support
3213
+ * - Color and additive color blending for effects
3214
+ * - Rotation, mirroring, and scaling transformations
3215
+ * - Camera system with position, scale, and rotation
3216
+ * - Multiple canvas support (main, WebGL, work canvases)
3217
+ * - Gradient fills and outlined shapes
3218
+ * - Image manipulation and color tinting
3160
3219
  *
3161
- * LittleJS uses a hybrid rendering solution with the best of both Canvas2D and WebGL.
3162
- * There are 3 canvas/contexts available to draw to...
3163
- * mainCanvas - 2D background canvas, non WebGL stuff like tile layers are drawn here.
3164
- * glCanvas - Used by the accelerated WebGL batch rendering system.
3220
+ * Rendering Architecture:
3221
+ * - glCanvas: WebGL canvas for accelerated sprite batch rendering
3222
+ * - mainCanvas: Canvas2D overlay for text, UI, and custom drawing
3223
+ * - All draw functions default to WebGL when enabled, can force Canvas2D with useWebGL parameter
3165
3224
  *
3166
- * The WebGL rendering system is very fast with some caveats...
3167
- * - Switching blend modes (additive) or textures causes another draw call which is expensive in excess
3168
- * - Group additive rendering together using renderOrder to mitigate this issue
3169
- *
3170
- * The LittleJS rendering solution is intentionally simple, feel free to adjust it for your needs!
3171
3225
  * @namespace Draw
3172
3226
  */
3173
3227
 
@@ -3258,6 +3312,7 @@ function tile(index=new Vector2, size=tileDefaultSize, texture=0, padding=tileDe
3258
3312
  // create tile info object
3259
3313
  const textureInfo = typeof texture === 'number' ?
3260
3314
  textureInfos[texture] : texture;
3315
+ ASSERT(textureInfo instanceof TextureInfo, 'tile texture is not loaded');
3261
3316
 
3262
3317
  // get the position of the tile
3263
3318
  const sizePaddedX = size.x + padding*2;
@@ -4301,11 +4356,14 @@ async function fontImageInit()
4301
4356
  }
4302
4357
  /**
4303
4358
  * LittleJS Input System
4304
- * - Tracks keyboard down, pressed, and released
4305
- * - Tracks mouse buttons, position, and wheel
4306
- * - Tracks multiple analog gamepads
4307
- * - Touch input is handled as mouse input
4308
- * - Virtual gamepad for touch devices
4359
+ * - Keyboard input with key down, pressed, and released states
4360
+ * - Mouse input with position (world and screen space), buttons, and wheel
4361
+ * - Gamepad support for multiple controllers with analog sticks and buttons
4362
+ * - Touch input mapped to mouse position and buttons
4363
+ * - Virtual on-screen gamepad for mobile devices
4364
+ * - Automatic gamepad vs keyboard/mouse detection
4365
+ * - Input event prevention for canvas focus
4366
+ * - Clipboard copy/paste support
4309
4367
  * @namespace Input
4310
4368
  */
4311
4369
 
@@ -4635,7 +4693,19 @@ function inputInit()
4635
4693
  inputData[0][remapKey(e.code)] = 3;
4636
4694
  }
4637
4695
 
4638
- // prevent arrow key from moving the page
4696
+ // try to prevent default browser handling of input
4697
+ if (!inputPreventDefault || !document.hasFocus() || !e.cancelable) return;
4698
+
4699
+ // don't break browser shortcuts
4700
+ if (e.ctrlKey || e.metaKey || e.altKey) return;
4701
+
4702
+ // don't interfere with user typing into UI fields
4703
+ if (isTextInput(e.target) || isTextInput(document.activeElement)) return;
4704
+
4705
+ // fix browser setting "Search for text when you start typing"
4706
+ const printable = typeof e.key === 'string' && e.key.length === 1;
4707
+
4708
+ // prevent arrow key and other default keys from messing with stuff
4639
4709
  const preventDefaultKeys =
4640
4710
  [
4641
4711
  'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', // scrolling
@@ -4643,9 +4713,15 @@ function inputInit()
4643
4713
  'Tab', // focus navigation
4644
4714
  'Backspace', // browser back
4645
4715
  ];
4646
- if (preventDefaultKeys.includes(e.code))
4647
- if (inputPreventDefault && document.hasFocus() && e.cancelable)
4716
+ if (preventDefaultKeys.includes(e.code) || printable)
4648
4717
  e.preventDefault();
4718
+
4719
+ function isTextInput(element)
4720
+ {
4721
+ const tag = element?.tagName;
4722
+ const editable = element?.isContentEditable;
4723
+ return editable || ['INPUT','TEXTAREA','SELECT'].includes(tag);
4724
+ }
4649
4725
  }
4650
4726
  function onKeyUp(e)
4651
4727
  {
@@ -5078,12 +5154,15 @@ function touchGamepadButtonCenter()
5078
5154
  }
5079
5155
  /**
5080
5156
  * LittleJS Audio System
5081
- * - <a href=https://killedbyapixel.github.io/ZzFX/>ZzFX Sound Effects</a> - ZzFX Sound Effect Generator
5082
- * - <a href=https://keithclark.github.io/ZzFXM/>ZzFXM Music</a> - ZzFXM Music System
5083
- * - Caches sounds and music for fast playback
5084
- * - Can attenuate and apply stereo panning to sounds
5085
- * - Ability to play mp3, ogg, and wave files
5086
- * - Speech synthesis functions
5157
+ * - Play audio files (mp3, ogg, wave) and generate sounds with ZzFX
5158
+ * - ZzFX sound generator integration: <a href=https://killedbyapixel.github.io/ZzFX/>ZzFX</a>
5159
+ * - Sound caching for fast playback and memory efficiency
5160
+ * - Volume control with attenuation and stereo panning
5161
+ * - 2D spatial audio based on camera position with distance-based falloff
5162
+ * - Sound instance management (pause, resume, stop)
5163
+ * - Speech synthesis for text-to-speech
5164
+ * - Music playback with ZzFXM support
5165
+ * - Web Audio API integration with master gain control
5087
5166
  * @namespace Audio
5088
5167
  */
5089
5168
 
@@ -5752,10 +5831,14 @@ function zzfxG
5752
5831
  }
5753
5832
  /**
5754
5833
  * LittleJS Tile Layer System
5755
- * - Caches arrays of tiles to off screen canvas for fast rendering
5756
- * - Unlimited numbers of layers, allocates canvases as needed
5757
- * - Tile layers can be drawn to using their context with canvas2d
5758
- * - Tile layers can also have collision with EngineObjects
5834
+ * - Renders large tile-based levels efficiently using cached canvases
5835
+ * - Unlimited tile layers with automatic canvas allocation
5836
+ * - Layers support both rendering and collision detection
5837
+ * - Direct canvas2d drawing access for custom tile rendering
5838
+ * - TileLayer for rendering, TileCollisionLayer for physics
5839
+ * - Collision callbacks for tile interactions with objects
5840
+ * - Optimized raycast support for tile-based physics
5841
+ * - Integration with Box2D physics via Box2DTileLayer plugin
5759
5842
  * @namespace TileLayers
5760
5843
  */
5761
5844
 
@@ -6460,9 +6543,14 @@ class TileCollisionLayer extends TileLayer
6460
6543
  }
6461
6544
  /**
6462
6545
  * LittleJS Particle System
6463
- * - A simple but fast and flexible particle system
6464
- * - Lightweight Particles are created and managed by ParticleEmitters
6465
- * - The particle design tool can be used to help create emitters
6546
+ * - Fast and flexible particle effects system
6547
+ * - ParticleEmitter spawns and manages lightweight Particle objects
6548
+ * - Particles support color gradients, fading, rotation, and scaling
6549
+ * - Physics simulation with velocity, gravity, and damping
6550
+ * - Collision detection with tile layers
6551
+ * - Additive blending for glowing effects
6552
+ * - Cone-based emission with randomization
6553
+ * - Particle design tool available for easy emitter creation
6466
6554
  * @namespace Particles
6467
6555
  */
6468
6556
 
@@ -6981,9 +7069,12 @@ class Particle
6981
7069
  }
6982
7070
  /**
6983
7071
  * LittleJS Medal System
6984
- * - Tracks and displays medals
6985
- * - Saves medals to local storage
6986
- * - Newgrounds integration
7072
+ * - Achievement/trophy system for games
7073
+ * - Medal class with name, description, icon, and unlock tracking
7074
+ * - Automatic saving to local storage
7075
+ * - Visual display queue with slide-in notifications
7076
+ * - Newgrounds API integration for online achievements
7077
+ * - Debug mode to unlock/reset medals during development
6987
7078
  * @namespace Medals
6988
7079
  */
6989
7080
 
@@ -7172,14 +7263,17 @@ class Medal
7172
7263
  }
7173
7264
  /**
7174
7265
  * LittleJS WebGL Interface
7175
- * - All WebGL used by the engine is wrapped up here
7176
- * - Will fall back to 2D canvas rendering if WebGL is not supported
7177
- * - For normal stuff you won't need to see or call anything in this file
7178
- * - For advanced stuff there are helper functions to create shaders, textures, etc
7179
- * - Can be disabled with glEnable to revert to 2D canvas rendering
7180
- * - Batches sprite rendering on GPU for incredibly fast performance
7181
- * - Sprite transform math is done in the shader where possible
7182
- * - Supports shadertoy style post processing shaders via plugin
7266
+ * - WebGL2 rendering engine for high-performance graphics
7267
+ * - Batched sprite rendering for drawing thousands of sprites efficiently
7268
+ * - Instanced rendering using vertex array objects (VAOs)
7269
+ * - Polygon rendering with triangle strip support
7270
+ * - Shader system with custom vertex and fragment shaders
7271
+ * - Texture management with automatic atlas support
7272
+ * - Post-processing effects via framebuffer and shader plugins
7273
+ * - Automatic fallback to Canvas2D if WebGL is unavailable
7274
+ * - Context loss and restoration handling
7275
+ * - Can be disabled with glEnable setting
7276
+ * - Advanced users can create custom shaders and render targets
7183
7277
  * @namespace WebGL
7184
7278
  */
7185
7279
 
@@ -8670,9 +8764,16 @@ class UISystemPlugin
8670
8764
  this.lastHoverObject = undefined;
8671
8765
  /** @property {UIObject} - Current confirm menu being shown */
8672
8766
  this.confirmDialog = undefined;
8767
+ /** @property {UIObject} - Object to send keyboard input to */
8768
+ this.keyInputObject = undefined;
8673
8769
 
8674
8770
  engineAddPlugin(uiUpdate, uiRender);
8675
8771
 
8772
+ // key down handler
8773
+ function onKeyDown(e)
8774
+ { uiSystem.keyInputObject?.onKeyDown(e); }
8775
+ document.addEventListener('keydown', onKeyDown);
8776
+
8676
8777
  // set object position in parent space
8677
8778
  function updateTransforms(o)
8678
8779
  {
@@ -8691,9 +8792,18 @@ class UISystemPlugin
8691
8792
  // reset hover object at start of update
8692
8793
  uiSystem.lastHoverObject = uiSystem.hoverObject;
8693
8794
  uiSystem.hoverObject = undefined;
8694
-
8795
+
8695
8796
  if (mouseWasPressed(0))
8696
8797
  {
8798
+ // exit navigation mode on mouse press
8799
+ uiSystem.navigationMode = false;
8800
+ uiSystem.navigationObject = undefined;
8801
+ }
8802
+ if (uiSystem.keyInputObject)
8803
+ {
8804
+ // handle text input
8805
+ uiSystem.activeObject = uiSystem.keyInputObject;
8806
+ uiSystem.hoverObject = uiSystem.keyInputObject;
8697
8807
  uiSystem.navigationMode = false;
8698
8808
  uiSystem.navigationObject = undefined;
8699
8809
  }
@@ -8702,7 +8812,7 @@ class UISystemPlugin
8702
8812
  const navigableObjects = uiSystem.getNavigableObjects();
8703
8813
  if (!navigableObjects.length)
8704
8814
  uiSystem.navigationObject = undefined;
8705
- else
8815
+ else if (!uiSystem.keyInputObject)
8706
8816
  {
8707
8817
  // unselect object if it is no longer navigable
8708
8818
  if (!navigableObjects.includes(uiSystem.navigationObject))
@@ -9015,6 +9125,7 @@ class UISystemPlugin
9015
9125
  this.activeObject = undefined;
9016
9126
  this.hoverObject = undefined;
9017
9127
  this.lastHoverObject = undefined;
9128
+ this.keyInputObject = undefined;
9018
9129
  }
9019
9130
 
9020
9131
  /** Get all navigable UI objects sorted by navigationIndex
@@ -9295,7 +9406,7 @@ class UIObject
9295
9406
  }
9296
9407
  }
9297
9408
 
9298
- /** Check if the mouse is overlapping a box in screen space
9409
+ /** Check if the mouse is overlapping this ui object
9299
9410
  * @return {boolean} - True if overlapping */
9300
9411
  isMouseOverlapping()
9301
9412
  {
@@ -9314,8 +9425,16 @@ class UIObject
9314
9425
  this.onUpdate();
9315
9426
 
9316
9427
  // unset active if disabled
9317
- if (this.disabled && this === uiSystem.activeObject)
9318
- uiSystem.activeObject = undefined;
9428
+ if (this.disabled)
9429
+ {
9430
+ if (this === uiSystem.activeObject)
9431
+ uiSystem.activeObject = undefined;
9432
+ if (this === uiSystem.keyInputObject)
9433
+ uiSystem.keyInputObject = undefined;
9434
+ }
9435
+
9436
+ if (uiSystem.keyInputObject)
9437
+ return;
9319
9438
 
9320
9439
  const wasHover = uiSystem.lastHoverObject === this;
9321
9440
  const isActive = this.isActiveObject();
@@ -9342,19 +9461,12 @@ class UIObject
9342
9461
  uiSystem.activeObject = this;
9343
9462
 
9344
9463
  if (uiSystem.activateOnPress)
9345
- {
9346
- this.onClick();
9347
- if (!this.soundPress && this.soundClick)
9348
- this.soundClick.play();
9349
- }
9464
+ this.click(!this.soundPress);
9350
9465
  }
9351
9466
  }
9352
9467
  if (!uiSystem.activateOnPress)
9353
9468
  if (!mouseDown && this.isActiveObject() && this.interactive)
9354
- {
9355
- this.onClick();
9356
- this.soundClick && this.soundClick.play();
9357
- }
9469
+ this.click();
9358
9470
  }
9359
9471
 
9360
9472
  // clear mouse was pressed state even when disabled
@@ -9406,11 +9518,7 @@ class UIObject
9406
9518
  }
9407
9519
 
9408
9520
  /** Called when the navigation button is pressed on this object */
9409
- navigatePressed()
9410
- {
9411
- this.onClick();
9412
- this.soundClick && this.soundClick.play();
9413
- }
9521
+ navigatePressed() { this.click(); }
9414
9522
 
9415
9523
  /** @return {boolean} - Is the mouse hovering over this element */
9416
9524
  isHoverObject() { return uiSystem.hoverObject === this; }
@@ -9421,6 +9529,9 @@ class UIObject
9421
9529
  /** @return {boolean} - Is the gamepad or keyboard navigation object */
9422
9530
  isNavigationObject() { return uiSystem.navigationObject === this; }
9423
9531
 
9532
+ /** @return {boolean} - Is this object in keyboard input mode */
9533
+ isKeyInputObject() { return uiSystem.keyInputObject === this; }
9534
+
9424
9535
  /** @return {boolean} - Can it be interacted with */
9425
9536
  isInteractive() { return this.interactive && this.visible && !this.disabled;}
9426
9537
 
@@ -9457,6 +9568,15 @@ class UIObject
9457
9568
  uiSystem.drawRect(this.pos, this.size, CLEAR_BLACK, 4, color);
9458
9569
  }
9459
9570
 
9571
+ /** Internal function called when object is clicked
9572
+ * @param {boolean} [playSound] */
9573
+ click(playSound)
9574
+ {
9575
+ this.onClick();
9576
+ if (playSound && this.soundClick)
9577
+ this.soundClick.play();
9578
+ }
9579
+
9460
9580
  /** Called each frame before object updates */
9461
9581
  onUpdate() {}
9462
9582
 
@@ -9532,6 +9652,102 @@ class UIText extends UIObject
9532
9652
  }
9533
9653
  }
9534
9654
 
9655
+ ///////////////////////////////////////////////////////////////////////////////
9656
+ /**
9657
+ * UITextInput - An editable text input field
9658
+ * - A simple text entry field that supports basic editing
9659
+ * - Suitable for short text input like names or numbers
9660
+ * @extends UIObject
9661
+ * @memberof UISystem
9662
+ */
9663
+ class UITextInput extends UIObject
9664
+ {
9665
+ /** Create a UITextInput object
9666
+ * @param {Vector2} [pos]
9667
+ * @param {Vector2} [size]
9668
+ * @param {string} [text]
9669
+ */
9670
+ constructor(pos, size, text='')
9671
+ {
9672
+ super(pos, size);
9673
+
9674
+ ASSERT(isString(text), 'ui text must be a string');
9675
+
9676
+ /** @property {number} - Max length of input (0 = no limit) */
9677
+ this.maxLength = 0;
9678
+
9679
+ // set properties
9680
+ this.text = text;
9681
+ this.interactive = true;
9682
+ this.canBeHover = true;
9683
+ }
9684
+
9685
+ click()
9686
+ {
9687
+ // start editing the text
9688
+ uiSystem.keyInputObject = this;
9689
+ this.onClick();
9690
+ }
9691
+
9692
+ /** Stop editing the text edited */
9693
+ stopEditing()
9694
+ {
9695
+ if (!this.isKeyInputObject())
9696
+ return;
9697
+
9698
+ if (this.soundRelease)
9699
+ this.soundRelease.play();
9700
+ uiSystem.activeObject = undefined;
9701
+ uiSystem.keyInputObject = undefined;
9702
+ this.onChange();
9703
+ }
9704
+
9705
+ /** Key down event handler if this object is being edited
9706
+ * @param {KeyboardEvent} [e] */
9707
+ onKeyDown(e)
9708
+ {
9709
+ const code = e.code, key = e.key
9710
+ if (code === 'Backspace')
9711
+ this.text = this.text.slice(0, -1);
9712
+ else if (code === 'Enter' || code === 'Escape')
9713
+ this.stopEditing();
9714
+ else if (key.length === 1) // printable characters
9715
+ {
9716
+ if (!this.maxLength || this.text.length < this.maxLength)
9717
+ this.text += key;
9718
+ }
9719
+ }
9720
+
9721
+ update()
9722
+ {
9723
+ super.update();
9724
+
9725
+ if (!this.isKeyInputObject())
9726
+ return;
9727
+
9728
+ // click off object to stop editing
9729
+ if (mouseWasPressed(0) && !this.isMouseOverlapping() ||
9730
+ gamepadWasPressed(0, gamepadPrimary))
9731
+ {
9732
+ this.stopEditing();
9733
+ inputClearKey(0,0);
9734
+ }
9735
+ }
9736
+
9737
+ render()
9738
+ {
9739
+ super.render();
9740
+
9741
+ // draw the text scaled to fit
9742
+ const textSize = this.getTextSize();
9743
+ let text = this.text;
9744
+ if (this.isKeyInputObject()) // add a cursor to end of text
9745
+ text += timeReal%1 < .5 ? '█' : '░';
9746
+ uiSystem.drawText(text, this.pos, textSize,
9747
+ this.textColor, this.textLineWidth, this.textLineColor, this.align, this.font, this.fontStyle, true, this.textShadow);
9748
+ }
9749
+ }
9750
+
9535
9751
  ///////////////////////////////////////////////////////////////////////////////
9536
9752
  /**
9537
9753
  * UITile - A UI object that displays a tile image
@@ -9643,9 +9859,10 @@ class UICheckbox extends UIObject
9643
9859
  this.color = color.copy();
9644
9860
  this.interactive = true;
9645
9861
  }
9646
- onClick()
9862
+ click()
9647
9863
  {
9648
9864
  this.checked = !this.checked;
9865
+ this.onClick();
9649
9866
  this.onChange();
9650
9867
  }
9651
9868
  render()