littlejsengine 1.26.0 → 1.27.0

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/CHANGELOG.md CHANGED
@@ -7,6 +7,34 @@ What changed in each release, newest first. Releases before 1.20.0 are on the
7
7
 
8
8
  Nothing yet.
9
9
 
10
+ ## 1.27.0 - 2026-10-08
11
+
12
+ ### New Changes
13
+ - 2D lights have cones and gels, as 3D spotlights do: `light.coneAngle` makes a light a cone, like a flashlight or a car's headlights, looking along its up and turned by its `angle`, its edge fading by `coneSoftness`; `light.gel` is a picture the light shines through, a tile or a texture across its square that turns with it, so one light can be a car's headlights and tail lights. Shadows still fall through both, and a light with neither draws as before. The Light System short has a swinging searchlight, and Light Shadows a mouse light shining through colored panes
14
+ - One way collision, platforms jumped up through and landed on: `layer.setOneWay(tile, direction)` makes the cells that draw a tile passable moving that way, up, down, left or right, so they block only what was wholly on their far side before it moved; turned and mirrored as each cell's tile is, so a platform turned a quarter is one way to the side. Objects, particles and raycasts all follow it, `collideWithTile` is not asked about a tile passed through, and returning false from it drops through. A solid object's `oneWay` does the same for objects, and a rising platform keeps its rider. The new Platform Physics short shows it with a lift, a crate, a bouncy ball and an ice floor
15
+ - `obj.isBullet` for small fast objects: it moves through the tiles as a point along a ray, so it never passes through a tile at any speed and is not held to objectMaxSpeed for them; `collideWithTile` sees it where it meets the tile, so sparks start on the surface; it bounces by restitution and goes on with the rest of its move, so it rolls along a floor as a box does. One with a size that collides with solid objects is still held to objectMaxSpeed. The platformer's bullets use it in place of their own raycast
16
+ - Cloud saves on Newgrounds and Wavedash, the same on both: `cloudSave(slot, data)` keeps any value JSON can hold for the player, `cloudLoad(slot)` gives it back, null for an empty slot and undefined for a load that failed, so a game does not save over a save it could not load, with `loadFailure(slot)` saying when the slot holds a file that is not a save, and off the platform or not logged in they do nothing; Wavedash stats, `setStat` and `getStat`, and presence, `setPresence(status, details)`; Newgrounds events, `logEvent(name)`. The Newgrounds calls are built from its API docs and not yet tried on the site
17
+
18
+ ### Fixes
19
+ - An object of no size is no longer held to objectMaxSpeed by `collideSolidObjects`, since it never collides with solids; one with `collideLevel` and a mass still is
20
+ - A Shader disposed before its first draw was drawn stayed in the engine's list of shaders, the flush compiling it put it back
21
+ - A ParticleEmitter3D callback that changed a particle's `age` or `lifeTime` had the change thrown away, now it is kept as `pos` and `velocity` are
22
+ - A light pass whose `renderLight` or `renderEmissive` throws hands back the screen, its viewport, texture and blend, where a game that caught the error went on drawing into the lightmap
23
+ - A tile layer redraw whose `onRedraw` or tile draw throws hands back the camera, the canvas and the draw target, where it left the game drawing into the layer
24
+ - 3D level editor: a scale drag keeps a mirrored axis mirrored, where it turned every negative axis to the smallest size; a turned object lands, drops and pastes by the box around it as turned; opening the editor lets go of a mouse the game captured; a frame that throws gives the game its camera back; an orthographic game camera's near is not copied to the editor's view; the box select is drawn over a 3D pass drawn after the 2D
25
+
26
+ ## 1.26.1 - 2026-10-08
27
+
28
+ ### New Changes
29
+ - A 3D Shader may define `void mainNormal(inout vec3 n)`, given the world normal after the normal map, to bend it per pixel for waves, ripples or a flag; the lighting, shadows, specular, Fresnel, environment reflection and fog all use it
30
+ - `postProcess.enabled`: false skips the post process pass, so an effect turns off and on without taking the plugin apart
31
+ - `averageFPS` is public and kept in release builds, where it read 0 unless debug or the watermark was on; it starts at the first frame's rate rather than climbing from 0
32
+ - `camera.align2DZ`: the z of the plane an align2D camera lines up with the 2D view, 0 as before
33
+ - The right click menu is left on links, as it is on text fields
34
+
35
+ ### Fixes
36
+ - A tap starts the audio with touch input off too
37
+
10
38
  ## 1.26.0 - 2026-10-07
11
39
 
12
40
  ### New Changes
package/FAQ.md CHANGED
@@ -282,7 +282,8 @@ Plugins are self-contained features that live alongside the engine but aren't pa
282
282
  | `pathFinder.js` | Grid-based A* pathfinding with optional path smoothing |
283
283
  | `tweakables.js` | A debug panel to change values live while the game runs, press 9 with the debug overlay open |
284
284
  | `medalSystem.js` | Achievement / medal tracking with popup notifications |
285
- | `newgrounds.js` | Newgrounds.io integration (medals held on the server, scoreboards) |
285
+ | `newgrounds.js` | Newgrounds.io integration (medals held on the server, scoreboards, cloud saves, events) |
286
+ | `wavedash.js` | Wavedash integration (achievements, leaderboards, cloud saves, stats, presence), the same shape as the Newgrounds one |
286
287
  | `audioEffects.js` | Web Audio effects like filter, reverb, delay, distortion and compressor, for a group of sounds or everything |
287
288
  | `drawUtilities.js` | Higher-level drawing helpers like nine-slice and three-slice |
288
289
  | `render3dVoxels.js` | `VoxelMap`, a 3D grid of blocks that draws itself and that objects collide with |
@@ -826,7 +827,7 @@ medal_firstWin.unlock();
826
827
 
827
828
  You can pass an image URL as the fifth argument to `Medal` instead of an emoji icon. The `saveName` you pass to `medalsInit` is used to track which medals have been unlocked in localStorage, so unlocks persist across visits. Give it a name of its own, not the one you pass to `readSaveData` and `writeSaveData`, or the medals and your save data overwrite each other.
828
829
 
829
- The plugin also supports [Newgrounds](https://www.newgrounds.com) scoreboards and medals held on the server through [plugins/newgrounds.js](plugins/newgrounds.js): create each medal as a `NewgroundsMedal` with the id Newgrounds gave it, call `medalsInit` as usual, then `new NewgroundsPlugin(app_id, cipher)`. The "LittleJS Medals & Newgrounds" section of REFERENCE.md has a short example.
830
+ The plugin also supports [Newgrounds](https://www.newgrounds.com) scoreboards and medals held on the server through [plugins/newgrounds.js](plugins/newgrounds.js): create each medal as a `NewgroundsMedal` with the id Newgrounds gave it, call `medalsInit` as usual, then `new NewgroundsPlugin(app_id, cipher)`. The "LittleJS Medals & Newgrounds" section of REFERENCE.md has a short example. [plugins/wavedash.js](plugins/wavedash.js) does the same for [Wavedash](https://wavedash.com/) with `WavedashMedal` and `WavedashPlugin`, and both have `cloudSave(slot, data)` and `cloudLoad(slot)` to keep a player's save on the platform.
830
831
 
831
832
  ---
832
833
 
package/README.md CHANGED
@@ -39,7 +39,7 @@ The code is very clean and well documented with many examples to get you started
39
39
 
40
40
  - Blazing fast WebGL2 + Canvas2D hybrid rendering system
41
41
  - Apply [Shadertoy](https://www.shadertoy.com) style shaders for post-processing effects, or on any object or draw
42
- - 2D lights with soft shadows, tinted glass and emissive objects
42
+ - 2D lights with soft shadows, tinted glass and emissive objects, a sun with long shadows, and spotlight cones and gels
43
43
  - Robust particle effect system with a library of ready made effects and an [effect design tool](https://killedbyapixel.github.io/LittleJS/examples/particles/)
44
44
  - Parallax background layers that repeat across a level of any size
45
45
  - Load sprites and animations into texture sheets at runtime, or import [TexturePacker](https://www.codeandweb.com/texturepacker) and [Aseprite](https://www.aseprite.org) atlases
@@ -74,7 +74,8 @@ The code is very clean and well documented with many examples to get you started
74
74
  ### 💥 Physics
75
75
 
76
76
  - Robust arcade physics system with collision handling
77
- - Fast tilemap collision and raycasting
77
+ - Fast tilemap collision and raycasting, with one-way platforms that turn with their tiles
78
+ - Swept bullet collision, so small fast objects never pass through a wall
78
79
  - Object collision that stays fast with thousands of objects
79
80
  - Full Box2D integration for realistic physics using [Box2D v2.3.1 wasm](https://github.com/kripken/box2d.js)
80
81
  - Grid-based A* pathfinding plugin with optional path smoothing
@@ -96,7 +97,7 @@ The code is very clean and well documented with many examples to get you started
96
97
  - UI system with buttons, sliders, text input and nine-slice skins
97
98
  - Tween system with easing curves, and a scene system for titles, menus and game states
98
99
  - Debug overlay and primitive rendering system
99
- - Medal tracking system with [Newgrounds](https://www.newgrounds.com/) support
100
+ - Medals, leaderboards and cloud saves on [Newgrounds](https://www.newgrounds.com/) and [Wavedash](https://wavedash.com/)
100
101
  - Node.js build system
101
102
 
102
103
  ### 🤖 AI Friendly
package/REFERENCE.md CHANGED
@@ -25,8 +25,9 @@ engineName // Name of the engine: 'LittleJS'
25
25
  engineVersion // Version of the engine
26
26
  frameRate // Fixed frame rate for updates (60)
27
27
  frame // Current update frame
28
- time // Game time since start in seconds (stops when paused)
28
+ time // Game time since start in seconds (stops when paused, falls behind the clock below 20 FPS)
29
29
  timeReal // Real time since start in seconds (keeps running when paused; the debug speed keys scale it)
30
+ averageFPS // Frames drawn per second, smoothed
30
31
  timeDelta // Seconds the update covers: 1/60, or the display frame's time with engineVariableStep
31
32
  timeScale = 1 // Game speed, more or fewer fixed updates per second; with engineVariableStep it scales timeDelta
32
33
  paused // Is the game paused? (set with setPaused)
@@ -325,7 +326,9 @@ toggleFullscreen()
325
326
  // Shader Object - a custom fragment shader for objects and draws, 2D or 3D
326
327
  new Shader(fragmentCode) // fragmentCode defines void mainImage(out vec4 c, vec2 uv) in the post processing
327
328
  // style; it gives the surface color, then the object's color and additive color
328
- // apply in 2D, and the lighting, shadows and fog in 3D
329
+ // apply in 2D, and the lighting, shadows and fog in 3D; in 3D it may also define
330
+ // void mainNormal(inout vec3 n), the world normal after the normal map, to bend
331
+ // it per pixel (waves, ripples) for the lighting, specular and reflection
329
332
  obj.shader = shader // any EngineObject or EngineObject3D; draws that share a Shader share a batch
330
333
  shader.dispose() // frees its programs and takes it off the engine's list, which keeps every Shader
331
334
  // for a lost context; for one made each time a scene starts; drawn again it
@@ -686,6 +689,12 @@ EngineObject.clampSpeed // Hold each axis of velocity to objectMaxSpeed while
686
689
  // while it has a mass, so it can not pass through a thin wall; true by default, false for
687
690
  // a fast bullet that collides; what does not collide moves as fast as it is told
688
691
  EngineObject.parent / children // Set by addChild, a child is placed by its parent and sits out solid collision
692
+ EngineObject.isBullet // Moves through the tiles as a point along a ray, never passing one at any speed and not held
693
+ // to objectMaxSpeed for them; collideWithTile sees it where it meets the tile; on a hit it
694
+ // bounces by restitution and goes on with the rest of its move, so it rolls along a floor;
695
+ // its collision with solid objects is the usual one, so one with a size is still clamped
696
+ EngineObject.oneWay // Up, down, left or right, like vec2(0, 1): a solid passed through that way, a platform
697
+ // jumped up through; it blocks only what was wholly on its far side or stood on it
689
698
 
690
699
  // Engine Object settings
691
700
  enablePhysicsSolver = true // Enable collisions, between objects and with tiles, and in 3D with solids and the level?
@@ -763,6 +772,12 @@ TileCollisionLayer.collisionTest(pos, size=(0,0), object) // Like tileCollisionT
763
772
  TileCollisionLayer.collisionRaycast(posStart, posEnd, object, normal) // Like tileCollisionRaycast for this layer only
764
773
  TileCollisionLayer.isSolid = true // Solid layers block objects and particles, the solidOnly tests
765
774
  // skip the others
775
+ TileCollisionLayer.setOneWay(tile, direction=vec2(0,1)) // The cells drawing that tile index are passed through
776
+ // moving that way and block only what was wholly on the far
777
+ // side, objects, particles and rays; turned and mirrored with
778
+ // each cell's art; vec2(0, 1) is a platform jumped up through
779
+ TileCollisionLayer.oneWayTiles // Map of the one way tiles, tile index to direction; delete one
780
+ // to make it solid again
766
781
  tileCollisionGetData(pos, solidOnly=true) // Get tile collision data at pos
767
782
  tileCollisionTest(pos, size=(0,0), object) // Check if collision should occur
768
783
  tileCollisionRaycast(posStart, posEnd, callbackObject, normal, solidOnly=true) // Where the ray meets the first tile hit,
@@ -1134,6 +1149,11 @@ new Light(pos, radius, color=WHITE, fadeRange=radius)
1134
1149
  light.glow = 0 // size across of a soft hazy glow over the light, like a lamp at night, 0 for none; added
1135
1150
  // over the lit scene after the lightmap, so it shows in the dark, in front of everything
1136
1151
  light.glowFalloff = 1 // how fast the glow fades from its middle, .5 a wide haze, 2 a tight bright core
1152
+ light.coneAngle = 0 // a cone, like a flashlight or headlight: radians from its up to the cone's edge, the beam
1153
+ // twice this across; it looks along getUp(), turned by its angle; 0 shines every way
1154
+ light.coneSoftness = .2 // how much of the cone is its fading edge: 0 a hard edge, 1 fading from the middle
1155
+ light.gel = tileInfo // or a whole TextureInfo: a picture the light shines through, across its square, its top
1156
+ // the way it looks, turned with it, multiplied into its color; shadows still fall
1137
1157
 
1138
1158
  // One directional light, a sun: lights the whole scene from one direction, added with the point lights
1139
1159
  new DirectionalLight(sunDirection=vec2(-1,1), color=WHITE) // toward the sun, as render3D.sunDirection; its color
@@ -1179,6 +1199,7 @@ postProcessBloom(threshold=.6, strength=1, size=6, includeMainCanvas=false) // s
1179
1199
  // and past a size of 32 it would take hundreds, so that is as wide as it goes
1180
1200
  postProcessBloomShader(threshold, strength, size) // its shader code, to pass to PostProcessPlugin or build on
1181
1201
  postProcess.setShaderCode(shaderCode) // shade with new code from the next frame, to switch effects while running
1202
+ postProcess.enabled = true // false skips the pass, the frame showing as drawn, to turn an effect off and back on
1182
1203
 
1183
1204
  // Built in effects: each is a piece of shader code with its settings written in, working on c, the pixel's color,
1184
1205
  // and uv, where it is on the screen from 0 to 1; postProcessEffects joins pieces in order into one shader, and your
@@ -1333,6 +1354,7 @@ render3D.camera.follow(target, offset, percent=1) // chase camera: ease toward t
1333
1354
  // is how far it moves each call, so call it every frame, from
1334
1355
  // gameUpdatePost once the target has moved
1335
1356
  render3D.camera.align2D = true // lock to the 2D camera so the z=0 plane matches world space, false by default
1357
+ render3D.camera.align2DZ = 0 // the z of the plane align2D lines up, the camera its distance in front
1336
1358
  render3D.camera.getForward() .getRight() .getUp() // the camera's axes as it is right now; render3D.cameraRight
1337
1359
  // .cameraUp .cameraForward are this frame's, read only
1338
1360
  render3D.viewMatrix .projectionMatrix .viewProjection .shadowMatrix // this frame's, rebuilt by updateMatrices()
@@ -1408,7 +1430,8 @@ render3D.shadowLight = flashlight // a spotlight, a Light3D with a coneAngle
1408
1430
  render3D.shadowRange = 40 // world size the map covers around shadowCenter, smaller is sharper
1409
1431
  // it is a square facing the light, so ~1.5x an area's width covers it
1410
1432
  render3D.shadowCenter = undefined // Vector3 center of the shadowed area, read each frame; undefined follows the camera
1411
- render3D.shadowBias = .003 // raise if lit surfaces get speckled with their own shadow, lower if shadows float away
1433
+ render3D.shadowBias = .003 // raise if lit surfaces get speckled with their own shadow, lower if shadows float away;
1434
+ // a share of the map's depth (2 * shadowRange for the sun), so the gap grows with the range
1412
1435
  // from their casters
1413
1436
  render3D.shadowSoftness = 1 // how far to blur the shadow edge, in shadow map pixels
1414
1437
 
@@ -2264,6 +2287,15 @@ newgrounds.postScore(id, value) // needs a logged in player and a whole num
2264
2287
  await newgrounds.getScores(id, user, social, skip, limit, period) // the scores are in result.data.scores; period
2265
2288
  // 'D' today (the server default), 'W', 'M', 'Y', 'A' all time; a user or social
2266
2289
  // narrows it down, and without either it is the whole board even when logged in
2290
+ await newgrounds.cloudSave(slot, data) // any value JSON can hold, to a slot numbered from 1, as many as the app's
2291
+ // settings give; needs a logged in player; true once saved, false with a
2292
+ // warning for a value JSON can not hold
2293
+ await newgrounds.cloudLoad(slot) // the value saved there, null when the slot is empty, undefined when it could
2294
+ // not be loaded or the player is not logged in: never save over one undefined
2295
+ newgrounds.loadFailure(slot) // why that slot's last cloudLoad gave undefined: 'notSave' for a file there that
2296
+ // is not a save, where a game may offer to start over, else 'failed'; a saved
2297
+ // null loads as null, as an empty slot does
2298
+ newgrounds.logEvent(name) // count an event of the game's own, by name, on its stats page
2267
2299
  newgrounds.unlockMedal(id) // low level request only, the medal is not changed; games call medal.unlock()
2268
2300
  newgrounds.pendingUnlocks // advanced: the unlocks in flight or waiting to be resent, with their promises
2269
2301
  newgrounds.resendUnlocks() // advanced: send the ones whose request did not reach the server again now, as
@@ -2308,6 +2340,18 @@ await wavedash.postScore(name, score) // a whole number, milliseconds for a time
2308
2340
  await wavedash.getScores(name, offset=0, limit=10, friendsOnly=false) // the entries as Wavedash gives them,
2309
2341
  // undefined off Wavedash or when they could not be read
2310
2342
  wavedash.leaderboards // each board's id promise by its name
2343
+ await wavedash.cloudSave(slot, data) // any value JSON can hold, kept across devices as saves/slot#.json; at most 30
2344
+ // saves a minute and 300 an hour, so at checkpoints; true once saved, false
2345
+ // with a warning for a value JSON can not hold
2346
+ await wavedash.cloudLoad(slot) // the value saved there, null when the slot is empty, undefined when it could
2347
+ // not be loaded or off Wavedash: never save over one that loaded undefined
2348
+ wavedash.loadFailure(slot) // why that slot's last cloudLoad gave undefined: 'notSave' for a file there that
2349
+ // is not a save, where a game may offer to start over, else 'failed'; a saved
2350
+ // null loads as null, as an empty slot does
2351
+ await wavedash.setStat(name, value, storeNow=false) // a stat made in the developer portal, kept a second later or
2352
+ // at once; the player's stats load the first time one is used
2353
+ await wavedash.getStat(name) // a stat's value, 0 for one never set or off Wavedash
2354
+ await wavedash.setPresence(status, details) // what the player is doing, kept with their presence; none clears it
2311
2355
  ```
2312
2356
 
2313
2357
  ## LittleJS Drawing Utilities
@@ -606,6 +606,8 @@ declare module "littlejsengine" {
606
606
  * @memberof Engine */
607
607
  export let frame: number;
608
608
  /** Current engine time since start in seconds
609
+ * - Below 20 FPS it falls behind the clock, since a frame catches up at most 50 ms of updates, so a timer in
610
+ * real seconds, one that must keep up on a slow device, goes by timeReal
609
611
  * @type {number}
610
612
  * @memberof Engine */
611
613
  export let time: number;
@@ -613,6 +615,10 @@ declare module "littlejsengine" {
613
615
  * @type {number}
614
616
  * @memberof Engine */
615
617
  export let timeReal: number;
618
+ /** Frames drawn per second, smoothed over the last few seconds, in release builds too
619
+ * @type {number}
620
+ * @memberof Engine */
621
+ export let averageFPS: number;
616
622
  /** Is the game paused? Causes time and objects to not be updated
617
623
  * @type {boolean}
618
624
  * @default false
@@ -647,7 +653,9 @@ declare module "littlejsengine" {
647
653
  * @param {GameCallback} [gameRender] - Called before objects are rendered, use for drawing backgrounds/world elements
648
654
  * @param {GameCallback} [gameRenderPost] - Called after objects are rendered, use for drawing UI/overlays
649
655
  * @param {Array<string>|string} [imageSources=[]] - List of image file paths to preload (e.g., ['player.png', 'tiles.png']), or one path
650
- * @param {HTMLElement} [rootElement] - Root DOM element to attach canvas to, defaults to document.body
656
+ * @param {HTMLElement} [rootElement] - Root DOM element to attach canvas to, defaults to document.body; it is
657
+ * styled for a game (no scroll bars or selection, touch-action none),
658
+ * where its own inline style does not say otherwise
651
659
  * It keeps its own inline styles and the canvas centers inside it, but the canvas is still sized from the window,
652
660
  * so set canvasFixedSize or canvasMaxSize to fit a smaller element
653
661
  * @example
@@ -2890,6 +2898,9 @@ declare module "littlejsengine" {
2890
2898
  * across the sprite or the mesh's own uv
2891
2899
  * - Names in 3D only: worldPos, worldNormal, cameraPos, sunDirection, sunColor, ambientColor, ambientGroundColor,
2892
2900
  * lightCount, lights[i], lightColors[i] and shadow()
2901
+ * - In 3D the snippet may also define void mainNormal(inout vec3 n), given the normal facing the camera after the
2902
+ * normal map, in world space, to bend it per pixel for waves or ripples; the lighting, specular, reflection and
2903
+ * fog then all use it, as they use a normal map
2893
2904
  * - In 3D the shadow map is drawn without the Shader, cut only by the texture's alpha, so a snippet that removes
2894
2905
  * parts of a surface still shadows with the whole of it
2895
2906
  * @example
@@ -4099,16 +4110,6 @@ declare module "littlejsengine" {
4099
4110
  * @return {Float32Array} - The audio samples
4100
4111
  * @memberof Audio */
4101
4112
  export function zzfxG(volume?: number, randomness?: number, frequency?: number, attack?: number, sustain?: number, release?: number, shape?: number, shapeCurve?: number, slide?: number, deltaSlide?: number, pitchJump?: number, pitchJumpTime?: number, repeatTime?: number, noise?: number, modulation?: number, bitCrush?: number, delay?: number, sustainVolume?: number, decay?: number, tremolo?: number, filter?: number): Float32Array;
4102
- /**
4103
- * LittleJS Object System
4104
- * - EngineObject is the base class for all game objects
4105
- * - Handles automatic updating, rendering, physics, and collision
4106
- * - Supports parent-child hierarchies with transform inheritance
4107
- * - 2D physics with velocity, acceleration, damping, and gravity
4108
- * - Collision system with tiles and other objects
4109
- * - Renders sprites from tile sheets with color and rotation
4110
- * - Objects sorted by renderOrder for layered rendering
4111
- */
4112
4113
  /**
4113
4114
  * LittleJS Object Base Object Class
4114
4115
  * - Top level object class used by the engine
@@ -4208,9 +4209,27 @@ declare module "littlejsengine" {
4208
4209
  children: Array<EngineObject>;
4209
4210
  /** @property {boolean} - Limit object speed along x and y axis */
4210
4211
  clampSpeed: boolean;
4212
+ /** @property {boolean} - Move through the tiles as a point along a ray, so it can not pass through one at
4213
+ * any speed, for small fast things like bullets: its speed is not held to objectMaxSpeed for the tiles,
4214
+ * collideWithTile is asked with this.pos where the ray goes into the tile, and on a hit it stops at the
4215
+ * surface, bounces off it by restitution and goes on with the rest of its move, so it slides along a floor
4216
+ * - Its collision with solid objects is the usual one, so one with a size that collides with them is still held
4217
+ * to objectMaxSpeed; one of no size is not, it never collides with them
4218
+ * - this.pos is set for each tile asked about and put where the move ends after, so a change collideWithTile
4219
+ * makes to it is not kept; move it after the physics, in update
4220
+ * - Once destroyed, as by its own collideWithTile, it is asked about no more tiles that frame, where an object
4221
+ * that is not a bullet still is */
4222
+ isBullet: boolean;
4211
4223
  /** @property {EngineObject|undefined} - Object we are standing on, if any
4212
4224
  * @type {EngineObject|undefined} */
4213
4225
  groundObject: EngineObject | undefined;
4226
+ /** @property {Vector2|undefined} - Makes a solid object one way, like a platform jumped up through: up, down,
4227
+ * left or right, the way others pass through it; it blocks only what was wholly on its far side before it
4228
+ * moved, or stood on it, and the rest are not stopped or asked through collideWithObject; with no mass it
4229
+ * moves before the other objects each frame, so a lift works the same at any render order; tiles are made one
4230
+ * way by the layer's setOneWay
4231
+ * @type {Vector2|undefined} */
4232
+ oneWay: Vector2 | undefined;
4214
4233
  /** @property {EngineObject|undefined} - Parent of object if in local space
4215
4234
  * @type {EngineObject|undefined} */
4216
4235
  parent: EngineObject | undefined;
@@ -4239,6 +4258,10 @@ declare module "littlejsengine" {
4239
4258
  * update: a solid an override moves for another object, a lift carrying a crate, is found there from the next
4240
4259
  * update on */
4241
4260
  updatePhysics(): void;
4261
+ /** @private */
4262
+ private updatePhysicsBullet;
4263
+ /** @private */
4264
+ private updatePhysicsTiles;
4242
4265
  /** Update the object, called automatically by engine once each frame. Does nothing by default */
4243
4266
  update(): void;
4244
4267
  /** Render the object, draws a tile by default, automatically called each frame, sorted by renderOrder */
@@ -4276,10 +4299,10 @@ declare module "littlejsengine" {
4276
4299
  /** Called to check if a tile collision should be resolved. Return true for physics to resolve the collision or false to ignore and resolve it manually
4277
4300
  * - Called for each solid tile the physics tests, which can be several times a frame for the same tile, and for
4278
4301
  * positions it only tries, so keep it free of side effects or guard them to once a frame
4279
- * - this.pos has already moved, so a check on where it came from, like a one way platform, needs the position
4280
- * saved in update, as the platformer example does
4281
- * - For the point of impact, like sparks where a bullet hit, raycast from where this frame's move started,
4282
- * this.pos minus this.velocity, to this.pos with tileCollisionRaycast, as the platformer's Bullet does
4302
+ * - this.pos has already moved, so a check on where it came from needs the position saved in update; for a one
4303
+ * way platform the layer's setOneWay does that, and this is not asked about a one way tile it passes through
4304
+ * - For the point of impact, like sparks where a bullet hit, set isBullet: this.pos is then where it meets the
4305
+ * tile, as the platformer's Bullet uses it
4283
4306
  * @param {number} tileData - the value of the tile at the position
4284
4307
  * @param {Vector2} pos - the tile's bottom left corner in world space
4285
4308
  * @return {boolean} - true if the collision should be resolved by modifying it's position and velocity */
@@ -4701,6 +4724,24 @@ declare module "littlejsengine" {
4701
4724
  /** @property {boolean} - In the light system's shadow pass, cast only from the cells with collision, drawn
4702
4725
  * as the layer shows them, so a floor in the same layer stays lit; false casts every tile */
4703
4726
  shadowSolidOnly: boolean;
4727
+ /** @property {Map<number, Vector2>} - The tiles set one way, by tile index, and the way each can be passed
4728
+ * through, see setOneWay
4729
+ * @type {Map<number, Vector2>} */
4730
+ oneWayTiles: Map<number, Vector2>;
4731
+ /** Make the cells that draw a tile one way, like a platform jumped up through and landed on: an object, a
4732
+ * particle or a ray passes through moving that way, and the tile blocks only what was wholly on its far side
4733
+ * before it moved, the side it is passed toward; turned and mirrored as each cell's tile is, so a platform turned
4734
+ * a quarter is one way to the side
4735
+ * - A moving object is not stopped or asked through collideWithTile until the tile would block it, so a
4736
+ * collideWithTile that returns false lets it drop through
4737
+ * - tileCollisionTest with an object goes by the object's pos, a raycast by its start, and a test with no object
4738
+ * or with a callback finds every one way tile solid
4739
+ * @param {number} tile - The tile index, as tile() and TileLayerData take it
4740
+ * @param {Vector2} [direction] - Up, down, left or right, the way it can be passed through: vec2(0, 1), the
4741
+ * default, is a platform landed on from above; layer.oneWayTiles.delete(tile) makes the tile solid again
4742
+ * @example
4743
+ * layer.setOneWay(5); // tile 5 is a platform, jumped up through and stood on */
4744
+ setOneWay(tile: number, direction?: Vector2): void;
4704
4745
  /** Clear and initialize tile collision, the size is the layer's own, the tile data and canvas keep it
4705
4746
  * @param {Vector2} size - width and height of tile collision 2d grid */
4706
4747
  initCollision(size: Vector2): void;
@@ -5122,6 +5163,8 @@ declare module "littlejsengine" {
5122
5163
  /** @property {Array<Object>} - Scoreboards fetched from Newgrounds, empty until ready
5123
5164
  * @type {Array<Object>} */
5124
5165
  scoreboards: Array<any>;
5166
+ /** @type {Map<number, 'failed'|'notSave'>} */
5167
+ loadFailures: Map<number, 'failed' | 'notSave'>;
5125
5168
  /** @property {{id: number, name: string, url: string, supporter: boolean}|null} - The logged in player once ready, null when not logged in
5126
5169
  * @type {{id: number, name: string, url: string, supporter: boolean}|null} */
5127
5170
  user: {
@@ -5171,6 +5214,42 @@ declare module "littlejsengine" {
5171
5214
  * result.data.scores, each with user.name, value and formatted_value; without a user or social it is the whole board
5172
5215
  */
5173
5216
  getScores(id: number, user?: string | number, social?: boolean, skip?: number, limit?: number, period?: string): Promise<any>;
5217
+ /** Save a value to one of the player's cloud save slots, which needs a logged in player
5218
+ * - Any value JSON can hold; the slots are numbered from 1, as many as the app's Newgrounds settings give it
5219
+ * @param {number} slot - The slot number
5220
+ * @param {*} data - The value to save
5221
+ * @return {Promise<boolean>} - Whether it saved, false when not logged in
5222
+ * @example
5223
+ * newgrounds.cloudSave(1, {level, coins}); */
5224
+ cloudSave(slot: number, data: any): Promise<boolean>;
5225
+ /** Load the value a cloud save slot holds: null when Newgrounds says it holds none, undefined when it could not be
5226
+ * loaded or the player is not logged in, said in the console when logged in
5227
+ * - Do not save over a slot that loaded as undefined, it may hold the player's save; loadFailure(slot) says why
5228
+ * it did: 'notSave' for a file there that is not a save, where a game may offer to start over, 'failed' otherwise
5229
+ * - A saved null loads as null, as an empty slot does
5230
+ * @param {number} slot - The slot number
5231
+ * @return {Promise<*>} - The value saved, null for none, undefined for a load that failed
5232
+ * @example
5233
+ * const save = await newgrounds.cloudLoad(1); */
5234
+ cloudLoad(slot: number): Promise<any>;
5235
+ /** Why a slot's last cloudLoad gave undefined: 'notSave' when the slot holds a file that is not a save, where a
5236
+ * game may offer to start over, 'failed' when it could not be loaded, as when not logged in, where a game must
5237
+ * not save over it; undefined when it loaded a save or found the slot empty, or was not loaded
5238
+ * @param {number} slot - The slot number
5239
+ * @return {'failed'|'notSave'|undefined} */
5240
+ loadFailure(slot: number): 'failed' | 'notSave' | undefined;
5241
+ /** A slot's load, its value and why it failed, if it did
5242
+ * @param {number} slot
5243
+ * @return {Promise<{value: *, failure: 'failed'|'notSave'|undefined}>}
5244
+ * @ignore */
5245
+ loadSlot(slot: number): Promise<{
5246
+ value: any;
5247
+ failure: 'failed' | 'notSave' | undefined;
5248
+ }>;
5249
+ /** Count an event of the game's own on its Newgrounds stats page, like a level finished or a button pressed
5250
+ * @param {string} name - The event's name
5251
+ * @return {Promise<Object>} - The response JSON object, undefined when the call failed */
5252
+ logEvent(name: string): Promise<any>;
5174
5253
  /** Encrypt text the way the Newgrounds gateway expects, AES-128 CBC with a random iv in front, as Base64
5175
5254
  * @param {string} text
5176
5255
  * @return {Promise<string>} */
@@ -5199,7 +5278,8 @@ declare module "littlejsengine" {
5199
5278
  }
5200
5279
  /**
5201
5280
  * LittleJS Wavedash Plugin
5202
- * - The Wavedash twin of the Newgrounds plugin: achievements and leaderboards, the same shape so a game can switch
5281
+ * - The Wavedash twin of the Newgrounds plugin: achievements, leaderboards and cloud saves, the same shape so a game
5282
+ * can switch, and stats and presence of Wavedash's own
5203
5283
  * - Wavedash serves the game's page and puts its SDK in window.Wavedash before the game runs, so nothing is bundled;
5204
5284
  * off Wavedash (local, itch, GitHub Pages) there is none, and every call does nothing
5205
5285
  * - Make the plugin when the game can draw, at the end of gameInit: Wavedash.init is called then, and until it is
@@ -5259,6 +5339,14 @@ declare module "littlejsengine" {
5259
5339
  };
5260
5340
  /** @type {number|undefined} */
5261
5341
  achievementRetry: number | undefined;
5342
+ /** @type {Promise<boolean>|undefined} */
5343
+ stats: Promise<boolean> | undefined;
5344
+ /** @type {Map<string, Promise<*>>} */
5345
+ slotQueues: Map<string, Promise<any>>;
5346
+ /** @type {Map<string, Set<Promise<*>>>} */
5347
+ slotCalls: Map<string, Set<Promise<any>>>;
5348
+ /** @type {Map<string, 'failed'|'notSave'>} */
5349
+ loadFailures: Map<string, 'failed' | 'notSave'>;
5262
5350
  /** Whether the game is on Wavedash, its SDK on the page
5263
5351
  * @return {boolean} */
5264
5352
  isActive(): boolean;
@@ -5287,6 +5375,74 @@ declare module "littlejsengine" {
5287
5375
  * @param {boolean} [friendsOnly] - Only the player and their friends
5288
5376
  * @return {Promise<Array<Object>|undefined>} - The entries, as Wavedash gives them */
5289
5377
  getScores(name: string, offset?: number, limit?: number, friendsOnly?: boolean): Promise<Array<any> | undefined>;
5378
+ /** Save a value to one of the player's cloud save slots, kept by Wavedash across devices; off Wavedash it does
5379
+ * nothing
5380
+ * - Any value JSON can hold, stored as the file saves/slot#.json
5381
+ * - Wavedash takes at most 30 saves a minute and 300 an hour from a player, so save at checkpoints, the end of a
5382
+ * level or when asked, not every frame; one over the limit is refused, said in the console
5383
+ * @param {number} slot - The slot number, any whole number the game picks
5384
+ * @param {*} data - The value to save
5385
+ * @return {Promise<boolean>} - Whether it saved
5386
+ * @example
5387
+ * wavedash.cloudSave(1, {level, coins}); */
5388
+ cloudSave(slot: number, data: any): Promise<boolean>;
5389
+ /** Load the value a cloud save slot holds: null when Wavedash says it holds none, undefined when it could not be
5390
+ * loaded or off Wavedash, said in the console on Wavedash
5391
+ * - Do not save over a slot that loaded as undefined, it may hold the player's save; loadFailure(slot) says why
5392
+ * it did: 'notSave' for a file there that is not a save, where a game may offer to start over, 'failed' otherwise
5393
+ * - A saved null loads as null, as an empty slot does
5394
+ * @param {number} slot - The slot number
5395
+ * @return {Promise<*>} - The value saved, null for none, undefined for a load that failed
5396
+ * @example
5397
+ * const save = await wavedash.cloudLoad(1); */
5398
+ cloudLoad(slot: number): Promise<any>;
5399
+ /** Why a slot's last cloudLoad gave undefined: 'notSave' when the slot holds a file that is not a save, where a
5400
+ * game may offer to start over, 'failed' when it could not be loaded, as off Wavedash or on a timeout, where a
5401
+ * game must not save over it; undefined when it loaded a save or found the slot empty, or was not loaded
5402
+ * @param {number} slot - The slot number
5403
+ * @return {'failed'|'notSave'|undefined} */
5404
+ loadFailure(slot: number): 'failed' | 'notSave' | undefined;
5405
+ /** A slot's load, its value and why it failed, if it did
5406
+ * @param {number} slot
5407
+ * @param {function(*): *} track
5408
+ * @return {Promise<{value: *, failure: 'failed'|'notSave'|undefined}>}
5409
+ * @ignore */
5410
+ loadSlot(slot: number, track: (arg0: any) => any): Promise<{
5411
+ value: any;
5412
+ failure: 'failed' | 'notSave' | undefined;
5413
+ }>;
5414
+ /** Run a slot's save or load after the one before it, as they share the slot's local file; other slots go on
5415
+ * at once, and one that fails does not hold up the next
5416
+ * - A call that timed out may still change the file when Wavedash gets to it, so the next waits for it to be
5417
+ * answered too, and gives up with failed when it is not, leaving the file alone
5418
+ * @param {number} slot
5419
+ * @param {*} failed - What the task gives when it can not run
5420
+ * @param {function(function(*): *): Promise<*>} task - Given track, which keeps a call's answer until it is in
5421
+ * @return {Promise<*>}
5422
+ * @ignore */
5423
+ slotQueue(slot: number, failed: any, task: (arg0: (arg0: any) => any) => Promise<any>): Promise<any>;
5424
+ /** Set one of the player's stats, as made for the game in the Wavedash developer portal, which can unlock an
5425
+ * achievement set to follow it; Wavedash keeps it about a second later, or at once with storeNow; off Wavedash it
5426
+ * does nothing
5427
+ * @param {string} name - The stat's identifier
5428
+ * @param {number} value
5429
+ * @param {boolean} [storeNow] - Keep it now, as at the end of a game, not a second later
5430
+ * @return {Promise<boolean>} - Whether Wavedash took it */
5431
+ setStat(name: string, value: number, storeNow?: boolean): Promise<boolean>;
5432
+ /** Read one of the player's stats, 0 for one never set or off Wavedash
5433
+ * @param {string} name - The stat's identifier
5434
+ * @return {Promise<number>} */
5435
+ getStat(name: string): Promise<number>;
5436
+ /** The player's stats loaded from Wavedash, which they must be before they are read or set, asked for the first
5437
+ * time a stat is
5438
+ * @return {Promise<boolean>} - Whether they loaded
5439
+ * @ignore */
5440
+ statsLoaded(): Promise<boolean>;
5441
+ /** Set what the player is doing, kept with their presence on Wavedash; off Wavedash it does nothing
5442
+ * @param {string} [status] - One line of what they are doing, like 'In a race'
5443
+ * @param {string} [details] - More about it, like 'Lap 2 of 3'; both left out clears it
5444
+ * @return {Promise<boolean>} - Whether it was set */
5445
+ setPresence(status?: string, details?: string): Promise<boolean>;
5290
5446
  /** Send every unlocked WavedashMedal's achievement Wavedash has not taken yet, again every two seconds while it
5291
5447
  * refuses some, as it does until it has loaded the player's achievements; one refused for about a minute is
5292
5448
  * taken as an identifier Wavedash does not have, said once in the console, and not sent again this visit
@@ -5350,6 +5506,9 @@ declare module "littlejsengine" {
5350
5506
  constructor(shaderCode?: string, includeMainCanvas?: boolean, feedbackTexture?: boolean);
5351
5507
  /** @property {string} - The shadertoy style mainImage code it shades with, see setShaderCode */
5352
5508
  shaderCode: string;
5509
+ /** @property {boolean} - Is the pass on? Off, the frame shows as the engine drew it, the shader is kept,
5510
+ * and the feedback texture holds the last frame drawn with it on */
5511
+ enabled: boolean;
5353
5512
  /** @property {Object<string, number|Array<number>>} - The game's own values for the shader, a uniform each
5354
5513
  * by its name, a number a float and a list of 2 to 4 numbers a vector, set every frame as they are; an
5355
5514
  * effect setting can be one of these names, so it changes every frame without making the shader again, all
@@ -5699,11 +5858,16 @@ declare module "littlejsengine" {
5699
5858
  * - A light inside a caster is blocked entirely, so the object that holds it, its lamp, a torch, the player
5700
5859
  * carrying it, needs a shadowCore that reaches past it, castShadow = false, or a renderShadow that leaves
5701
5860
  * the light's spot out
5861
+ * - A coneAngle makes it a cone along its up, turned by its angle, and a gel is a picture it shines through; both
5862
+ * are applied over the round light with its shadows, so they cost next to nothing
5702
5863
  * @extends EngineObject
5703
5864
  * @memberof LightSystem
5704
5865
  * @example
5705
5866
  * new Light(vec2(5, 5), 4, rgb(1, 0.5, 0)); // orange light, full soft blob
5706
5867
  * new Light(vec2(0, 0), 8, rgb(1, 1, 1), 2); // white core with 2-unit soft halo
5868
+ * const flashlight = new Light(vec2(), 10); // a flashlight looking right, its beam 60 degrees across
5869
+ * flashlight.coneAngle = PI/6;
5870
+ * flashlight.angle = PI/2;
5707
5871
  */
5708
5872
  export class Light extends EngineObject {
5709
5873
  /** Create a light object and add it to the engine object list
@@ -5728,6 +5892,19 @@ declare module "littlejsengine" {
5728
5892
  glowFalloff: number;
5729
5893
  /** @type {TileInfo|undefined} */
5730
5894
  glowTileInfo: TileInfo | undefined;
5895
+ /** @property {number} - Makes it a cone, like a flashlight or headlight: the angle in radians from its up out
5896
+ * to the edge of the cone, so the beam is twice this across; it looks along getUp(), turned by its angle and
5897
+ * by what it is attached to; 0 for a light that shines every way */
5898
+ coneAngle: number;
5899
+ /** @property {number} - How much of the cone is its fading edge: 0 a hard edge, .2 by default, the outer
5900
+ * fifth, 1 fading all the way from the middle of the beam */
5901
+ coneSoftness: number;
5902
+ /** @property {TileInfo|TextureInfo|undefined} - A gel, a picture the light shines through, called a cookie in
5903
+ * some engines: stretched across the light's square, twice its radius, its top the way the light looks,
5904
+ * turned with it, and multiplied into its color, so one light can be a car's headlights and tail lights;
5905
+ * a TileInfo's own tile, or a whole TextureInfo; shadows still fall through it
5906
+ * @type {TileInfo|TextureInfo|undefined} */
5907
+ gel: TileInfo | TextureInfo | undefined;
5731
5908
  /** Draw this light's glow, soft and round, its glow size across and in its color, added over the lit scene;
5732
5909
  * called by LightSystemPlugin after the lightmap is applied */
5733
5910
  renderGlow(): void;
@@ -8054,7 +8231,9 @@ declare module "littlejsengine" {
8054
8231
  * and 1 with the screen, a Vector2 to follow x and y by different amounts
8055
8232
  * @param {number} [renderOrder] - Low to draw behind the game, far layers lowest
8056
8233
  * @param {function(OffscreenCanvasRenderingContext2D, Vector2, ParallaxLayer): void} [drawFunction] - Draws
8057
- * the image, given the canvas context, its size in pixels and the layer; mountains when not given
8234
+ * the image, given the canvas context, its size in pixels and the layer; mountains when not given; called
8235
+ * once when the layer is made and on redraw(), not each frame, so it draws with the canvas 2D context, not
8236
+ * the engine's draws like drawTile
8058
8237
  * @param {Vector2} [canvasSize] - Size of the image in pixels */
8059
8238
  constructor(pos?: Vector2, size?: Vector2, parallax?: number | Vector2, renderOrder?: number, drawFunction?: (arg0: OffscreenCanvasRenderingContext2D, arg1: Vector2, arg2: ParallaxLayer) => void, canvasSize?: Vector2);
8060
8239
  /** @property {Vector2} - How much of the camera's movement it follows on each axis, 0 stays with the world
@@ -8788,7 +8967,8 @@ declare module "littlejsengine" {
8788
8967
  * - EngineObject3D is an EngineObject with a 3D position, rotation and mesh
8789
8968
  * - The 3D scene draws under the 2D sprites, so HUD and text land on top
8790
8969
  * - Lighting is the sun plus ambient, with optional extra lights, fog and shadows
8791
- * - Any object or draw can bring its own Shader, a mainImage snippet the lighting then applies to
8970
+ * - Any object or draw can bring its own Shader, a mainImage snippet the lighting then applies to, and a
8971
+ * mainNormal in it bends the normal the lighting uses
8792
8972
  * - Meshes and the basic builders are in render3dMesh.js, EngineObject3D, instancing and lights in
8793
8973
  * render3dObject.js, both right after this one; the other builders, terrain, particles, camera controls and the
8794
8974
  * OBJ loader are in the Render3D Extras plugin, which goes after those
@@ -8873,7 +9053,9 @@ declare module "littlejsengine" {
8873
9053
  /** @property {Vector3|undefined} - Center of the shadowed area, read each frame, undefined follows the camera
8874
9054
  * @type {Vector3|undefined} */
8875
9055
  shadowCenter: Vector3 | undefined;
8876
- /** @property {number} - Stops surfaces shadowing themselves, raise for speckles, lower if shadows drift off */
9056
+ /** @property {number} - Stops surfaces shadowing themselves, raise for speckles, lower if shadows drift off;
9057
+ * a share of the shadow map's depth, twice shadowRange for the sun and a spotlight's radius, so the gap behind
9058
+ * a caster grows with the range as the map's texels do, and the speckles they make stay away */
8877
9059
  shadowBias: number;
8878
9060
  /** @property {number} - How much to blur the shadow edges */
8879
9061
  shadowSoftness: number;
@@ -9417,6 +9599,9 @@ declare module "littlejsengine" {
9417
9599
  /** @property {boolean} - Line the 3D camera up with the 2D camera, so 3D things at z=0 sit on the 2D sprites;
9418
9600
  * it lines up with the 2D view of the main canvas, whatever canvas size a screenToRay is given */
9419
9601
  align2D: boolean;
9602
+ /** @property {number} - The z of the plane align2D lines up with the 2D view, the camera sitting its
9603
+ * distance in front of it */
9604
+ align2DZ: number;
9420
9605
  /** Returns the camera's world transform
9421
9606
  * @return {Matrix4} */
9422
9607
  getMatrix(): Matrix4;