littlejsengine 1.25.0 → 1.26.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/FAQ.md +49 -11
  3. package/README.md +6 -2
  4. package/REFERENCE.md +120 -18
  5. package/dist/littlejs.d.ts +464 -99
  6. package/dist/littlejs.esm.js +2853 -704
  7. package/dist/littlejs.esm.min.js +3 -3
  8. package/dist/littlejs.js +2844 -701
  9. package/dist/littlejs.min.js +2 -2
  10. package/dist/littlejs.release.js +2658 -512
  11. package/package.json +4 -4
  12. package/plugins/box2d.js +110 -34
  13. package/plugins/gltf.js +74 -22
  14. package/plugins/lightSystem.js +410 -28
  15. package/plugins/math3d.js +4 -2
  16. package/plugins/medalSystem.js +19 -7
  17. package/plugins/newgrounds.js +4 -2
  18. package/plugins/parallax.js +1 -0
  19. package/plugins/particleEffects.js +44 -10
  20. package/plugins/pathFinder.js +9 -11
  21. package/plugins/pluginExport.js +9 -0
  22. package/plugins/postProcess.js +4 -2
  23. package/plugins/render3d.js +485 -2385
  24. package/plugins/render3dDebug.js +5 -8
  25. package/plugins/render3dEditor.js +81 -6
  26. package/plugins/render3dExtras.js +103 -31
  27. package/plugins/render3dLevel.js +53 -5
  28. package/plugins/render3dMesh.js +1158 -0
  29. package/plugins/render3dObject.js +1451 -0
  30. package/plugins/render3dVoxels.js +15 -3
  31. package/plugins/textureSheet.js +9 -8
  32. package/plugins/threejs.js +1 -1
  33. package/plugins/tweenSystem.js +76 -51
  34. package/plugins/uiSystem.js +37 -8
  35. package/plugins/wavedash.js +229 -0
  36. package/src/engine.js +184 -36
  37. package/src/engineAudio.js +37 -21
  38. package/src/engineBuild.mjs +6 -1
  39. package/src/engineDebug.js +22 -162
  40. package/src/engineDraw.js +139 -34
  41. package/src/engineEditor.js +79 -18
  42. package/src/engineExport.js +0 -3
  43. package/src/engineInput.js +27 -18
  44. package/src/engineMath.js +35 -14
  45. package/src/engineObject.js +60 -41
  46. package/src/engineParticles.js +58 -11
  47. package/src/engineRelease.js +1 -5
  48. package/src/engineSettings.js +48 -9
  49. package/src/engineTileLayer.js +11 -4
  50. package/src/engineUtilities.js +41 -9
  51. package/src/engineWebGL.js +37 -13
package/CHANGELOG.md CHANGED
@@ -7,6 +7,93 @@ 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.26.0 - 2026-10-07
11
+
12
+ ### New Changes
13
+
14
+ - The package exports its plugins and engine source, littlejsengine/plugins/* and littlejsengine/src/*, which it shipped but would not let a tool reach; they are plain scripts, for a script tag or a copy, while the ESM build already holds every plugin
15
+ - DirectionalLight, a sun for the 2D light system: it lights the whole scene from one side, its sunDirection toward the sun as in 3D, foreground casters throw long shadows that fade by shadowLength, and a background marked castBackgroundShadow is lit only at the edges that face it, Frank Engine's look; one per scene, nothing changes for a game without one
16
+ - Wavedash plugin: achievements and leaderboards on Wavedash, the same shape as the Newgrounds plugin. A `WavedashMedal` is a medal with the identifier of its Wavedash achievement, sent until Wavedash takes it and shown with Wavedash's own toast there; `new WavedashPlugin(leaderboards)` calls `Wavedash.init` and makes the game's leaderboards with how each sorts and shows; `wavedash.postScore` and `getScores`, which give up after 15 seconds. An achievement Wavedash refuses for a minute warns once with its identifier. Off Wavedash every call does nothing
17
+ - Common mistakes say so: drawTile given a color where its tile goes asserts, engineInit takes one image file given as a string, a save that can not be read or written warns in release builds too, and the color constants like RED are frozen in release builds too, where changing one changed it everywhere
18
+ - Clearer errors for common mistakes in debug builds: keyIsDown with a number past the mouse buttons, mouseIsDown with a name, a vec2 of strings or a number where a vec2 goes, setGravity, setCameraPos, setCanvasFixedSize, setCanvasMaxSize, setTileDefaultSize and setCanvasClearColor given a number, a Timer of a string, setHex of the wrong length, drawText with a vec2 size, ParticleEmitter arguments out of order, and changing a color constant each say what to pass; rgb and hsl warn once when given values out of 255 or percents
19
+ - A failed assert's error says what went wrong, its message and values, where it said only 'Assert failed!' and left the message in the console, so the example browser's error box, a game's error overlay and a test now show it
20
+ - 3D environment maps: `render3D.environment` is a cube map that reflective surfaces reflect, sharp at a high shininess and blurred at a low one, and `render3D.skyBox` draws one as the sky; `makeCubeMap` paints one in code from a color for each direction and `loadCubeMap` loads six images. A cube map made with no faces captures the scene around a point (`capture`), the way three.js's CubeCamera does, and an object's `environment` reflects one of its own. A 3D level's scene block may name a sky box and an environment as six image urls each, which the 3D editor's Scene box edits. An object's `roughness`, 0 a mirror to 1 matte, sets its shininess the way glTF and three.js measure it, and a glTF material's roughnessFactor is read into it, so a loaded model's highlights and reflections are as sharp as its file says; a part whose material has none is matte, its shininess 1 where it was 16, which shows only with specular or reflectivity set. The 3D Reflections short shows a chrome sphere reflecting the scene around it under a sky painted in code
21
+ - A skinned glTF model bends about a fifth faster: the skin writes the GPU data as it bends, so the upload sends it without packing it again, and its joints' matrices are worked out with no garbage
22
+ - The level editors autosave a big map or 3D level once the edits stop for a moment, where every stroke on a 512 by 512 map wrote megabytes; a small one is saved after every stroke as before, and anything waiting is written when the editor closes, the page hides, the map is saved or another one loads
23
+ - The FAQ and REFERENCE say how far a world can go and how long a game can run before 32-bit floats on the GPU run out of precision
24
+ - The TypeScript example is typed by the shipped littlejs.d.ts in strict mode and imports 'littlejsengine' as an npm game does, an import map giving the browser the build; the Vite starter installs 1.25, where its lock held 1.18.19
25
+ - A Shader's or a post process's code comes after the engine's own in the shader, so a define in it can not rewrite the engine's names, where a #define of one, the vertex color say, changed what was drawn; the 3D shader drops see through texels at the end, after the normal map and texture reads that need the pixels around them
26
+ - Examples: the raycaster walks on a phone, shorts sized for the example browser fill any window, the starter, module and TypeScript examples match again, 3D shorts share one mesh among objects of the same shape, as the instancing one teaches, and smaller fixes
27
+ - Object collision with many solids is far faster: a mover checks only the solids near it, found through a grid, where it checked every one; 2000 movers went from about 1.3 s a frame to 12 ms in a test, and every contact resolves exactly as before, in the same order, but for a solid moved during physics by something other than its own update (a collision callback moving a third object, a lift's updatePhysics moving a crate), which is found where it went from the next update on
28
+ - Input: a key, button or tap released and pressed again in one frame reads as both, where the release was lost, and a keydown the browser does not mark as a repeat for a key already held does not press it again, but with Cmd held it does, since a Mac sends no keyup for a key let go under Cmd
29
+ - Canvas2D draws a texture smooth or pixelated as its own setting says, as WebGL does; the main canvas starts each frame with no alpha, filter or shadow a game left on it; toggleFullscreen refused by the browser leaves no error in the console
30
+ - A tile layer's shadow is cast by its solid cells only, not by negative markers; a color with a NaN in it is black as text in a release build too; glDeleteTexture leaves the engine holding nothing of the texture
31
+ - A tween whose callback throws no longer stops the other tweens moving that update, its error coming out after them
32
+ - glTF: parseGLTF takes the bytes as a typed array or a Node Buffer too, and an accessor off its values' alignment, or a sparse one past its buffer, says so by name where it threw a bare RangeError
33
+ - Matrix4.getRotation is exact just short of straight up or down, where it was off by up to 5e-4; getNearestClearNode searches each ring alone, the same node found
34
+ - A tinted drawTextureWrapped on Canvas2D bakes its tint once and keeps it for an image, where it baked the whole image on every draw (a canvas, which a game may draw into, is still baked at every draw), and draws smooth or pixelated as its texture says
35
+ - Level editor: a Save whose file was written is not taken for a failed one when what follows throws, which downloaded a copy; layers a game makes in code again and again no longer pile up in it; a map shrunk from the top drops a point or rectangle on its top edge
36
+ - Docs: align2D with any canvas size, an exported prefab names the prefabs inside it, a large time scale costs as many updates, the engineStep example, and why a Box2D world made again does not step as a fresh one
37
+ - A medal unlocked before medalsInit stays unlocked and is saved when it is called, where the save it loaded took it back; a later medalsInit with another save name starts from that save
38
+
39
+ ### Breaking Changes
40
+
41
+ - The built in video capture is removed: key 8 on the debug overlay, `debugVideoCaptureStart`, `debugVideoCaptureStop` and `debugVideoCaptureIsActive`. A screen recorder does it better, and the FAQ shows how to save a game frame by frame at an exact 60 fps with `setEngineManualStep` and `engineStep`
42
+ - Node 22.12 or later, the version CI runs: package.json's engines asks for it, so installing littlejsengine on Node 20 warns, or fails with engine-strict; a game in the browser needs no Node at all
43
+ - The Electron example is removed, with Electron and electron-packager from the dev dependencies: a game for the desktop sets up Electron or another wrapper itself, loading its index.html as any page
44
+ - render3d.js is in three files: `render3d.js`, the renderer, then `render3dMesh.js`, meshes and the basic builders, and `render3dObject.js`, EngineObject3D, instancing and lights; nothing changes for a game on a build in `dist`, one that loads the plugins one by one adds the two after `render3d.js`
45
+
46
+ ### Fixes
47
+
48
+ - A ParallaxLayer casts no shadow in the light system, as a backdrop threw huge ones, a sun's above all; set its castShadow to true for one that should
49
+ - A tween whose callback throws is ended, at any call: made, mid-run, restarted or starting a loop's next iteration, where it was called and threw at every update
50
+ - A texture sheet load that throws, as an atlas whose data can not be read, is forgotten and loaded again on the next call, as a failed file already was; loadSprite and loadTiles too
51
+ - A text field's Backspace takes off a whole character as a reader counts it, an emoji family too, and maxLength counts those; an image font draws a letter with an accent of its own, as é, as its letter
52
+ - An EngineObject3D on a height map is kept above a ridge it goes over in one move, where only the ends of the move were checked; a short move starting on a voxel block's face hits it there
53
+ - particleEffect keeps a negative speed and angle speed, and particleEffectFromEmitter of a finished emitter gives the time it emitted for, where it gave 0, for ever
54
+ - loadSound and a glTF's buffers and images name the file they could not load, with the hint for a page opened from disk; writeSaveData says when the data can not be written as JSON rather than blaming storage
55
+ - A lens flare made while the game is paused shows, as the 3D editor's Scene box makes one, where it waited for an update that never came, and follows the camera while the game stays paused
56
+ - A release build goes on past an error in its first frame, as it does in any later one, where it stopped the game
57
+ - The 3D settle of a shoved object sees a wall that blocks solids without being solid itself; a see-through part of an additive owner passes the fade assert
58
+ - Examples: the platformer's bullets spark where they hit the wall; the Light Shadows short has a sun
59
+ - setEnablePhysicsSolver(false) stops 3D collision too, solids, the level, the speed cap and 3D particles hitting the level, as it does in 2D, where 3D objects still collided
60
+ - Shader.dispose() frees a shader's programs and takes it off the engine's list, which kept every Shader made for the life of the page; a scene that makes its shaders each time it starts can let them go
61
+ - Wavedash: a board or achievement named like a built-in property, as constructor or toString, is one like any other
62
+ - A Box2dObject's EngineObject physics fields, which Box2D does not read (velocity, angleVelocity, damping, angleDamping, mass, friction, restitution, gravityScale), are documented as unused with the Box2D call to use, and a debug build warns once when one is set; damping out of 0 to 1 asserts in setObjectDefaultDamping and setObjectDefaultAngleDamping, and on a 3D object as on a 2D one, so a debug build that set one out of range stops there now
63
+ - A release build's second engineInit prints no second version line and hands back the first call's promise, done when the game is set up, where it was done at once
64
+ - The script build no longer has a top level colorHex, so a game's own colorHex is not a redeclaration
65
+ - Medals: a tab of the game opened before another tab unlocked a medal keeps that unlock when it saves its own, where it wrote the medal locked again; medalsReset still clears them all
66
+ - A 3D object a collision callback sends across a height map's ridge stays where it was sent, and the object pushing it is not carried back, where the settle swept it back to the ridge
67
+ - Sounds played or changed with a volume, rate or pan of NaN or Infinity, as a sound at the camera in a hidden page, play silent, at the normal rate or centered in a release build, where the browser's audio threw
68
+ - Box2D in a release build takes NaN or Infinity in a joint's force, torque, frequency, limits, a mass and a pulley ratio of 0 as the nearest value it can take, makes no joint of an object to itself and no fixture of an infinite box or circle (addCircle returns undefined then), where the wasm stopped for good
69
+ - UI: two quick taps in frames one after the other click twice, and a press over a button whose release was missed in a hitch releases and clicks it first, where a click and a release were lost; a held button let go of by the window losing focus is released, not clicked
70
+ - A tween whose easing throws is ended, as one whose callback throws is
71
+ - Shader.dispose() in the frame it drew in draws that batch first, where the batch compiled it again and kept it
72
+ - tileCollisionRaycast on a layer away from the origin gives a hit inside the tile it met, where it could land on its edge, in the empty tile beside it
73
+ - writeSaveData with data whose JSON is nothing, as a toJSON that returns nothing, writes nothing and says so, where it stored the text undefined
74
+ - A lens flare whose sun or light is hidden when it is made starts hidden, where it showed at full and faded out over its fade time
75
+ - A moving particle emitter, 2D or 3D, spreads each update's particles along its move since the last one, where they came out in a clump at where it is, so a fast emitter leaves an even trail instead of beads; a jump further than twice objectMaxSpeed in one update, a teleport or a respawn, spreads nothing, so an emitter moved by hand faster than that, as one following the mouse, is not spread either, and a local space emitter is unchanged
76
+ - A sound whose file fails to load calls its onloadCallback too, with isLoaded false, so a game counting its sounds before it starts no longer waits for ever on a missing file; a callback written for success checks isLoaded()
77
+ - The speed cap, objectMaxSpeed, is applied after damping and gravity, 2D and 3D alike: each axis of a colliding object's move is within it, gravity included, where a falling object moved the cap plus its gravity a frame; what does not collide is not capped, a velocity over the cap keeps the capped value, and a cap of 0 holds a colliding 3D object still as it does a 2D one
78
+ - Audio: a one shot paused after its end and before its ended event stays at its end, where a resume played it again; a zzfx array's own randomness is kept to 0 to 1, as one passed in is; a sound at exactly its range, 2D or 3D, is out of it, not a silent voice
79
+ - particleEffect: an option that is not a value of its kind keeps the effect's own value, saying so in a debug build, where it quietly became the library's default ({speed: null} on fire, an emitTime of '0.5' that never stopped); a vec2 emitSize is a rectangle, as ParticleEmitter takes one; an option of no name asserts in a debug build
80
+ - A one shot particle effect gives the same count whichever frame it is made on, 1 at once and its rate for its emit time, where the rounding of time decided its last frame (an explosion gave 61 or 71); a burst shorter than a frame gives its rate for that time, and 2D and 3D emitters agree
81
+ - Image fonts and 3D text read text as a reader counts characters: an emoji, joiners and all, is one box where it was two to eight, and a letter with a combining accent draws as its letter; text with Windows line endings breaks lines as `\n` does in image fonts, canvas text, 3D text and the UI, where the `\r` drew a box or a space and shifted the line
82
+ - A font the canvas does not take, as a family name with spaces and digits like Press Start 2P left unquoted, warns once in a debug build, where the text drew as 10px sans-serif with no word; setFontDefault says to quote such names
83
+ - setSoundVolume asserts its volume is a number, and a NaN one is not handed to the audio, where it threw every frame; setSoundEnable says it is a setting for before sounds are made, setSoundVolume(0) the way to mute while playing
84
+ - 3D solids meet as in 2D: a fast object hitting a thin solid, or two solid objects meeting, are pushed back to the sides they came from, by where both were when the frame began, where the least overlap sent one on out the far side once past the other's middle; it holds the same whichever was made first and whichever of the two moves, a moving platform too, up to about the speeds 2D holds; turned boxes still push by least overlap. A 3D object shoved into a wall by another is settled against it the same frame, through the wall's collideWithObject, and the other takes what it could not move, so a pusher stops against a crate held by a wall and a heavy object resting on a light one no longer presses it into the floor
85
+ - A 3D particle crossing a ridge of a height map, both ends of its move above the ground, hits it, where only the ends were checked and it passed through; a move over several slopes hits the first it meets, and one leaving the map hits before it leaves
86
+ - A glTF model's parts, an attached prefab's parts and a voxel map's transparent blocks draw with the settings set on the model, handle or map (emissive, reflectivity, shader, shadows, renderAfter2D, renderOrder and the rest), each part keeping its own where the owner's is left at the default, so an unlit or rough glTF part keeps its file's material, and a part keeps its own values as its own (a part taken off draws with them); they kept the defaults
87
+ - Level editor: a reload of the file a Save wrote drops the autosave the file now has, and does not add an empty Objects layer, where an Objects layer the editor made for an object taken back again kept its empty list in the autosave; an Objects layer a Save wrote stays when its objects go after
88
+ - In a release build, the 3D renderer or the light system whose shaders do not build on a device says so once and draws nothing, where it drew every frame with programs that failed, and a device that fell back to Canvas2D is told 3D can not be drawn
89
+ - Tile collision stops an object against a wall or a ceiling, as it already did on a floor, so a game sees objects stop up to a frame's move further on at walls and ceilings, where it went back to where it was the frame before and stopped short by up to a frame's move, a gap that never closed for a game setting its velocity every frame; a bounce starts from the tile too, and a box landing with its top on a grid line no longer snaps into the tile above it
90
+ - A 3D particle emitter that outlives a scaled or turned parent keeps the parent's scale and turn, where it kept only its place and its particles fell at a quarter of the speed under a parent scaled by four; a particle's create callback reads the emitter's scale when it is emitted by hand before the first update or from inside another callback
91
+ - A UI object moved to another parent or detached in its own update is updated once that update, where it could be updated twice
92
+
93
+ ### New Demos
94
+
95
+ - [3D Reflections](https://killedbyapixel.github.io/LittleJS/examples/?example=3D%20Reflections) - A chrome ball reflecting the scene around it, captured live, and balls from polished to rough under a sky painted in code
96
+
10
97
  ## 1.25.0 - 2026-10-04
11
98
 
12
99
  ### New Changes
package/FAQ.md CHANGED
@@ -10,7 +10,10 @@ If you don't find an answer here, feel free to ask the community or check the do
10
10
  - [How do I use LittleJS as an ES module?](#how-do-i-use-littlejs-as-an-es-module)
11
11
  - [How do I use LittleJS with TypeScript?](#how-do-i-use-littlejs-with-typescript)
12
12
  - [Why do I see a blank screen when I run my game?](#why-do-i-see-a-blank-screen-when-i-run-my-game)
13
+ - [Why does my game say a name "has already been declared"?](#why-does-my-game-say-a-name-has-already-been-declared)
13
14
  - [Do I need a local server to run LittleJS games, and how do I set one up?](#do-i-need-a-local-server-to-run-littlejs-games-and-how-do-i-set-one-up)
15
+ - [Which browsers does LittleJS run in?](#which-browsers-does-littlejs-run-in)
16
+ - [How far can my world go, and how long can my game run?](#how-far-can-my-world-go-and-how-long-can-my-game-run)
14
17
  - [How does the camera and world coordinate systems work?](#how-does-the-camera-and-world-coordinate-systems-work)
15
18
  - [How do I configure engine settings?](#how-do-i-configure-engine-settings)
16
19
  - [What plugins ship with LittleJS?](#what-plugins-ship-with-littlejs)
@@ -48,6 +51,7 @@ If you don't find an answer here, feel free to ask the community or check the do
48
51
 
49
52
  **Debugging and Development**
50
53
  - [How do I debug my game in LittleJS?](#how-do-i-debug-my-game-in-littlejs)
54
+ - [How do I record a smooth video of my game, like a trailer?](#how-do-i-record-a-smooth-video-of-my-game-like-a-trailer)
51
55
 
52
56
  ---
53
57
 
@@ -55,16 +59,16 @@ If you don't find an answer here, feel free to ask the community or check the do
55
59
  ### What is LittleJS, and how is it different from other JavaScript game engines?
56
60
 
57
61
  LittleJS is a lightweight, high-performance JavaScript game engine designed for simplicity and speed.
58
- It offers a hybrid rendering system that combines the advantages of WebGL and 2D Canvas, and in our sprite-rendering benchmarks it outperforms larger engines like Phaser, Pixi, and Kaboom.
59
- Unlike those feature-heavy engines, LittleJS focuses on 2D games and providing a comprehensive set of simple, easy to use features.
62
+ It offers a hybrid rendering system that combines the advantages of WebGL and 2D Canvas, and [bench/](bench/) runs the same scenes in it and in three.js, PixiJS and Phaser.
63
+ Unlike those feature-heavy engines, LittleJS keeps to a comprehensive set of simple, easy to use features, for 2D games first, with a 3D renderer of its own as well.
60
64
  LittleJS is perfect for developers who want a minimal yet powerful engine to bring their 2D game ideas to life without the complexity of larger frameworks, and it's especially well suited for size-constrained competitions like js13kGames.
61
65
 
62
66
  ### How do I set up a basic LittleJS project?
63
67
 
64
- Download the LittleJS repository via GitHub or npm.
68
+ Clone the LittleJS repository, or use Download ZIP on its GitHub page: the examples are only there, the npm package and the release zip hold the engine alone.
65
69
  Include one of the LittleJS builds from the dist folder.
66
- Several examples are included for you to build on.
67
- The most basic example is just an empty project.
70
+ Several examples are included for you to build on, and they need a local web server: serve the repository's root, for example with `npx http-server`, and open `http://localhost:8080/examples/empty/` (see [Do I need a local server](#do-i-need-a-local-server-to-run-littlejs-games-and-how-do-i-set-one-up)).
71
+ The most basic example is just an empty project. Its `import '../../dist/littlejs.esm.js'` finds the engine from inside the repository, so a copy of the folder somewhere else changes that path to where the engine is.
68
72
 
69
73
  [Empty Example HTML file:](https://github.com/KilledByAPixel/LittleJS/blob/main/examples/empty/index.html)
70
74
  ```html
@@ -132,7 +136,7 @@ LJS.engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost,
132
136
 
133
137
  The empty example is an ES module, so engine names come from the `LJS` namespace. For a classic setup where the engine is a plain `<script src=../../dist/littlejs.js>` tag and every engine name is a global, start from [examples/starter](https://github.com/KilledByAPixel/LittleJS/tree/main/examples/starter) instead.
134
138
 
135
- This is the simplest setup — two files served by a local web server. If you'd rather start from a bundler-based project with hot reload, see [How do I use Vite with LittleJS?](#how-do-i-use-vite-with-littlejs) for the official Vite starter template.
139
+ This is the simplest setup — three files served by a local web server. If you'd rather start from a bundler-based project with hot reload, see [How do I use Vite with LittleJS?](#how-do-i-use-vite-with-littlejs) for the official Vite starter template.
136
140
 
137
141
  ### How do I use LittleJS as an ES module?
138
142
 
@@ -193,8 +197,8 @@ See [examples/typescript/](examples/typescript/) for a complete TypeScript proje
193
197
 
194
198
  If you are seeing a blank screen, first try opening the dev tools console (F12 in most browsers).
195
199
  This will show you any errors that occur and allows stepping through code to help debug.
196
- A common issue is the image data failing to load with a message like "The image element contains cross-origin data, and may not be loaded."
197
- This is probably because the game was loaded directly without using a web server!
200
+ A common issue is the game opened from a file on disk with no web server: the console then says an image was "blocked by CORS policy" from origin 'null' and `failed to load image: tiles.png`, or that a module script was blocked, and the game runs with no sprites or not at all.
201
+ Serve it from a local web server, see [Do I need a local server](#do-i-need-a-local-server-to-run-littlejs-games-and-how-do-i-set-one-up).
198
202
 
199
203
  ### Why does my game say a name "has already been declared"?
200
204
 
@@ -225,6 +229,12 @@ There is also a function you can use called getCameraSize() to get the viewable
225
229
 
226
230
  One thing to keep in mind: in **world space**, Y increases *upward* and tile coordinates use a bottom-left origin. This often surprises developers coming from Canvas2D or other 2D libraries. **Screen space** (used when you pass `screenSpace=true` to a draw function, or when reading `mousePosScreen`) follows the Canvas2D convention instead — Y increases *downward* with a top-left origin. Pick the right one for what you're doing: world space for gameplay, screen space for HUDs and UI overlays.
227
231
 
232
+ ### How far can my world go, and how long can my game run?
233
+
234
+ The GPU draws with 32-bit floats, which hold about 7 digits, so precision runs out far from the origin and late in a long run. These are limits every engine has, not bugs, and most games never come near them:
235
+ - **Distance:** positions go to the GPU as they are, in world units, not relative to the camera. A 32-bit float steps by about .001 at 10,000 units from the origin and .008 at 100,000, so far out sprites and meshes start to jitter as they move. Keep the play area within about ±10,000 units, and a game that travels farther can move the world and the camera back toward the origin together now and then.
236
+ - **Time:** `time` itself is a 64-bit number and is fine, but shaders see it as the 32-bit `iTime`, which steps by about .0002 seconds after an hour and .008, half a frame, after a day, so a shader animated by it gets choppy in a game left running for days. A post process can be given a time of its own that wraps, through `postProcess.values`.
237
+
228
238
  ### How do I configure engine settings?
229
239
 
230
240
  LittleJS exposes settings through `setX()` functions for every tunable value. Most settings need to be applied **before** `engineInit()` because they affect canvas creation, WebGL setup, or input registration:
@@ -257,7 +267,9 @@ Plugins are self-contained features that live alongside the engine but aren't pa
257
267
  | Plugin | Purpose |
258
268
  |--------|---------|
259
269
  | `math3d.js` | `Vector3` and `Matrix4` for 3D games and plugins, with 3D collision and raycast helpers |
260
- | `render3d.js` | The built-in 3D renderer: meshes, lights, shadows, fog and EngineObject3D, into the same canvas as the 2D scene (needs `math3d.js`) |
270
+ | `render3d.js` | The built-in 3D renderer: the camera, lighting, shadows and fog, into the same canvas as the 2D scene (needs `math3d.js`) |
271
+ | `render3dMesh.js` | Meshes and the basic shape builders, `buildBox`, `buildSphere`, `buildGrid`, `buildLathe` and `buildSky` (goes after `render3d.js`) |
272
+ | `render3dObject.js` | `EngineObject3D`, `InstancedMesh3D` and the lights (goes after `render3dMesh.js`) |
261
273
  | `render3dExtras.js` | The rest of the 3D toolkit on top of it: more shape builders, terrain, camera controls, 3D particles and trails, and the OBJ loader |
262
274
  | `gltf.js` | Loads glTF and GLB models onto the 3D renderer: meshes, node placement and node animations, colors and textures |
263
275
  | `threejs.js` | Renders a Three.js scene behind the LittleJS canvas, an alternative to `render3d.js` |
@@ -302,7 +314,7 @@ Key things the template sets up for you:
302
314
  - A `public/.nojekyll` marker so GitHub Pages serves Vite's `_`-prefixed chunk files correctly.
303
315
  - A full page reload on save instead of partial HMR, since LittleJS has engine-level global state (canvas, WebGL, input listeners, the RAF loop) that doesn't survive a module hot swap. The full reload is a small plugin in `vite.config.js`.
304
316
 
305
- Requires Node 20.19+ or 22.12+ (Vite 7 requirement). Other community projects like [Michael Haynie's LittleJS Jam project](https://github.com/michael-dean-haynie/littlejs-game-jam-2024) are good real-world references for more elaborate Vite setups.
317
+ Requires Node 22.12+. Other community projects like [Michael Haynie's LittleJS Jam project](https://github.com/michael-dean-haynie/littlejs-game-jam-2024) are good real-world references for more elaborate Vite setups.
306
318
 
307
319
  ### How do I use Box2D with Vite?
308
320
 
@@ -384,7 +396,7 @@ LittleJS has a dedicated [js13k branch](https://github.com/KilledByAPixel/Little
384
396
  - **Lean on built-ins to avoid asset files**: `Sound` with ZzFX parameters generates sound effects from a tiny array, and built-in shape drawing avoids shipping image assets.
385
397
  - **Use a packer**: the js13k branch is set up to compress the final bundle with Roadroller or a similar packer. Check the branch's build script for the current toolchain.
386
398
 
387
- For normal production (non-13KB) builds, the engine ships `dist/littlejs.release.js` (asserts stripped, about 1.5 MB with every plugin and its comments) and `dist/littlejs.min.js` (minified, about 420 KB, about 125 KB gzipped). A bundler like Vite leaves out the plugins a game does not use, which makes a small game far smaller.
399
+ For normal production (non-13KB) builds, the engine ships `dist/littlejs.release.js` (asserts stripped, about 1.6 MB with every plugin and its comments) and `dist/littlejs.min.js` (minified, about 450 KB, about 135 KB gzipped). A bundler like Vite leaves out the plugins a game does not use, which makes a small game far smaller.
388
400
 
389
401
  ---
390
402
 
@@ -845,6 +857,32 @@ LOG('spawned enemy at', pos);
845
857
 
846
858
  ---
847
859
 
860
+ ### How do I record a smooth video of my game, like a trailer?
861
+
862
+ A screen recorder such as OBS, the Xbox Game Bar or the macOS screen recorder is the quickest. For every frame at exactly 60 fps, even on a machine that cannot keep up, step the engine yourself and save each frame as an image: with `setEngineManualStep(true)` the game moves only when `engineStep` is called, one update of 1/60 of a second and its render, however long that takes.
863
+
864
+ ```javascript
865
+ // record a number of seconds of the game, one image every 1/60 of a second
866
+ function recordFrames(seconds)
867
+ {
868
+ setEngineManualStep(true); // the engine moves only when engineStep is called
869
+ let frame = 0;
870
+ const next = ()=>
871
+ {
872
+ engineStep(1); // one update of 1/60 second, and its render
873
+ combineCanvases(); // the WebGL and 2D canvases as one image
874
+ saveCanvas(mainCanvas, 'frame' + String(frame).padStart(5, '0'));
875
+ if (++frame < seconds * 60)
876
+ setTimeout(next, 100); // a moment for the browser to save each one
877
+ else
878
+ setEngineManualStep(false); // back to real time
879
+ };
880
+ next();
881
+ }
882
+ ```
883
+
884
+ The browser asks once to allow many downloads. The game plays slower than real time while it records, so this suits a scripted or replayed scene. Then make the video from the images, with ffmpeg for example: `ffmpeg -framerate 60 -i frame%05d.png -pix_fmt yuv420p trailer.mp4`.
885
+
848
886
  ## Contribute to the FAQ
849
887
  If you have additional questions or think something should be added to this FAQ, please open an issue or pull request on the [LittleJS GitHub repository](https://github.com/KilledByAPixel/LittleJS).
850
888
 
package/README.md CHANGED
@@ -52,6 +52,7 @@ The code is very clean and well documented with many examples to get you started
52
52
  - Height map terrain and voxel maps with collision and raycasts
53
53
  - 3D levels and prefabs, built in the 3D level editor and loaded with one call
54
54
  - Shadow maps, colored lights, spotlights, lens flares, specular, emissive glow and fog
55
+ - Reflections of a sky box or the scene itself, from mirror sharp to rough
55
56
  - Particles, trails, billboards and instanced drawing
56
57
  - Orbit, chase and first person cameras with mouse picking
57
58
  - Custom shaders on any object
@@ -74,6 +75,7 @@ The code is very clean and well documented with many examples to get you started
74
75
 
75
76
  - Robust arcade physics system with collision handling
76
77
  - Fast tilemap collision and raycasting
78
+ - Object collision that stays fast with thousands of objects
77
79
  - Full Box2D integration for realistic physics using [Box2D v2.3.1 wasm](https://github.com/kripken/box2d.js)
78
80
  - Grid-based A* pathfinding plugin with optional path smoothing
79
81
 
@@ -120,7 +122,9 @@ npm install
120
122
  npm run dev
121
123
  ```
122
124
 
123
- Here is a minimal Hello World example game, with the script path pointing at the engine file from the download or npm package.
125
+ The examples are only in the repository: clone it, or use Download ZIP on its GitHub page; the npm package and the release zip hold the engine alone. A page that loads images or modules needs a local web server, as browsers block them from a file on disk: serve the repository's root, for example with `npx http-server`, then open `http://localhost:8080/examples/starter/`, the place to start. The [FAQ](FAQ.md#do-i-need-a-local-server-to-run-littlejs-games-and-how-do-i-set-one-up) has more.
126
+
127
+ Here is a minimal Hello World example game, with the script path pointing at the engine file from the download or npm package; it loads nothing, so it runs straight from the file.
124
128
 
125
129
  ```html
126
130
  <!DOCTYPE html>
@@ -172,7 +176,7 @@ LittleJS comes with several demos both for learning and using as starter project
172
176
  | [littlejs.esm.js](https://github.com/KilledByAPixel/LittleJS/blob/main/dist/littlejs.esm.js) | Debug | ESM | Debug mode with asserts |
173
177
  | [littlejs.esm.min.js](https://github.com/KilledByAPixel/LittleJS/blob/main/dist/littlejs.esm.min.js) | Release | ESM | Optimized for release and minified |
174
178
 
175
- Every build includes the plugins. littlejs.min.js is about 125 KB gzipped, and the 3D plugins are a large part of that. A bundler like Vite leaves out the plugins a game does not use, so a small 2D game comes to about 26 KB gzipped, and a lit 3D scene to about 47 KB.
179
+ Every build includes the plugins. littlejs.min.js is about 135 KB gzipped, and the 3D plugins are a large part of that. A bundler like Vite leaves out the plugins a game does not use, so a small 2D game comes to about 26 KB gzipped, and a lit 3D scene to about 47 KB.
176
180
 
177
181
  ## Games Made With LittleJS
178
182
 
package/REFERENCE.md CHANGED
@@ -94,7 +94,11 @@ let remaining = 36000;
94
94
  gravity, at the fixed 60 updates a second
95
95
  - **Seconds:** `Timer`, `Tween`, `time`, a particle emitter's `emitTime` and `particleTime` and its `emitRate` (per
96
96
  second) and `lifeTime`; a particle's `fadeRate` is a part of its life, not seconds
97
+ - **Materials:** `roughness`, `reflectivity` and `specular` run 0 to 1, `shininess` 1 up, about 10,000 a mirror (a roughness of 0 sets it far higher), and
98
+ `emissive` from 0 up
97
99
  - **Milliseconds:** `gamepadVibrate`'s duration, `vibrate` patterns and `saveDataURL`'s `revokeTime`
100
+ - **Limits:** the GPU draws with 32-bit floats: keep the world within about ±10,000 units of the origin, and a
101
+ shader's `iTime` gets coarse in a game left running for days; the FAQ says more
98
102
 
99
103
  ## What functions give back
100
104
  - **Raycasts:** where the ray hits, a Vector2: `tileCollisionRaycast`, `lineTest`; how far along the ray, a number:
@@ -323,6 +327,9 @@ new Shader(fragmentCode) // fragmentCode defines void mainImage(out vec
323
327
  // style; it gives the surface color, then the object's color and additive color
324
328
  // apply in 2D, and the lighting, shadows and fog in 3D
325
329
  obj.shader = shader // any EngineObject or EngineObject3D; draws that share a Shader share a batch
330
+ shader.dispose() // frees its programs and takes it off the engine's list, which keeps every Shader
331
+ // for a lost context; for one made each time a scene starts; drawn again it
332
+ // compiles again, so let go of only one nothing draws with
326
333
  // names in the snippet: iChannel0 the texture, iTime, iResolution, localUV 0 to 1 across the sprite or the mesh uv,
327
334
  // premultipliedTexture true for a render target or a smooth image (tilesPixelated false), whose rgb and alpha
328
335
  // change together;
@@ -447,7 +454,9 @@ glDraw(x, y, sizeX, sizeY, angle=0, uv0X, uv0Y, uv1X, uv1Y, rgba=-1, rgbaAdditiv
447
454
  ```javascript
448
455
  // Sound Object
449
456
  Sound(zzfxSound, randomness, range, taper, onloadCallback) // Create a zzfx sound
450
- Sound(filename, randomness, range, taper, onloadCallback) // Load a wave, mp3, or ogg
457
+ zzfx(...zzfxSound) // Generate and play a zzfx sound at once, no Sound kept
458
+ Sound(filename, randomness, range, taper, onloadCallback) // Load a wave, mp3, or ogg; onloadCallback(sound) is
459
+ // called once it loads or fails to, isLoaded says which
451
460
  Sound.play(pos, volume=1, pitch=1, randomnessScale=1, loop=false, paused=false) // Play a sound, returns SoundInstance
452
461
  Sound.playLoop(pos, volume=1, pitch=1, randomnessScale=1, paused=false) // Play on a loop, like play with loop on
453
462
  Sound.playMusic(volume=1, loop=true, paused=false) // Play as music with looping
@@ -480,7 +489,8 @@ speakStop() // Stop all queued speech
480
489
  getNoteFrequency(semitoneOffset, rootFrequency=220) // Get frequency for musical notes
481
490
 
482
491
  // Audio settings
483
- soundEnable = true // Should sound be enabled?
492
+ soundEnable = true // Should sound be enabled? set it before sounds are made, setSoundEnable(false)
493
+ // before they are, setSoundVolume(0) to mute while the game runs
484
494
  soundIgnoreSilentSwitch = false // play with an iPhone's silent switch on, as media, pausing the player's music;
485
495
  // off, the switch mutes the game; setSoundIgnoreSilentSwitch(true)
486
496
  soundVolume = .3 // Volume scale to apply to all sound
@@ -678,7 +688,7 @@ EngineObject.clampSpeed // Hold each axis of velocity to objectMaxSpeed while
678
688
  EngineObject.parent / children // Set by addChild, a child is placed by its parent and sits out solid collision
679
689
 
680
690
  // Engine Object settings
681
- enablePhysicsSolver = true // Enable collisions, between objects and with tiles?
691
+ enablePhysicsSolver = true // Enable collisions, between objects and with tiles, and in 3D with solids and the level?
682
692
  objectDefaultMass = 1 // Default object mass for collisions
683
693
  objectDefaultDamping = 1 // Fraction of velocity kept each frame (1 keeps all)
684
694
  objectDefaultAngleDamping = 1 // Fraction of angular velocity kept each frame (1 keeps all)
@@ -849,7 +859,8 @@ particleEffectApply(emitter, effect) // set a live 2D emitter to an effect
849
859
  particleEffectApply3D(emitter, effect) // the same for a ParticleEmitter3D, its particles, place and scale kept
850
860
  particleEffectFromEmitter(emitter, name='Effect') // a 2D emitter's settings as an effect, to save or build in 3D
851
861
  particleEffectsAddBehavior(name, update, update3D, min=-2, max=2, value=1, description='') // add a behavior effects
852
- // can name, with 2D and 3D pushes; min, max and value for the designer
862
+ // can name, with 2D and 3D pushes; min and max for the designer and to clamp
863
+ // a strength to, value to start at
853
864
  particleEffectsAdd(effects) // add effects to play by name, one of the same name replaces it
854
865
  particleEffectsGet(name) // an effect's data, any case, to change or build by hand
855
866
  await particleEffectsLoad(url) // load the file the particle designer's Save Library wrote, and add its effects
@@ -1124,6 +1135,20 @@ light.glow = 0 // size across of a soft hazy glow over the light, like
1124
1135
  // over the lit scene after the lightmap, so it shows in the dark, in front of everything
1125
1136
  light.glowFalloff = 1 // how fast the glow fades from its middle, .5 a wide haze, 2 a tight bright core
1126
1137
 
1138
+ // One directional light, a sun: lights the whole scene from one direction, added with the point lights
1139
+ new DirectionalLight(sunDirection=vec2(-1,1), color=WHITE) // toward the sun, as render3D.sunDirection; its color
1140
+ sun.sunDirection // toward the sun, it shines the other way
1141
+ sun.castShadow = true // foreground casters (castShadow) throw long shadows; no lightSystem.shadows needed
1142
+ sun.shadowLength = 20 // world units a long shadow reaches before it has faded out; a caster casts from inside
1143
+ // the shadow map only, (shadowMapScale - 1) / 2 of a view past the screen
1144
+ sun.backgroundDepth = 3 // world units the light gets into a background area from its edges facing it
1145
+ lightSystem.directionalLight // the one there is, or undefined; a second one asserts, destroy the first
1146
+ // (a release build destroys the first itself)
1147
+ lightSystem.directionalTextureSize = 512 // pixels across its work textures, which cover the shadow map's area; a
1148
+ // power of two, as shadowMapSize is, or the shadows shimmer as the view pans
1149
+ obj.castBackgroundShadow = false // a background: dark to the directional light inside, lit at its edges;
1150
+ // a background layer sets castShadow = false and this true
1151
+
1127
1152
  // Per-object lightmap contribution hook (on every EngineObject)
1128
1153
  class LavaTile extends EngineObject {
1129
1154
  renderLight() {
@@ -1394,6 +1419,19 @@ render3D.setSky(topColor, horizonColor, bottomColor, ambient=.5) // dome colors
1394
1419
  // color from below, both times ambient; 0 leaves ambient dark
1395
1420
  render3D.setFog(fogStart, fogEnd, fogColor) // the fog distances and color at once, no color keeps the current one
1396
1421
  render3D.sky = buildSky(topColor, horizonColor, bottomColor, sides, rings) // or set a dome yourself
1422
+ render3D.skyBox = cubeMap // a CubeMap drawn behind everything in place of the dome, undefined by default;
1423
+ // fog fades to fogColor, so set it to the sky box's horizon color
1424
+ render3D.environment = cubeMap // what reflective surfaces reflect, undefined reflects the sky's colors; one
1425
+ // made again and again disposes the one it replaces, its GPU memory
1426
+ makeCubeMap(size, colorOf) // a CubeMap of six size by size faces, colorOf(direction) the Color each way,
1427
+ // a unit Vector3; paint a sky in code, 64 or so is plenty for a blurry one
1428
+ await loadCubeMap([px, nx, py, ny, pz, nz]) // a CubeMap from six square image urls, +x, -x, +y, -y, +z and -z,
1429
+ // a sky box set shown as three.js shows it, each face as its image is drawn
1430
+ cubeMap.size cubeMap.faces cubeMap.dispose() // its face size, its six faces, and freeing its GPU texture
1431
+ const mirror = new CubeMap(128) // no faces: a cube map to draw the scene into
1432
+ mirror.capture(pos3D) // draw the scene around pos3D into it in the next frame's pass, six views of
1433
+ // the whole scene: once for a still scene, every few frames for a moving one
1434
+ mirror.capturePos // where it was last captured from, captured again from there after a lost context
1397
1435
 
1398
1436
  // Draw state, read at each draw; the pass sets it from each object's flags before render3D() and resets it before each
1399
1437
  // callback, so set it inside those, or use the object flags below
@@ -1405,6 +1443,7 @@ render3D.specular = 0 // Phong highlight strength, the shiny spo
1405
1443
  // out; shininess sets the size of the spot
1406
1444
  render3D.shininess = 16 // the highlight's exponent, 4 broad like rubber, 100 sharp like polished metal
1407
1445
  render3D.normalMap render3D.normalScale render3D.reflectivity render3D.emissiveMap render3D.emissiveMapColor
1446
+ render3D.environmentMap // the cube map reflected in place of render3D.environment, from obj.environment
1408
1447
  // the material, set from each object's fields of the same names
1409
1448
  render3D.receiveShadow = true // false keeps the next draws out of the shadow map's darkening
1410
1449
  render3D.shader = undefined // a Shader for the next draws, set from each object's shader; with emissive 1
@@ -1525,13 +1564,20 @@ obj.emissive = 1 // 0 by default; how much it lights itse
1525
1564
  // things, between partly self lit, above 1 brighter for bloom; still casts
1526
1565
  obj.specular = .5 // highlight strength, 0 is none and 1 is full, as render3D.specular; 0 by default
1527
1566
  obj.shininess = 100 // 16 by default; the highlight's exponent, higher is smaller and sharper
1567
+ obj.roughness = .5 // shininess set the way glTF measures it, 0 a mirror to 1 matte: it sets
1568
+ // shininess = 2 / roughness^4 - 2, at least 1, and reads it back, as
1569
+ // .9 from about .9 up, where shininess is 1
1528
1570
  obj.normalMap = textureInfo // bumps and grooves that catch the light, read at the color texture's uvs;
1529
1571
  // green points up the image (OpenGL and glTF), flip the green of a DirectX one;
1530
1572
  // no tangents needed, any mesh with uvs works; normalMapFromHeight makes one
1531
1573
  obj.normalScale = 1 // how strongly it bends the surface, 0 turns it off
1532
- obj.reflectivity = .5 // 0 by default, 1 a mirror of the sky; more at a glancing angle (Fresnel);
1533
- // shows the colors of render3D.sky from setSky or buildSky, or the ambient
1534
- // ones with no sky; not the scene
1574
+ obj.reflectivity = .5 // 0 by default, 1 a mirror; more at a glancing angle (Fresnel); shows
1575
+ // render3D.environment, sharper the higher shininess, as the highlight
1576
+ // is: 10000 a mirror, 1000 polished, 10 a wide blur;
1577
+ // with none, the colors of render3D.sky from setSky or buildSky, or the
1578
+ // ambient ones with no sky
1579
+ obj.environment = mirror // a CubeMap it reflects in place of render3D.environment, as one captured
1580
+ // from its own middle; undefined by default
1535
1581
  obj.emissiveMap = textureInfo // where it glows, added on top of the lit surface so it shows in the dark
1536
1582
  obj.emissiveMapColor = WHITE // multiplies the emissive map
1537
1583
  obj.castShadow = false // true by default, false keeps it out of the shadow map; sprites and cut out
@@ -1752,7 +1798,7 @@ model.parts // one GLTFPart per primitive of every node
1752
1798
  // are the set the texture's texCoord names, moved by KHR_texture_transform;
1753
1799
  // normalMap and normalScale from normalTexture, emissiveMap and
1754
1800
  // emissiveMapColor from emissiveTexture and emissiveFactor, read at the base
1755
- // color texture's uvs
1801
+ // color texture's uvs, and roughness from roughnessFactor, 1 when it has none
1756
1802
  model.mesh, model.textureInfo // everything as one Mesh tinted by its materials, and its texture when every
1757
1803
  // part uses the same one; a model mixing plain and textured parts, or using
1758
1804
  // several textures or unlit parts, draws right through createObject
@@ -1795,6 +1841,7 @@ emitter3D.gravityScale = 0 // a share of render3D.gravity added on top of its ow
1795
1841
  // an emitter with an emitTime destroys itself once its last particle is gone, so a burst is fire and forget
1796
1842
  // untextured particles are soft round dots, textured ones are billboards of the tile
1797
1843
  emitter.collideLevel = false // particles hit the height maps and voxel maps, bounce by restitution, slide by friction
1844
+ // only: not solid objects, as 2D particles hit only tile layers
1798
1845
  emitter.particleCreateCallback / particleUpdateCallback / particleCollideCallback / particleDestroyCallback
1799
1846
  // as in 2D, each given a Particle3D {pos, velocity, age, lifeTime, emitter, destroy()}, one object
1800
1847
  // the emitter reuses, so copy what you keep; the collide callback gets (particle, level, pos) and
@@ -1875,7 +1922,9 @@ material.side = THREE.DoubleSide // mesh.doubleSided = true
1875
1922
  material.emissiveIntensity // obj.emissive
1876
1923
  material.normalMap, normalScale // obj.normalMap, obj.normalScale (a number, three.js takes a Vector2)
1877
1924
  material.shininess (MeshPhongMaterial) // obj.shininess, with obj.specular the strength
1878
- material.envMap, reflectivity // obj.reflectivity, which reflects the sky's colors, not an environment map
1925
+ material.reflectivity // obj.reflectivity, with render3D.environment or obj.environment the map
1926
+ material.metalness // none; a dark color with reflectivity reads as metal
1927
+ material.roughness (MeshStandardMaterial) // obj.roughness, which sets obj.shininess: 0 a mirror, 1 matte
1879
1928
  material.emissiveMap, emissive // obj.emissiveMap, obj.emissiveMapColor
1880
1929
  material.transparent, blending // obj.transparent, obj.additive
1881
1930
  new THREE.ShaderMaterial({fragmentShader}) // obj.shader = new Shader(code), a mainImage snippet the engine wraps;
@@ -1890,7 +1939,12 @@ new THREE.SpotLight(color, i, d, angle, penumbra) // a Light3D with coneAngle =
1890
1939
  // for its castShadow
1891
1940
  light.castShadow, light.shadow.camera // render3D.shadows, shadowRange and shadowCenter
1892
1941
  scene.fog = new THREE.Fog(c, near, far) // render3D.setFog(near, far, c)
1893
- scene.background // render3D.setSky(topColor, horizonColor, bottomColor)
1942
+ scene.background // render3D.setSky(topColor, horizonColor, bottomColor), or
1943
+ // render3D.skyBox for a cube map
1944
+ scene.environment // render3D.environment
1945
+ material.envMap on one mesh // obj.environment
1946
+ new THREE.CubeCamera(near, far, target) // new CubeMap(size), then cubeMap.capture(pos3D) in place of update
1947
+ new THREE.CubeTextureLoader().load(urls) // await loadCubeMap(urls), the same order
1894
1948
  OrbitControls // new CameraControl3D(target, distance)
1895
1949
  PointerLockControls // new FirstPersonCamera3D
1896
1950
  new THREE.Raycaster() // render3D.screenToRay, pick and engineObjectsRaycast3D
@@ -1936,7 +1990,11 @@ level3DVoxelSetup(tileInfo, (map)=> {}) // The sheet a level's block m
1936
1990
  "fog": [25, 70], "fogColor": "#d6ecff", // start and end, an end of 0 for none; the horizon color
1937
1991
  // when the level has a sky and no fogColor
1938
1992
  "shadows": true,
1939
- "lensFlare": true}, // the sun's lens flare, a LensFlare3D made for it
1993
+ "lensFlare": true, // the sun's lens flare, a LensFlare3D made for it
1994
+ "skyBox": ["px.png", "nx.png", "py.png", "ny.png", "pz.png", "nz.png"], // render3D.skyBox, six images
1995
+ "environment": ["px.png", "nx.png", "py.png", "ny.png", "pz.png", "nz.png"]}, // as loadCubeMap takes
1996
+ // them, loaded in the background, the same urls once;
1997
+ // urls are from the page, not from the level's file
1940
1998
  "objects": []}
1941
1999
 
1942
2000
  // A level can hold a map of blocks, made a VoxelMap when it loads: its corner, its size in cells, and its blocks
@@ -1952,7 +2010,8 @@ level3DVoxelSetup(tileInfo, (map)=> {}) // The sheet a level's block m
1952
2010
  // Prefabs - a prefab is a small level, objects about its own origin, placed many times under one name. The level
1953
2011
  // editor saves one as it saves a level, so it is the prefab editor too, and there every instance follows its prefab
1954
2012
  level3DAddPrefab(name, prefab) // Add {objects: [...]} as a type; its objects may be of other prefabs
1955
- await level3DLoadPrefab(name, url) // Fetch a prefab file and add it
2013
+ await level3DLoadPrefab(name, url) // Fetch a prefab file and add it; a prefab used inside it is
2014
+ // named by its type, the game adds that one too
1956
2015
  level3DSpawn(type, pos3D, rotation3D, scale3D, properties) // Make one object of any type from code, a prefab's
1957
2016
  // instance or a plain type; rotation in radians
1958
2017
  prefab = new Prefab3D // what a prefab's type makes, a handle that draws nothing:
@@ -2051,6 +2110,12 @@ obj.syncMesh() // copy the 2D transform to the mesh
2051
2110
  read only, move it with `setPosition`, `setAngle` or `setTransform` and push it with velocities and forces
2052
2111
  - Joints, raycasting, polygon/circle/edge fixtures
2053
2112
  - Angular values are clockwise like `angle`: angular velocity, torque, joint angles and limits, motor speeds
2113
+ - The EngineObject's own physics fields do nothing on a Box2dObject, Box2D moves it: `velocity`, `angleVelocity`,
2114
+ `damping`, `angleDamping`, `mass`, `friction`, `restitution` and `gravityScale`. Use `getLinearVelocity`,
2115
+ `setLinearVelocity`, `setAngularVelocity`, `setLinearDamping`, `setAngularDamping`, `setMass`, the friction and
2116
+ restitution given to its shapes, and `setGravityScale`; a debug build warns once when one is set
2117
+ - A world made again after `engineObjectsDestroy` does not step exactly as a fresh one, as Box2D reuses its ids, so
2118
+ a replay or a lockstep game that needs the same steps every time reloads the page
2054
2119
  - See `examples/box2d/` for a full demo
2055
2120
 
2056
2121
  ```javascript
@@ -2093,8 +2158,8 @@ obj.setAwake(awake=true)
2093
2158
  obj.setFixedRotation(isFixed=true) // Stop the body from rotating
2094
2159
  obj.setBullet(isBullet=true) // Continuous collision for fast bodies, so they don't pass through thin ones
2095
2160
  obj.setSensor(isSensor=true) // The fixtures it has now detect contacts without colliding
2096
- obj.setLinearDamping(damping) // Box2D's damping, 0 is none, larger slows it faster
2097
- obj.setAngularDamping(damping)
2161
+ obj.setLinearDamping(damping) // Box2D's damping, a rate: 0 is none, larger slows it faster, no upper limit
2162
+ obj.setAngularDamping(damping) // the same for its turning, where an EngineObject's damping is 0 to 1
2098
2163
  obj.setGravityScale(scale=1)
2099
2164
  obj.setMassData(localCenter, mass, momentOfInertia) // undefined leaves that one as it is, inertia is about the
2100
2165
  // center of mass
@@ -2205,6 +2270,46 @@ newgrounds.resendUnlocks() // advanced: send the ones whose request di
2205
2270
  // the minute's session check does; one the server refused is not resent
2206
2271
  ```
2207
2272
 
2273
+ ## LittleJS Wavedash
2274
+ - Optional plugin for Wavedash achievements and leaderboards, the same shape as the Newgrounds one
2275
+ - Wavedash serves the game's page and puts its SDK in `window.Wavedash`, so nothing is bundled; off Wavedash every call
2276
+ does nothing and medals unlock as any do
2277
+ - Make the plugin at the end of `gameInit`: it calls `Wavedash.init()`, until which Wavedash keeps its loading screen
2278
+ - Achievements are made with the Wavedash CLI, `--description` required; numbering the identifiers (`ACH_01_...`)
2279
+ orders them, since Wavedash lists them by identifier
2280
+ - A game built with its own minifier keeps the SDK's names (`Wavedash`, `init`, `success`, `data`, `id` and the
2281
+ methods) and real booleans: the SDK throws on a renamed call or a 1 for true (Terser's booleans_as_integers); the
2282
+ engine's own builds keep both
2283
+
2284
+ ```javascript
2285
+ // the medals, each with the identifier of its achievement on Wavedash
2286
+ const medal_finish = new WavedashMedal(0, 'ACH_01_FINISH', 'Finish', 'Finish a level');
2287
+ function gameInit()
2288
+ {
2289
+ medalsInit('My Game');
2290
+ new WavedashPlugin({LEVEL_1: {lowerWins: true, display: 'milliseconds'}, HIGH_SCORE: {}}); // last, it calls init
2291
+ }
2292
+ // later, in game code
2293
+ medal_finish.unlock(); // Wavedash's toast on Wavedash, the engine's popup anywhere else
2294
+ wavedash.postScore('LEVEL_1', timeMs);
2295
+
2296
+ new WavedashMedal(id, achievement, name, description, icon, src) // a Medal that is also a Wavedash achievement; its
2297
+ // unlock is sent until Wavedash takes it, which it refuses until it has loaded
2298
+ // the player's, a moment after launch; saved unlocks are sent too; one refused
2299
+ // for a minute warns in the console with its identifier, a typo or not made
2300
+ medal.achievement // the identifier of its Wavedash achievement
2301
+ new WavedashPlugin(leaderboards) // sets the wavedash global, calls Wavedash.init and makes the leaderboards: by
2302
+ // name, lowerWins for times and golf (higher wins when left out) and display
2303
+ // 'number' (the default), 'seconds', 'milliseconds' or 'ticks' (60 a second)
2304
+ wavedash.isActive() // whether the game is on Wavedash, its SDK on the page
2305
+ await wavedash.postScore(name, score) // a whole number, milliseconds for a time, Wavedash keeping the best; true once
2306
+ // posted, false off Wavedash or when it failed or took over 15 seconds; a board
2307
+ // not in the table is made then, higher wins
2308
+ await wavedash.getScores(name, offset=0, limit=10, friendsOnly=false) // the entries as Wavedash gives them,
2309
+ // undefined off Wavedash or when they could not be read
2310
+ wavedash.leaderboards // each board's id promise by its name
2311
+ ```
2312
+
2208
2313
  ## LittleJS Drawing Utilities
2209
2314
  - Optional plugin: nine-slice and three-slice helpers for scalable UI panels, plus a crescent shape
2210
2315
  - World-space (WebGL or 2D) and screen-space (2D by default, useWebGL for WebGL) variants
@@ -2339,7 +2444,7 @@ setLevelEditor(new MyEditor) // Make it the one in use, before the editor open
2339
2444
  - Press Escape key to toggle debug overlay
2340
2445
  - Number keys toggle debug functions while the overlay is open: 1 physics, 2 tiles (each tile layer's bounds, the
2341
2446
  collision values on screen, and the tiles under the mouse; pressing 2 again steps through the layers one at a time,
2342
- then off), 3 particles, 4 raycasts, 5 gamepads, 6 sound, 7 screenshot, 8 video capture, 9 tweakables panel,
2447
+ then off), 3 particles, 4 raycasts, 5 gamepads, 6 sound, 7 screenshot, 9 tweakables panel,
2343
2448
  0 level editor
2344
2449
  - C, while the overlay is open, is a free camera for a 3D game: the mouse looks once captured or with the right
2345
2450
  button held, WASD and QE fly, Shift is faster and the wheel sets the speed; the game runs on and reads no keys or
@@ -2364,9 +2469,6 @@ debugOverlap(posA, sizeA, posB, sizeB, color) // Draw a debug overlap between tw
2364
2469
  debugClear() // Clear all debug primitives
2365
2470
  debugScreenshot() // Save a screenshot at the end of this frame
2366
2471
  debugShowErrors() // Show full page error message when an error occurs
2367
- debugVideoCaptureStart() // Start capturing a video of the canvas
2368
- debugVideoCaptureStop() // Stop capturing and save the video to disk
2369
- debugVideoCaptureIsActive() // Is video currently being captured?
2370
2472
  createCanvasContext(width, height=width, willReadFrequently=false) // Offscreen canvas to draw into, returns its
2371
2473
  // 2D context; the canvas is context.canvas
2372
2474
  saveCanvas(canvas, filename='screenshot', type='image/png') // Save canvas to a file