littlejsengine 1.23.0 → 1.23.1

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