littlejsengine 1.23.0 → 1.24.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/REFERENCE.md ADDED
@@ -0,0 +1,2327 @@
1
+ # LittleJS Engine Quick Reference Sheet
2
+
3
+ ## This cheat sheet contains all LittleJS essentials.
4
+ - [LittleJS on GitHub](https://github.com/KilledByAPixel/LittleJS) - Official LittleJS website with more info
5
+ - [LittleJS Documentation](https://killedbyapixel.github.io/LittleJS/docs) - LittleJS documentation browser
6
+ - [Particle System Designer](https://killedbyapixel.github.io/LittleJS/examples/particles) - Editor for LittleJS Particle Systems
7
+ - [Sound Effect Designer](https://killedbyapixel.github.io/ZzFX) - Tool for creating ZzFX sound effects
8
+ - [Starter Project](https://killedbyapixel.github.io/LittleJS/examples/starter) - Simple LittleJS demo to start with
9
+
10
+ ## LittleJS Setup
11
+
12
+ To start LittleJS, you need to create a few functions and pass them to engineInit.
13
+
14
+ ```javascript
15
+ // Start up LittleJS engine with your callback functions
16
+ engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
17
+ // a rootElement keeps its inline styles and holds the canvas, which is still sized from the window
18
+ // startup: the splash if it is on, then gameInit once the images are in, and the game loop once gameInit and
19
+ // everything loaded while it ran are done; the loading screen shows if that takes more than half a second
20
+ engineAddLoad(promise) // wait for this too, and count it on the loading screen; images from loadTexture and sounds
21
+ // from files are added on their own, a failed load counts as done; after startup does nothing
22
+
23
+ // Engine globals
24
+ engineName // Name of the engine: 'LittleJS'
25
+ engineVersion // Version of the engine
26
+ frameRate // Fixed frame rate for updates (60)
27
+ frame // Current update frame
28
+ time // Game time since start in seconds (stops when paused)
29
+ timeReal // Real time since start in seconds (keeps running when paused; the debug speed keys scale it)
30
+ timeDelta // Seconds the update covers: 1/60, or the display frame's time with engineVariableStep
31
+ timeScale = 1 // Game speed, more or fewer fixed updates per second; with engineVariableStep it scales timeDelta
32
+ paused // Is the game paused? (set with setPaused)
33
+ headlessMode = false // Run without rendering for testing/servers (set before engineInit)
34
+ engineManualStep // Advance only via engineStep, default false; can be turned on and off while running
35
+ engineStep(frames=1) // Advance the engine manually, needs engineManualStep
36
+ engineVariableStep // One update per display frame, default false; velocity, gravity and other per-frame values are not scaled
37
+ ```
38
+
39
+ ### Headless testing
40
+
41
+ `headlessMode` disables rendering, audio, and input. `engineManualStep` additionally
42
+ stops the engine driving itself with `requestAnimationFrame`, so it only advances when
43
+ you call `engineStep`. Together they make time-driven game logic deterministic and testable —
44
+ `Timer`, time-based spawns, cooldowns, and physics all advance exactly as many frames as you
45
+ ask for. `engineStep` drives the real update loop, so `timeScale` scales fixed updates per
46
+ step just as it does under `requestAnimationFrame`: at `timeScale = .5`, ten steps run five
47
+ fixed updates. Leave `timeScale` at 1 when you want frame counts to match exactly.
48
+
49
+ ```javascript
50
+ setHeadlessMode(true); // no rendering, audio, or input
51
+ setEngineManualStep(true); // no requestAnimationFrame loop
52
+ await engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost);
53
+
54
+ // In manual step mode engineInit runs gameInit but no update frame, so this
55
+ // is where you assert on what initialization produced.
56
+ engineStep(); // advance exactly one fixed update
57
+ engineStep(600); // advance 10 seconds of game time at 60fps (timeScale 1)
58
+
59
+ // engineStep respects paused, exactly as the normal update loop does
60
+ setPaused(true);
61
+ engineStep(5); // gameUpdatePost runs, time and frame do not advance
62
+ ```
63
+
64
+ `headlessMode` must be set before `engineInit`; `engineManualStep` can be, or it can be turned
65
+ on and off later to stop and restart the loop. Input is not synthesized in headless mode, so
66
+ tests drive game state directly rather than through `keyIsDown` and friends.
67
+
68
+ `engineStep` is synchronous and does not yield — every frame runs back to back before
69
+ it returns. That is what makes it deterministic, and in headless mode it is what you
70
+ want. Outside headless mode each step also renders, so a large count blocks the tab
71
+ and throws away every frame but the last. To advance a lot of time while staying
72
+ responsive, chunk it yourself and let the browser paint between chunks:
73
+
74
+ ```javascript
75
+ // advance 10 minutes of game time without freezing the tab
76
+ let remaining = 36000;
77
+ (function chunk()
78
+ {
79
+ engineStep(min(remaining, 60));
80
+ if ((remaining -= 60) > 0)
81
+ requestAnimationFrame(chunk);
82
+ })();
83
+ ```
84
+
85
+ ## LittleJS Utilities Classes and Functions
86
+ - General purpose math library
87
+ - Vector2 - Fast, simple, easy 2D vector class
88
+ - Color - Holds a rgba color with some math functions
89
+ - Timer - Tracks time automatically
90
+ - RandomGenerator - Seeded random number generator
91
+
92
+ ```javascript
93
+
94
+ // Object Constructors
95
+ vec2(x=0, y=x) // Create a 2D vector with Vector or floats
96
+ rgb(r=1, g=1, b=1, a=1) // Create a color object with RGBA values
97
+ hsl(h=0, s=0, l=1, a=1) // Create a color object with HSLA values
98
+ tile(index=0, size=(16,16), texture=0, padding=0, bleed=0) // Create a tile info object
99
+ tileInfo.frame(n) // Tile offset by n animation frames
100
+ tileInfo.setColumns(columns) // Frames per row, so frame() wraps to the next row
101
+
102
+ // Helper functions
103
+ abs(value) // Get absolute value
104
+ min(...values) // Get lowest of values
105
+ max(...values) // Get highest of values
106
+ sign(value) // Get the sign of value
107
+ mod(dividend, divisor=1) // Get remainder of division
108
+ clamp(value, min=0, max=1) // Clamps between values
109
+ percent(value, valueA, valueB) // Get percentage between values
110
+ lerp(valueA, valueB, percent) // Linearly interpolates between values
111
+ percentLerp(value, percentA, percentB, lerpA, lerpB) // Lerp that remaps the percent
112
+ distanceWrap(valueA, valueB, wrapSize=1) // Signed wrapped distance between values
113
+ lerpWrap(valueA, valueB, percent, wrapSize=1) // Linearly interpolates with wrapping
114
+ distanceAngle(angleA, angleB) // Signed wrapped distance between angles
115
+ lerpAngle(angleA, angleB, percent) // Linearly interpolates with wrapping
116
+ smoothStep(percent) // Applies smoothstep function, percent clamped to 0-1
117
+ isPowerOfTwo(value) // Checks if the value is a power of two
118
+ nearestPowerOfTwo(value) // Smallest power of two not less than the value
119
+ isOverlapping(pointA, sizeA, pointB, sizeB) // Checks if bounding boxes overlap
120
+ isIntersecting(start, end, pos, size) // Checks if ray intersects box
121
+ // the collide helpers answer how far out, where isOverlapping answers whether; boxes are axis aligned and centered
122
+ // with a full size, and each returns undefined when the shapes are not touching
123
+ collideCircleCircle(posA, radiusA, posB, radiusB) // vector that moves circle A clear of circle B
124
+ collideCircleBox(pos, radius, boxPos, boxSize) // vector that moves a circle clear of a box
125
+ collideBoxBox(posA, sizeA, posB, sizeB) // vector that moves box A clear of box B, the shortest way out
126
+ oscillate(frequency=1, amplitude=1, t=time, offset=0, type=0) // Oscillating wave
127
+ lineTest(posStart, posEnd, testFunction, normal) // Step along a line until test passes
128
+ formatTime(t) // Formats seconds for display
129
+
130
+ // Math aliases (prefer over Math.X)
131
+ PI, abs, floor, ceil, round, min, max, sign, hypot, log2, sin, cos, tan, atan2
132
+
133
+ // Type checking helpers
134
+ isNumber(n) // Is it a number and not NaN?
135
+ isStringLike(s) // Can it be converted to a string?
136
+ isArray(a) // Is it an array?
137
+ isVector2(v) // Is it a valid Vector2?
138
+ isColor(c) // Is it a valid Color?
139
+
140
+ // Utility functions
141
+ noise1D(x) // Smooth 1D value noise (0 to 1)
142
+ noise2D(x, y) // Smooth 2D value noise (0 to 1)
143
+ fetchJSON(url) // Fetch and parse a JSON file (async)
144
+ shareURL(title, url, callback) // Share a URL via the navigator share API
145
+ readSaveData(saveName, defaultSaveData) // Read game save data from localStorage, default must be an object
146
+ // the result has the default's type in TypeScript
147
+ writeSaveData(saveName, saveData) // Write game save data to localStorage; give medalsInit a different name
148
+
149
+ // Random functions
150
+ rand(valueA=1, valueB=0) // Random float between values
151
+ randInt(valueA, valueB=0) // Random integer between values
152
+ randBool(chance=.5) // Random boolean with given chance (0 to 1)
153
+ randSign() // Randomly either -1 or 1
154
+ randVec2(length=1) // Random Vector2 with the passed in length
155
+ randInCircle(radius=1, minRadius=0) // Random Vector2 within a circle
156
+ randColor(colorA, colorB, linear) // Random color between values
157
+
158
+ // 2D vector math
159
+ Vector2(x=0, y=0) // Create a 2D vector
160
+ Vector2.copy() // Copy this vector
161
+ Vector2.set(x=0, y=0) // Set this vector's components
162
+ Vector2.setFrom(v) // Set this vector from another vector
163
+ Vector2.add(v) // Add a vector
164
+ Vector2.subtract(v) // Subtract a vector
165
+ Vector2.multiply(v) // Multiply by a vector
166
+ Vector2.divide(v) // Divide by a vector
167
+ Vector2.scale(s) // Scale by a float
168
+ Vector2.length() // Get length
169
+ Vector2.lengthSquared() // Get length squared
170
+ Vector2.distance(v) // Get distance to vector
171
+ Vector2.distanceSquared(v) // Get distance to vector squared
172
+ Vector2.normalize(length=1) // Normalize this vector to length
173
+ Vector2.clampLength(length=1) // Clamp this vector to length
174
+ Vector2.dot(v) // Dot product with vector
175
+ Vector2.cross(v) // Cross product with vector
176
+ Vector2.reflect(normal, restitution=1) // Reflect off a surface normal
177
+ Vector2.floor() // Floor this vector
178
+ Vector2.round() // Round this vector
179
+ Vector2.abs() // Get copy with absolute value components
180
+ Vector2.snap(grid) // Snap down to the grid, grid is steps per unit
181
+ Vector2.mod(divisor=1) // Get modulo of each component
182
+ Vector2.area() // Get area covered by this vector as a rectangle
183
+ Vector2.lerp(v, percent) // Interpolate between vectors
184
+ Vector2.arrayCheck(arraySize) // Check if in bounds of array size
185
+ Vector2.angle() // Angle of this vector, up is 0
186
+ Vector2.setAngle(angle=0, length=1) // Set angle and length
187
+ Vector2.rotate(angle) // Rotate by angle
188
+ Vector2.setDirection(direction, length=1) // Set integer direction (0-3) and length
189
+ Vector2.direction() // Get integer direction (0-3)
190
+ Vector2.toString(digits=3) // Get string representation
191
+
192
+ // RGBA color object
193
+ Color(r=1, g=1, b=1, a=1) // Create an RGBA color
194
+ Color.copy() // Copy this color
195
+ Color.set(r=1, g=1, b=1, a=1) // Set this color's values
196
+ Color.setFrom(c) // Set this color from another color
197
+ Color.add(c) // Add a color
198
+ Color.subtract(c) // Subtract a color
199
+ Color.multiply(c) // Multiply by a color
200
+ Color.divide(c) // Divide by a color
201
+ Color.scale(scale, alphaScale=scale) // Scale by a float
202
+ Color.clamp() // Clamp this color
203
+ Color.lerp(c, percent) // Interpolate between colors
204
+ Color.setHSLA(h=0, s=0, l=1, a=1) // Set the color from HSLA values
205
+ Color.HSLA() // Get the color in HSLA format
206
+ Color.mutate(amount=.05, alphaAmount=0) // Randomly diverge from this color
207
+ Color.setHex(hex) // Set this color from a hex code
208
+ Color.setAlpha(a=1) // Set the alpha of this color
209
+ Color.withAlpha(a=1) // Get a copy of this color with the alpha set
210
+ Color.rgbaInt() // Get this color as 32 bit RGBA value
211
+ Color.toString(useAlpha=true) // Get hex color code as a string
212
+
213
+ // Color constants (frozen, use .copy() to modify)
214
+ WHITE, BLACK, GRAY, CLEAR_WHITE, CLEAR_BLACK
215
+ RED, ORANGE, YELLOW, GREEN, CYAN, BLUE, PURPLE, MAGENTA
216
+
217
+ // Seeded random number generator
218
+ RandomGenerator(seed) // Create a random number generator, reseed by setting r.seed;
219
+ // a seed that is 0 as an integer uses the default seed
220
+ RandomGenerator.float(valueA=1, valueB=0) // Random float between values
221
+ RandomGenerator.int(valueA, valueB=0) // Random integer between values
222
+ RandomGenerator.sign() // Randomly either -1 or 1
223
+ RandomGenerator.bool(chance=.5) // Random boolean with given chance (0 to 1)
224
+ RandomGenerator.floatSign(valueA=1, valueB=0) // Random float between values with a random sign
225
+ RandomGenerator.angle() // Random angle between -PI and PI
226
+ RandomGenerator.vec2(valueA=1, valueB=0) // Random Vector2, each component between values
227
+ RandomGenerator.direction(length=1) // Random Vector2 with the passed in length, like randVec2
228
+ RandomGenerator.randColor(colorA=WHITE, colorB=BLACK, linear=false) // Random color between values
229
+ RandomGenerator.mutateColor(color, amount=.05, alphaAmount=0) // Copy of a color randomly diverged
230
+
231
+ // Time tracking system
232
+ Timer(timeLeft, useRealTime=false) // Create a timer object
233
+ Timer.set(timeLeft=0) // Set the timer with seconds passed in
234
+ Timer.setUseRealTime(useRealTime=true) // Keep running while the game is paused
235
+ Timer.unset() // Unset the timer
236
+ Timer.isSet() // Returns true if set
237
+ Timer.active() // Returns true if set and has not elapsed
238
+ Timer.elapsed() // Returns true if set and elapsed
239
+ Timer.get() // Get how long since elapsed, 0 if not set
240
+ Timer.getPercent() // Get percent elapsed, 0 if not set
241
+ Timer.getSetTime() // Get the time it was set to, 0 if not set
242
+ Timer.toString() // Get this timer expressed as a string
243
+ Timer.valueOf() // Get how long since elapsed, 0 if not set
244
+ ```
245
+
246
+ ## LittleJS Drawing System
247
+ - Hybrid system with both Canvas2D and WebGL available
248
+ - Super fast tile sheet rendering with WebGL
249
+ - Can apply rotation, mirror, color and additive color
250
+ - Text and font rendering system with built in engine font
251
+
252
+ ```javascript
253
+ // Drawing functions
254
+ // Most also accept optional trailing params: useWebGL=glEnable, screenSpace=drawScreenSpace, context
255
+ // Canvas2D draws (text, useWebGL=false) go to a canvas above every WebGL draw, whatever order they are drawn in
256
+ drawTile(pos, size, tileInfo, color=WHITE, angle=0, mirror, additiveColor)
257
+ drawRect(pos, size, color=WHITE, angle=0)
258
+ drawRectGradient(pos, size, colorTop=WHITE, colorBottom=CLEAR_WHITE, angle=0)
259
+ drawTextureWrapped(pos, size, wrapCount, texture=0, color=WHITE, angle=0, additiveColor)
260
+ drawLine(posA, posB, width=.1, color=WHITE, pos=(0,0), angle=0)
261
+ drawLineList(points, width=.1, color=WHITE, wrap=false, pos=(0,0), angle=0)
262
+ drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos, angle=0)
263
+ drawRegularPoly(pos, size=(1,1), sides=3, color=WHITE, lineWidth=0, lineColor=BLACK, angle=0)
264
+ drawEllipse(pos, size=(1,1), color=WHITE, angle=0, lineWidth=0, lineColor=BLACK)
265
+ drawCircle(pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK)
266
+ drawEllipseGradient(pos, size=(1,1), colorInner=WHITE, colorOuter=CLEAR_WHITE, angle=0)
267
+ drawCircleGradient(pos, size=1, colorInner=WHITE, colorOuter=CLEAR_WHITE)
268
+ drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpace=drawScreenSpace, context)
269
+
270
+ // Text functions
271
+ drawText(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font, fontStyle, maxWidth, angle=0)
272
+ drawTextScreen(text, pos, size, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font, fontStyle, maxWidth, angle=0)
273
+
274
+ // Utility drawing functions
275
+ setAdditiveBlendMode(additive)
276
+ setShader(shader) // draw with a custom Shader from now on, none for the engine's own; objects set
277
+ // their own, so this is for draws in gameRender and gameRenderPost
278
+ isFullscreen()
279
+ toggleFullscreen()
280
+
281
+ // Shader Object - a custom fragment shader for objects and draws, 2D or 3D
282
+ new Shader(fragmentCode) // fragmentCode defines void mainImage(out vec4 c, vec2 uv) in the post processing
283
+ // style; it gives the surface color, then the object's color and additive color
284
+ // apply in 2D, and the lighting, shadows and fog in 3D
285
+ obj.shader = shader // any EngineObject or EngineObject3D; draws that share a Shader share a batch
286
+ // names in the snippet: iChannel0 the texture, iTime, iResolution, localUV 0 to 1 across the sprite or the mesh uv,
287
+ // premultipliedTexture true for a render target or a smooth image (tilesPixelated false), whose rgb and alpha
288
+ // change together;
289
+ // in 2D untextured draws like drawRect are not shaded; a bad snippet throws with the GLSL log in debug builds;
290
+ // in 3D the shadow map is drawn without the Shader, so a snippet that cuts holes still casts the whole shadow
291
+
292
+ // Tile Info Object
293
+ TileInfo(pos, size, textureInfo, padding=0, bleed=0) // Create a tile info object
294
+ TileInfo.pos // Top left corner of tile in pixels
295
+ TileInfo.size // Size of tile in pixels
296
+ TileInfo.padding // How many pixels padding around tiles
297
+ TileInfo.offset(offset) // Offset this tile by a certain amount in pixels
298
+ TileInfo.frame(frame) // Offset this tile by a number of animation frames
299
+ TileInfo.textureInfo // The texture info for this tile
300
+
301
+ // Sprite Animation Object - steps a tile through the frames along its row over time, paused with the game
302
+ new SpriteAnimation(tileInfo, frameCount, frameTime=.1) // first frame, how many, seconds each; loops from the start
303
+ animation.loop() .play() .pingPong() // start over from the first frame: repeat, run once and hold the last
304
+ // frame, or there and back; each returns the animation
305
+ animation.stop() // hold the current frame, a mode call starts it again
306
+ animation.tileInfo // the frame to draw now, read it in update or before a drawTile
307
+ animation.frame // its index, 0 to frameCount-1
308
+ animation.isDone // true once a play has run through
309
+ animation.speed = 1 // rate multiplier, set before starting
310
+ animation.play(onEnd) // onEnd is called once when the play ends, on the first read after it
311
+
312
+ // Sprite Animator - a character's animations by name and the one showing now
313
+ const hero = new SpriteAnimator({idle, walk, attack}) // clips are SpriteAnimations in their own modes, first shows
314
+ hero.set('walk') // show a clip, starting over only when it changes or a play of it has ended
315
+ hero.set('attack', ()=> hero.set('idle')) // with a callback for when a play clip ends
316
+ hero.tileInfo // the frame to draw now; also hero.frame, hero.isDone, hero.name, hero.clip
317
+
318
+ // Texture Info Object
319
+ TextureInfo(image, useWebGL=true, wrap=false, pixelated) // Created automatically for each image; pixelated makes
320
+ // this texture hard edged or smooth on its own, undefined follows tilesPixelated
321
+ TextureInfo.setPixelated(pixelated) // Hard edged or smooth for this texture alone, a soft glow in a pixel art game
322
+ TextureInfo.image // Image source
323
+ TextureInfo.size // Size of the image
324
+ TextureInfo.glTexture // WebGL texture
325
+ TextureInfo.wrap // Whether texture is set to REPEAT (true) or CLAMP_TO_EDGE
326
+ TextureInfo.setWrap(wrap=true) // Enable or disable wrapping for this texture, or give the WebGL modes across and
327
+ // down, like [gl.CLAMP_TO_EDGE, gl.MIRRORED_REPEAT]
328
+ await loadTexture(textureIndex, src) // Load an image after engineInit into textureInfos[textureIndex], for tile(i, size, textureIndex);
329
+ // resolves to the TextureInfo, and an image that fails to load logs a warning
330
+
331
+ // Image Font Object draws text using characters in an image
332
+ ImageFont(tileInfo) // Create a font from a tile sheet
333
+ ImageFont.drawText(text, pos, size=1, center=true, color=WHITE, useWebGL=glEnable, context)
334
+ // Draw text in world space, size is a character's size in world units, a number or vec2
335
+ // stays upright when the camera turns, glyphs snap to whole pixels
336
+ ImageFont.drawTextScreen(text, pos, size, center=true, color=WHITE, useWebGL=glEnable, context)
337
+ // Draw text in screen space, size in pixels is required
338
+ // for both, center also centers the lines of multi-line text on pos, like the engine's drawText
339
+
340
+ // Camera settings
341
+ cameraPos = (0,0) // Position of camera in world space
342
+ cameraAngle = 0 // Rotation angle of camera in world space
343
+ cameraScale = 32 // Scale of camera in world space
344
+ screenToWorld(screenPos) // Convert from screen to world space coordinates
345
+ worldToScreen(worldPos) // Convert from world to screen space coordinates
346
+ screenToWorldDelta(screenDelta) // Convert a screen space delta to world space
347
+ worldToScreenDelta(worldDelta) // Convert a world space delta to screen space
348
+ screenToWorldTransform(screenPos, screenSize, screenAngle=0) // Convert a whole transform
349
+ getCameraSize() // Get the camera's visible area in world space
350
+ cameraFit(center, size, worldMargin, screenInset) // Fit the camera to a world space rectangle
351
+
352
+ // Display settings
353
+ canvasMaxSize = (3840, 2160) // The max size of the canvas in css pixels
354
+ canvasFixedSize = (0, 0) // Fixed size of the canvas
355
+ canvasMinAspect = 0 // Min aspect ratio, fits to height (0 = disabled)
356
+ canvasMaxAspect = 0 // Max aspect ratio, fits to width (0 = disabled)
357
+ canvasPixelRatio = 1 // Scales render resolution only (undefined tracks devicePixelRatio)
358
+ getCanvasPixelRatio() // Get the pixel ratio currently applied to the backing store
359
+ canvasClearColor = CLEAR_BLACK // Color to clear the canvas to each frame, alpha 0 does not clear
360
+ canvasColorTiles = true // Allow tiles to be tinted when drawn
361
+ fontDefault = 'arial' // Default font used for text rendering
362
+ canvasPixelated = false // Use nearest neighbor canvas scaling for more pixelated look
363
+ tilesPixelated = true // Disable filtering for crisper pixel art, when false textures get mipmaps at any size
364
+ // and upload premultiplied, so smooth edges do not darken
365
+ showSplashScreen = false // Show the LittleJS splash screen on startup
366
+ loadingScreen // Drawn after the splash while the game loads, if that takes over half a second:
367
+ // setLoadingScreen((progress)=> ...) draws your own on mainContext, progress 0 to 1,
368
+ // setLoadingScreen() for none; the default says Loading over a bar
369
+ glEnable = true // Enable fast WebGL rendering
370
+ drawScreenSpace = false // What screenSpace defaults to in the draw and debug functions; turn it on for a HUD or
371
+ // a 3D game's 2D drawing; the engine's own drawing stays in world space, but a game's
372
+ // render() overrides follow it too; sizes and widths are still world sized by default,
373
+ // so pass pixels; drawText stays in world space, drawTextScreen draws on the screen
374
+
375
+ // Tile sheet settings
376
+ tileDefaultSize = (16,16) // Default size of tiles in pixels
377
+ tileDefaultPadding = 0 // Default padding around tiles in pixels
378
+ tileDefaultBleed = 0 // How much smaller to draw tiles to prevent bleeding
379
+
380
+ // Canvas and context globals
381
+ mainCanvas / mainContext // The main 2D canvas and its context
382
+ drawContext // Context currently being drawn to
383
+ glCanvas / glContext // The WebGL canvas and context
384
+ mainCanvasSize // Size of the main canvas in css pixels (screen space)
385
+ backgroundCanvas // Extra canvas composited behind the engine canvases
386
+ setBackgroundCanvas(canvas) // Set a plugin canvas to include when combining
387
+ setCursor(cursorStyle) // Set the CSS cursor style
388
+ isOnScreen(pos, size) // Is a world space area visible on screen?
389
+ combineCanvases() // Combine all canvases onto mainCanvas (for screenshots)
390
+
391
+ // Very many sprites: drawTile works out the tile's uvs and packs the color on every call, so a game drawing
392
+ // thousands a frame can work those out once and write each sprite into the WebGL batch itself (glEnable only)
393
+ glSetTexture(textureInfo.glTexture) // Bind the sheet the sprites come from, once
394
+ glDraw(x, y, sizeX, sizeY, angle=0, uv0X, uv0Y, uv1X, uv1Y, rgba=-1, rgbaAdditive=0) // One sprite in world space;
395
+ // uvs are tile pos and pos + size times textureInfo.sizeInverse, a negative sizeX mirrors it,
396
+ // rgba is color.rgbaInt(), kept from frame to frame while the color holds; the batch flushes itself
397
+ ```
398
+
399
+ ## LittleJS Audio System
400
+ - Caches sounds and music for fast playback, every play shares one decoded buffer
401
+ - Individual sound instance control with pause/resume capabilities
402
+ - Can attenuate and apply stereo panning to sounds
403
+ - Ability to play mp3, ogg, and wave files
404
+ - Route sounds or everything through effects with the audio effects plugin
405
+ - [ZzFX Sound Effect Generator](https://killedbyapixel.github.io/ZzFX)
406
+
407
+ ```javascript
408
+ // Sound Object
409
+ Sound(zzfxSound, randomness, range, taper, onloadCallback) // Create a zzfx sound
410
+ Sound(filename, randomness, range, taper, onloadCallback) // Load a wave, mp3, or ogg
411
+ Sound.play(pos, volume=1, pitch=1, randomnessScale=1, loop=false, paused=false) // Play a sound, returns SoundInstance
412
+ Sound.playLoop(pos, volume=1, pitch=1, randomnessScale=1, paused=false) // Play on a loop, like play with loop on
413
+ Sound.playMusic(volume=1, loop=true, paused=false) // Play as music with looping
414
+ // (played before the first input, a sound waits and starts once audio runs;
415
+ // only the newest play of each sound waits)
416
+ Sound.playNote(semitoneOffset, pos, volume=1) // Play as note with a semitone offset
417
+ Sound.getDuration() // Get length of sound in seconds (0 if loading)
418
+ Sound.isLoaded() // Check if sound is fully loaded
419
+ Sound.loadedPercent // 0 until a file is decoded, then 1
420
+ Sound.output // Optional node or effect to route every play through
421
+
422
+ // SoundInstance
423
+ SoundInstance.start(offset=0) // Start or restart from a time in seconds, to seek
424
+ SoundInstance.setVolume(volume, fadeTime=0) // Change volume during playback, fading to it if given a time
425
+ SoundInstance.setRate(rate) // Change speed and pitch during playback, like an engine loop following speed
426
+ SoundInstance.setPan(pan) // Change stereo pan during playback (-1 left to 1 right), to follow its source
427
+ SoundInstance.stop(fadeTime=0) // Stop with optional fade out
428
+ SoundInstance.pause() // Pause the sound
429
+ SoundInstance.resume() // Resume paused sound
430
+ SoundInstance.isPlaying() // Check if currently playing
431
+ SoundInstance.isPaused() // Check if paused or stopped, not playing
432
+ SoundInstance.getCurrentTime() // Where it is in the sound, in the sound's own seconds whatever the rate
433
+ SoundInstance.getDuration() // Length of the sound, the same at any rate; divide by rate for time to play
434
+ SoundInstance.getSource() // Get AudioBufferSourceNode
435
+ SoundInstance.onendedCallback // Called when it plays to its end, not on stop or pause; set it any time
436
+
437
+ // Audio functions
438
+ speak(text, volume=1, rate=1, pitch=1, language='') // Speak text line
439
+ speakStop() // Stop all queued speech
440
+ getNoteFrequency(semitoneOffset, rootFrequency=220) // Get frequency for musical notes
441
+
442
+ // Audio settings
443
+ soundEnable = true // Should sound be enabled?
444
+ soundVolume = .3 // Volume scale to apply to all sound
445
+ soundDefaultRange = 40 // Default range where sound no longer plays
446
+ soundDefaultTaper = .7 // Default range percent to taper off sound (0-1)
447
+ soundPauseWhenHidden = true // Pause all sound while the page is hidden, the way the game stops; off keeps
448
+ // music playing in a background tab
449
+
450
+ // Audio globals
451
+ audioContext // The shared Web Audio context
452
+ audioMasterGain // Master gain node all sound routes through
453
+ setAudioMasterEffect(input, output) // Route all sound through a node or effect, or a chain's first and last; no args to remove it
454
+ audioIsRunning() // Is the audio context running? (requires user interaction)
455
+ playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate, gainNode, offset=0, onended, output, pannerNode) // Low level sample playback
456
+ createAudioBuffer(sampleChannels, sampleRate) // Copy arrays of samples into an audio buffer
457
+ playAudioBuffer(buffer, volume=1, rate=1, pan=0, loop=false, gainNode, offset=0, onended, output, pannerNode) // Play an audio buffer, shareable between sounds
458
+ ```
459
+
460
+ ## LittleJS Audio Effects
461
+ - Optional plugin with Web Audio effects, each with a wet/dry mix
462
+ - Route a group of sounds with `sound.output = effect`, or everything with `setAudioMasterEffect(effect)`
463
+ - Chain effects with `effect.connect(next)`, never by assigning `effect.output`, that is the effect's own node
464
+
465
+ ```javascript
466
+ AudioEffect(mix=1) // Base class, input and output gain nodes with a mix between
467
+ AudioEffect.input / AudioEffect.output // Its own nodes, output feeds the master gain until connect() moves it
468
+ AudioEffect.setMix(mix, fadeTime=0) // 0 is fully dry, 1 is fully wet
469
+ AudioEffect.connect(effectOrNode) // Send output into the next effect instead, returns it
470
+ AudioEffect.disconnect() // Stop sending output anywhere
471
+ AudioFilter(type='lowpass', frequency=1000, q=1, mix=1) // Muffle sounds, setFrequency(hz, fadeTime) to sweep
472
+ AudioReverb(duration=2, decay=2, mix=.5) // Room or cave from generated noise, setRoom(duration, decay) to change it
473
+ AudioDelay(time=.3, feedback=.4, mix=.5) // Echoes, setTime and setFeedback while playing
474
+ AudioDistortion(amount=.5, mix=1) // Overdrive, setAmount rebuilds the curve
475
+ AudioCompressor(threshold=-24, ratio=12, mix=1) // Stops clipping on the master bus, setThreshold, setRatio
476
+ AudioFilter.node, AudioReverb.node, ... // Each effect's wrapped Web Audio node, for anything the above doesn't cover
477
+ ```
478
+
479
+ ## LittleJS Input System
480
+ - Tracks keyboard down, pressed, and released
481
+ - Tracks mouse buttons, position, and wheel
482
+ - Tracks multiple analog gamepads
483
+ - Routes touch input to mouse
484
+ - Virtual gamepad for touch devices
485
+
486
+ ```javascript
487
+ // Keyboard, keys are KeyboardEvent.code names like 'KeyW', 'Space' or 'ArrowUp', not characters
488
+ keyIsDown(key) // Is key down?
489
+ keyWasPressed(key) // Was key pressed this frame?
490
+ keyWasReleased(key) // Was key released this frame?
491
+ keyDirection(up, down, left, right) // Get input vector from arrow keys or wasd
492
+ inputClear() // Clear all input state
493
+ inputClearKey(key) // Clear input state for a specific key
494
+
495
+ // Mouse / Touch
496
+ mousePos // World space mouse position
497
+ mousePosScreen // Screen space mouse position
498
+ mouseDelta // World space mouse movement delta
499
+ mouseDeltaScreen // Screen space mouse movement delta
500
+ mouseWheel // Delta mouse wheel this frame, a touch pinch turns it too
501
+ touchPinch // Pinch of two fingers this frame in wheel units: apart is negative like
502
+ // the wheel up, 1 for every 50 pixels; the mouse is between the fingers
503
+ touchPinchWheel = true // Add touchPinch to mouseWheel? A second finger then lets go of the button
504
+ // the first pressed; setTouchPinchWheel(false) leaves the first finger the
505
+ // mouse, for a game with its own use for two fingers
506
+ mouseIsDown(button) // Is mouse button down?
507
+ mouseWasPressed(button) // Was mouse button pressed this frame?
508
+ mouseWasReleased(button) // Was mouse button released this frame?
509
+ mouseInWindow // Is the mouse inside the window?
510
+ isTouchDevice // Is this a touch capable device?
511
+
512
+ // Pointer Lock
513
+ pointerLockRequest() // Request pointer lock on the canvas
514
+ pointerLockExit() // Exit pointer lock
515
+ pointerLockIsActive() // Is pointer lock currently active?
516
+
517
+ // Last input device (most recently used)
518
+ lastInputDevice // 'mouse' | 'keyboard' | 'gamepad' (sticky while idle)
519
+ usingMouseInput() // Is the mouse the most recently used device?
520
+ usingKeyboardInput() // Is the keyboard the most recently used device?
521
+ usingGamepadInput() // Is a gamepad the most recently used device?
522
+
523
+ // Gamepad
524
+ isUsingGamepad // Is a gamepad the most recently used device? (= usingGamepadInput())
525
+ gamepadPrimary // Index of the primary gamepad (first one with input, until it disconnects)
526
+ // every gamepad function below defaults to gamepad=gamepadPrimary, not pad 0
527
+ gamepadIsDown(button, gamepad=gamepadPrimary) // Is gamepad button down?
528
+ gamepadWasPressed(button, gamepad=gamepadPrimary) // Was gamepad button pressed this frame?
529
+ gamepadWasReleased(button, gamepad=gamepadPrimary) // Was gamepad button released this frame?
530
+ gamepadStick(stick, gamepad=gamepadPrimary) // Get gamepad analog stick value
531
+ gamepadDpad(gamepad=gamepadPrimary) // Get gamepad dpad as a direction vector
532
+ gamepadStickCount(gamepad=gamepadPrimary) // Get number of analog sticks
533
+ gamepadConnected(gamepad=gamepadPrimary) // Is the gamepad connected?
534
+ gamepadVibrate(gamepad=gamepadPrimary, duration=200, strongMagnitude=1, weakMagnitude=1, startDelay=0) // Rumble,
535
+ // durations in ms, browsers stop at 5 seconds with the delay
536
+ gamepadVibrateStop(gamepad=gamepadPrimary) // Stop gamepad vibration
537
+
538
+ // Touch Gamepad
539
+ touchGamepadEnable = false // Is on screen touch gamepad enabled? A real gamepad in use takes over
540
+ touchGamepadAnalog = true // Is touch gamepad analog or 8 way dpad?
541
+ touchGamepadSize = 100 // Size of touch gamepad
542
+ touchGamepadAlpha = .3 // Alpha of touch gamepad
543
+ touchGamepadPassthrough = false // Also route touches outside the gamepad to mouse?
544
+ touchGamepadButtonCount = 4 // Number of right side buttons (0-4)
545
+ touchGamepadCenterButtonSize = 0 // Size of center start button (0 = disabled)
546
+ touchGamepadLeftStick = true // Show analog stick on the left side?
547
+ touchGamepadLeftButtonCount = 0 // Number of left side buttons (when left stick is off)
548
+ touchGamepadRightStick = false // Use a stick instead of buttons on the right side?
549
+ touchGamepadFloating = false // Directional controls float to where you press?
550
+ touchGamepadDisplayTime = 3 // Seconds to display when unused (0 = always show)
551
+ touchGamepadVibration = 0 // Vibrate duration in ms on button press (0 = off)
552
+
553
+ // Vibration
554
+ vibrate(pattern=100) // Pulse the vibration hardware if it exists
555
+ vibrateStop() // Stop all vibration
556
+
557
+ // Input settings
558
+ gamepadsEnable = true // Should gamepads be allowed?
559
+ gamepadDirectionEmulateStick = true // Should dpad be routed to the left analog stick?
560
+ gamepadAxisFilterEnable = true // Ignore axes that don't rest near center (steering wheels)
561
+ inputWASDEmulateDirection = true // Should WASD keys be routed to the direction keys? Off for two players on one
562
+ // keyboard, WASD and arrows
563
+ inputPreventDefault = true // Should input events prevent default browser handling?
564
+ inputMouseMoveThreshold = 6 // Screen-px mouse movement per frame that counts as mouse use
565
+ vibrateEnable = true // Allow vibration hardware if it exists?
566
+ touchInputEnable = true // Should touch input route to mouse events?
567
+ ```
568
+
569
+ ## LittleJS Object System
570
+ - Top level object class used by the engine
571
+ - Automatically adds self to object list
572
+ - Will be updated and rendered each frame
573
+ - Renders as a sprite from a tile sheet by default
574
+ - Can have color and additive color applied
575
+ - 2D Physics and collision system
576
+ - Sorted by renderOrder before drawing
577
+ - Objects can have children in local space
578
+ - Parents are updated before children
579
+ - Call destroy() to get rid of objects
580
+
581
+ ```javascript
582
+ // Engine Object
583
+ EngineObject(pos, size=(1,1), tileInfo, angle=0, color, renderOrder=0)
584
+ EngineObject.update() // Update object, called automatically
585
+ EngineObject.render() // Render object, called automatically
586
+ EngineObject.destroy(immediate=false) // Destroy this object and children, removed at the end of the frame;
587
+ // immediate cuts attached emitters off instead of letting them finish
588
+ EngineObject.collideWithTile(tileData, pos) // Tile collision resolve check
589
+ EngineObject.collideWithObject(object, push) // Object collision resolve check, push is 3D only
590
+ EngineObject.getAliveTime() // How long since object was created
591
+ EngineObject.getSpeed() // Length of the velocity
592
+ EngineObject.applyAcceleration(acceleration) // Apply acceleration
593
+ EngineObject.applyForce(force) // Apply force
594
+ EngineObject.getMirrorSign() // Get mirror direction (1 or -1)
595
+ EngineObject.localToWorld(pos) // Convert a point from this object's space to world space
596
+ EngineObject.worldToLocal(pos) // Convert a world space point to this object's space
597
+ EngineObject.localToWorldVector(vec) // Convert a direction, rotation only, and back with
598
+ // worldToLocalVector(vec)
599
+ EngineObject.isOverlappingObject(object) // Do the two boxes overlap?
600
+ EngineObject.isOverlapping(pos, size=(0,0)) // Does its box overlap a box, or a point with no size?
601
+ EngineObject.addChild(child, localPos, localAngle) // Attach a child at an offset; localPos only exists on a child
602
+ EngineObject.attach(child) // Attach a child where it is, the offset worked out for it
603
+ EngineObject.removeChild(child) // Remove a child, it stays where it was in the world
604
+ EngineObject.setCollision(solids=true, isSolid=true, level=true, raycast=true) // Set collision, level is collideLevel,
605
+ // the tile collision layers; raycast=false
606
+ // leaves it out of engineObjectsRaycast; an object with no width
607
+ // or height is not a solid obstacle, so it blocks nothing and
608
+ // nothing blocks it
609
+ EngineObject.persistent = false // true skips it in engineObjectsDestroy, for things that outlive
610
+ // a level like a camera; destroy() still destroys it
611
+
612
+ // Engine Object Members
613
+ EngineObject.pos // World space position
614
+ EngineObject.size // World space width and height
615
+ EngineObject.drawSize // Size of object used for drawing if set
616
+ EngineObject.tileInfo // Tile info to render object
617
+ EngineObject.angle // Rotation angle for rendering
618
+ EngineObject.color // Color to apply when rendered
619
+ EngineObject.additiveColor // Additive color to apply when rendered
620
+ EngineObject.mirror // Should it flip along y axis when rendered
621
+ EngineObject.mass // Weight of object, static if 0: it moves by its velocity but only others collide with it
622
+ EngineObject.damping // Fraction of velocity kept each frame, 1 keeps all, 0 stops at once
623
+ EngineObject.angleDamping // Fraction of angular velocity kept each frame, 1 keeps all, 0 stops at once
624
+ EngineObject.restitution // How bouncy is it when colliding (0-1)
625
+ EngineObject.friction // Fraction of sliding speed kept each frame on the ground, 1 is no friction; the
626
+ // more slippery of the object and the ground is used
627
+ EngineObject.gravityScale // How much to scale gravity by
628
+ EngineObject.renderOrder // Objects are sorted by render order
629
+ EngineObject.velocity // Velocity of the object, world units per frame
630
+ EngineObject.angleVelocity // Angular velocity of the object, radians per frame
631
+ EngineObject.groundObject // What it stands on this frame, a tile layer or an object, undefined in the air; a
632
+ // moving platform carries its rider through velocity.x, so a controller adds to it
633
+ EngineObject.clampSpeed // Hold each axis of velocity to objectMaxSpeed while it collides, with solids or with tiles
634
+ // while it has a mass, so it can not pass through a thin wall; true by default, false for
635
+ // a fast bullet that collides; what does not collide moves as fast as it is told
636
+ EngineObject.parent / children // Set by addChild, a child is placed by its parent and sits out solid collision
637
+
638
+ // Engine Object settings
639
+ enablePhysicsSolver = true // Enable collisions, between objects and with tiles?
640
+ objectDefaultMass = 1 // Default object mass for collisions
641
+ objectDefaultDamping = 1 // Fraction of velocity kept each frame (1 keeps all)
642
+ objectDefaultAngleDamping = 1 // Fraction of angular velocity kept each frame (1 keeps all)
643
+ objectDefaultRestitution = 0 // How much to bounce when a collision occurs (0-1)
644
+ objectDefaultFriction = .8 // Fraction of sliding speed kept each frame on the ground (1 is no friction)
645
+ objectMaxSpeed = 1 // Clamp each axis of velocity, world units per frame, so fast objects don't miss
646
+ // collisions; each axis on its own, so a faster diagonal bends toward 45 degrees
647
+ gravity = (0,0) // How much gravity to apply to objects, added to velocity each frame
648
+
649
+ // Engine Object functions
650
+ engineObjectsCollect(pos, size, objects=engineObjects, testCenters=false) // size is a circle's diameter or a box's
651
+ // full size, objects whose box overlaps it; testCenters
652
+ // only those whose center is inside, a little faster
653
+ engineObjectsCallback(pos, size, callbackFunction, objects=engineObjects, testCenters=false) // destroyed objects are
654
+ // left out
655
+ engineObjectsRaycast(start, end, objects=engineObjects) // only objects with collideRaycast set, which
656
+ // setCollision turns on
657
+ engineObjectsDestroy(immediate=true) // destroy every object except the persistent ones
658
+ ```
659
+
660
+ ## LittleJS Tile Layer System
661
+ - Caches arrays of tiles to off screen canvas for fast rendering
662
+ - Unlimited numbers of layers, allocates canvases as needed
663
+ - Interfaces with EngineObject for collision
664
+ - Collision layer is separate from visible layers
665
+ - It is recommended to have a visible layer that matches the collision
666
+ - Tile layers made without WebGL can be drawn to using their context with Canvas2D
667
+ - Drawn with WebGL by default, or straight to the main canvas when made with useWebGL=false
668
+
669
+ ```javascript
670
+
671
+ // Canvas Layer
672
+ CanvasLayer(pos, size, angle=0, renderOrder=0, canvasSize=(512,512), useWebGL=true) // Create a canvas layer object
673
+ CanvasLayer.canvas // The canvas used by this layer
674
+ CanvasLayer.context // The 2D context of the canvas, read it back with context.getImageData(...)
675
+ CanvasLayer.updateWebGL() // Creates or updates WebGL texture
676
+ CanvasLayer.draw(pos, size, color=WHITE, angle=0, mirror=false, additiveColor, screenSpace=drawScreenSpace, context)
677
+ // Draw the layer centered at pos
678
+
679
+ // LittleJS Layer System
680
+ TileLayer(pos, size, tileInfo, renderOrder=0, useWebGL=true) // Create a tile layer object
681
+ TileLayer.setData(layerPos, data, redraw) // Set data at position
682
+ TileLayer.clearData(layerPos, redraw) // Clear data at position
683
+ TileLayer.getData(layerPos) // Get data at position
684
+ TileLayer.debugShow = true // Shown by the debug overlay's 2: Debug Tiles, off to leave a layer out
685
+ TileLayer.redraw() // Draw to an offscreen canvas
686
+ TileLayer.redrawStart(clear=false) // Start drawing to the layer, for updating parts of it
687
+ TileLayer.redrawEnd() // Finish drawing to the layer
688
+ TileLayer.drawTileData(layerPos, clear=true) // Draw the tile, inside redrawStart/End
689
+ TileLayer.drawLayerTile(pos, size=(1,1), tileInfo, color=WHITE, angle=0, mirror, additiveColor) // Draw a tile in
690
+ // layer pixels, inside redrawStart/End
691
+ TileLayer.drawLayerRect(pos, size, color, angle=0) // Draw a rectangle in layer pixels, inside redrawStart/End
692
+ TileLayer.drawRect(pos, size, color, angle) // Draw a rectangle onto the layer canvas in world space
693
+ TileLayer.drawTile(pos, size=(1,1), tileInfo, color, angle, mirror) // Draw a tile onto the layer in world space
694
+ TileLayer.clearLayerRect(pos, size) // Clear a rectangle in layer pixels, inside redrawStart/End
695
+ // to draw on a layer made with useWebGL=false (or with WebGL off), pass its context to drawCanvas2D:
696
+ // drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, layer.context)
697
+ // on a WebGL layer use drawLayerTile/drawLayerRect inside redrawStart/End, its canvas is not shown
698
+
699
+ // Tile Layer Data Object
700
+ TileLayerData(tile, direction=0, mirror=false, color=WHITE) // Create tile data object, tile from 0 like tile(),
701
+ // undefined for an empty cell
702
+ TileLayerData.clear() // Clear this tile data, it draws nothing
703
+
704
+ // Tile Collision Layer
705
+ TileCollisionLayer(pos, size, tileInfo=tile()) // Create a tile collision layer object, with no tile when no
706
+ // image is loaded, for a collision only layer
707
+ TileCollisionLayer.setCollisionData(layerPos, data=1) // Set tile collision data at a cell in the layer
708
+ TileCollisionLayer.getCollisionData(layerPos) // Get tile collision data at a cell, 0 outside the layer
709
+ TileCollisionLayer.clearCollisionData(layerPos) // Clear tile collision data at a cell
710
+ TileCollisionLayer.collisionTest(pos, size=(0,0), object) // Like tileCollisionTest for this layer only
711
+ TileCollisionLayer.collisionRaycast(posStart, posEnd, object, normal) // Like tileCollisionRaycast for this layer only
712
+ TileCollisionLayer.isSolid = true // Solid layers block objects and particles, the solidOnly tests
713
+ // skip the others
714
+ tileCollisionGetData(pos) // Get tile collision data at pos
715
+ tileCollisionTest(pos, size=(0,0), object) // Check if collision should occur
716
+ tileCollisionRaycast(posStart, posEnd, object, normal, solidOnly=true) // Where the ray meets the first tile hit,
717
+ // or undefined; a normal vec2 passed in is set to the surface's
718
+ tileCollisionLayers // List of all tile collision layers
719
+ tileLayersLoad(tileMapData, tileInfo=tile(), renderOrder=0, collisionLayer, draw=true) // collisionLayer is the index
720
+ // of the layer that gets collision, no tile when no image is loaded
721
+ // Load tile layers from exported data, Tiled flips and turns included;
722
+ // groups are flattened and layer indices count that flat list,
723
+ // hidden layers load with collision but are not drawn; a
724
+ // tileset in the map with a margin or spacing is read where
725
+ // its tiles are
726
+ tileLayersFromLDtk(ldtk, level=0) // A Tiled map of an LDtk level, by index or identifier, to give
727
+ // tileLayersLoad and objectLayersLoad: its layers bottom first, an
728
+ // IntGrid layer with no tiles a hidden layer for collision, tiles
729
+ // stacked in a cell in layers above, named (2), (3); its entities
730
+ // objects with their fields as properties
731
+ objectLayersAddType(name, make, defaults={}, tileInfo) // Name a type of object in a Tiled map: a class made
732
+ // with new make(pos), or an arrow function called make(pos),
733
+ // defaults set on what it made; tileInfo is an editor icon
734
+ objectLayersLoad(tileMapData) // Make the objects of a map's object layers, returns them
735
+
736
+ ```
737
+
738
+ ## LittleJS Particle System
739
+ - Simple kinematic particle system with many parameters
740
+ - [Particle Effect Designer](https://killedbyapixel.github.io/LittleJS/examples/particles) - Editor for creating LittleJS Particle Systems
741
+
742
+ ```javascript
743
+ // Particle Emitter Object
744
+ ParticleEmitter(pos, angle, emitSize, emitTime, emitRate, emitConeAngle, tileInfo, colorStartA, colorStartB,
745
+ colorEndA, colorEndB, particleTime, sizeStart, sizeEnd, speed, angleSpeed, damping, angleDamping,
746
+ gravityScale, particleConeAngle, fadeRate, randomness, collideLevel, additive, randomColorLinear,
747
+ renderOrder, localSpace) // Create a particle system, speeds are per frame; collideLevel is for world space only
748
+ emitter.trailScale / velocityInheritance / restitution / friction / emitCircle // More settings, set after making it
749
+ emitter.particleCreateCallback / particleDestroyCallback / particleCollideCallback // Called with each particle
750
+ emitter.particleUpdateCallback // Called with each particle every update, after it moves
751
+ emitter.scale = 1 // grows the whole effect: spawn area, sizes, speed and fall; each particle keeps its own
752
+ emitter.gravity = 0 // the effect's own fall per frame, added to world gravity times gravityScale
753
+ ParticleEmitter.emitParticle() // Spawn one particle
754
+
755
+ // Particle Settings
756
+ particleEmitRateScale = 1 // Scales particles emit rate
757
+ ```
758
+
759
+ ## LittleJS Particle Effects
760
+ - Ready made effects in one line, 2D and 3D, each tuned around a one unit emitter
761
+ - Effects are plain data, {name, settings, behaviors}, the format the particle designer saves, so a designed library
762
+ plays by name
763
+
764
+ ```javascript
765
+ particleEffect(name, pos, {scale, hue, saturation, angle}) // play an effect, returns its ParticleEmitter; a
766
+ // continuous one goes until destroyed, a one-shot ends itself; any setting
767
+ // in the options replaces the effect's own: {emitTime: .5} for a burst,
768
+ // {emitTime: 0, emitRate: 30} to keep a one-shot going
769
+ particleEffect3D(name, pos3D, {scale, hue, saturation, angle, flatten}) // the same effect as a
770
+ // ParticleEmitter3D, angle turning it about z; a circle area becomes a sphere
771
+ // and a rectangle a box as deep as it is wide, flatten makes them a disc or a
772
+ // sheet across the way it emits
773
+ // both take {tileInfo} to draw the game's own art in place of the shape
774
+ particleEffectsBuiltIn // the built-in names: fire, torch, smoke, steam, explosion, sparks, hit,
775
+ // dust, debris, sparkle, magic, heal, poison, portal, rain, snow, leaves,
776
+ // bubbles, fireflies, trail, muzzle, blood, confetti and splash
777
+ // each at scale 1 fits a one unit object, with the hue it starts from for options.hue to turn; a one-shot ends
778
+ // itself, a continuous one goes until destroyed
779
+ 'fire' // hue .08, continuous
780
+ 'torch' // hue .09, continuous
781
+ 'smoke' // no hue, grey, continuous
782
+ 'steam' // no hue, white, continuous
783
+ 'explosion' // hue .12, one-shot
784
+ 'sparks' // hue .17, continuous
785
+ 'hit' // hue 0, red, one-shot
786
+ 'dust' // hue .1, one-shot
787
+ 'debris' // hue .08, one-shot
788
+ 'sparkle' // hue .14, continuous
789
+ 'magic' // hue .55 to .79, continuous
790
+ 'heal' // hue .33, continuous
791
+ 'poison' // hue .3 and .8, continuous
792
+ 'portal' // hue .75, continuous
793
+ 'rain' // hue .63, continuous
794
+ 'snow' // no hue, white, continuous
795
+ 'leaves' // hue .25 to .05, continuous
796
+ 'bubbles' // hue .55, continuous
797
+ 'fireflies' // hue .2, continuous
798
+ 'trail' // hue .08, continuous
799
+ 'muzzle' // hue .14, one-shot
800
+ 'blood' // hue 0, red, one-shot
801
+ 'confetti' // hue 0 and .6, mixed, one-shot
802
+ 'splash' // hue .58, one-shot
803
+ particleEffectApply(emitter, effect) // set a live 2D emitter to an effect
804
+ particleEffectApply3D(emitter3D, effect) // the same for a ParticleEmitter3D, its particles, place and scale kept
805
+ particleEffectFromEmitter(emitter) // a 2D emitter's settings as an effect, to save or build in 3D
806
+ particleEffectsAddBehavior(name, update, update3D) // add a behavior effects can name, with 2D and 3D pushes
807
+ particleEffectsAdd(effects) // add effects to play by name, one of the same name replaces it
808
+ particleEffectsGet(name) // an effect's data, any case, to change or build by hand
809
+ await particleEffectsLoad(url) // load the file the particle designer's Save Library wrote, and add its effects
810
+ particleEffectsParse(text) // a library file's text to effects, particleEffectsText(effects) back
811
+ particleEffectSanitize(raw) // any input to a whole effect, defaults filled, bad values clamped
812
+ particleEffectRecolor(effect, hue, saturation) // a copy turned around the color wheel and its saturation scaled
813
+ particleEffectSettings // every setting an effect has, with particleEffectGroups, the groups
814
+ particleEffectBehaviors // the behaviors an effect can name: wobble, swirl, turbulence, attract,
815
+ // orbit, wind and stick
816
+ particleEffectShapes // the shapes the plugin draws: dot, soft, glow, smoke, spark, square,
817
+ // triangle, ring, star and plus
818
+ particleEffectShapeTile(name) // a built-in shape's tile, on a sheet drawn once, undefined headless or
819
+ // without WebGL, where effects draw untextured
820
+ ```
821
+
822
+ ## LittleJS Tween System
823
+ - Animate numbers, Vector2, Color, or any value with a `.lerp(other, percent)` method
824
+ - Pauses with the game by default; opt-in real-time mode keeps tweens running while paused
825
+ - Easing curves with looping, ping-pong, and chained completion callbacks
826
+ - Auto-registers via `engineAddPlugin` — no setup needed
827
+ - See the Tween and Tween Advanced shorts (`examples/shorts/tween.js`, `tweenAdvanced.js`) for demos
828
+
829
+ ```javascript
830
+ // Tween a property by dot-path (common case)
831
+ tweenProperty(target, propertyPath, start, end, duration=1, options)
832
+
833
+ // Tween via custom callback
834
+ new Tween(callback, start=0, end=1, duration=1, options)
835
+ Tween.setEase(easeFn) // Set easing curve, returns this
836
+ Tween.then(callback) // Set onComplete, called after the last pass or loop, returns this
837
+ Tween.onComplete // Completion callback, kept by restart, not called by stop
838
+ Tween.loop(count=Infinity) // Repeat n times, returns this
839
+ Tween.pingPong(count=Infinity) // Bounce between endpoints, returns this
840
+ Tween.pause() // Pause this tween
841
+ Tween.resume() // Resume a paused tween
842
+ Tween.restart() // Reset to start and replay
843
+ Tween.stop() // Remove from active list
844
+ Tween.isActive() // True if running and not paused
845
+ Tween.getPercent() // Progress 0..1
846
+ Tween.getValue() // Current interpolated value
847
+
848
+ // Easing curves — pass to setEase or options.ease
849
+ Ease.LINEAR, Ease.SINE, Ease.CIRC, Ease.EXPO
850
+ Ease.BACK, Ease.ELASTIC, Ease.SPRING, Ease.BOUNCE
851
+ Ease.POWER(n) // Returns x => x**n
852
+ Ease.BEZIER(x1, y1, x2, y2) // CSS cubic-bezier solver
853
+
854
+ // Direction modifiers — wrap a curve to flip its direction
855
+ Ease.OUT(curve) // Reverse to ease-out
856
+ Ease.IN_OUT(curve) // Symmetric S-curve
857
+ Ease.IN(curve) // No-op (curves are already ease-in)
858
+ Ease.PIECEWISE(...curves) // Run different curves over equal sections
859
+
860
+ // Tween options
861
+ options.ease // Easing function (default Ease.LINEAR)
862
+ options.useRealTime // Advance even when game is paused (default false)
863
+ options.paused // Start in paused state (default false)
864
+
865
+ // Global helper
866
+ tweenStopAll() // Stop every active tween (e.g. on level reset)
867
+ ```
868
+
869
+ ## LittleJS Scene System
870
+ - A game's states, like a title, the game and game over, as scene objects
871
+ - `setScene` leaves the current scene, destroys every object that is not persistent, and enters the next
872
+ - Pause is the engine's `paused`: a scene's update stops with gameUpdate, its render hooks keep drawing
873
+ - See the Scenes short and the Box2D example
874
+
875
+ ```javascript
876
+ setScene(scene) // Leave the current scene, clear objects, enter scene; the current one restarts it
877
+ getScene() // The current scene, or undefined
878
+
879
+ // A scene is any object, every hook is optional
880
+ scene.enter() // After the old scene left and the objects were cleared
881
+ scene.leave() // Before the objects are cleared, while the scene is still current
882
+ scene.update() // Each update after gameUpdate, not while paused or at time scale 0
883
+ scene.render() // Before gameRender, under the objects
884
+ scene.renderPost() // After gameRenderPost, over the game, while paused too; plugins made after the first setScene draw over it
885
+ ```
886
+
887
+ ## LittleJS Parallax
888
+ - A background layer that follows the camera by a part of its movement and repeats across the view
889
+ - Draws mountains by default, or anything a game draws into its canvas
890
+ - See the Parallax short and the platformer example
891
+
892
+ ```javascript
893
+ new ParallaxLayer(pos, size=vec2(32,16), parallax=.5, renderOrder=-1e3, drawFunction, canvasSize=vec2(512,256))
894
+ // a CanvasLayer; pos is where the image's middle is when the camera is there too,
895
+ // size the world size of one copy; parallax is how much of the camera's movement it
896
+ // follows, 0 with the world, 1 with the screen, a Vector2 for x and y apart
897
+ drawFunction(context, canvasSize, layer) // draws the image once, parallaxMountains() when not given; an image
898
+ // that meets itself at its left and right edges repeats with no seam
899
+ parallaxMountains(topColor, bottomColor, seed) // a draw function: a range of mountains that repeats with no seam,
900
+ // shaded from its peaks to its feet; the same seed gives the same range
901
+ layer.wrapX = true // repeat across the view; wrapY = false, up and down too
902
+ layer.zoomFollow = 1 // how much the camera's zoom changes its size on screen, 0 keeps it the size it has at
903
+ layer.zoomScale // this camera scale, the cameraScale when it was made
904
+ layer.redraw() // draw the image again, after changing layer.drawFunction or what it draws from
905
+ layer.getDrawPositions() // where each copy is drawn now in the world; getDrawSize() the size of one
906
+
907
+ // three ranges, the far ones lighter, higher and slower
908
+ for (let i = 3; i--;)
909
+ new ParallaxLayer(vec2(0, i*2), vec2(40, 20), .3 + i*.25, -1e3 - i,
910
+ parallaxMountains(hsl(.6, .3, .4 + i*.15), hsl(.6, .5, .2)));
911
+ ```
912
+
913
+ ## LittleJS PathFinding System
914
+ - A* pathfinding on a grid
915
+ - Works with a TileCollisionLayer or a bare grid with custom walkability
916
+ - Optional path smoothing (corner cleanup + string-pull line-of-sight)
917
+ - See `examples/shorts/pathFinder.js` for a demo
918
+
919
+ ```javascript
920
+ // Construct from a TileCollisionLayer or a Vector2 grid size
921
+ const pf = new PathFinder(tileCollisionLayer);
922
+ const pf = new PathFinder(vec2(50, 50));
923
+ pf.isWalkable = (x, y) => myGrid[y*50 + x] === 0; // bare grid: provide your own
924
+
925
+ // Tunables (set freely)
926
+ pf.heuristicWeight = 1 // > 1 = greedier search, faster but less optimal
927
+ pf.maxLoop = undefined // max A* steps per search, undefined for the cell count so a search always finishes
928
+ pf.searchGaveUp // true when the last search stopped at maxLoop, an empty path then means it gave up
929
+ pf.smoothPath = true // run smoothing pass on result
930
+ pf.debug = false // draw search visualization
931
+ pf.debugTime = 1 // seconds debug visuals persist
932
+
933
+ // Main API
934
+ pf.findPath(startPos, endPos, rebuild=true) // Returns array of world positions, or empty if no path;
935
+ // rebuild false reuses the grid read from the layer last time
936
+ pf.buildNodeData() // Read the grid from the layer again, what findPath does when rebuild is true
937
+ pf.getNearestClearNode(worldPos, searchRange=10, rebuild=true) // Snap an obstructed point to the nearest open tile
938
+ pf.isWalkable(x, y) // Override for custom walkability
939
+ pf.getCost(x, y) // Override for weighted tiles (0 = clear)
940
+
941
+ // Conversion helpers
942
+ pf.worldToTile(worldPos) // Vector2 -> tile coords
943
+ pf.tileToWorld(x, y) // tile coords -> Vector2 (tile center)
944
+ pf.getNode(x, y) // Get PathFinderNode at tile coords
945
+ ```
946
+
947
+ ## LittleJS UI System
948
+ - Standalone UI plugin with buttons, text, sliders, checkboxes, text input, video, and auto-layout
949
+ - Auto-registers via `engineAddPlugin` — `new UISystemPlugin()` is all you need
950
+ - Keyboard listener only attached while a UITextInput is being edited
951
+ - A click on the UI, or a navigation press (Space, Enter, gamepad A) that activates it, is used up before objects update and `gameUpdatePost`, so read world clicks there; `gameUpdate` runs first and still sees it, so check `uiSystem.isMouseOverUI()` there; only the press is used up, `mouseIsDown` stays true while it is held, so a held action like auto fire checks `uiSystem.isMouseOverUI()` too
952
+ - See `examples/uiSystem/`, `examples/shorts/uiSystem.js` and `examples/shorts/uiSlice.js` for demos
953
+
954
+ ```javascript
955
+ // Setup
956
+ const ui = new UISystemPlugin() // Creates global uiSystem
957
+ uiSystem.defaultColor // Default style values used by all widgets
958
+ uiSystem.defaultLineColor // (override before constructing widgets)
959
+ uiSystem.defaultTextColor / defaultButtonColor / defaultHoverColor / defaultDisabledColor / defaultGradientColor
960
+ uiSystem.defaultLineWidth / defaultCornerRadius / defaultTextFitScale / defaultFont
961
+ uiSystem.defaultSoundPress / defaultSoundRelease / defaultSoundClick // Sounds every widget plays
962
+ uiSystem.defaultShadowColor / defaultShadowBlur / defaultShadowOffset
963
+ uiSystem.defaultSlice // a TileSlice drawn in place of every widget's rectangle, tinted by its state
964
+ // color, undefined for rectangles; needs the drawUtilities plugin
965
+ uiSystem.defaultHandleSlice // a TileSlice for slider handles, undefined for the slider's own slice
966
+ obj.slice / slider.handleSlice // the same for one widget, starting from the defaults
967
+ uiSystem.drawSlice(slice, pos, size, color=WHITE) // Draw a TileSlice to the UI context
968
+ uiSystem.nativeHeight // If set, UI coords are normalized to this height
969
+ uiSystem.destroyObjects() // Remove all UI elements
970
+ uiSystem.isMouseOverUI() // True if the mouse is over a visible hoverable UI object, or a confirm dialog is open
971
+ uiSetDebug(enable) // Toggle uiDebug rendering of widget bounds
972
+
973
+ // Confirm dialog
974
+ uiSystem.showConfirmDialog(text='Are you sure?', yes, no, size, exitKey='Escape') // the exit key or gamepad B answers no, the title and buttons scale with size
975
+
976
+ // Drawing helpers (use these instead of the engine's draw* during UI rendering)
977
+ uiSystem.drawRect(pos, size, color, lineWidth, lineColor, cornerRadius, gradientColor, shadowColor, shadowBlur, shadowOffset)
978
+ uiSystem.drawTile(pos, size, tileInfo, color, angle, mirror, shadowColor, shadowBlur, shadowOffset)
979
+ uiSystem.drawText(text, pos, size, color, lineWidth, lineColor, align, font, fontStyle, applyMaxWidth, textShadow, shadowColor, shadowBlur, shadowOffset)
980
+ uiSystem.drawLine(posA, posB, lineWidth, lineColor)
981
+
982
+ // Base widget
983
+ new UIObject(pos=vec2(), size=vec2())
984
+ UIObject.anchor // vec2 in [-1,1]; anchors to parent (or canvas if root) + self-pivot; default vec2()=center
985
+ UIObject.localPos // Position from its anchor, what the pos passed in sets; move or tween this,
986
+ // nativePos is worked out from it each frame
987
+ UIObject.visible / disabled // Hidden objects are skipped, disabled ones draw but do not respond
988
+ UIObject.text / textHeight // The text it draws, and a fixed height per line, undefined fits it to the
989
+ // size, its lines sharing the height
990
+ UIObject.navigationIndex // Order for keyboard and gamepad navigation, undefined leaves it out
991
+ UIObject.addChild(child) // Returns child, parents it
992
+ UIObject.removeChild(child)
993
+ UIObject.destroy()
994
+ UIObject.isHoverObject() // True if mouse is over this object
995
+ UIObject.isInteractive()
996
+ UIObject.onClick / onPress / onRelease / onChange / onEnter / onLeave / onUpdate / onRender // Hooks
997
+ UIObject.onKeyDown(e) // Each key while this object is uiSystem.keyInputObject
998
+ uiSystem.keyInputObject // The object being typed into; end typing with field.stopEditing(),
999
+ // which also fires its onChange
1000
+
1001
+ // Widgets
1002
+ new UIText(pos, size, text='', align='center', font)
1003
+ new UITile(pos, size, tileInfo, color, angle=0, mirror=false)
1004
+ new UIButton(pos, size, text='', color)
1005
+ new UICheckbox(pos, size, checked=false, text='', color) // .checked toggles on click
1006
+ new UISlider(pos, size, value=.5, text='', color, handleColor) // .value in [0, 1]
1007
+ new UITextInput(pos, size, text='') // .text holds current value; reads a physical keyboard, no on-screen keyboard,
1008
+ // IME or paste, use an HTML input for those
1009
+ new UIVideo(pos, size, src, autoplay=false, loop=false, volume=1)
1010
+ UIVideo.play() .pause() .stop() .setTime(time) .setVolume(volume) .setPlaybackRate(rate)
1011
+ UIVideo.isPlaying() .isPaused() .isLoading() .hasEnded() .getCurrentTime() .getDuration()
1012
+
1013
+ // Auto-layout container — arranges children into a grid
1014
+ new UILayout(pos, columns=1, gap=10, padding=10, transparent=false)
1015
+ UILayout.addChild(child) // Triggers relayout
1016
+ UILayout.relayout() // Call manually if you mutate a child's size
1017
+ ```
1018
+
1019
+ ## LittleJS Light System
1020
+ - 2D dynamic lighting overlay
1021
+ - Lights are first-class EngineObjects (the `Light` class) that draw soft falloff blobs
1022
+ - Lights accumulate ADDITIVELY in the lightmap (red + blue = magenta)
1023
+ - The lightmap is MULTIPLIED with the scene during composite — draw your world at full brightness and the lightmap handles the darkening
1024
+ - Any EngineObject can override `renderLight()` to additively contribute to the lightmap (lava tiles, weapon flashes, glowing crystals, etc.)
1025
+ - Shadows: set `lightSystem.shadows` and every object draws black into a shadow map that blocks the lights by its alpha, so smoke or a fading sprite casts a partial shadow; a `TileCollisionLayer` casts only from its cells with collision, and a plain floor `TileLayer` needs `castShadow = false` or it blacks out the map
1026
+ - The lightmap darkens WebGL draws only, so a HUD drawn with WebGL in gameRenderPost goes dark too; draw it with `useWebGL=false` or from a plugin made after this one
1027
+ - See `examples/shorts/lightShadows.js` for shadows, glass, a coin left out with castShadow and an emissive lava brick
1028
+ - Must be constructed BEFORE `PostProcessPlugin` so post-process sees lit pixels
1029
+ - See `examples/shorts/lightSystem.js` for a demo
1030
+
1031
+ ```javascript
1032
+ // Setup (call in gameInit, after engineInit has made the WebGL context)
1033
+ new LightSystemPlugin() // Defaults: full-canvas lightmap, BLACK ambient
1034
+ new LightSystemPlugin(vec2(512, 512)) // Lower-res lightmap (perf knob)
1035
+ new LightSystemPlugin(undefined, rgb(.1,.1,.15)) // Faint moonlight ambient
1036
+
1037
+ // Tunables
1038
+ lightSystem.enabled = true // Skip the render pass entirely when false
1039
+ lightSystem.ambientColor = rgb(0, 0, 0) // Color of unlit areas
1040
+
1041
+ // Shadows: once a frame every object draws black into a shadow map, and each light's rays stop at them
1042
+ lightSystem.shadows = false // on for shadows; off costs nothing
1043
+ lightSystem.shadowMapSize = 1024 // pixels across the shadow map, a square of world around the camera; this
1044
+ // and the other sizes fall back to the largest texture the device makes
1045
+ lightSystem.shadowMapScale = 2 // how many views the map spans, so casters just off screen still cast in;
1046
+ // raise it when lights reach further than a view past the screen
1047
+ lightSystem.shadowTextureSize = 256 // pixels across each light's own shadow texture, larger is sharper; a gap
1048
+ // narrower than about 4*radius/shadowTextureSize between casters closes
1049
+ lightSystem.shadowPassCount = 16 // stretch passes per light, fewer is cheaper and shorter shadows
1050
+ lightSystem.shadowSoftness = .5 // light bled into a caster's near side, 0 hard, 1 most; it reaches further
1051
+ // in under a big light, lower it if thin walls let light in
1052
+ lightSystem.shadowPass // read only: true inside the shadow pass, so a render() can skip its text or glow
1053
+ lightSystem.emissivePass // read only: true while emissive objects draw into the lightmap
1054
+ lightSystem.setShadowTransparent(on) // in the shadow pass the draws that follow keep their color, tinting the light
1055
+ // through them (glass, colored smoke); nothing outside it, so call it around
1056
+ // the draws and set it back
1057
+ light.castShadow = true // this light's rays stop at casters; a light inside a caster is blocked
1058
+ light.shadowCore = 0 // radius around the light where casters are left out, so its lamp, torch
1059
+ // or the player carrying it does not block it; reach past its corners
1060
+ obj.emissive = 0 // 1 shows it at full brightness in its own colors, lit or not, between partly;
1061
+ // exact for solid pixels, a half alpha one shows at a quarter
1062
+ obj.renderEmissive() // draws its glowing shape into the lightmap, render() by default; override to
1063
+ // glow only a part, like a robot's eyes
1064
+ obj.castShadow = true // draws into the shadow map; false for a floor TileLayer, a background, a pickup
1065
+ layer.shadowSolidOnly = true // a TileCollisionLayer casts only from its cells with collision, as they are
1066
+ // drawn, so a floor in the same layer stays lit; false casts every tile
1067
+ obj.renderShadow() // draws the shadow shape, render() by default, screen space draws skipped;
1068
+ // a figure draws a blob at its feet
1069
+ // to stay lit; additive draws add black so glows cast nothing; WebGL draws only
1070
+
1071
+ // Lights are EngineObjects — auto-register, destroy() to remove
1072
+ new Light(pos, radius, color=WHITE, fadeRange=radius)
1073
+ // pos Vector2 World space position
1074
+ // radius number Total extent in world units
1075
+ // color Color Light color; alpha modulates intensity
1076
+ // fadeRange number Width of the soft edge (0 = hard disc, radius = fully soft blob)
1077
+ light.glow = 0 // size across of a soft hazy glow over the light, like a lamp at night, 0 for none; added
1078
+ // over the lit scene after the lightmap, so it shows in the dark, in front of everything
1079
+ light.glowFalloff = 1 // how fast the glow fades from its middle, .5 a wide haze, 2 a tight bright core
1080
+
1081
+ // Per-object lightmap contribution hook (on every EngineObject)
1082
+ class LavaTile extends EngineObject {
1083
+ renderLight() {
1084
+ // Called during the lightmap pass with additive blending active.
1085
+ // drawRect / drawTile / drawCircle all land in the lightmap.
1086
+ drawRect(this.pos, vec2(3), rgb(1, 0.4, 0));
1087
+ }
1088
+ }
1089
+ ```
1090
+
1091
+ ## LittleJS Post Processing
1092
+ - Optional plugin that applies a full screen WebGL shader to the rendered output
1093
+ - Shadertoy style uniforms: iTime, iResolution, iChannel0 (the frame just drawn), and iChannel1 (the previous
1094
+ frame's output) when feedbackTexture is set, for trails and echoes
1095
+ - With render3D.depthTexture on, iChannel2 is the 3D depth, and sceneDepth(uv) gives the distance from the camera
1096
+ along its view in world units, uv 0 to 1 across the screen, for ambient occlusion, fog and outlines; only the solid
1097
+ objects of the default 3D layer are in it
1098
+ - See `examples/shorts/postProcess.js` for your own shader code, and `examples/shorts/postEffects.js` for the built in
1099
+ effects
1100
+
1101
+ ```javascript
1102
+ new PostProcessPlugin(shaderCode, includeMainCanvas=false, feedbackTexture=false) // call in gameInit; with no
1103
+ // shaderCode the image passes through unchanged
1104
+ postProcess // Global instance created by the plugin
1105
+ postProcessBloom(threshold=.6, strength=1, size=6, includeMainCanvas=false) // set up a ready made bloom effect, so
1106
+ // bright colors and lights glow; threshold is where the glow starts, and the 2D canvas
1107
+ // is left out so HUD text stays crisp; a wider glow takes more samples to stay smooth,
1108
+ // and past a size of 32 it would take hundreds, so that is as wide as it goes
1109
+ postProcessBloomShader(threshold, strength, size) // its shader code, to pass to PostProcessPlugin or build on
1110
+ postProcess.setShaderCode(shaderCode) // shade with new code from the next frame, to switch effects while running
1111
+
1112
+ // Built in effects: each is a piece of shader code with its settings written in, working on c, the pixel's color,
1113
+ // and uv, where it is on the screen from 0 to 1; postProcessEffects joins pieces in order into one shader, and your
1114
+ // own GLSL is a piece too; put the ones that bend or sample the picture (curve, chromatic, glow) first
1115
+ new PostProcessPlugin(postProcessEffects(postProcessScanlines(.5), postProcessVignette(), 'c.rgb *= vec3(1, .9, .8);'))
1116
+ postProcessEffects(...effects) // one shader from the pieces, none passes the frame through
1117
+ postProcessTV({noise, scanlines, scanlineSpacing, glow, vignette, curve}) // an old TV in one piece, curve 0 by
1118
+ // default for flat; any setting at 0 leaves that part out
1119
+ postProcessScanlines(strength=.5, spacing=6) // lines across the screen, spacing in pixels
1120
+ postProcessNoise(strength=.1, size=2) // static that changes every frame, size of a speck in pixels
1121
+ postProcessVignette(strength=1, falloff=3) // darker toward the corners, a higher falloff only the corners
1122
+ postProcessCurve(strength=.1) // the bulged glass of an old TV, black past the corners
1123
+ postProcessChromatic(strength=.005) // red and blue split apart toward the edges
1124
+ postProcessGlow(threshold=.6, strength=1, size=6) // the bloom as a piece
1125
+ postProcessOutline(color=BLACK, thickness=1, threshold=.02) // lines where the 3D depth jumps, needs
1126
+ // render3D.depthTexture
1127
+ ```
1128
+
1129
+ ## LittleJS 3D Math
1130
+ - Optional plugin with `Vector3` and `Matrix4` for 3D games and plugins
1131
+ - Right handed, Y up, camera looks down -Z, angles in radians
1132
+ - Used by the Render3D plugin, but has no rendering dependencies
1133
+
1134
+ ```javascript
1135
+ // Vectors
1136
+ vec3(x, y, z) // vec3() is zero, vec3(s) fills all, vec3(x, y) has z=0
1137
+ isVector3(v) // true if v is a Vector3
1138
+ randVector3(length=1, coneAngle=PI) // random direction, uniform over the sphere or a cone around +Y
1139
+ randInSphere(radius=1, minRadius=0) // random point inside a sphere, even through its volume, like randInCircle
1140
+ v.add(v2) v.subtract(v2) v.multiply(v2) v.divide(v2) v.scale(s)
1141
+ v.dot(v2) v.cross(v2) v.length() v.lengthSquared() v.distance(v2) v.distanceSquared(v2)
1142
+ v.normalize(length=1) v.clampLength(length=1) v.lerp(v2, percent) // percent is clamped like lerp
1143
+ v.rotate(axis, angle) // turned around a unit axis, counter clockwise when the axis points at you
1144
+ v.rotateX(angle) v.rotateY(angle) v.rotateZ(angle) // turned around one axis, the way rotation3D turns things
1145
+ v.reflect(normal, restitution=1) // bounce off a surface, 0 slides along it
1146
+ v.abs() v.floor() v.round() v.snap(grid) v.copy() v.set(x, y, z) v.setFrom(v2) v.isValid() v.toString(digits=3)
1147
+
1148
+ // Matrices - m.m is a column major Float32Array(16), the order WebGL wants, so it goes to a shader as is
1149
+ // euler is vec3(pitch, yaw, roll) in radians: applied to points as roll (Z), then pitch (X), then yaw (Y)
1150
+ // +pitch looks up, +yaw turns left for something facing -Z, +roll rolls counter clockwise around Z
1151
+ Matrix4.translation(v) Matrix4.rotation(euler, out) Matrix4.scaling(v) // new Matrix4 or Matrix4.identity() is the identity
1152
+ Matrix4.perspective(fov, aspect, near, far) // fov is vertical, radians
1153
+ Matrix4.orthographic(left, right, bottom, top, near, far)
1154
+ Matrix4.lookAt(eye, target, up) // transform of an object at eye facing target, invert it for the view
1155
+ // matrix of a camera there
1156
+ m.multiply(m2) // m = m * m2, m2 is applied to points first
1157
+ m.translate(v) m.rotate(euler) m.scale(v) // append a transform, returns self
1158
+ m.invert() m.transpose() // in place, return self
1159
+ m.copy() m.transformPoint(v) m.transformDirection(v) m.getTranslation() // or v.transform(m), v.transformDirection(m)
1160
+ m.getScale() m.getRotation() // the scale and the vec3(pitch, yaw, roll) back out of a transform, a mirror is a negative x
1161
+ m.determinant() // of the rotation and scale part: negative when it mirrors, 0 when it flattens
1162
+ buildMatrix(pos, rotation, scale, out) // translate * rotate * scale, any arg optional; out is written into
1163
+ // instead of a new matrix, for a loop that builds many each frame
1164
+
1165
+ // Collision - boxes are centered with full size, upright unless given a rotation, an Euler vec3 like rotation3D;
1166
+ // spheres and cylinders take a radius, cylinders stand on Y
1167
+ isPointInBox3D(point, pos, size, rotation) // true if point is in the box, boundary inclusive
1168
+ isOverlapping3D(posA, sizeA, posB, sizeB, rotationA, rotationB) // box vs box, touching edges do not overlap; no sizeB
1169
+ // tests a point
1170
+ collideSphereSphere(posA, radiusA, posB, radiusB) // push A out of B, or undefined
1171
+ collideSphereBox(pos, radius, boxPos, boxSize, boxRotation) // push a sphere out of a box, or undefined
1172
+ collideSphereInBox(pos, radius, boxPos, boxSize) // push a sphere back inside a box, for rooms and arenas
1173
+ collideSphereCylinder(pos, radius, cylinderPos, cylinderRadius, cylinderHeight) // push a sphere out of a cylinder,
1174
+ // or undefined
1175
+ collideBoxBox3D(posA, sizeA, posB, sizeB, rotationA, rotationB) // push A out of B the shortest way, or undefined;
1176
+ // the 3D twin of collideBoxBox, turned boxes by the separating axis test
1177
+ // raycasts return the distance t where the hit is ray.getPosition(t), so scale direction and t scales too
1178
+ new Ray3D(origin, direction) // a start and a direction, what screenToRay returns
1179
+ ray.getPosition(distance) // the point a distance along it, distance is what the raycasts return
1180
+ raycastSphere(ray, pos, radius) // distance t to the sphere, or undefined; a ray that starts
1181
+ // inside a sphere or a box is already there and gets back 0
1182
+ raycastPlane(ray, planePos, planeNormal) // distance t to the plane, or undefined
1183
+ raycastBox(ray, pos, size, rotation) // distance t to the box, or undefined
1184
+ ```
1185
+
1186
+ ## LittleJS 3D Rendering
1187
+ - Optional plugin in the dist bundle like the others, draws meshes, billboards (flat pictures that turn to face the
1188
+ camera), lines and particles into the engine's WebGL canvas, under the 2D layer by default
1189
+ - Requires the 3D Math plugin. One shader: directional, ambient and point lights, specular, fog, shadows, textures,
1190
+ vertex colors; any object or draw can bring its own Shader on top of it, see render3D.shader
1191
+ - EngineObject3D extends EngineObject, so update, destroy, timers, children and renderOrder all work; the 2D pos is
1192
+ ignored by rendering
1193
+ - The 3D pass is the part of the frame that draws 3D, and it draws in two rounds: the opaque stage for solid things
1194
+ first, then the transparent stage for see-through things; every object and callback runs with the draw state set from
1195
+ the object's flags
1196
+ - 3D draws only work inside the 3D pass, from an object's `render3D()` or from `onRenderOpaque` / `onRenderTransparent`;
1197
+ drawing from `gameRender` asserts, and anything that fades (soft discs, soft shadows) must be in the transparent stage
1198
+ - Builders and draws take full sizes (diameters) like buildBox and drawCircle; collision helpers and lights take radii
1199
+ - Textures sample through mipmaps in 3D so floors do not shimmer in the distance, crisp up close when tilesPixelated
1200
+ is on; render3D.mipmaps = false keeps each texture's own filtering, and 2D sprites always do
1201
+ - The 3D scene is on the engine's WebGL canvas, so the post processing plugin shades it too: call postProcessBloom()
1202
+ after new Render3DPlugin to make lights glow, see the render3dGlow demo
1203
+ - Opaque draws drop texels under half alpha, so cut out art like a fence or a leafy tree works and its shadow matches;
1204
+ see through draws blend instead, set obj.transparent for a sprite that fades
1205
+ - A texture comes before its tint like drawTile, except where per vertex colors are part of the geometry (drawStrip,
1206
+ drawRibbon)
1207
+ - Y is up and -Z is forward, so the ground is the XZ plane: 2D input maps to it as vec3(move.x, 0, -move.y), forward for
1208
+ a yaw is vec3(-sin(yaw), 0, -cos(yaw)) and right is vec3(cos(yaw), 0, -sin(yaw))
1209
+ - Below, `name = value` shows a value to set; when that is not the default, the comment says what the default is,
1210
+ or `e.g.` marks it as an example
1211
+ - See the `examples/shorts/render3d*.js` demos - features: render3dBasics to start, render3dShapes,
1212
+ render3dBillboards, render3dHeightMap terrain, render3dCollision with picking, render3dFirstPerson for a walking
1213
+ camera, render3dLights, render3dParticles, render3dTrails, render3dDraw for immediate drawing, render3dText,
1214
+ render3dMesh for OBJ loading, render3dLayers for 3D layers in a 2D scene, render3dInstancing, render3dTextures,
1215
+ render3dGlow, render3dShaders; games: render3dDodgeGame, render3dRacingGame, render3dPuzzleGame; and `examples/3d`
1216
+ is a full example with all of it in one scene
1217
+
1218
+ ```javascript
1219
+ // Setup (call in gameInit)
1220
+ new Render3DPlugin; // creates global render3D, renders automatically before gameRender
1221
+
1222
+ // A first 3D scene
1223
+ function gameInit()
1224
+ {
1225
+ new Render3DPlugin; // draws itself every frame, before gameRender
1226
+ render3D.setSky(); // dome colors, also sets the fog color
1227
+ new EngineObject3D(vec3(), buildGrid(vec2(20), 20)); // the ground is the XZ plane, Y is up
1228
+ new EngineObject3D(vec3(0, 1, 0), buildBox(), undefined, RED);
1229
+ render3D.camera.orbit(vec3(0, 1, 0), 12, 0, .4); // or set camera.pos and camera.lookAt(target)
1230
+ }
1231
+
1232
+ // Camera
1233
+ render3D.camera.pos = vec3(0, 5, 10) // Camera3D: pos (0, 0, 10), rotation (pitch, yaw, roll), fov PI/3, near .1,
1234
+ // far 1000, which can be Infinity for a perspective view
1235
+ render3D.camera.orthographic = 20 // visible height in world units with no perspective, so distance does not
1236
+ // shrink things; 0 is the normal perspective view; near and far still clip,
1237
+ // and far must be a real distance here, Infinity is perspective only
1238
+ render3D.camera.lookAt(target) // set the rotation to face a target now, clears roll
1239
+ render3D.camera.orbit(target, distance, yaw, pitch=.5) // put the camera on an orbit looking at the target
1240
+ new CameraControl3D(target, distance, pitch=.4, idleSpin=0) // an object that orbits the camera around its pos3D:
1241
+ // drag to turn, wheel to zoom; fields for dragButton, dragSpeed, zoomSpeed, zoomRange,
1242
+ // pitchRange, yaw and idleSpin; destroy it to hand the camera back
1243
+ new FirstPersonCamera3D(pos3D, yaw, pitch) // mouse look and WASD or arrows to move, the camera at its pos3D;
1244
+ // click captures the mouse, Esc lets it go; each defaults to where the camera is now,
1245
+ // so it takes over without a jump; fields for moveSpeed, lookSpeed, pitchRange,
1246
+ // lockPointer, and fly to move the way it looks instead of walking level; give it a
1247
+ // size3D and setCollision() to walk into solids, destroy it to hand the camera back
1248
+ camera.jumpSpeed = 0 // above 0, Space or gamepad button 0 jumps while it stands on something: a height map,
1249
+ // a voxel map or a solid, see groundObject
1250
+ camera.eyeHeight = 0 // how far above its position the eye is, toward the top of its size3D body
1251
+ render3D.camera.follow(target, offset, percent=1) // chase camera: ease toward target + offset and look at it, percent
1252
+ // is how far it moves each call, so call it every frame, from
1253
+ // gameUpdatePost once the target has moved
1254
+ render3D.camera.align2D = true // lock to the 2D camera so the z=0 plane matches world space, false by default
1255
+ render3D.camera.getForward() .getRight() .getUp() // the camera's axes as it is right now; render3D.cameraRight
1256
+ // .cameraUp .cameraForward are this frame's, read only
1257
+ render3D.viewMatrix .projectionMatrix .viewProjection .shadowMatrix // this frame's, rebuilt by updateMatrices()
1258
+ render3D.camera.getMatrix() .getViewMatrix() .getProjectionMatrix(aspect) // built from the camera as it is now
1259
+ render3D.worldToScreen(pos, canvasSize) // Vector3 -> screen pixels, undefined when behind the camera; the opposite
1260
+ // of screenToRay and it takes the same canvas, so the two agree
1261
+ render3D.worldToClip(pos) // Vector3 -> -1 to 1 across and up the screen, z is depth; undefined when behind
1262
+ // the camera
1263
+ render3D.screenToRay(screenPos, canvasSize) // Ray3D under a screen point, always returns one; canvasSize
1264
+ // defaults to the main canvas, and both of these bring the matrices up to
1265
+ // date for it, so worldToScreen keeps agreeing with them
1266
+ render3D.screenToGround(screenPos, groundHeight=0, canvasSize) // where that ray meets a flat ground plane, or
1267
+ // undefined; terrain has HeightMap.raycast
1268
+ render3D.pick(screenPos or ray, objects) // {object, distance} of the nearest object hit, on its mesh's
1269
+ // triangles, the faces that show, both sides when doubleSided,
1270
+ // or a sprite's quad; a screen position goes through screenToRay
1271
+ render3D.soundDefaultRange = 100 // how far a sound with no range of its own is heard in 3D, the 2D
1272
+ // soundDefaultRange is 40; a Sound made with a range keeps it
1273
+ render3D.playSound(sound, pos3D, volume, pitch, randomnessScale, loop, paused) // like sound.play(pos): quieter with
1274
+ // distance from the camera, panned by side
1275
+ render3D.playSoundLoop(sound, pos3D, volume, pitch, randomnessScale) // the same on a loop; its volume and pan
1276
+ // are set when it starts
1277
+ render3D.isSphereVisible(center, radius) // the same is-it-on-screen test drawMesh uses, for skipping your own draws
1278
+
1279
+ // Lights and fog, read at each draw
1280
+ render3D.sunDirection = vec3(-.3, 1, .5) // toward the sun, where its light comes from, like a directional Light3D;
1281
+ // any length, it is normalized for you; the sun casts the shadows, or
1282
+ // a spotlight set as render3D.shadowLight
1283
+ render3D.sunColor = hsl(.08, 1, .95) // e.g., WHITE by default
1284
+ render3D.ambientColor = hsl(0, 0, .3) // from above once ambientGroundColor is set
1285
+ render3D.ambientGroundColor = undefined // set, ambient blends from it on faces pointing down to ambientColor on
1286
+ // faces pointing up, the way a sky and a ground light a scene; setSky sets both
1287
+ render3D.fogColor = undefined // uses canvasClearColor when undefined
1288
+ render3D.fogStart = 20; render3D.fogEnd = 100 // e.g., both 0 by default which is no fog; measured by camera distance,
1289
+ // fogEnd 0 disables fog; additive draws fade out with distance instead
1290
+ // of taking the fog color, since adding it would brighten them
1291
+ render3D.gravity = vec3(0, -.01, 0) // e.g., vec3() by default so nothing falls; objects with a mass fall by this each
1292
+ // frame, times their gravityScale, and slow by their damping, which is 1 by
1293
+ // default for no slowing
1294
+ new Light3D(pos3D, radius, color, intensity=1) // point light, an EngineObject3D; it drops off fast, so a small
1295
+ // radius wants an intensity above 1; an alpha, intensity or radius of 0 is off
1296
+ light.intensity = 2 // brightness, multiplies the color, above 1 is brighter than white
1297
+ light.coneAngle = .5 // a spotlight: the angle in radians from its forward out to the edge of its
1298
+ // cone, 0 by default for a light that shines every way; it shines along its
1299
+ // own forward, so rotation3D or what it is attached to aims it
1300
+ light.coneSoftness = .2 // how much of the cone is its fading edge: 0 a hard edge, 1 fading from the middle
1301
+ light.glow = 1 // a soft hazy glow over the light this big, like a lamp at night; 0 by default,
1302
+ // added onto what is behind it, hidden by what is in front
1303
+ light.glowFalloff = 1 // how fast the glow fades from its middle, .5 a wide haze, 2 a tight bright core
1304
+ new DirectionalLight3D(pos3D, color, intensity=1) // a Light3D that shines from far away with no falloff, from its
1305
+ // position toward the origin like a three.js DirectionalLight; moving it or
1306
+ // its parent swings the light, so parent it to a sun mesh and it follows
1307
+ // 8 lights reach the shader each frame: every directional light first, then the point lights nearest the camera;
1308
+ // a light switched off by its alpha, intensity or radius is left out so it cannot take a slot from one that is on;
1309
+ // they cast no shadows, the sun does, or one spotlight set as render3D.shadowLight; each makes its own highlight
1310
+ // when specular is set
1311
+
1312
+ // Shadows - one shadow map, from the sun or from render3D.shadowLight; lit opaque objects and draws on the default side of the 2D scene
1313
+ // cast and receive
1314
+ render3D.depthTexture = true // off by default and free when off; draws the solid objects' depth from the camera
1315
+ // for PostProcessPlugin, which reads it with sceneDepth(uv); one more draw, depth only
1316
+ render3D.shadows = true // off by default and free when off, soft shadows (drawSoftShadow) still work alongside
1317
+ render3D.shadowMapSize = 1024 // pixels across the shadow map, rebuilt when it changes; a size the device
1318
+ // does not have falls back to the largest it has, glMaxTextureSize
1319
+ glClampTextureSize(size) // that fallback, for a texture a game makes by size itself
1320
+ render3D.shadowLight = flashlight // a spotlight, a Light3D with a coneAngle, casts the shadows in place of the
1321
+ // sun, down its cone as far as its radius; the sun still lights the scene;
1322
+ // undefined for the sun; one caster at a time; while it casts them its
1323
+ // cone is at most 1.35, what its shadow map covers
1324
+ render3D.shadowRange = 40 // world size the map covers around shadowCenter, smaller is sharper
1325
+ // it is a square facing the light, so ~1.5x an area's width covers it
1326
+ render3D.shadowCenter = undefined // Vector3 center of the shadowed area, read each frame; undefined follows the camera
1327
+ render3D.shadowBias = .003 // raise if lit surfaces get speckled with their own shadow, lower if shadows float away
1328
+ // from their casters
1329
+ render3D.shadowSoftness = 1 // how far to blur the shadow edge, in shadow map pixels
1330
+
1331
+ // Sky
1332
+ render3D.setSky(topColor, horizonColor, bottomColor, ambient=.5) // dome colors straight up, level and straight
1333
+ // down; sets render3D.sky, fogColor to the horizon, and the
1334
+ // ambient light to the top color from above and the bottom
1335
+ // color from below, both times ambient; 0 leaves ambient dark
1336
+ render3D.setFog(fogStart, fogEnd, fogColor) // the fog distances and color at once, no color keeps the current one
1337
+ render3D.sky = buildSky(topColor, horizonColor, bottomColor, sides, rings) // or set a dome yourself
1338
+
1339
+ // Draw state, read at each draw; the pass sets it from each object's flags before render3D() and resets it before each
1340
+ // callback, so set it inside those, or use the object flags below
1341
+ render3D.lighting = true // false draws plain vertex color times texture, as billboards and lines do
1342
+ render3D.emissive = 0 // how much a surface lights itself, set from each object's emissive
1343
+ render3D.additive = false // additive blending in the transparent stage
1344
+ render3D.specular = 0 // Phong highlight strength, the shiny spot where the sun and each
1345
+ // Light3D reflect: 1 adds a light's full color at its peak, more burns
1346
+ // out; shininess sets the size of the spot
1347
+ render3D.shininess = 16 // the highlight's exponent, 4 broad like rubber, 100 sharp like polished metal
1348
+ render3D.normalMap render3D.normalScale render3D.reflectivity render3D.emissiveMap render3D.emissiveMapColor
1349
+ // the material, set from each object's fields of the same names
1350
+ render3D.receiveShadow = true // false keeps the next draws out of the shadow map's darkening
1351
+ render3D.shader = undefined // a Shader for the next draws, set from each object's shader; with emissive 1
1352
+ // the snippet's color is final, so it can light itself from these 3D names:
1353
+ // worldPos, worldNormal, cameraPos, sunDirection (toward the sun), sunColor,
1354
+ // ambientColor, ambientGroundColor, lightCount, lights[i] (xyz position, or
1355
+ // direction toward a directional one, w radius, negative when directional),
1356
+ // lightColors[i] (rgb, a strength) and shadow(), the sun shadow 0 to 1 here
1357
+ render3D.cullBackFaces render3D.mirrored // set from each mesh as it draws: its doubleSided, and whether its
1358
+ // transform mirrors it; strips leave both off
1359
+ render3D.depthTest = true; render3D.depthWrite = true // the transparent stage turns depth writes off, so see-through
1360
+ // draws never hide each other
1361
+
1362
+ // The pass
1363
+ render3D.onRenderOpaque = ()=> {} // after the opaque objects: world geometry drawn outside of objects, also called for
1364
+ // the shadow map
1365
+ render3D.onRenderTransparent = ()=> {} // after the transparent objects: billboards, glows and soft shadows drawn
1366
+ // outside of objects
1367
+ render3D.queueTransparent(pos, draw) // sort your own transparent draw in with the rest, draws now when sorting is off
1368
+ render3D.isRendering render3D.shadowPass // read only: inside the 3D pass, and inside the shadow map part of it
1369
+ render3D.sortTransparent = true // transparent draws sort far to near by depth along the view, right for an
1370
+ // orthographic camera too, so alpha and additive mix; false keeps object order
1371
+ render3D.frustumCulling = true // drawMesh skips meshes whose bounding sphere is outside the frustum, the wedge of
1372
+ // space the camera can see
1373
+ render3D.mipmaps = true // textures sample through mipmaps so they do not shimmer far away, false keeps each
1374
+ // texture's own filtering like 2D; magnification follows tilesPixelated either way
1375
+ obj.pixelated = true // false by default; keep one object's texture pixels hard edged, no mipmaps or blending
1376
+ // between them, for pixel art that should not blur or bleed into its neighbors
1377
+ render3D.anisotropy = 4 // sharper textures seen at an angle, 1 to 16, 1 is off; needs mipmaps
1378
+ render3D.instancing = true // every use of a mesh in the opaque stage is one draw call however many there are,
1379
+ // mesh.instanced = false keeps one mesh drawing in object order instead
1380
+ new InstancedMesh3D(mesh, count, tileInfo, color) // many copies of a mesh as one draw with their transforms kept on the
1381
+ // GPU: an instance costs nothing per frame until it changes, for big sets that
1382
+ // mostly stay put; an EngineObject3D whose flags cover the whole set
1383
+ set.setTransformAt(i, pos, rotation, scale) // place an instance, in world space, the object's own transform does not
1384
+ // move them; buildMatrix's arguments written straight in, the fastest way
1385
+ set.setTransforms(start, count, x, y, z, rx, ry, rz, sx, sy, sz) // place a run from arrays indexed by instance,
1386
+ // plain or typed, in one loop, the fastest way to move many every frame;
1387
+ // rotations and scales are optional, all three or none
1388
+ set.setMatrixAt(i, matrix) // place one by a matrix; getMatrixAt(i) reads one back
1389
+ set.setColorAt(i, color) // color one, they start in the object's color; only changed colors upload
1390
+ set.matrixData / set.colorData // 16 and 8 floats an instance, edit them and call markDirty(i) / markColorDirty(i),
1391
+ // or markDirtyRange(start, end) for a run of matrices
1392
+ set.radius = 50 // fix the culling sphere, which otherwise grows to hold every instance set
1393
+ set.count = 500 // draw the first 500 of the count it was made with
1394
+ render3D.renderAfter2D = false // true draws the 3D scene on top of the 2D scene instead of under it; objects that do
1395
+ // not set their own renderAfter2D follow this
1396
+ render3D.smoothShading = true // default for every builder's smooth argument, false (flat) by default;
1397
+ // meshes already built keep the normals they have
1398
+
1399
+ // Objects - EngineObject with a 3D transform, drawn by the 3D pass
1400
+ new EngineObject3D(pos3D, mesh, tileInfo, color) // a tileInfo with no mesh draws a sprite billboard of size3D in the
1401
+ // transparent stage, already transparent without setting the flag;
1402
+ // size3D.z does not change how a sprite draws, only how it collides;
1403
+ // a whole TextureInfo is kept as the
1404
+ // tile covering it, so obj.tileInfo is always a TileInfo as it is in 2D
1405
+ obj.pos3D obj.rotation3D obj.scale3D // Vector3, rotation is (pitch, yaw, roll); change them in place or assign new ones
1406
+ obj.velocity3D obj.angleVelocity3D // added to pos3D and rotation3D by the engine before update, like the 2D physics,
1407
+ // no super.update() needed; damping and angleDamping slow them as in 2D, and
1408
+ // clampSpeed keeps each axis of velocity3D within objectMaxSpeed while it collides
1409
+ obj.updatePhysics() // moves it and pushes it out of solids; bounce off anything else in update, which
1410
+ // runs once every object has moved, so the fix lands before the frame draws
1411
+ obj.mass = 1 // objects start with no mass and are not pulled; with a mass render3D.gravity and gravityScale act on
1412
+ // velocity3D; damping slows every object, damped first and gravity added after as in 2D, and damping is
1413
+ // 1 by default for no slowing
1414
+ obj.size3D // full size for engineObjectsCollect3D, solid collision and
1415
+ // sprites, which it also picks by, a mesh is picked by its own box;
1416
+ // starts at the size of the mesh's box, 1 with no mesh; the box is
1417
+ // centered on pos3D, so center() a mesh whose origin is not its middle;
1418
+ // scale3D and a parent's scale grow it
1419
+ obj.setCollision(solids, isSolid, level) // the same flags as in 2D, but the collision happens in 3D against size3D;
1420
+ // isSolid needs solids, an object cannot block without colliding;
1421
+ // a 3D object has no 2D size, so it is never an obstacle in a 2D scene;
1422
+ // both objects of a pair need solids, and a pair where neither one blocks
1423
+ // passes through, so movers hit the level without shoving each other;
1424
+ // heavier objects move less and each bounces by its own restitution; mass 0
1425
+ // stays put and keeps its velocity, a wall the other bounces off;
1426
+ // collideLevel is on by default: voxel maps and height maps, which an
1427
+ // object with mass is kept out of, or the tile layers for a sync2D object;
1428
+ // the raycast half is 2D only and defaults off here;
1429
+ // a sync2D object collides in 2D instead, against the 2D size, so set that
1430
+ // as well as size3D; a child rides with its parent so it sits solid collision
1431
+ // out, the same rule as in 2D; the solid box turns with rotation3D, so a
1432
+ // turned wall or a ramp collides as it looks, but against height maps and
1433
+ // voxel maps an object is still its upright box
1434
+ obj.collideAsSphere3D = true // collide as the sphere that fits size3D instead of the box, false by default
1435
+ obj.collideWithObject(object, push) // called when it touches a solid object, both objects are asked and either
1436
+ // returning false leaves the push and the bounce to you; push is the
1437
+ // shortest way to move this one clear, along the surface's normal, so it
1438
+ // says which way what it touched faces; when it stands there the engine
1439
+ // lifts it straight up instead; push is undefined in 2D
1440
+ obj.collideWithVoxel(type, cell) // asked by a VoxelMap whether a block stops it, true by default; return false
1441
+ // to pass through, as for water
1442
+ obj.groundObject // what it stands on this frame, a height map, a voxel map or a solid,
1443
+ // undefined in the air; sliding on it slows by friction, the less grippy of
1444
+ // the two, relative to it, so a moving platform carries what rides it
1445
+ obj.groundAngle = PI/4 // the steepest slope it stands on, in radians under PI/2: resting on a
1446
+ // box's face within this of flat holds it still, steeper it slides down, and
1447
+ // a sphere, an edge or a corner is nothing to stand on; a player climbs more
1448
+ obj.softShadow = 2 // 0 by default; a soft shadow of that diameter under the object on
1449
+ // render3D.softShadowHeight; scale3D and a parent's scale grow it, so set it
1450
+ // for the unscaled object
1451
+ obj.upright = true // a sprite stands on world up instead of tilting toward the camera, false by
1452
+ // default
1453
+ // a sprite also turns with rotation3D.z, like a 2D object turns with angle
1454
+ obj.sync2D = true // false by default; copy the 2D pos and angle into pos3D and rotation3D each frame; the 2D physics only run for a
1455
+ // sync2D object, so set its mass to have them move it
1456
+ // these inherited EngineObject fields are 2D only and do nothing on a 3D object: angle, angleVelocity,
1457
+ // additiveColor, drawSize, mirror; sync2D is the one way in
1458
+ obj.mesh obj.tileInfo obj.color // what to draw and how
1459
+ obj.setMesh(mesh) // draw a different mesh and free the GPU buffer of the one it replaces, for
1460
+ // text and terrain built again as things change; a mesh another object is
1461
+ // still drawing is left alone, so shared builders are safe
1462
+ obj.transparent = true // draw in the transparent stage, blended, sorted far to near, no depth writes;
1463
+ // false by default, true for a sprite
1464
+ obj.additive = true // additive blending, implies the transparent stage, false by default
1465
+ obj.emissive = 1 // 0 by default; how much it lights itself: 0 lit, 1 its own color for lamps and glowing
1466
+ // things, between partly self lit, above 1 brighter for bloom; still casts
1467
+ obj.specular = .5 // highlight strength, 0 is none and 1 is full, as render3D.specular; 0 by default
1468
+ obj.shininess = 100 // 16 by default; the highlight's exponent, higher is smaller and sharper
1469
+ obj.normalMap = textureInfo // bumps and grooves that catch the light, read at the color texture's uvs;
1470
+ // green points up the image (OpenGL and glTF), flip the green of a DirectX one;
1471
+ // no tangents needed, any mesh with uvs works; normalMapFromHeight makes one
1472
+ obj.normalScale = 1 // how strongly it bends the surface, 0 turns it off
1473
+ obj.reflectivity = .5 // 0 by default, 1 a mirror of the sky; more at a glancing angle (Fresnel);
1474
+ // shows the colors of render3D.sky from setSky or buildSky, or the ambient
1475
+ // ones with no sky; not the scene
1476
+ obj.emissiveMap = textureInfo // where it glows, added on top of the lit surface so it shows in the dark
1477
+ obj.emissiveMapColor = WHITE // multiplies the emissive map
1478
+ obj.castShadow = false // true by default, false keeps it out of the shadow map; sprites and cut out
1479
+ // textures cast their outline, an object faded below half its alpha casts
1480
+ // nothing, a see through one casts only when textured, additive never casts
1481
+ obj.receiveShadow = false // true by default, false draws it without the shadow map's darkening
1482
+ obj.renderOrder // sorts the opaque stage; instanced meshes draw as batches, so set
1483
+ // mesh.instanced = false on a mesh whose order matters
1484
+ obj.renderAfter2D = true // this object on top of the 2D scene, or false for under it; undefined follows
1485
+ // render3D.renderAfter2D
1486
+ // the layer under the 2D scene and the layer over it are drawn separately with their own depth, so neither hides the
1487
+ // other; the sky, callbacks and debug primitives draw with the default side
1488
+ obj.getMatrix() // buildMatrix(pos3D, rotation3D, scale3D), or localMatrix when set, composed with an EngineObject3D
1489
+ // parent's
1490
+ obj.localMatrix // a Matrix4 from the parent used in place of pos3D, rotation3D and scale3D, for a pose they cannot
1491
+ // hold; glTF animations pose parts with it and it stays after they stop, set it to undefined to move a
1492
+ // part by pos3D again; undefined by default
1493
+ obj.getWorldPos3D() // world position, pos3D is local when parented
1494
+ obj.getForward3D() .getRight3D() .getUp3D() // the object's axes in the world, forward is -Z
1495
+ engineObjectsCollect3D(pos, size, objects, testCenters) // the EngineObject3D objects whose boxes overlap a sphere,
1496
+ // size a number (diameter, 0 for a point), or a box, size a vec3;
1497
+ // testCenters as in 2D
1498
+ engineObjectsCallback3D(pos, size, callback, objects, testCenters)
1499
+ engineObjectsRaycast3D(ray, objects) // every object along the ray, nearest first, like engineObjectsRaycast in
1500
+ // 2D; the ray has no end, use render3D.pick for just the nearest one
1501
+ obj.lookAt(target) // turn -Z toward a world space point: sets pitch and yaw, clears roll;
1502
+ // a child aims through its parent, since its rotation3D is local
1503
+ obj.render3D() // override for custom drawing, the draw state is already set from the flags; render() is empty
1504
+ // children attached with addChild follow an EngineObject3D parent's 3D transform, pos3D is then local; addChild's 2D
1505
+ // offset arguments do nothing in 3D; attach keeps a child where it is and works its pos3D, rotation3D and scale3D out,
1506
+ // and removeChild leaves it where it was in the world, both as close as those three values can get under a shear,
1507
+ // and exactly for a child with a localMatrix
1508
+
1509
+ // Draw right now, no object needed - only inside render3D() or a pass callback, drawing elsewhere asserts; strips batch
1510
+ // into one draw per texture and state
1511
+ render3D.drawBox(pos, size, color, rotation) // size is a vec3 or a number, untextured
1512
+ render3D.drawSphere(pos, size, color) // size is the diameter, untextured
1513
+ render3D.boxMesh render3D.sphereMesh // the size 1 meshes those use, for any box or sphere
1514
+ // object so they all draw in one batch; set scale3D and
1515
+ // color on the object, editing the mesh changes them all
1516
+ render3D.planeMesh render3D.planeMeshDoubleSided // a size 1 square facing +Y, seen from above only, or seen
1517
+ // and lit from both sides for signs and cards; stand it
1518
+ // up with rotation3D, size it with scale3D
1519
+ render3D.billboardMesh // a size 1 square facing +Z with the tile across it, what a
1520
+ // ParticleEmitter3D draws its particles as instances of
1521
+ render3D.drawMesh(mesh, matrix, tileInfo, color) // any mesh, batched with its other uses; tileInfo can be a TextureInfo
1522
+ // for the whole texture, uvs past 1 repeat when it wraps
1523
+ render3D.drawBillboard(pos, size, tileInfo, color, angle, upright) // camera facing quad, unlit, size is a Vector2;
1524
+ // upright stands on world up; opaque ones,
1525
+ // and sprite objects that are not transparent,
1526
+ // are batched: a sheet's sprites are one
1527
+ // instanced draw, its clear pixels cut out
1528
+ // list points counter clockwise as seen from the front, or the face points away and a culling mesh hides it
1529
+ render3D.drawQuad(a, b, c, d, tileInfo, color) // corners in loop order, a is the texture's top left
1530
+ render3D.drawTriangle(a, b, c, color)
1531
+ render3D.drawLine(posA, posB, width, color) // camera facing ribbon, unlit
1532
+ render3D.drawRibbon(points, width, tileInfo, color, side) // strip along a path, unlit, two sided; width and color one
1533
+ // or per point, texture runs along it, side faces the camera
1534
+ // unless given; a path ending where it starts joins as a loop
1535
+ // soft discs and shadows fade to transparent, so draw them from a transparent object or onRenderTransparent
1536
+ render3D.drawSoftDisc(pos, size, color, normal, sides) // fades to transparent at the rim, unlit, faces the camera
1537
+ // unless a normal is given
1538
+ render3D.drawSoftShadow(pos, size, floorHeight, color, lift) // soft blob shadow under pos, unlit; floorHeight can be
1539
+ // (x, z)=> y to follow terrain
1540
+ render3D.softShadowHeight = 0 // floor for objects with a softShadow: a height, a HeightMap,
1541
+ // or (x, z)=> y
1542
+ // strip order, counter clockwise as seen from the front: the first three points make a triangle, then every extra
1543
+ // point makes another with the two before it, so the shape is walked in pairs
1544
+ render3D.drawStrip(points, normals, uvs, colors, tileInfo) // a raw triangle strip; normals, uvs and colors are one
1545
+ // value or one per point
1546
+ render3D.drawStripUnlit(points, normals, uvs, colors, tileInfo) // same with lighting off
1547
+ render3D.flush() // draw what is pending, automatic when needed
1548
+ render3D.bake(()=> { ...draws... }) // returns the strips drawn inside as a Mesh
1549
+
1550
+ // Debug primitives - like debugRect and friends, drawn on top of the scene in debug builds; debugClear clears them too
1551
+ debugBox3D(pos, size, color, time, rotation)
1552
+ debugSphere3D(pos, size, color, time)
1553
+ debugLine3D(posA, posB, color, width, time)
1554
+ debugPoint3D(pos, color, time, size)
1555
+
1556
+ // Meshes - triangle strips, sent to the GPU on first render, drawn by matrix; dispose a mesh you stop using to free
1557
+ // its GPU buffer, or use obj.setMesh to swap the mesh of an object and free the old one in a single call
1558
+ const mesh = new Mesh
1559
+ mesh.addStrip(points, normals, uvs, colors) // one strip in strip order, counter clockwise from the front; normals, uvs
1560
+ // and colors are one value or one per point; extra strips in one mesh are
1561
+ // joined by an invisible flat triangle, so they do not look connected
1562
+ mesh.addQuad(a, b, c, d, color, uvs) // corners in loop order, counter clockwise seen from the front; color and uvs one
1563
+ // or per corner
1564
+ mesh.addTriangles(points, indices, normals, uvs, colors) // triangles over their own vertices, the form a model file
1565
+ // comes in: each vertex once, three indices per triangle counter clockwise from
1566
+ // the front; the mesh becomes indexed, a strip already in it is welded first
1567
+ mesh.toIndexed() // turn a strip mesh into the indexed form in place, each distinct vertex once
1568
+ mesh.indices // the triangles of an indexed mesh, undefined for a strip; either form draws the same
1569
+ mesh.combine(otherMesh, matrix, color) // append a transformed, tinted copy, to build one shape out of several;
1570
+ // matrix can be a vec3 when the part only needs moving into place
1571
+ mesh.mirror(axis=vec3(1,0,0)) // a new mesh: this one and its image across the plane through the
1572
+ // origin facing axis, for modeling half a shape against that plane
1573
+ mesh.spin(count, axis=vec3(0,1,0)) // a new mesh of count copies turned evenly around the axis
1574
+ mesh.union(otherMesh, matrix) // CSG, each a new closed indexed mesh from two closed ones (not
1575
+ mesh.subtract(otherMesh, matrix) // grids or ribbons), neither changed: all of both, this one with the
1576
+ mesh.intersect(otherMesh, matrix) // other cut out, or only what is in both; matrix or a vec3 places
1577
+ // the other; cut faces take its normals, uvs and colors; parts that
1578
+ // overlap must be joined with union, not combine, mirror or spin;
1579
+ // cuts add triangles, so build shapes this way at load time
1580
+ mesh.scaleUVs(scale) // repeat a wrapping texture across the mesh, a vec2 or a number
1581
+ mesh.transform(matrix) // move every vertex in place, a mirror turns the faces too
1582
+ mesh.flipNormals() // turn it inside out, for rooms and domes seen from within
1583
+ mesh.setColor(color) // every vertex color
1584
+ mesh.computeNormals(smooth=false) // derive normals from the triangles
1585
+ mesh.getBounds() // {min, max} around the vertices
1586
+ mesh.center() mesh.fit(size) // move the bounds onto the origin, scale the largest extent to size; both edit in place
1587
+ // and return the mesh
1588
+ mesh.render(matrix, tileInfo, color) // draw it now with the current draw state
1589
+ mesh.dispose() // free the GPU buffer now, the CPU data stays; optional, a mesh
1590
+ // that is garbage collected frees its buffer anyway, some time later
1591
+ mesh.points mesh.normals mesh.uvs mesh.colors // the vertex arrays, one entry per strip vertex, or per vertex of an
1592
+ // indexed mesh; building one by hand you can fill points alone, the rest fall
1593
+ // back to up, zero and white
1594
+ mesh.instanced = false // draw this mesh one call per use, in object order, instead of batching it
1595
+ mesh.doubleSided = true // draw both sides, each lit as the side seen; off, the default, skips faces pointing
1596
+ // away, faster for closed shapes; buildGrid, buildRibbon and open lathes turn it
1597
+ // on, and combine keeps it on if any part had it
1598
+ mesh.dirty = true; mesh.upload() // re-upload edited arrays on the next draw, or upload now; upload also measures
1599
+ // mesh.radius; every method that edits a mesh sets dirty itself
1600
+ mesh.dynamicDraw = true // set once for a mesh whose values change every frame, a water surface or a
1601
+ // cloth: it keeps its GPU layout, so a dirty upload only rewrites the vertices;
1602
+ // the strip must keep the same points in the same order, a new point count asserts
1603
+ mesh.vertexCount mesh.radius mesh.bounds // vertices, the bounding sphere for culling, and the box for picking
1604
+ mesh.computeRadius() // measure mesh.radius now, without uploading
1605
+ mesh.getTriangles() // {vertices, indices}: the strip as the indexed triangle list upload sends,
1606
+ // its distinct vertices and real triangles, for an exporter or a check; an
1607
+ // indexed mesh gives its own, read clockwise as the pass draws
1608
+
1609
+ // Shape builders - return a Mesh centered on the origin, sizes are full sizes, smooth defaults to the plugin setting
1610
+ buildBox(size=1, bevel=0, bevelSegments=1) // a vec3 or a number, six flat faces with uvs; bevel cuts the edges
1611
+ // and corners, 1 segment a flat chamfer, more round them, clamped
1612
+ // to half the smallest side
1613
+ buildSphere(size=1, sides=16, rings=8, smooth)
1614
+ buildCylinder(size=1, height=1, sides=16, smooth, capped=true, bevel=0, bevelSegments=1) // bevel cuts both rims
1615
+ buildCone(size=1, height=1, sides=16, smooth, capped=true, bevel=0, bevelSegments=1) // point up, bevel cuts the base rim
1616
+ buildCapsule(size=1, height=1, sides=16, rings=4, smooth) // total height including the rounded ends, at least the size
1617
+ buildTorus(size=1, tubeSize=.3, sides=16, tubeSides=8, smooth) // size is the diameter of the whole donut, outside edge
1618
+ // to outside edge; it lies flat in the XZ plane like a
1619
+ // coin on a table, so rotation3D.x = PI/2 stands it up
1620
+ buildLathe(profile, sides=16, smooth, capped=true) // spins an outline around the Y axis like a vase on a wheel; profile
1621
+ // is [[radius, y], ...] bottom to top, a closed profile is a ring;
1622
+ // an uncapped end makes it doubleSided, so its inside shows;
1623
+ // smooth, an end on the axis within 45 degrees of level is a
1624
+ // round pole like a sphere's, a steeper one a point like a cone's
1625
+ buildRibbon(points, width=1, color, closed, up) // lit quads along a path, for roads and tracks; width and color one or
1626
+ // per point; doubleSided, so it shows from below too
1627
+ buildGrid(size=vec2(1), segments=1, color, heightFunction, smooth) // XZ plane; size and segments a number or vec2,
1628
+ // height is (x, z)=> y; doubleSided, turn it
1629
+ // off for ground only seen from above
1630
+ // color is a Color or (x, z)=> Color, where x and z are positions on the mesh itself with (0, 0) at its center; it is
1631
+ // called per vertex when smooth and once per cell center when flat, so a checker needs cell sized steps; a grid with
1632
+ // no heightFunction is flat shaded unless smooth is passed, pass it for a color gradient to blend; with
1633
+ // buildGrid(vec2(30), 15) the cells are 2 units, so (x, z)=> (floor(x/2) + floor(z/2)) & 1 ? GRAY : WHITE
1634
+ buildLoft(stations) // a hull from diamond shaped cross sections, the stations: [[z, width, top, bottom, sideHeight],
1635
+ // ...] nose first at the largest z, always flat; sideHeight is 0 to 1, where the side corners sit
1636
+ // between the bottom and the top; the other order would build the hull inside out, so it asserts
1637
+ buildSky(topColor, horizonColor, bottomColor, sides, rings) // dome colored by height, set as render3D.sky
1638
+ normalMapFromHeight(size, heightFunction, strength=1) // a wrapping normal map TextureInfo made in code, no image:
1639
+ // heightFunction(x, y) is 0 to 1 at each pixel, the slope strength times steeper
1640
+ buildExtrude(pixels, size, depth) // 3D sprite: each solid pixel of a tileInfo given thickness, like a block model,
1641
+ // colors kept; or rows of pixels (Color, truthy for white, falsy for empty)
1642
+ buildText3D(text, size, depth, font) // extruded glyphs from an ImageFont, the white engine font by default so the
1643
+ // object color tints it; centered, faces +Z, a new mesh each call; newlines
1644
+ // stack downward with a gap, since extruded lines that touch overlap at an angle
1645
+
1646
+ // Height map terrain - from a 2D array [row][column] of 0-1 heights or an image's red channel; row 0 is the far edge at
1647
+ // -Z, column 0 the left edge at -X
1648
+ const terrain = new HeightMap(heights, mapSize=vec2(1), height=1, colors, pos3D=vec3(), smooth) // an EngineObject3D
1649
+ // that draws itself and that objects with collideLevel stand on; heights and colors take the array, an image
1650
+ // or a canvas; pos3D is its center, and it stays upright and unscaled
1651
+ terrain.rebuild() // make the mesh again after changing heights, colors or smooth
1652
+ terrain.buildMesh(smooth) // one vertex per sample, centered on its own origin, for other uses
1653
+ terrain.getHeight(pos3D) or (x, z) // world height of the drawn mesh there, to stand things on it; the
1654
+ // lookups are in world space, with its position taken off
1655
+ terrain.getNormal(pos3D) or (x, z) // surface normal there, to tilt things to the slope
1656
+ terrain.raycast(ray) // distance along a ray to where it crosses the ground, or undefined,
1657
+ // for clicking; exact, a hill the ray only grazes is still hit, and a
1658
+ // ray starting underneath crosses on its way out
1659
+ terrain.getColor(pos3D) or (x, z) // nearest sample color
1660
+ terrain.rows terrain.columns // samples along Z and X
1661
+
1662
+ // Voxel map - a 3D tile map, from the render3dVoxels plugin
1663
+ const map = new VoxelMap(pos3D=vec3(), mapSize=vec3(16), tileInfo=tile()) // a grid of blocks that draws itself and
1664
+ // that objects with collideLevel collide with; pos3D is its corner and a cell is 1 unit, it stays upright
1665
+ map.setVoxel(cell, type) / map.getVoxel(cell) // type 1-255 shows that tile on every face, 0 is empty, a cell outside
1666
+ // the map is ignored and reads 0
1667
+ map.setBlockType(type, faces, {seeThrough, transparent}) // faces: a tile, six (+x, -x, +y, -y, +z, -z), or
1668
+ // {top, side, bottom}; seeThrough for holes like leaves, transparent to blend like water, drawn after the rest
1669
+ map.raycast(ray, maxDistance, test) // {distance, cell, normal, type} of the first block a ray hits, or
1670
+ // undefined; test(type, cell) says which blocks count
1671
+ map.data / map.rebuild() // the Uint8Array of types, x + mapSize.x*(y + mapSize.y*z); rebuild
1672
+ // after editing it directly
1673
+ map.ambientOcclusion = true // darken corners where blocks meet, rebuild after changing it
1674
+
1675
+ // OBJ meshes - v, vt, vn and f lines, convex polygons, no materials
1676
+ parseOBJ(text, smooth) // Mesh from OBJ text, smooth normals when the file has none
1677
+ await loadOBJ(url, smooth) // fetch then parse, in an async gameInit; chain .center().fit(size) for models of unknown
1678
+ // units
1679
+
1680
+ // glTF models - the glTF plugin, .gltf with its files beside it or .glb in one file; meshes with their node placement,
1681
+ // vertex colors, material colors and base color textures, and node animations; no skins or morph targets, and no
1682
+ // Draco or meshopt compressed geometry, which throws saying so
1683
+ const model = await loadGLTF(url) // a GLTFModel, in an async gameInit; or await parseGLTF(data, baseUrl) on bytes or
1684
+ // JSON you already have
1685
+ model.parts // one GLTFPart per primitive of every node: name, mesh in model space, color,
1686
+ // textureInfo when the material has one and WebGL is on, transparent for a
1687
+ // blending material or glass (KHR_materials_transmission), which comes in
1688
+ // as a faint tint of its color, and unlit for KHR_materials_unlit; the uvs
1689
+ // are the set the texture's texCoord names, moved by KHR_texture_transform;
1690
+ // normalMap and normalScale from normalTexture, emissiveMap and
1691
+ // emissiveMapColor from emissiveTexture and emissiveFactor, read at the base
1692
+ // color texture's uvs
1693
+ model.mesh, model.textureInfo // everything as one Mesh tinted by its materials, and its texture when every
1694
+ // part uses the same one; a model mixing plain and textured parts, or using
1695
+ // several textures or unlit parts, draws right through createObject
1696
+ model.createObject(pos3D) // a GLTFObject, an EngineObject3D with a child per part, each with its own
1697
+ // texture and blending, so windows and other see through parts show, and
1698
+ // emissive 1 for an unlit one; move and turn the root and the parts follow
1699
+ model.animations // one GLTFAnimation each: name, duration in seconds, and the channels that
1700
+ // move, turn and scale nodes; model.getAnimation(nameOrNumber) finds one
1701
+ object.play(animation=0, loop=true, speed=1) // play one on a GLTFObject by name or number, its parts move with it;
1702
+ // speed below 0 plays it backward, and one that does not loop holds its end
1703
+ object.stop() // hold the pose where it is; object.setAnimationTime(t) poses it at a time
1704
+ object.animation .animationTime .animationSpeed .animationLoop .animationPlaying
1705
+ model.getPose(animation, time) // one Matrix4 per part, how far it moved from its resting place
1706
+ model.dispose() // free the part meshes, the combined mesh and the textures of a model that
1707
+ // is done with; destroy the objects createObject made first
1708
+ // colors come in converted from glTF's linear values, a NEAREST sampler makes a part pixelated, sparse accessors
1709
+ // are read, and object.parts is what an animation poses
1710
+ model.center().fit(size) // move the model's bounds onto the origin and scale its largest extent to
1711
+ // size, every part together, like Mesh.center and fit; getBounds and
1712
+ // transform(matrix) as well
1713
+
1714
+ // Particles - the 3D twin of ParticleEmitter, camera facing billboards sorted with everything transparent
1715
+ new ParticleEmitter3D(pos3D, emitSize, emitTime, emitRate, emitConeAngle, tileInfo,
1716
+ colorStartA, colorStartB, colorEndA, colorEndB, particleTime, sizeStart, sizeEnd,
1717
+ speed, damping, gravity, fadeRate, randomness, additive)
1718
+ emitter3D.stick = 0 // how much a particle grips where it lands, 0 to 1, on top of the friction; 1 stops it
1719
+ emitter3D.emitFlat = false // flatten the spawn area across the way it emits: a sphere a disc, a box a flat rectangle
1720
+ // particles shoot out along the emitter's own up axis, so rotation3D aims the spray; emitSize is a sphere diameter or a
1721
+ // vec3 box; speeds are per frame, sizes are world units, gravity changes velocity y per frame so it is negative to
1722
+ // fall, and it is the emitter's own number rather than render3D.gravity, so an effect falls the same wherever it is used
1723
+ emitter3D.gravityScale = 0 // a share of render3D.gravity added on top of its own gravity, 1 falls with the world
1724
+ // emitConeAngle is the half angle around that direction, PI is every direction; damping multiplies velocity each frame,
1725
+ // 1 by default for no slowing; fadeRate is the fraction of life spent fading, half in and half out; randomness is extra
1726
+ // randomness on speed, size and life
1727
+ // an emitter with an emitTime destroys itself once its last particle is gone, so a burst is fire and forget
1728
+ // untextured particles are soft round dots, textured ones are billboards of the tile
1729
+ emitter.collideLevel = false // particles hit the height maps and voxel maps, bounce by restitution, slide by friction
1730
+ emitter.particleCreateCallback / particleUpdateCallback / particleCollideCallback / particleDestroyCallback
1731
+ // as in 2D, each given a Particle3D {pos, velocity, age, lifeTime, emitter, destroy()}, one object
1732
+ // the emitter reuses, so copy what you keep; the collide callback gets (particle, level, pos) and
1733
+ // returns true to stop the particle there
1734
+ emitter.trailTime = .2 // 0 by default; draw each particle as a ribbon along its last .2 seconds instead (trailTime * 60 updates with engineVariableStep), the texture stretches along
1735
+ // it
1736
+ // scale3D on the emitter, its own or a parent's, grows the whole effect: spawn area, sizes, speed and fall
1737
+ emitter.angleSpeed = .05; emitter.angleDamping = 1 // tumble each particle in the camera plane, either way from a random
1738
+ // start, damped each frame; 0 is no spin, which is the default, and the 2D emitter takes these
1739
+ // as constructor arguments instead
1740
+ emitter.emitParticle() // fire one particle now, on top of the emit rate
1741
+ emitter.particleCount // how many are alive; they live in emitter.particleData, 21 floats each, owned by the
1742
+ // emitter and drawn as one instanced batch of render3D.billboardMesh, so nothing else touches them
1743
+
1744
+ // Lens flare - light in the lens when the sun is in view
1745
+ new LensFlare3D(size=1, count=7, intensity=1, saturation=1, color) // the sun's lens flare, the old kind: a glow at
1746
+ // the sun and a row of discs and rings along the line through the middle of the
1747
+ // screen; it fades as the sun leaves the screen or goes behind something
1748
+ flare.flareSize = 1.5 // scales every part; count is how many ghosts, intensity how bright, saturation
1749
+ // how colorful, 0 all its own color; seed picks another arrangement
1750
+ flare.shapes = ['hex', 'streak'] // what the ghosts are picked from: glow, disc, ring, hex, streak or star; glowSize
1751
+ // scales the glow at the sun, 0 for none, and ghostSize the ghosts
1752
+ flare.elements = [{at: .5, size: .1, color: hsl(.6,1,.6,.3), shape: 'disc'}] // or parts of your own, each a
1753
+ // LensFlareElement: at 0 the sun, 1 the middle, 2 as far past it; size a part of
1754
+ // the screen's height, or a vec2 for a wide one; shape one of the six, or
1755
+ // tileInfo for a tile of your own; angle turns it
1756
+ flare.visible // how much of the sun shows, 0 to 1, eased over fadeTime, to read; what is not
1757
+ // see through hides it, flare.occlusion = false turns that off
1758
+ light.addFlare(size, count, intensity, saturation) // gives a Light3D a flare of its own and returns it: at the
1759
+ // light, in its color, smaller from farther than its radius, hidden by what is
1760
+ // in front of it, a spotlight's only from inside its beam
1761
+ light.flare // the light's LensFlare3D, its child, or undefined; set one of your own, and
1762
+ // destroy it to take it away; it goes with the light; a level's Light has a
1763
+ // lensFlare property
1764
+ flare.light = lamp // the same by hand, a flare made on its own and pointed at a light; it goes when
1765
+ // the light does; a DirectionalLight3D's flare is far away, where it shines from
1766
+ // Trails - a ribbon through where the object has been, parent it to something that moves
1767
+ new Trail3D(pos3D, lifeTime, width, tileInfo, color, colorEnd, additive) // thins and fades from head to tail over
1768
+ // lifeTime seconds; Infinity keeps every
1769
+ // sample at full width, a path that only
1770
+ // grows, and destroy() then takes it at once
1771
+ trail.side // Vector3 for which way the ribbon lies flat, recorded with each sample; undefined turns it to face the
1772
+ // camera
1773
+ // the samples are world space, so width is a world width and scale3D does nothing to a trail; a Light3D's radius is
1774
+ // a world distance too, so scale3D does nothing there either
1775
+ trail.clear() // forget the trail, for when the object teleports
1776
+ ```
1777
+
1778
+ ### Coming from three.js
1779
+ - The same conventions: right handed, Y up, cameras and objects face -Z, column major matrices, angles in radians
1780
+ - rotation3D is vec3(pitch, yaw, roll), applied roll then pitch then yaw, which is three.js Euler order 'YXZ'
1781
+ - Traps when porting code over:
1782
+ - Vector3 methods return a new vector; three.js add, normalize and multiplyScalar change the vector itself, so ported
1783
+ code that relies on that quietly computes the wrong thing
1784
+ - Builders take diameters and full sizes; three.js SphereGeometry, CylinderGeometry and TorusGeometry take radii
1785
+ - camera.fov is in radians, a three.js PerspectiveCamera fov is in degrees
1786
+ - A plane lies in XZ facing +Y, a three.js PlaneGeometry stands in XY facing +Z
1787
+ - Directional lights point toward where their light comes from, like three.js: render3D.sunDirection, and a
1788
+ DirectionalLight3D shines from its position toward the origin
1789
+ - Colors are plain 0 to 1 values with no color management, and a point light fades out by a radius, not by
1790
+ physical intensity units
1791
+
1792
+ ```javascript
1793
+ // three.js LittleJS 3D
1794
+ scene.add(mesh) // new EngineObject3D(pos3D, mesh), or render3D.drawMesh each frame
1795
+ mesh.position .rotation .scale // obj.pos3D .rotation3D .scale3D
1796
+ parent.add(child) // parent.addChild(child), and child.pos3D is then local
1797
+ parent.attach(child) // parent.attach(child), kept where it is; remove(child) is removeChild(child)
1798
+ new THREE.BoxGeometry(w, h, d) // buildBox(vec3(w, h, d)), or render3D.boxMesh with scale3D
1799
+ new THREE.SphereGeometry(r) // buildSphere(r*2), or render3D.sphereMesh with scale3D
1800
+ new THREE.PlaneGeometry(w, h) // render3D.planeMesh with scale3D, lying flat
1801
+ new THREE.InstancedMesh(geometry, m, n) // new InstancedMesh3D(mesh, n) with setMatrixAt, or setTransformAt from a
1802
+ // position, rotation and scale, and setColorAt; its
1803
+ // instances are in world space, the object's transform does not move them;
1804
+ // objects and drawMesh batch by themselves too, rebuilt each frame
1805
+ material.color, material.map // obj.color, and a TileInfo or TextureInfo as obj.tileInfo
1806
+ material.side = THREE.DoubleSide // mesh.doubleSided = true
1807
+ material.emissiveIntensity // obj.emissive
1808
+ material.normalMap, normalScale // obj.normalMap, obj.normalScale (a number, three.js takes a Vector2)
1809
+ material.shininess (MeshPhongMaterial) // obj.shininess, with obj.specular the strength
1810
+ material.envMap, reflectivity // obj.reflectivity, which reflects the sky's colors, not an environment map
1811
+ material.emissiveMap, emissive // obj.emissiveMap, obj.emissiveMapColor
1812
+ material.transparent, blending // obj.transparent, obj.additive
1813
+ new THREE.ShaderMaterial({fragmentShader}) // obj.shader = new Shader(code), a mainImage snippet the engine wraps;
1814
+ // set emissive = 1 for the snippet to do its own lighting
1815
+ new THREE.AmbientLight(color) // render3D.ambientColor
1816
+ new THREE.HemisphereLight(sky, ground) // render3D.ambientColor and ambientGroundColor, which setSky sets from its colors
1817
+ new THREE.DirectionalLight(color) // new DirectionalLight3D(pos3D, color), it shines from its position the
1818
+ // same way; or render3D.sunDirection and sunColor, the one that shadows
1819
+ new THREE.PointLight(color, i, distance) // new Light3D(pos3D, radius, color, intensity)
1820
+ new THREE.SpotLight(color, i, d, angle, penumbra) // a Light3D with coneAngle = angle and coneSoftness = penumbra,
1821
+ // aimed by rotation3D instead of a target; render3D.shadowLight = light
1822
+ // for its castShadow
1823
+ light.castShadow, light.shadow.camera // render3D.shadows, shadowRange and shadowCenter
1824
+ scene.fog = new THREE.Fog(c, near, far) // render3D.setFog(near, far, c)
1825
+ scene.background // render3D.setSky(topColor, horizonColor, bottomColor)
1826
+ OrbitControls // new CameraControl3D(target, distance)
1827
+ PointerLockControls // new FirstPersonCamera3D
1828
+ new THREE.Raycaster() // render3D.screenToRay, pick and engineObjectsRaycast3D
1829
+ OBJLoader // loadOBJ(url) or parseOBJ(text)
1830
+ GLTFLoader // loadGLTF(url): model.createObject(pos) is the scene as objects, model.mesh
1831
+ // is everything as one Mesh
1832
+ mixer.clipAction(clip).play() // object.play(name) on the object createObject made, node animation only,
1833
+ // no skinned characters
1834
+ EffectComposer and UnrealBloomPass // postProcessBloom()
1835
+ renderer.render(scene, camera) // nothing to do, the engine draws every frame and handles resizing
1836
+ position.setUsage(THREE.DynamicDrawUsage) // mesh.dynamicDraw = true once, then mesh.dirty = true when the points
1837
+ // move, like position.needsUpdate = true
1838
+ geometry.dispose() // optional here, a collected mesh frees its buffer; mesh.dispose()
1839
+ // frees it now, and setMesh frees the mesh it replaces
1840
+ ```
1841
+
1842
+ ## LittleJS 3D Levels
1843
+ - A 3D level is a list of objects as plain JSON, simple enough to write by hand
1844
+ - Box, Sphere, Cylinder and Light are built in, to block out and light a level with no code
1845
+ - A game names its own types, as objectLayersAddType does in 2D
1846
+ - The 3D level editor, in debug builds, edits the level a game loaded: 0 on the debug overlay
1847
+
1848
+ ```javascript
1849
+ level3DAddType(name, make, defaults={}, tileInfo) // A type by name: a class made with new make(pos3D, properties),
1850
+ // or an arrow function called with them, like a player start
1851
+ level3DAddMesh(name, mesh, tileInfo, color=WHITE) // A static prop type of a mesh, with color and solid properties
1852
+ level3DLoad(level) // Make every object of a level, returns what was made
1853
+ level3DVoxelSetup(tileInfo, (map)=> {}) // The sheet a level's block map uses, and a function to set each
1854
+ // map up, its block types' faces; texture 0 without it
1855
+
1856
+ // A level, rotation in degrees, and rotation, scale and properties left out when they are the default
1857
+ {"littlejs3D": 1, "objects": [
1858
+ {"id": 1, "type": "Box", "pos": [0, 0.5, 0], "scale": [4, 1, 4], "properties": {"color": "#88aa55"}},
1859
+ {"id": 2, "type": "Crate", "pos": [2, 1.5, 1], "rotation": [0, 45, 0], "properties": {"health": 5}},
1860
+ {"id": 3, "type": "PlayerStart", "pos": [0, 2, 3]}]}
1861
+
1862
+ // A level can set the scene too, in a scene block beside its objects; level3DLoad applies what the block has, what
1863
+ // it leaves out stays as the game set it, and a level with no block changes nothing
1864
+ {"littlejs3D": 1,
1865
+ "scene": {"sky": ["#5aa6f0", "#d6ecff", "#d6ecff"], // top, horizon and bottom colors, as render3D.setSky
1866
+ "ambient": 0.5, // how much of the sky colors lights the scene
1867
+ "sunDirection": [0.5, 1, 0.3], "sunColor": "#ffffff",
1868
+ "fog": [25, 70], "fogColor": "#d6ecff", // start and end, an end of 0 for none; the horizon color
1869
+ // when the level has a sky and no fogColor
1870
+ "shadows": true,
1871
+ "lensFlare": true}, // the sun's lens flare, a LensFlare3D made for it
1872
+ "objects": []}
1873
+
1874
+ // A level can hold a map of blocks, made a VoxelMap when it loads: its corner, its size in cells, and its blocks
1875
+ // as runs of a count and a type, along x, then y, then z; here one row of 32 blocks of type 1 along x, the rest
1876
+ // empty; the 3D level editor's Blocks tool paints it
1877
+ {"littlejs3D": 1, "voxels": {"pos": [-16, 0, -16], "size": [32, 16, 32], "blocks": [32, 1, 16352, 0]}, "objects": []}
1878
+
1879
+ // A level can hold a terrain, made a HeightMap when it loads: its center, its size in the world, how tall a full
1880
+ // height is, its color, and its heights, rows of 0 to 1; the 3D level editor's Terrain tool sculpts it
1881
+ {"littlejs3D": 1, "terrain": {"pos": [0, -3.2, 0], "size": [64, 64], "height": 16, "color": "#6a9955",
1882
+ "heights": [[0.2, 0.2, 0.2], [0.2, 0.5, 0.2], [0.2, 0.2, 0.2]]}, "objects": []}
1883
+
1884
+ // Prefabs - a prefab is a small level, objects about its own origin, placed many times under one name. The level
1885
+ // editor saves one as it saves a level, so it is the prefab editor too, and there every instance follows its prefab
1886
+ level3DAddPrefab(name, prefab) // Add {objects: [...]} as a type; its objects may be of other prefabs
1887
+ await level3DLoadPrefab(name, url) // Fetch a prefab file and add it
1888
+ level3DSpawn(type, pos3D, rotation3D, scale3D, properties) // Make one object of any type from code, a prefab's
1889
+ // instance or a plain type; rotation in radians
1890
+ prefab = new Prefab3D // what a prefab's type makes, a handle that draws nothing:
1891
+ prefab.parts // what its objects made, objects of their own in the world where
1892
+ // the handle's place, turn and size put them, solid as if placed
1893
+ // by hand; move the handle and they follow with its next update,
1894
+ prefab.placeParts() // or at once; destroying the handle destroys them
1895
+ {"attached": true, "objects": [...]} // an attached prefab's parts are the handle's children: one body
1896
+ // that moves together, its parts with no collision of their own,
1897
+ // a prefab inside it attached too; its handle is at the middle
1898
+ // of the box around them and its size3D is that box, so
1899
+ prefab.setCollision() // makes the body solid where it is seen, a vehicle or a creature
1900
+ prefab.originOffset // from the prefab's origin to that handle, in the prefab's space
1901
+ {"prefabs": {"Tower": {"objects": [...]}}, "objects": [{"type": "Tower", "pos": [5, 0, 5]}]} // a level's own
1902
+ // prefabs, added when it loads; one the game added keeps its place
1903
+ // an instance sized unevenly that holds turned parts is as near as boxes can be, a turned box can not be sheared
1904
+
1905
+ // Built-in types and their properties
1906
+ Box, Sphere, Cylinder // color, tile (-1 for none), solid (true); 1 unit across, the scale is the size
1907
+ Light // color, radius (5), intensity (1); cone (0), degrees from its forward to the edge of
1908
+ // its beam, makes it a spotlight aimed by its rotation, softness (.2), shadows (false);
1909
+ // lensFlare (false) gives it a lens flare
1910
+ ```
1911
+
1912
+ ### 3D level editor
1913
+ Debug builds only. `0` on the debug overlay opens it for a level loaded with `level3DLoad`, the game pauses under it.
1914
+ To make it your own game's editor, with your own types, keys and panel buttons, see [EDITOR.md](EDITOR.md).
1915
+ The panel's Scene box edits the level's scene block: "Level sets the scene" starts one from what is on screen, and
1916
+ off leaves the sky, sun, fog, shadows and lens flare to the game.
1917
+
1918
+ ```javascript
1919
+ // Tools, the keys Unity, Unreal and Godot use
1920
+ Q select · W move · E rotate · R scale // drag a handle, or the object itself
1921
+ B // blocks: click or drag places the type picked in the panel, Shift
1922
+ // removes, Ctrl repaints, P picks the type under the mouse; X is box
1923
+ // fill, a drag fills the rectangle dragged, as tall as the panel's
1924
+ // height; the panel adds the level's block map and resizes it
1925
+ T // terrain: hold the mouse to raise the ground under the brush, Shift
1926
+ // lowers, Ctrl smooths; X goes to the next brush: flatten, to the height
1927
+ // pressed, and paint, a color on the ground, Shift takes it off, saved
1928
+ // as "paint": {"colors": [...], "cells": [count, color, ...]}, 0 for
1929
+ // the terrain's own color; the panel adds the level's terrain, up to 128
1930
+ // cells a side, and has the brush's size and strength
1931
+ G // grid snap, Ctrl flips it for a drag; steps are in the panel
1932
+ L // own axes: the Move handles follow the object's turn, not the world
1933
+ End // drop the selection to the ground
1934
+ // Selecting and editing
1935
+ Left click, Shift+click, left drag from empty space // select, add or take away, box select
1936
+ Delete · Ctrl+C / X / V · Ctrl+D · Ctrl+Z / Y // Ctrl+D duplicates
1937
+ Ctrl+G · Ctrl+Shift+G // make the selection a prefab, kept in the level's prefabs block, with
1938
+ // one instance where it was; unpack an instance into its objects; the
1939
+ // panel names it, exports it to a file and sets it attached, and its
1940
+ // Load prefab button adds one from a file, by the file's name
1941
+ Enter · Backspace // open the selected instance's prefab to edit it alone, a prefab inside
1942
+ // it the same way; Backspace with nothing selected goes back, and every
1943
+ // instance follows; Save inside one writes the prefab's own file
1944
+ // The camera
1945
+ Right button // look, and WASD and QE fly while it is held, Shift faster
1946
+ Wheel · Middle drag or Space+drag · Alt+drag · F // zoom, pan, orbit, frame the selection
1947
+ // Playing
1948
+ Esc // play and edit
1949
+ Restart · Reset to file // panel buttons: rebuild with your edits, or go back to the file
1950
+ levelEditor.onRestart = loadLevel // the same hooks as the 2D editor
1951
+ levelEditor.onPlayFrom = (pos3D)=> {}
1952
+ levelEditor.use3D = undefined // true or false picks the 3D or the 2D editor
1953
+ ```
1954
+
1955
+ ## LittleJS Three.js Integration
1956
+ - Optional plugin that renders a three.js scene on a canvas behind the LittleJS canvas
1957
+ - You load three.js yourself (import map or bundler) and pass the module in
1958
+ - Aligned camera mode locks the 3D camera to the 2D camera so the z=0 plane matches world space
1959
+ - Recommended: `setGLEnable(false)` before engineInit so three.js owns the only WebGL context
1960
+ - Keep `canvasClearColor` transparent (the default) so the 3D scene shows through, set the background with `threeJS.scene.background`
1961
+ - Do not call `renderer.setPixelRatio`, the plugin manages canvas size and DPR
1962
+ - See `examples/threejs/` for a 3D platformer demo
1963
+
1964
+ ```javascript
1965
+ // Setup (call in gameInit), THREE is the three.js module you loaded
1966
+ new ThreeJSPlugin(THREE, cameraFOV=60) // creates global threeJS, renders automatically
1967
+ threeJS.scene // three.js scene, add lights and meshes here
1968
+ threeJS.camera // three.js perspective camera
1969
+ threeJS.cameraAlign2D = true // lock camera to the LittleJS 2D camera (default)
1970
+ threeJS.alignCamera2D() // align manually, called automatically when locked
1971
+ threeJS.cameraNear / cameraFar // near and far planes while aligned, set these rather than camera.near/far
1972
+
1973
+ // Objects - littlejs physics drives a three.js mesh
1974
+ new ThreeJSObject(pos, size, mesh, z=0) // adds mesh to the scene, syncs transform
1975
+ obj.mesh // the three.js object3d
1976
+ obj.z // mesh height above the 2D plane
1977
+ obj.syncMesh() // copy the 2D transform to the mesh
1978
+ ```
1979
+
1980
+ ## LittleJS Box2D Physics
1981
+ - Optional plugin wrapping the Box2D physics engine (via box2d.wasm.js)
1982
+ - Drop-in replacement for engine objects: `Box2dObject extends EngineObject`; Box2D moves it, so pos and angle are
1983
+ read only, move it with `setPosition`, `setAngle` or `setTransform` and push it with velocities and forces
1984
+ - Joints, raycasting, polygon/circle/edge fixtures
1985
+ - Angular values are clockwise like `angle`: angular velocity, torque, joint angles and limits, motor speeds
1986
+ - See `examples/box2d/` for a full demo
1987
+
1988
+ ```javascript
1989
+ // Setup (call once, awaited in gameInit)
1990
+ await box2dInit() // Loads the WASM and creates global box2d / Box2dPlugin
1991
+ box2dSetDebug(true) // Toggle debug rendering of physics shapes (box2dDebug)
1992
+ setGravity(vec2(0,-20)) // World gravity is the engine's gravity, copied into the world every step; Box2D
1993
+ // reads it as units per second squared, its velocities are per second, not per frame
1994
+
1995
+ // Bodies — extend EngineObject, integrate with physics
1996
+ new Box2dObject(pos, size, tileInfo, angle, color, bodyType, renderOrder) // Dynamic by default
1997
+ new Box2dStaticObject(pos, size, tileInfo, angle, color, renderOrder) // Immovable
1998
+ new Box2dKinematicObject(pos, size, tileInfo, angle, color, renderOrder) // Moves but ignores forces
1999
+ new Box2dTileLayer(tileLayer) // Static collision from a TileCollisionLayer, built now
2000
+ b.buildCollision(friction=.2, restitution=0) // Build it again after changing the collision data
2001
+ obj.beginContact(otherObject, fixture, otherFixture) // override; fixture is which of this object's shapes touched,
2002
+ obj.endContact(otherObject, fixture, otherFixture) // as addBox and the others returned it, like a foot sensor
2003
+ // In beginContact/endContact the world is stepping: destroys and setTransform, setBodyType and setMassData
2004
+ // wait until after the step, and creating objects, fixtures or joints there is not allowed
2005
+
2006
+ // Common fixture setup (call from constructor or after creation)
2007
+ obj.addBox(size, offset, angle, density, friction, restitution, isSensor)
2008
+ obj.addCircle(diameter, offset, density, friction, restitution, isSensor)
2009
+ obj.addPoly(points, density, friction, restitution, isSensor) // points are local to the body, 3 to 8 of
2010
+ // them, not all in a line
2011
+ obj.addEdgeList(points, density, friction, restitution, isSensor)
2012
+ obj.setFilterData(categoryBits=1, ignoreCategoryBits=0, groupIndex=0) // collides with every category not ignored,
2013
+ // applies to the fixtures the body has now
2014
+
2015
+ // Forces and motion
2016
+ obj.applyForce(force, pos) // Force in Newtons at world pos (sustained)
2017
+ obj.applyAcceleration(accel, pos) // Δvelocity = accel per call (mass-independent like EngineObject, per second)
2018
+ obj.applyImpulse(impulse, pos) // Δvelocity = impulse / mass (instantaneous; use for one-shot hits)
2019
+ obj.applyTorque(torque)
2020
+ obj.applyAngularAcceleration(accel) // Δangular velocity = accel per call (mass-independent)
2021
+ obj.applyAngularImpulse(impulse) // Δangular velocity = impulse / inertia (instantaneous)
2022
+ obj.setLinearVelocity(vel)
2023
+ obj.setAngularVelocity(av)
2024
+ obj.setAwake(awake=true)
2025
+ obj.setFixedRotation(isFixed=true) // Stop the body from rotating
2026
+ obj.setBullet(isBullet=true) // Continuous collision for fast bodies, so they don't pass through thin ones
2027
+ obj.setSensor(isSensor=true) // The fixtures it has now detect contacts without colliding
2028
+ obj.setLinearDamping(damping) // Box2D's damping, 0 is none, larger slows it faster
2029
+ obj.setAngularDamping(damping)
2030
+ obj.setGravityScale(scale=1)
2031
+ obj.setMassData(localCenter, mass, momentOfInertia) // undefined leaves that one as it is, inertia is about the
2032
+ // center of mass
2033
+ obj.getMass() / getCenterOfMass() / getInertia() // the center of mass in world space, setMassData takes a local one
2034
+
2035
+ // Raycasting and queries
2036
+ box2d.raycast(start, end, includeSensors=false) // Returns the closest Box2dRaycastResult or undefined; sensors
2037
+ // are passed through unless includeSensors
2038
+ box2d.raycastAll(start, end, includeSensors=false) // Every Box2dRaycastResult along the ray, nearest first
2039
+ box2d.boxCast(pos, size, includeSensors=false) / boxCastAll(pos, size, includeSensors=false) // An object, or all
2040
+ // of them, whose shapes overlap the box; sensors are passed through unless included
2041
+ box2d.circleCast(pos, diameter, includeSensors=false) / circleCastAll(pos, diameter, includeSensors=false) // The
2042
+ // nearest object, or all of them, whose position is in the circle, wherever its shapes
2043
+ // are; objects of only sensors are passed over unless included
2044
+ box2d.pointCast(pos, dynamicOnly=true, includeSensors=false) // The object with a shape under the point, sensors
2045
+ // passed through unless included, so a pickup radius does not grab from afar
2046
+
2047
+ // Joints — all extend Box2dJoint; a joint goes along with either of its objects, check joint.isDestroyed()
2048
+ // before using a kept one, joint.isActive() is false then
2049
+ new Box2dTargetJoint(object, fixedObject, worldPos) // Drag toward a point (mouse-follow)
2050
+ new Box2dDistanceJoint(objectA, objectB, anchorA, anchorB)
2051
+ new Box2dPinJoint(objectA, objectB, pos=objectA.pos) // pins the two together at pos, turning freely; a revolute joint
2052
+ new Box2dRopeJoint(objectA, objectB, anchorA, anchorB, extraLength=0) // max length is the anchors' distance
2053
+ // plus extraLength
2054
+ new Box2dRevoluteJoint(objectA, objectB, anchor)
2055
+ new Box2dPrismaticJoint(objectA, objectB, anchor, axis)
2056
+ new Box2dWheelJoint(objectA, objectB, anchor, axis)
2057
+ new Box2dWeldJoint(objectA, objectB, anchor)
2058
+ new Box2dFrictionJoint(objectA, objectB, anchor)
2059
+ new Box2dPulleyJoint(objectA, objectB, groundA, groundB, anchorA, anchorB, ratio)
2060
+ new Box2dMotorJoint(objectA, objectB)
2061
+ new Box2dGearJoint(objectA, objectB, joint1, joint2, ratio=1) // turns objectB of each joint, which must be dynamic
2062
+ ```
2063
+
2064
+ ## LittleJS Medals & Newgrounds
2065
+ - Achievement/medal system with on-screen popup, save/restore via localStorage
2066
+ - Optional Newgrounds plugin for medals held on the server and for scoreboards
2067
+ - While a player is logged in to Newgrounds, the server holds their NewgroundsMedals: they unlock once it confirms and
2068
+ the local save leaves them alone; a plain Medal is never touched, and once the session is lost, at load or later,
2069
+ the game plays as not logged in
2070
+ - Without a session the medal and scoreboard lists still come in, so the medals get their names and icons; unlocking
2071
+ on the server and posting a score need a logged in player, and an unlock earned while not logged in stays local
2072
+ - See `examples/shorts/medals.js` for a demo
2073
+
2074
+ ```javascript
2075
+ // Medals
2076
+ new Medal(id, name, description='', icon='🏆', src) // src is optional image url
2077
+ medal.unlock() // Mark unlocked, save, and queue the popup; the promise it returns is optional
2078
+ // and resolves with whether the medal is unlocked, right away unless a server
2079
+ // has to confirm
2080
+ medal.unlocked // True once unlocked; for a logged in NewgroundsMedal, once the server confirms
2081
+ medal.isLocal() // true while the local save holds the medal; false for a NewgroundsMedal while a
2082
+ // player is logged in to Newgrounds, whose server holds it
2083
+
2084
+ medals // Global { [id]: Medal } map
2085
+ medalsInit(saveName) // Restore unlocked state from localStorage under saveName, a different name
2086
+ // from the game's own save data or each overwrites the other; skipping
2087
+ // NewgroundsMedals while logged in; call it after making the medals, since it
2088
+ // drops saved medals that do not exist (called before any, each medal reads its
2089
+ // own unlock as it is made); still needed with Newgrounds, before or after
2090
+ medalsForEach(callback) // Iterate all registered medals
2091
+ medalsReset() // Lock all medals and persist the cleared catalog; NewgroundsMedals while logged
2092
+ // in are left alone
2093
+ medalsPreventUnlock // Block unlocks (testing / debug)
2094
+
2095
+ // Display tuning
2096
+ medalDisplayTime / setMedalDisplayTime(seconds)
2097
+ medalDisplaySlideTime / setMedalDisplaySlideTime(seconds)
2098
+ medalDisplaySize / setMedalDisplaySize(vec2)
2099
+
2100
+ // Newgrounds in a game: the App ID, the cipher and every medal and scoreboard id come from the project's API Tools page
2101
+ const medal_win = new NewgroundsMedal(81234, 'Win', 'Beat the game'); // its id on Newgrounds
2102
+ const boardId = 14567; // a scoreboard id
2103
+ async function gameInit()
2104
+ {
2105
+ medalsInit('My Game');
2106
+ new NewgroundsPlugin('12345:AbCdEfGh', 'cipherFromApiTools=='); // the cipher only with encryption on
2107
+ const response = await newgrounds.getScores(boardId, undefined, false, 0, 10, 'A'); // top 10 of all time
2108
+ const scores = response?.result?.data?.scores || []; // each with user.name, value and formatted_value
2109
+ }
2110
+ // later, in game code
2111
+ medal_win.unlock();
2112
+ newgrounds.postScore(boardId, score);
2113
+
2114
+ // Newgrounds plugin (a session only comes from the Newgrounds host)
2115
+ new NewgroundsMedal(id, name, description, icon, src) // when logged in, unlock() asks the server and the medal only
2116
+ // unlocks and shows once it confirms; await the promise for the outcome
2117
+ new NewgroundsPlugin(app_id, cipher) // sets the newgrounds global, logs a view and fetches the lists; with the app's
2118
+ // cipher, medal unlocks and posted scores are encrypted by the browser's own
2119
+ // WebCrypto, so the page has to be https or localhost and no library is needed
2120
+ await newgrounds.ready // resolves once the session is checked and the lists are in, empty if the server
2121
+ // could not be reached; needed before reading the lists, user, a NewgroundsMedal's
2122
+ // server fields and, when logged in, its unlocked state
2123
+ newgrounds.session_id // the player's session id, null when not logged in or once the session is lost
2124
+ newgrounds.user // the logged in player once ready, with id, name, url, supporter; null otherwise
2125
+ newgrounds.medals // the server's medal list once ready; each NewgroundsMedal takes its name, image,
2126
+ // value, difficulty and description with " (value)" added
2127
+ newgrounds.scoreboards // the server's scoreboard list once ready, each with its id and name
2128
+ newgrounds.postScore(id, value) // needs a logged in player and a whole number; result.data.success says if it
2129
+ // posted; an answer that the session is gone drops it, like a failed session check
2130
+ await newgrounds.getScores(id, user, social, skip, limit, period) // the scores are in result.data.scores; period
2131
+ // 'D' today (the server default), 'W', 'M', 'Y', 'A' all time; a user or social
2132
+ // narrows it down, and without either it is the whole board even when logged in
2133
+ newgrounds.unlockMedal(id) // low level request only, the medal is not changed; games call medal.unlock()
2134
+ newgrounds.pendingUnlocks // advanced: the unlocks in flight or waiting to be resent, with their promises
2135
+ newgrounds.resendUnlocks() // advanced: send the ones whose request did not reach the server again now, as
2136
+ // the minute's session check does; one the server refused is not resent
2137
+ ```
2138
+
2139
+ ## LittleJS Drawing Utilities
2140
+ - Optional plugin: nine-slice and three-slice helpers for scalable UI panels, plus a crescent shape
2141
+ - World-space (WebGL or 2D) and screen-space (2D by default, useWebGL for WebGL) variants
2142
+ - See `examples/shorts/nineSlice.js`, `examples/shorts/uiSlice.js` and `examples/shorts/crescent.js`
2143
+
2144
+ ```javascript
2145
+ // Nine-slice — 3x3 tile grid scaled to fit
2146
+ drawNineSlice(pos, size, startTile, color, borderSize, additiveColor, extraSpace, angle=0, useWebGL=glEnable, screenSpace=drawScreenSpace, context)
2147
+ drawNineSliceScreen(pos, size, startTile, color=WHITE, borderSize=32, additiveColor, extraSpace=2, angle=0, useWebGL=false, context)
2148
+
2149
+ // Three-slice — 1x3 tile strip (corner / side / center) rotated around the box
2150
+ drawThreeSlice(pos, size, startTile, color, borderSize, additiveColor, extraSpace, angle=0, useWebGL=glEnable, screenSpace=drawScreenSpace, context)
2151
+ drawThreeSliceScreen(pos, size, startTile, color=WHITE, borderSize=32, additiveColor, extraSpace=2, angle=0, useWebGL=false, context)
2152
+
2153
+ // TileSlice — a tile kept as a box style: slices 9 is a nine-slice, 3 a three-slice, 1 the whole tile stretched
2154
+ new TileSlice(tileInfo, slices=9, borderSize, extraSpace) // borderSize and extraSpace default to the draw's own
2155
+ slice.draw(pos, size, color=WHITE, additiveColor, angle=0, useWebGL=glEnable, screenSpace=drawScreenSpace, context)
2156
+ slice.drawScreen(pos, size, color=WHITE, additiveColor, angle=0, useWebGL=false, context)
2157
+
2158
+ // Crescent — moon-phase shape (percent: 0=new, .25=first quarter, .5=full, .75=last quarter)
2159
+ drawCrescent(pos, size=1, percent=0, color=WHITE, angle=0, invert=false, lineWidth=0, lineColor=BLACK, useWebGL=glEnable, screenSpace, context)
2160
+ // lit side up at angle 0, invert draws the unlit part
2161
+ getCrescentPoints(pos, size=1, percent=0, angle=0, invert=false, sides=glCircleSides) // crescent points for drawPoly
2162
+ ```
2163
+
2164
+ ## LittleJS Texture Sheets
2165
+ - Optional plugin: packs images into texture sheets at runtime and returns a TileInfo
2166
+ - Sheets are created and filled automatically, there is nothing to set up first
2167
+ - Grid images keep their layout, frames wrap to the next row, and `tileInfo.frame(n)` follows them; a tile layer or
2168
+ ImageFont made from one counts its tiles the same way
2169
+ - See `examples/shorts/textureSheet.js`
2170
+
2171
+ ```javascript
2172
+ loadSprite(src, frameSize, padding=1, sourcePadding=0) // Load an image and pack it, returns a TileInfo
2173
+ loadAtlas(imageSrc, jsonSrc, padding=1) // Load a TexturePacker or Aseprite atlas, returns name->TileInfo object
2174
+ parseAtlas(data) // Parse atlas json into named frame groups, used by loadAtlas
2175
+ spritesReady() // Promise resolved when all sprites are packed
2176
+ textureSheets // Array of TextureSheet created by loadSprite
2177
+ new TextureSheet(size=2048) // A texture that images are packed into
2178
+ sheet.tryAdd(imageSize, frameSize, padding, sourcePadding) // Reserve a spot, returns a TileInfo
2179
+ sheet.drawImage(image, tileInfo, update=true, sourcePadding=0) // Draw an image into a reserved spot
2180
+ sheet.updateTexture() // Upload batched images to WebGL
2181
+
2182
+ // Settings
2183
+ textureSheetSize = 2048 // Size in pixels of texture sheets created by loadSprite
2184
+ textureSheetPadding = 1 // Default padding around each frame packed by loadSprite
2185
+
2186
+ // Load a sprite and an animation, then wait for both
2187
+ async function gameInit()
2188
+ {
2189
+ playerTile = loadSprite('player.png');
2190
+ runTile = loadSprite('run.png', vec2(16));
2191
+ await spritesReady();
2192
+ }
2193
+ ```
2194
+
2195
+ ## LittleJS Tweakables
2196
+ - A panel to change values live while the game runs, debug builds only
2197
+ - Press 9 while the debug overlay is open to show it, or set debugTweakables = true to show it from the start
2198
+ - A number gets a slider when it has a min and max, a boolean a checkbox, a Color a picker and an alpha slider,
2199
+ a Vector2 or Vector3 a number for each axis
2200
+ - Changes are saved and come back after a refresh, until the value in the code changes
2201
+ - Copy puts the changed values on the clipboard as lines of code, Reset puts back the code values
2202
+ - Paths are found by name, so only a script's globals work; an ES module game passes the object option
2203
+ - Release builds have none of its code, tweak and the rest do nothing, and the code's values are used as they are
2204
+ - See `examples/shorts/tweakables.js` for a demo
2205
+
2206
+ ```javascript
2207
+ tweak(path, options) // Add a global, or a dotted path to a field on one, call it at the end of gameInit
2208
+ // options: {min, max, step, label, object}, the slider shows when min and max are set
2209
+ tweak('speed', {object: settings}) // A field of an object, for an ES module game; with two objects that share a
2210
+ // field, give each a label, the name it is known and saved by
2211
+ tweakDivider(label) // Add a divider with a label for the tweaks after it
2212
+ tweakButton(label, callback) // Add a button that calls a function, like one to restart the level
2213
+ tweakEngineDefaults() // Add gravity, timeScale, cameraScale and soundVolume
2214
+ ```
2215
+
2216
+ ## LittleJS Level Editor
2217
+ - Press 0 while the debug overlay is open to pause the game and paint its tile layers, 0 again to keep playing
2218
+ - Edits the Tiled map the game passed to tileLayersLoad, so a game that loads it again gets the changes
2219
+ - Every change is autosaved and comes back after a reload, Save writes the map as Tiled JSON
2220
+ - Left paints, right click picks, right drag selects, middle or Space drag pans, the wheel zooms, 1-9 pick a layer
2221
+ - F fills, Delete clears the selection, Ctrl+C/X/V copy, cut and paste it as a stamp, R turns, M mirrors, ? lists the keys
2222
+ - Left drag a selection to move it, Escape or a right click puts it back
2223
+ - A map's object layers come after its tile layers: left places the palette's type or selects and drags,
2224
+ right click picks, right drag box-selects, a selected object's properties show in the panel
2225
+ - The panel's Advanced section has Play from mouse, the level's size to resize it, and Reset to file
2226
+ - Debug builds only, release builds have none of its code
2227
+ - To make it your own game's editor, with your own types, keys and panel buttons, see [EDITOR.md](EDITOR.md)
2228
+
2229
+ ```javascript
2230
+ levelEditor.isOpen // Is the editor open? read only
2231
+ levelEditor.open() // Open the editor, pausing the game; close() plays on with the changes
2232
+ levelEditor.onTile = (layer, layerPos, tile)=> {} // What the game does with a painted tile, like set its
2233
+ // collision, tile undefined when erased
2234
+ levelEditor.onRestart = ()=> {} // Rebuild the level, adds a Restart button
2235
+ levelEditor.onPlayFrom = (pos)=> {} // Put the player at pos, adds Play from mouse to the Advanced section
2236
+ levelEditor.paletteTiles = [0, 1, 10] // The tiles the palette shows, in order; undefined shows the whole sheet
2237
+ levelEditor.use3D = undefined // true opens the 3D level editor, false the 2D one; undefined the 3D one with a 3D level
2238
+ levelEditor.is3D // Is the editor in use the 3D one? read only
2239
+
2240
+ // Making it your own game's editor, the same calls in the 2D and the 3D editor; see EDITOR.md
2241
+ levelEditor.addKey('k', (shift)=> {}, 'K: what it does') // A key of your own: a letter, a name like 'F2', 'ctrl+k'
2242
+ levelEditor.addButton('Label', ()=> {}, 'title') // A button of your own in the panel
2243
+ levelEditor.addTool('Name', {key, hint, onPress, onDrag, onRelease, onDraw}) // A tool of your own: while it is on
2244
+ // the left button is its; each callback gets {pos, cell, ray, shift, ctrl}, and a
2245
+ // press, drag and release are one undo
2246
+ levelEditor.tool // The name of your tool that is on, undefined for one of the editor's
2247
+ levelEditor.onOpen = ()=> {} onClose onUpdate onDraw // Moments the editor calls: opened, closed, each
2248
+ // frame while open, and while it draws the level, for overlays
2249
+ levelEditor.onPanel = (box)=> {} // Once, when the panel is made: a box in it for your own controls
2250
+ levelEditor.onSave = (text, fileName)=> true // Save: return true when you kept the file yourself
2251
+ levelEditor.edit3D // The 3D editor's edit functions: level, objects, selection, selected(), made(id),
2252
+ // change((list)=> ...), changePart(name, (part)=> ...), strokeEnd(), strokeCancel(),
2253
+ // place(type, pos3D), setTransform(object, pos3D, rotationDegrees, scale3D),
2254
+ // setProperty(object, name, value), pos(object), rotation(object), scale(object),
2255
+ // mousePoint(), undo(redo), toJSON()
2256
+ levelEditor.edit2D // The 2D editor's: map, layer, objects, hover, selection, paint(cell, tile, direction,
2257
+ // mirror), changeObjects((list)=> ...), strokeEnd(), strokeCancel(), bulk(()=> ...),
2258
+ // undo(redo), toJSON(); a change is one undo once strokeEnd() is called
2259
+ class MyEditor extends LevelEditor { onRestart() {} onDraw() {} } // Or a class of your own, its methods the hooks
2260
+ setLevelEditor(new MyEditor) // Make it the one in use, before the editor opens
2261
+ // In release builds LevelEditor takes all of this and calls none of it, so a game needs no guards
2262
+ ```
2263
+
2264
+ ## LittleJS Debugging System
2265
+ - Press Escape key to toggle debug overlay
2266
+ - Number keys toggle debug functions while the overlay is open: 1 physics, 2 tiles (each tile layer's bounds, the
2267
+ collision values on screen, and the tiles under the mouse; pressing 2 again steps through the layers one at a time,
2268
+ then off), 3 particles, 4 raycasts, 5 gamepads, 6 sound, 7 screenshot, 8 video capture, 9 tweakables panel,
2269
+ 0 level editor
2270
+ - C, while the overlay is open, is a free camera for a 3D game: the mouse looks once captured or with the right
2271
+ button held, WASD and QE fly, Shift is faster and the wheel sets the speed; the game runs on and reads no keys or
2272
+ mouse until C or Escape
2273
+ - +/- keys apply time scale to update while the overlay is open
2274
+ - setDebugKeysAlways(true) lets the number and +/- keys work with the overlay closed, for a game that does not use them
2275
+ - Debug primitive rendering system
2276
+ - Debug functions are only active in debug builds
2277
+
2278
+ ```javascript
2279
+ ASSERT(assert, output) // Asserts if the expression is false
2280
+ LOG(...output) // Logs output to the console (stripped from release builds)
2281
+ debugRect(pos, size, color=WHITE, time=0, angle=0, fill) // Draw debug rectangle
2282
+ debugCircle(pos, size, color=WHITE, time=0, fill) // Draw debug circle
2283
+ debugPoint(pos, color, time, angle) // Draw debug point
2284
+ debugLine(posA, posB, color, width=.1, time) // Draw debug line
2285
+ debugPoly(pos, points, color=WHITE, time=0, angle=0, fill) // Draw debug polygon
2286
+ debugText(text, pos, size=1, color=WHITE, time=0, angle=0) // Draw debug text
2287
+ debugOverlap(pA, sA, pB, sB, color) // Draw a debug overlap between two boxes
2288
+ // each debug draw also takes screenSpace after the params above, defaulting to drawScreenSpace
2289
+ debugClear() // Clear all debug primitives
2290
+ debugScreenshot() // Save a screenshot at the end of this frame
2291
+ debugShowErrors() // Show full page error message when an error occurs
2292
+ debugVideoCaptureStart() // Start capturing a video of the canvas
2293
+ debugVideoCaptureStop() // Stop capturing and save the video to disk
2294
+ debugVideoCaptureIsActive() // Is video currently being captured?
2295
+ createCanvasContext(width, height=width, willReadFrequently=false) // Offscreen canvas to draw into, returns its
2296
+ // 2D context; the canvas is context.canvas
2297
+ saveCanvas(canvas, filename='screenshot', type='image/png') // Save canvas to a file
2298
+ saveText(text, filename='text', type='text/plain') // Save text to a file
2299
+ saveDataURL(url, filename='download', revokeTime) // Save url to a file, revokeTime is ms before
2300
+ // URL.revokeObjectURL frees an object url
2301
+
2302
+ // Debug settings
2303
+ debug // Is debug enabled?
2304
+ debugPointSize = .5 // Size to render debug points by default
2305
+ debugKey = 'Escape' // Key code used to toggle debug mode
2306
+ debugKeysAlways = false // The number and +/- keys work with the overlay closed too, setDebugKeysAlways(enable=true)
2307
+ debugOverlay // Is the debug overlay active? setDebugOverlay(show=true) opens or closes it from code
2308
+ debugTweakables = false // Is the tweakables panel shown? setDebugTweakables(show=true)
2309
+ debugWatermark // Should watermark with FPS appear in debug mode?
2310
+ ```
2311
+
2312
+ ## Deprecated
2313
+
2314
+ Old names and argument orders that still work, marked `@deprecated` in the types; use the new ones, the old are
2315
+ kept until 1.25 at least.
2316
+
2317
+ ```javascript
2318
+ obj.collideTiles // since 1.20, use obj.collideLevel, the same flag
2319
+ weldJoint.setSpringDampingRatio(ratio) // since 1.20, use setDampingRatio, and getDampingRatio for the getter
2320
+ newgrounds.logView() // since 1.20, does nothing, the view is logged when the plugin starts
2321
+ drawNineSliceScreen(pos, size, startTile, borderSize, extraSpace, angle) // since 1.20, the order is now
2322
+ drawThreeSliceScreen(pos, size, startTile, borderSize, extraSpace, angle) // (pos, size, startTile, color, ...)
2323
+ ```
2324
+
2325
+ [LittleJS Engine](https://github.com/KilledByAPixel/LittleJS) Copyright 2021 Frank Force
2326
+
2327
+ ![LittleJS Logo](examples/favicon.png)