littlejsengine 1.22.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/COPYRIGHT.txt +65 -38
- package/EDITOR.md +359 -0
- package/README.md +12 -7
- package/REFERENCE.md +2314 -0
- package/dist/littlejs.d.ts +640 -100
- package/dist/littlejs.esm.js +3596 -1444
- package/dist/littlejs.esm.min.js +3 -3
- package/dist/littlejs.js +3583 -1444
- package/dist/littlejs.min.js +2 -2
- package/dist/littlejs.release.js +2106 -1355
- package/package.json +4 -3
- package/plugins/gltf.js +29 -13
- package/plugins/lightSystem.js +8 -6
- package/plugins/parallax.js +174 -0
- package/plugins/particleEffects.js +37 -22
- package/plugins/pluginExport.js +8 -0
- package/plugins/postProcess.js +5 -2
- package/plugins/render3d.js +59 -21
- package/plugins/render3dEditor.js +1058 -118
- package/plugins/render3dExtras.js +82 -21
- package/plugins/render3dLevel.js +328 -8
- package/plugins/textureSheet.js +1 -0
- package/plugins/tweenSystem.js +39 -34
- package/plugins/uiSystem.js +7 -3
- package/plugins/zzfxm.js +161 -159
- package/src/engine.js +1 -1
- package/src/engineAudio.js +17 -3
- package/src/engineBuild.mjs +18 -2
- package/src/engineDraw.js +26 -10
- package/src/engineEditor.js +515 -52
- package/src/engineExport.js +5 -0
- package/src/engineInput.js +48 -6
- package/src/engineObject.js +675 -675
- package/src/engineRelease.js +17 -2
- package/src/engineSettings.js +14 -0
- package/src/engineTileLayer.js +2 -1
- package/src/engineUtilities.js +291 -291
- package/src/engineWebGL.js +12 -3
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
|
+

|