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.
- package/CHANGELOG.md +87 -0
- package/FAQ.md +49 -11
- package/README.md +6 -2
- package/REFERENCE.md +120 -18
- package/dist/littlejs.d.ts +464 -99
- package/dist/littlejs.esm.js +2853 -704
- package/dist/littlejs.esm.min.js +3 -3
- package/dist/littlejs.js +2844 -701
- package/dist/littlejs.min.js +2 -2
- package/dist/littlejs.release.js +2658 -512
- package/package.json +4 -4
- package/plugins/box2d.js +110 -34
- package/plugins/gltf.js +74 -22
- package/plugins/lightSystem.js +410 -28
- package/plugins/math3d.js +4 -2
- package/plugins/medalSystem.js +19 -7
- package/plugins/newgrounds.js +4 -2
- package/plugins/parallax.js +1 -0
- package/plugins/particleEffects.js +44 -10
- package/plugins/pathFinder.js +9 -11
- package/plugins/pluginExport.js +9 -0
- package/plugins/postProcess.js +4 -2
- package/plugins/render3d.js +485 -2385
- package/plugins/render3dDebug.js +5 -8
- package/plugins/render3dEditor.js +81 -6
- package/plugins/render3dExtras.js +103 -31
- package/plugins/render3dLevel.js +53 -5
- package/plugins/render3dMesh.js +1158 -0
- package/plugins/render3dObject.js +1451 -0
- package/plugins/render3dVoxels.js +15 -3
- package/plugins/textureSheet.js +9 -8
- package/plugins/threejs.js +1 -1
- package/plugins/tweenSystem.js +76 -51
- package/plugins/uiSystem.js +37 -8
- package/plugins/wavedash.js +229 -0
- package/src/engine.js +184 -36
- package/src/engineAudio.js +37 -21
- package/src/engineBuild.mjs +6 -1
- package/src/engineDebug.js +22 -162
- package/src/engineDraw.js +139 -34
- package/src/engineEditor.js +79 -18
- package/src/engineExport.js +0 -3
- package/src/engineInput.js +27 -18
- package/src/engineMath.js +35 -14
- package/src/engineObject.js +60 -41
- package/src/engineParticles.js +58 -11
- package/src/engineRelease.js +1 -5
- package/src/engineSettings.js +48 -9
- package/src/engineTileLayer.js +11 -4
- package/src/engineUtilities.js +41 -9
- 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
|
|
59
|
-
Unlike those feature-heavy engines, LittleJS
|
|
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
|
-
|
|
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 —
|
|
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
|
|
197
|
-
|
|
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:
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
1533
|
-
//
|
|
1534
|
-
//
|
|
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.
|
|
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
|
|
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,
|
|
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
|