littlejsengine 1.24.3 → 1.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/EDITOR.md +2 -2
- package/FAQ.md +6 -0
- package/REFERENCE.md +84 -40
- package/dist/littlejs.d.ts +474 -219
- package/dist/littlejs.esm.js +2318 -1743
- package/dist/littlejs.esm.min.js +38 -24
- package/dist/littlejs.js +2316 -1743
- package/dist/littlejs.min.js +37 -23
- package/dist/littlejs.release.js +2313 -1786
- package/package.json +1 -1
- package/plugins/box2d.js +32 -23
- package/plugins/drawUtilities.js +374 -380
- package/plugins/gltf.js +325 -54
- package/plugins/lightSystem.js +2 -2
- package/plugins/math3d.js +1013 -1014
- package/plugins/newgrounds.js +1 -4
- package/plugins/particleEffects.js +3 -4
- package/plugins/pathFinder.js +52 -28
- package/plugins/pluginExport.js +2 -0
- package/plugins/postProcess.js +196 -65
- package/plugins/render3d.js +8 -6
- package/plugins/render3dEditor.js +3 -3
- package/plugins/render3dExtras.js +19 -2
- package/plugins/render3dLevel.js +15 -4
- package/plugins/tweenSystem.js +43 -38
- package/plugins/uiSystem.js +25 -20
- package/src/engine.js +9 -5
- package/src/engineAudio.js +10 -6
- package/src/engineDebug.js +3 -2
- package/src/engineDraw.js +9 -9
- package/src/engineEditor.js +75 -23
- package/src/engineInput.js +4 -7
- package/src/engineMath.js +3 -6
- package/src/engineObject.js +8 -9
- package/src/engineParticles.js +8 -7
- package/src/engineRelease.js +17 -10
- package/src/engineTileLayer.js +33 -14
- package/src/engineUtilities.js +15 -2
- package/src/engineWebGL.js +31 -9
package/src/engineEditor.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* @callback EditorPlayFromCallback - Puts the player at a world position, for the level editor's Play from mouse
|
|
17
|
-
* @param {
|
|
17
|
+
* @param {any} pos - Where to start playing, a Vector2 from the 2D editor and a Vector3 from the 3D one
|
|
18
18
|
* @memberof Editor
|
|
19
19
|
*/
|
|
20
20
|
|
|
@@ -32,25 +32,29 @@
|
|
|
32
32
|
*/
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* @
|
|
36
|
-
* @
|
|
35
|
+
* @template [P=Vector2]
|
|
36
|
+
* @typedef {Object} EditorToolAt - What the level editor gives the callbacks of a tool of the game's own; P is
|
|
37
|
+
* the type of pos, a Vector2 for a 2D tool, the default, and Vector3|undefined for a 3D one
|
|
38
|
+
* @property {P} pos - Where the mouse is in the level: a Vector2 in the 2D editor; in the 3D one a Vector3 on
|
|
37
39
|
* the level or the ground, undefined when the mouse is over the panel
|
|
38
40
|
* @property {Vector2|undefined} cell - 2D: the tile cell under the mouse on the selected tile layer
|
|
39
|
-
* @property {
|
|
41
|
+
* @property {Ray3D|undefined} ray - 3D: the mouse's Ray3D
|
|
40
42
|
* @property {boolean} shift - Is Shift held
|
|
41
43
|
* @property {boolean} ctrl - Is Ctrl held
|
|
42
44
|
* @memberof Editor
|
|
43
45
|
*/
|
|
44
46
|
|
|
45
47
|
/**
|
|
46
|
-
* @
|
|
48
|
+
* @template [P=Vector2]
|
|
49
|
+
* @typedef {Object} EditorTool - A tool of the game's own for the level editor, see LevelEditor.addTool; P is the
|
|
50
|
+
* type of its pos, as EditorToolAt has it
|
|
47
51
|
* @property {string} [key] - The key that picks it, as addKey spells one
|
|
48
52
|
* @property {string} [hint] - The hint line while it is on
|
|
49
|
-
* @property {function(EditorToolAt): any} [onPress] - The left button went down in the level; returning false
|
|
53
|
+
* @property {function(EditorToolAt<P>): any} [onPress] - The left button went down in the level; returning false
|
|
50
54
|
* says the press was not the tool's
|
|
51
|
-
* @property {function(EditorToolAt): any} [onDrag] - Each frame it is held
|
|
52
|
-
* @property {function(EditorToolAt): any} [onRelease] - It was let go
|
|
53
|
-
* @property {function(EditorToolAt): any} [onDraw] - Each frame the tool is on, to draw its cursor or preview
|
|
55
|
+
* @property {function(EditorToolAt<P>): any} [onDrag] - Each frame it is held
|
|
56
|
+
* @property {function(EditorToolAt<P>): any} [onRelease] - It was let go
|
|
57
|
+
* @property {function(EditorToolAt<P>): any} [onDraw] - Each frame the tool is on, to draw its cursor or preview
|
|
54
58
|
* @memberof Editor
|
|
55
59
|
*/
|
|
56
60
|
|
|
@@ -143,7 +147,7 @@ class LevelEditor
|
|
|
143
147
|
this.keys = {};
|
|
144
148
|
/** @type {Array<{label: string, onClick: Function, title: string}>} */
|
|
145
149
|
this.buttons = [];
|
|
146
|
-
/** @type {Object<string, EditorTool
|
|
150
|
+
/** @type {Object<string, EditorTool<any>>} */
|
|
147
151
|
this.tools = {};
|
|
148
152
|
}
|
|
149
153
|
|
|
@@ -161,7 +165,7 @@ class LevelEditor
|
|
|
161
165
|
|
|
162
166
|
/** Put the player at a position, a Vector2 in the 2D editor and a Vector3 in the 3D one; set it or override it
|
|
163
167
|
* and the editor has Play from mouse, which starts play there
|
|
164
|
-
* @param {any} pos */
|
|
168
|
+
* @param {any} pos - A Vector2 from the 2D editor, a Vector3 from the 3D one */
|
|
165
169
|
onPlayFrom(pos) {}
|
|
166
170
|
|
|
167
171
|
/** Called when the editor opens, to set or override */
|
|
@@ -244,8 +248,9 @@ class LevelEditor
|
|
|
244
248
|
* in 3D
|
|
245
249
|
* - What the callbacks change through edit2D or edit3D is one undo: the editor ends the stroke at the release,
|
|
246
250
|
* and takes it back when the right button or Escape ends the press
|
|
251
|
+
* @template [P=Vector2]
|
|
247
252
|
* @param {string} name
|
|
248
|
-
* @param {EditorTool} tool - {key, hint, onPress, onDrag, onRelease, onDraw}, each optional; onPress returning
|
|
253
|
+
* @param {EditorTool<P>} tool - {key, hint, onPress, onDrag, onRelease, onDraw}, each optional; onPress returning
|
|
249
254
|
* false says the press was not the tool's */
|
|
250
255
|
addTool(name, tool)
|
|
251
256
|
{
|
|
@@ -260,7 +265,10 @@ class LevelEditor
|
|
|
260
265
|
open()
|
|
261
266
|
{
|
|
262
267
|
if (editorOther?.wanted())
|
|
263
|
-
|
|
268
|
+
{
|
|
269
|
+
editorOther.open();
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
264
272
|
if (!editorSession)
|
|
265
273
|
editorCameraScale = cameraScale; // a new session starts at the game's zoom, a return keeps the editor's
|
|
266
274
|
editorSession = true;
|
|
@@ -272,7 +280,10 @@ class LevelEditor
|
|
|
272
280
|
close()
|
|
273
281
|
{
|
|
274
282
|
if (editorOther?.active())
|
|
275
|
-
|
|
283
|
+
{
|
|
284
|
+
editorOther.close();
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
276
287
|
editorSession = false;
|
|
277
288
|
editorSetOpen(false);
|
|
278
289
|
}
|
|
@@ -1084,6 +1095,8 @@ let editorStroke;
|
|
|
1084
1095
|
|
|
1085
1096
|
// during a big edit, the layers whose cell by cell redraws are held off, with the redraw each had of its own
|
|
1086
1097
|
let editorHeldRedraws;
|
|
1098
|
+
// the live layers a change can only show by drawing them again whole, drawn when it is done
|
|
1099
|
+
const editorRedrawWhole = new Set;
|
|
1087
1100
|
|
|
1088
1101
|
// make a big edit, a fill, clear, undo, apply or revert, with each layer's cell by cell redraws held off, a game's
|
|
1089
1102
|
// tile callback's included; the stroke's end draws each layer it touched again whole
|
|
@@ -1110,6 +1123,11 @@ function editorSetCell(layer, pos, gid)
|
|
|
1110
1123
|
source.data[index] = gid;
|
|
1111
1124
|
if (live.destroyed) return before; // a layer the game let go of, the map still has the change
|
|
1112
1125
|
|
|
1126
|
+
// a plain cell draws itself here; held draws, a game's onTile, which may change the cells around, and its own
|
|
1127
|
+
// drawing over the layer show only when the whole layer draws again, once the change is done
|
|
1128
|
+
if (editorHeldRedraws || editorHas('onTile') || live.onRedraw !== TileLayer.prototype.onRedraw)
|
|
1129
|
+
editorRedrawWhole.add(live);
|
|
1130
|
+
|
|
1113
1131
|
if (editorHeldRedraws && !editorHeldRedraws.has(live))
|
|
1114
1132
|
{
|
|
1115
1133
|
editorHeldRedraws.set(live, live.hasOwnProperty('redrawTileData') ? live.redrawTileData : undefined);
|
|
@@ -1187,8 +1205,7 @@ function editorUndo(redo=false)
|
|
|
1187
1205
|
editorChanged(stroke);
|
|
1188
1206
|
}
|
|
1189
1207
|
|
|
1190
|
-
// after a change the tile layers it touched draw again whole,
|
|
1191
|
-
// and the maps it touched are autosaved; a resized map is made again by the game, with the editor staying open
|
|
1208
|
+
// after a change the tile layers it touched that need it draw again whole, and the maps it touched are autosaved; a resized map is made again by the game, with the editor staying open
|
|
1192
1209
|
function editorChanged(stroke)
|
|
1193
1210
|
{
|
|
1194
1211
|
editorRedraw(stroke);
|
|
@@ -1197,11 +1214,11 @@ function editorChanged(stroke)
|
|
|
1197
1214
|
stroke.some((entry)=> entry.resize) && editorHas('onRestart') && levelEditor.onRestart();
|
|
1198
1215
|
}
|
|
1199
1216
|
|
|
1200
|
-
// the tile layers a stroke touched draw again whole
|
|
1217
|
+
// the tile layers a stroke touched that need it draw again whole
|
|
1201
1218
|
function editorRedraw(stroke)
|
|
1202
1219
|
{
|
|
1203
1220
|
for (const layer of new Set(stroke.filter((entry)=> entry.layer).map((entry)=> entry.layer)))
|
|
1204
|
-
layer.live.destroyed
|
|
1221
|
+
editorRedrawWhole.delete(layer.live) && !layer.live.destroyed && layer.live.redraw();
|
|
1205
1222
|
}
|
|
1206
1223
|
|
|
1207
1224
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -1708,6 +1725,40 @@ function editorGidMirror(gid)
|
|
|
1708
1725
|
return t ? editorTileToGid(t.tile, (4 - t.direction) % 4, !t.mirror) : gid;
|
|
1709
1726
|
}
|
|
1710
1727
|
|
|
1728
|
+
// a stamp's object turned a quarter clockwise with it: its place by the stamp, its body by its own rotation, which
|
|
1729
|
+
// Tiled turns clockwise about the object's place, so the place needs no other move
|
|
1730
|
+
function editorStampObjectTurn(entry, width)
|
|
1731
|
+
{
|
|
1732
|
+
const offset = vec2(entry.offset.y, width - entry.offset.x), object = entry.object;
|
|
1733
|
+
return {...entry, offset, ...(object && {object: {...object, rotation: ((object.rotation || 0) + 90) % 360}})};
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
// a stamp's object mirrored left to right with it: its rotation the other way, a polygon's points mirrored about its
|
|
1737
|
+
// place, a tile object's image flipped, and a box, ellipse, text or tile, placed by a corner, placed by the corner
|
|
1738
|
+
// that becomes it, its width along its turn from the mirrored place; mirrored twice it is as it was
|
|
1739
|
+
function editorStampObjectMirror(entry, width)
|
|
1740
|
+
{
|
|
1741
|
+
const offset = vec2(width - entry.offset.x, entry.offset.y), object = entry.object;
|
|
1742
|
+
if (!object) return {...entry, offset};
|
|
1743
|
+
const mirrored = {...object, rotation: object.rotation ? -object.rotation : 0};
|
|
1744
|
+
const points = (list)=> list.map((p)=> ({...p, x: -p.x}));
|
|
1745
|
+
if (object.polygon || object.polyline)
|
|
1746
|
+
{
|
|
1747
|
+
object.polygon && (mirrored.polygon = points(object.polygon));
|
|
1748
|
+
object.polyline && (mirrored.polyline = points(object.polyline));
|
|
1749
|
+
}
|
|
1750
|
+
else
|
|
1751
|
+
{
|
|
1752
|
+
// Tiled's pixels run down, the stamp's cells up
|
|
1753
|
+
const angle = (object.rotation || 0) * PI / 180, w = object.width || 0, {x: tw, y: th} = entry.tileSize ?? vec2(1);
|
|
1754
|
+
offset.x -= w * cos(angle) / tw;
|
|
1755
|
+
offset.y -= w * sin(angle) / th;
|
|
1756
|
+
}
|
|
1757
|
+
if (object.gid)
|
|
1758
|
+
mirrored.gid = (object.gid ^ 0x80000000) >>> 0; // Tiled's flip across
|
|
1759
|
+
return {...entry, offset, object: mirrored};
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1711
1762
|
// a stamp turned a quarter turn clockwise on screen, or back, its cells, their tiles and its objects together; cell
|
|
1712
1763
|
// (x, y) goes to (y, width - 1 - x), so the bottom row becomes the left column, its left end at the top
|
|
1713
1764
|
function editorStampTurn(stamp, back=false)
|
|
@@ -1715,7 +1766,7 @@ function editorStampTurn(stamp, back=false)
|
|
|
1715
1766
|
for (let turns = back ? 3 : 1; turns--;)
|
|
1716
1767
|
{
|
|
1717
1768
|
const {width, height} = stamp;
|
|
1718
|
-
const objects = stamp.objects?.map((
|
|
1769
|
+
const objects = stamp.objects?.map((entry)=> editorStampObjectTurn(entry, width));
|
|
1719
1770
|
stamp = {width: height, height: width, grids: stamp.grids.map((grid)=>
|
|
1720
1771
|
{
|
|
1721
1772
|
const turned = [];
|
|
@@ -1732,7 +1783,7 @@ function editorStampTurn(stamp, back=false)
|
|
|
1732
1783
|
function editorStampMirror(stamp)
|
|
1733
1784
|
{
|
|
1734
1785
|
const {width, height} = stamp;
|
|
1735
|
-
const objects = stamp.objects?.map((
|
|
1786
|
+
const objects = stamp.objects?.map((entry)=> editorStampObjectMirror(entry, width));
|
|
1736
1787
|
return {width, height, grids: stamp.grids.map((grid)=>
|
|
1737
1788
|
{
|
|
1738
1789
|
const mirrored = [];
|
|
@@ -1864,8 +1915,9 @@ function editorClear()
|
|
|
1864
1915
|
editorStrokeEnd();
|
|
1865
1916
|
}
|
|
1866
1917
|
|
|
1867
|
-
// the objects of a map's object layers inside a tile area, each {group, type, offset, object}: the index of
|
|
1868
|
-
// object layer, its offset from the area's bottom left corner,
|
|
1918
|
+
// the objects of a map's object layers inside a tile area, each {group, type, offset, object, tileSize}: the index of
|
|
1919
|
+
// its object layer, its offset from the area's bottom left corner, a copy of it, and the map's tile size in pixels,
|
|
1920
|
+
// which a mirror reads its width in cells by, as a stamp keeps them
|
|
1869
1921
|
function editorAreaObjects(layer, area)
|
|
1870
1922
|
{
|
|
1871
1923
|
const corner = layer.live.pos.add(area.min), far = layer.live.pos.add(area.max).add(vec2(1));
|
|
@@ -1874,7 +1926,7 @@ function editorAreaObjects(layer, area)
|
|
|
1874
1926
|
const ids = new Set(editorObjectsIn(objectLayer, corner, far));
|
|
1875
1927
|
return (objectLayer.group?.objects ?? []).filter((object)=> ids.has(object.id)).map((object)=>
|
|
1876
1928
|
({group, type: object.type || object.class, offset: editorObjectPos(layer.record, object).subtract(corner),
|
|
1877
|
-
object: editorObjectsCopy(object)}));
|
|
1929
|
+
object: editorObjectsCopy(object), tileSize: vec2(layer.record.map.tilewidth || 1, layer.record.map.tileheight || 1)}));
|
|
1878
1930
|
});
|
|
1879
1931
|
}
|
|
1880
1932
|
|
package/src/engineInput.js
CHANGED
|
@@ -248,7 +248,6 @@ function keyDirection(up='ArrowUp', down='ArrowDown', left='ArrowLeft', right='A
|
|
|
248
248
|
}
|
|
249
249
|
|
|
250
250
|
/** Returns true if mouse button is down
|
|
251
|
-
* @function
|
|
252
251
|
* @param {number} button
|
|
253
252
|
* @return {boolean}
|
|
254
253
|
* @memberof Input */
|
|
@@ -259,7 +258,6 @@ function mouseIsDown(button)
|
|
|
259
258
|
}
|
|
260
259
|
|
|
261
260
|
/** Returns true if mouse button was pressed
|
|
262
|
-
* @function
|
|
263
261
|
* @param {number} button
|
|
264
262
|
* @return {boolean}
|
|
265
263
|
* @memberof Input */
|
|
@@ -270,7 +268,6 @@ function mouseWasPressed(button)
|
|
|
270
268
|
}
|
|
271
269
|
|
|
272
270
|
/** Returns true if mouse button was released
|
|
273
|
-
* @function
|
|
274
271
|
* @param {number} button
|
|
275
272
|
* @return {boolean}
|
|
276
273
|
* @memberof Input */
|
|
@@ -498,7 +495,7 @@ function inputInit()
|
|
|
498
495
|
{
|
|
499
496
|
// fix stalled audio requiring user interaction, a keyboard only game has no other gesture
|
|
500
497
|
if (soundEnable && !headlessMode && audioContext && !audioIsRunning())
|
|
501
|
-
|
|
498
|
+
audioResume();
|
|
502
499
|
|
|
503
500
|
// keys typed into an html text field are the player's typing, not game input;
|
|
504
501
|
// a key already down still releases on keyup, which only lets go of keys that are down
|
|
@@ -584,7 +581,7 @@ function inputInit()
|
|
|
584
581
|
|
|
585
582
|
// fix stalled audio requiring user interaction
|
|
586
583
|
if (soundEnable && !headlessMode && audioContext && !audioIsRunning())
|
|
587
|
-
|
|
584
|
+
audioResume();
|
|
588
585
|
|
|
589
586
|
// a press in the bars around a letterboxed canvas is not one on its edge; a drag out of it still moves; under
|
|
590
587
|
// pointer lock the mouse stays where the lock began, which may be in a bar, and every click is the game's
|
|
@@ -684,7 +681,7 @@ function inputInit()
|
|
|
684
681
|
|
|
685
682
|
// fix stalled audio requiring user interaction
|
|
686
683
|
if (soundEnable && !headlessMode && audioContext && !audioIsRunning())
|
|
687
|
-
|
|
684
|
+
audioResume();
|
|
688
685
|
|
|
689
686
|
// when the touch gamepad is enabled it owns touch input: suppress the
|
|
690
687
|
// touch->mouse passthrough entirely unless touchGamepadPassthrough is set
|
|
@@ -1449,7 +1446,7 @@ function touchGamepadPointerDown(e, zone)
|
|
|
1449
1446
|
|
|
1450
1447
|
// resume audio on first interaction
|
|
1451
1448
|
if (soundEnable && !headlessMode && audioContext && !audioIsRunning())
|
|
1452
|
-
|
|
1449
|
+
audioResume();
|
|
1453
1450
|
|
|
1454
1451
|
// while paused, any touch is the start button; a control belongs to the first finger on it until that
|
|
1455
1452
|
// finger lifts, so a second finger landing on the same one neither takes it over nor lets it go
|
package/src/engineMath.js
CHANGED
|
@@ -413,8 +413,10 @@ function lineTest(posStart, posEnd, testFunction, normal)
|
|
|
413
413
|
const totalLength = (dx*dx + dy*dy)**.5;
|
|
414
414
|
if (!totalLength) return;
|
|
415
415
|
|
|
416
|
-
// current integer cell we are in
|
|
416
|
+
// current integer cell we are in, the one below a whole start heading down the grid, which it goes into
|
|
417
417
|
const pos = posStart.floor();
|
|
418
|
+
if (dx < 0 && pos.x === posStart.x) --pos.x;
|
|
419
|
+
if (dy < 0 && pos.y === posStart.y) --pos.y;
|
|
418
420
|
|
|
419
421
|
// normalize ray direction
|
|
420
422
|
const dirX = dx / totalLength;
|
|
@@ -687,11 +689,6 @@ function isVector2(v) { return v instanceof Vector2 && v.isValid(); }
|
|
|
687
689
|
// vector2 asserts
|
|
688
690
|
function ASSERT_VECTOR2_VALID(v) { ASSERT(isVector2(v), 'Vector2 is invalid.', v); }
|
|
689
691
|
function ASSERT_NUMBER_VALID(n) { ASSERT(isNumber(n), 'Number is invalid.', n); }
|
|
690
|
-
function ASSERT_VECTOR2_NORMAL(v)
|
|
691
|
-
{
|
|
692
|
-
ASSERT_VECTOR2_VALID(v);
|
|
693
|
-
ASSERT(abs(v.lengthSquared()-1) < .01, 'Vector2 is not normal.', v);
|
|
694
|
-
}
|
|
695
692
|
|
|
696
693
|
/**
|
|
697
694
|
* 2D Vector object with vector math library
|
package/src/engineObject.js
CHANGED
|
@@ -483,19 +483,23 @@ class EngineObject
|
|
|
483
483
|
}
|
|
484
484
|
|
|
485
485
|
/** Convert from local space to world space
|
|
486
|
-
* @param {Vector2} pos - local space point
|
|
486
|
+
* @param {Vector2} pos - local space point
|
|
487
|
+
* @return {Vector2} */
|
|
487
488
|
localToWorld(pos) { return this.pos.add(pos.rotate(this.angle)); }
|
|
488
489
|
|
|
489
490
|
/** Convert from world space to local space
|
|
490
|
-
* @param {Vector2} pos - world space point
|
|
491
|
+
* @param {Vector2} pos - world space point
|
|
492
|
+
* @return {Vector2} */
|
|
491
493
|
worldToLocal(pos) { return pos.subtract(this.pos).rotate(-this.angle); }
|
|
492
494
|
|
|
493
495
|
/** Convert from local space to world space for a vector (rotation only)
|
|
494
|
-
* @param {Vector2} vec - local space vector
|
|
496
|
+
* @param {Vector2} vec - local space vector
|
|
497
|
+
* @return {Vector2} */
|
|
495
498
|
localToWorldVector(vec) { return vec.rotate(this.angle); }
|
|
496
499
|
|
|
497
500
|
/** Convert from world space to local space for a vector (rotation only)
|
|
498
|
-
* @param {Vector2} vec - world space vector
|
|
501
|
+
* @param {Vector2} vec - world space vector
|
|
502
|
+
* @return {Vector2} */
|
|
499
503
|
worldToLocalVector(vec) { return vec.rotate(-this.angle); }
|
|
500
504
|
|
|
501
505
|
/** Called to check if a tile collision should be resolved. Return true for physics to resolve the collision or false to ignore and resolve it manually.
|
|
@@ -634,11 +638,6 @@ class EngineObject
|
|
|
634
638
|
this.collideRaycast = collideRaycast;
|
|
635
639
|
}
|
|
636
640
|
|
|
637
|
-
/** @deprecated since 1.20, use collideLevel
|
|
638
|
-
* @type {boolean} */
|
|
639
|
-
get collideTiles() { return this.collideLevel; }
|
|
640
|
-
set collideTiles(collide) { this.collideLevel = collide; }
|
|
641
|
-
|
|
642
641
|
/** Returns string containing info about this object for debugging
|
|
643
642
|
* @return {string} */
|
|
644
643
|
toString()
|
package/src/engineParticles.js
CHANGED
|
@@ -37,13 +37,13 @@
|
|
|
37
37
|
* let pos = vec2(2,3);
|
|
38
38
|
* let particleEmitter = new ParticleEmitter
|
|
39
39
|
* (
|
|
40
|
-
* pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate,
|
|
40
|
+
* pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emitConeAngle
|
|
41
41
|
* tile(0, 16), // tileInfo
|
|
42
42
|
* rgb(1,1,1,1), rgb(0,0,0,1), // colorStartA, colorStartB
|
|
43
43
|
* rgb(1,1,1,0), rgb(0,0,0,0), // colorEndA, colorEndB
|
|
44
|
-
* 1, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd,
|
|
45
|
-
* .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale,
|
|
46
|
-
* .5, true // randomness,
|
|
44
|
+
* 1, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, speed, angleSpeed
|
|
45
|
+
* .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleConeAngle, fadeRate
|
|
46
|
+
* .5, true // randomness, collideLevel
|
|
47
47
|
* );
|
|
48
48
|
*/
|
|
49
49
|
class ParticleEmitter extends EngineObject
|
|
@@ -336,7 +336,8 @@ class ParticleEmitter extends EngineObject
|
|
|
336
336
|
this.additive && setAdditiveBlendMode(false);
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
-
/** is emitter actively spawning
|
|
339
|
+
/** is emitter actively spawning
|
|
340
|
+
* @return {boolean} */
|
|
340
341
|
isActive() { return !this.emitTime || this.getAliveTime() < this.emitTime; }
|
|
341
342
|
|
|
342
343
|
/** Destroy the particle emitter
|
|
@@ -548,8 +549,8 @@ class Particle
|
|
|
548
549
|
}
|
|
549
550
|
if (isBlockedY || !isBlockedX)
|
|
550
551
|
{
|
|
551
|
-
// down is the world's gravity
|
|
552
|
-
const down =
|
|
552
|
+
// down is the way it falls, the world's gravity by its gravityScale and the emitter's own
|
|
553
|
+
const down = gravityY;
|
|
553
554
|
const wasFalling = this.velocity.y < 0 && down < 0 || this.velocity.y > 0 && down > 0;
|
|
554
555
|
if (wasFalling)
|
|
555
556
|
this.groundObject = hitLayer;
|
package/src/engineRelease.js
CHANGED
|
@@ -10,18 +10,17 @@
|
|
|
10
10
|
|
|
11
11
|
'use strict';
|
|
12
12
|
|
|
13
|
-
let debugWatermark =
|
|
13
|
+
let debugWatermark = false;
|
|
14
14
|
let debugKey = '';
|
|
15
15
|
let debugKeysAlways = false;
|
|
16
16
|
let debugTweakables = false;
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const debugSound = 0;
|
|
17
|
+
const debug = false;
|
|
18
|
+
const debugOverlay = false;
|
|
19
|
+
const debugPhysics = false;
|
|
20
|
+
const debugParticles = false;
|
|
21
|
+
const debugRaycast = false;
|
|
22
|
+
const debugGamepads = false;
|
|
23
|
+
const debugSound = false;
|
|
25
24
|
const debugPointSize = .5;
|
|
26
25
|
|
|
27
26
|
// debug commands are automatically removed from the final build
|
|
@@ -52,6 +51,14 @@ function tweak(){}
|
|
|
52
51
|
function tweakButton(){}
|
|
53
52
|
function tweakDivider(){}
|
|
54
53
|
function tweakEngineDefaults(){}
|
|
54
|
+
// the 2D editor's edit functions in a release build, which has no editor: there is nothing to edit and nothing
|
|
55
|
+
// changes, and a game that calls them needs no guard; edit3D stays undefined, as it is with no 3D plugins
|
|
56
|
+
const editorEdit2DRelease =
|
|
57
|
+
{
|
|
58
|
+
map: undefined, layer: undefined, hover: undefined, get objects() { return []; }, get selection() { return new Set; },
|
|
59
|
+
paint(){ return false; }, changeObjects(){ return false; }, strokeEnd(){}, strokeCancel(){}, bulk(){},
|
|
60
|
+
undo(){ return false; }, toJSON(){ return ''; },
|
|
61
|
+
};
|
|
55
62
|
class LevelEditor
|
|
56
63
|
{
|
|
57
64
|
constructor()
|
|
@@ -61,7 +68,7 @@ class LevelEditor
|
|
|
61
68
|
}
|
|
62
69
|
get isOpen() { return false; }
|
|
63
70
|
get is3D() { return false; }
|
|
64
|
-
get edit2D() { return
|
|
71
|
+
get edit2D() { return editorEdit2DRelease; }
|
|
65
72
|
get edit3D() { return undefined; }
|
|
66
73
|
open(){} close(){} addKey(){} addButton(){} addTool(){}
|
|
67
74
|
onTile(){} onRestart(){} onPlayFrom(){} onOpen(){} onClose(){} onUpdate(){} onDraw(){} onPanel(){}
|
package/src/engineTileLayer.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* - TileLayer for rendering, TileCollisionLayer for physics
|
|
8
8
|
* - Collision callbacks for tile interactions with objects
|
|
9
9
|
* - Optimized raycast support for tile-based physics
|
|
10
|
-
* - Integration with Box2D physics via
|
|
10
|
+
* - Integration with Box2D physics via the Box2dTileLayer plugin
|
|
11
11
|
* @namespace TileLayers
|
|
12
12
|
*/
|
|
13
13
|
|
|
@@ -16,6 +16,28 @@
|
|
|
16
16
|
///////////////////////////////////////////////////////////////////////////////
|
|
17
17
|
// Tile Layer System
|
|
18
18
|
|
|
19
|
+
/** A Tiled map as Tiled saves it as JSON, what tileLayersLoad and objectLayersLoad take and tileLayersFromLDtk
|
|
20
|
+
* makes; the fields read are listed, and the rest of the file is kept as it is
|
|
21
|
+
* @typedef {Object} TiledMap
|
|
22
|
+
* @property {number} width - Cells across
|
|
23
|
+
* @property {number} height - Cells up
|
|
24
|
+
* @property {number} [tilewidth] - A tile's width in pixels
|
|
25
|
+
* @property {number} [tileheight] - A tile's height in pixels
|
|
26
|
+
* @property {Array<Object>} layers - Tile layers, object layers and groups of them, bottom first
|
|
27
|
+
* @property {Array<Object>} [tilesets] - The first one's margin, spacing and columns are read
|
|
28
|
+
* @property {number} [nextlayerid]
|
|
29
|
+
* @property {number} [nextobjectid]
|
|
30
|
+
* @property {string} [orientation]
|
|
31
|
+
* @property {string} [renderorder]
|
|
32
|
+
* @property {boolean} [infinite]
|
|
33
|
+
* @property {string} [type]
|
|
34
|
+
* @property {string|number} [version] - A number in files from Tiled before 1.6
|
|
35
|
+
* @property {string} [tiledversion]
|
|
36
|
+
* @property {number} [compressionlevel]
|
|
37
|
+
* @property {string} [backgroundcolor]
|
|
38
|
+
* @property {Array<Object>} [properties]
|
|
39
|
+
* @memberof TileLayers */
|
|
40
|
+
|
|
19
41
|
/** Keep track of all tile layers with collision
|
|
20
42
|
* @type {Array<TileCollisionLayer>}
|
|
21
43
|
* @memberof TileLayers */
|
|
@@ -90,7 +112,7 @@ function tileCollisionTest(pos, size=vec2(), callbackObject, solidOnly=true)
|
|
|
90
112
|
* @param {Vector2} posEnd
|
|
91
113
|
* @param {EngineObject|TileCollisionCallback} [callbackObject] - Callback, engine object, or undefined
|
|
92
114
|
* @param {Vector2} [normal] - Optional normal of the surface hit
|
|
93
|
-
* @param {boolean} [solidOnly
|
|
115
|
+
* @param {boolean} [solidOnly] - Only check solid layers?
|
|
94
116
|
* @return {Vector2|undefined} - where the ray meets the first tile hit, nudged just inside it, or undefined if no hit
|
|
95
117
|
* @memberof TileLayers */
|
|
96
118
|
function tileCollisionRaycast(posStart, posEnd, callbackObject, normal, solidOnly=true)
|
|
@@ -151,7 +173,7 @@ function tileLayerTileInfo(t, tile)
|
|
|
151
173
|
* - An object or image layer keeps its index, with its slot in the returned array left empty
|
|
152
174
|
* - A hidden layer (visible false) is loaded, its collision included, but not drawn; its render
|
|
153
175
|
* is a no-op, delete that and call redraw() to show it
|
|
154
|
-
* @param {
|
|
176
|
+
* @param {TiledMap} [tileMapData] - Level data from exported data, a 50 by 50 empty level when left out
|
|
155
177
|
* @param {TileInfo} [tileInfo] - Default tile info (used for size and texture), tile() by default, none when no image is loaded
|
|
156
178
|
* @param {number} [renderOrder] - Render order of the top layer
|
|
157
179
|
* @param {number|string} [collisionLayer] - Layer to use for collision if any, by its index or its name
|
|
@@ -165,10 +187,7 @@ function tileLayersLoad(tileMapData, tileInfo=tileLayerDefaultTile(), renderOrde
|
|
|
165
187
|
{
|
|
166
188
|
// default level data if loading failed
|
|
167
189
|
const s = 50;
|
|
168
|
-
tileMapData = {};
|
|
169
|
-
tileMapData.height = tileMapData.width = s;
|
|
170
|
-
tileMapData.layers = [{}];
|
|
171
|
-
tileMapData.layers[0].data = new Array(s*s).fill(0);
|
|
190
|
+
tileMapData = {width: s, height: s, layers: [{data: new Array(s*s).fill(0)}]};
|
|
172
191
|
}
|
|
173
192
|
|
|
174
193
|
// the editor, in debug builds, keeps the map as the source of its edits and brings back autosaved ones
|
|
@@ -310,7 +329,7 @@ const tileLayersLDtkTypes = {Int: 'int', Float: 'float', Bool: 'bool', String: '
|
|
|
310
329
|
* - The level editor edits the map this returns, and saves it as a Tiled map
|
|
311
330
|
* @param {Object} ldtk - The LDtk project, its JSON
|
|
312
331
|
* @param {number|string} [level] - Which level, by its index or its identifier
|
|
313
|
-
* @return {
|
|
332
|
+
* @return {TiledMap} - A Tiled map: width, height, tilewidth, tileheight, tilesets and layers
|
|
314
333
|
* @example
|
|
315
334
|
* const map = tileLayersFromLDtk(await fetchJSON('world.ldtk'), 'Level_0');
|
|
316
335
|
* const layers = tileLayersLoad(map, tile(0, 16), 0, 'Collisions'); // its IntGrid layer is solid
|
|
@@ -441,7 +460,7 @@ const objectLayersTypes = new Map;
|
|
|
441
460
|
* - Adding a name again replaces it
|
|
442
461
|
* @param {string} name - The type the objects have in Tiled
|
|
443
462
|
* @param {Function} make - A class made at each object's position, or a function called with it
|
|
444
|
-
* @param {Object} [defaults] - Properties set on each one made, the level editor shows inputs for them
|
|
463
|
+
* @param {Object<string, any>} [defaults] - Properties set on each one made, the level editor shows inputs for them
|
|
445
464
|
* @param {TileInfo} [tileInfo] - An icon for the level editor
|
|
446
465
|
* @memberof TileLayers
|
|
447
466
|
* @example
|
|
@@ -462,7 +481,7 @@ function objectLayersAddType(name, make, defaults={}, tileInfo)
|
|
|
462
481
|
* - The object's properties in Tiled are set over the type's defaults: numbers, booleans, strings, and colors,
|
|
463
482
|
* and for a Vector2 default the string x,y
|
|
464
483
|
* - An object whose type was not added is skipped, with a warning in debug builds
|
|
465
|
-
* @param {
|
|
484
|
+
* @param {TiledMap} tileMapData - The same Tiled map given to tileLayersLoad
|
|
466
485
|
* @return {Array<any>} - What each object's type made, a function that made nothing is left out
|
|
467
486
|
* @memberof TileLayers */
|
|
468
487
|
function objectLayersLoad(tileMapData)
|
|
@@ -556,7 +575,7 @@ class TileLayerData
|
|
|
556
575
|
/** Create a tile layer data object, one for each tile in a TileLayer
|
|
557
576
|
* @param {number} [tile] - The tile to use, from 0 like tile(); undefined is an empty cell that draws nothing
|
|
558
577
|
* @param {number} [direction] - Integer direction of tile, in 90 degree increments
|
|
559
|
-
* @param {boolean} [mirror] - If the tile
|
|
578
|
+
* @param {boolean} [mirror] - If the tile is flipped left to right
|
|
560
579
|
* @param {Color} [color] - Color of the tile */
|
|
561
580
|
constructor(tile, direction=0, mirror=false, color=new Color)
|
|
562
581
|
{
|
|
@@ -565,7 +584,7 @@ class TileLayerData
|
|
|
565
584
|
this.tile = tile;
|
|
566
585
|
/** @property {number} - Integer direction of tile, in 90 degree increments */
|
|
567
586
|
this.direction = direction;
|
|
568
|
-
/** @property {boolean} - If the tile
|
|
587
|
+
/** @property {boolean} - If the tile is flipped left to right */
|
|
569
588
|
this.mirror = mirror;
|
|
570
589
|
/** @property {Color} - Color of the tile */
|
|
571
590
|
this.color = color.copy();
|
|
@@ -641,7 +660,7 @@ class CanvasLayer extends EngineObject
|
|
|
641
660
|
* @param {Vector2} [size] - Size in world space
|
|
642
661
|
* @param {Color} [color] - Color to modulate with
|
|
643
662
|
* @param {number} [angle] - Angle to rotate by
|
|
644
|
-
* @param {boolean} [mirror] - If true image is flipped
|
|
663
|
+
* @param {boolean} [mirror] - If true the image is flipped left to right
|
|
645
664
|
* @param {Color} [additiveColor] - Additive color to be applied if any
|
|
646
665
|
* @param {boolean} [screenSpace=drawScreenSpace] - If true the pos and size are in screen space
|
|
647
666
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to */
|
|
@@ -931,7 +950,7 @@ class TileLayer extends CanvasLayer
|
|
|
931
950
|
* @param {TileInfo} [tileInfo] - Tile info to use, untextured if undefined
|
|
932
951
|
* @param {Color} [color=WHITE] - Color to modulate with
|
|
933
952
|
* @param {number} [angle] - Angle to rotate by
|
|
934
|
-
* @param {boolean} [mirror] - Is image flipped
|
|
953
|
+
* @param {boolean} [mirror] - Is the image flipped left to right?
|
|
935
954
|
* @param {Color} [additiveColor] - Additive color to be applied if any */
|
|
936
955
|
drawLayerTile(pos, size=vec2(1), tileInfo, color=WHITE,
|
|
937
956
|
angle=0, mirror, additiveColor)
|
package/src/engineUtilities.js
CHANGED
|
@@ -117,18 +117,29 @@ function formatTime(t)
|
|
|
117
117
|
|
|
118
118
|
/** Fetches a JSON file from a URL and returns the parsed JSON object. Must be used with await!
|
|
119
119
|
* @param {string} url - URL of JSON file
|
|
120
|
-
* @return {Promise<
|
|
120
|
+
* @return {Promise<any>} - The parsed JSON, any shape, as response.json() gives it
|
|
121
121
|
* @memberof Utilities */
|
|
122
122
|
async function fetchJSON(url)
|
|
123
123
|
{
|
|
124
124
|
const response = await fetch(url);
|
|
125
125
|
if (!response.ok)
|
|
126
126
|
throw new Error(`Failed to fetch JSON from ${url}: ${response.status} ${response.statusText}`);
|
|
127
|
-
const
|
|
127
|
+
const text = await response.text();
|
|
128
|
+
let json;
|
|
129
|
+
try { json = JSON.parse(text); }
|
|
130
|
+
catch (e)
|
|
131
|
+
{
|
|
132
|
+
// a dev server answers a mistyped path with its own page, which is no JSON, so the error says which file
|
|
133
|
+
throw new Error(`fetchJSON: ${url} is not JSON` + (loadIsWebPage(text) ?
|
|
134
|
+
', it is a web page, so the path may be wrong' : ': ' + e.message));
|
|
135
|
+
}
|
|
128
136
|
editorJSONFetched(url, json); // debug builds remember the file a level came from, to save it by that name
|
|
129
137
|
return json;
|
|
130
138
|
}
|
|
131
139
|
|
|
140
|
+
// is a file's text a web page, as a dev server sends for a path it does not have
|
|
141
|
+
const loadIsWebPage = (text)=> /^\s*</.test(text);
|
|
142
|
+
|
|
132
143
|
///////////////////////////////////////////////////////////////////////////////
|
|
133
144
|
|
|
134
145
|
/** Save a text file to disk
|
|
@@ -239,6 +250,8 @@ function readSaveData(saveName, defaultSaveData)
|
|
|
239
250
|
{
|
|
240
251
|
try { loadedData = JSON.parse(data); }
|
|
241
252
|
catch { LOG('readSaveData: corrupt JSON for', saveName, '— using defaults'); }
|
|
253
|
+
if (!loadedData || typeof loadedData !== 'object' || isArray(loadedData))
|
|
254
|
+
loadedData = {}; // only an object of saved values, never a string spread into its letters
|
|
242
255
|
}
|
|
243
256
|
}
|
|
244
257
|
catch { LOG('readSaveData: localStorage unavailable — using defaults'); }
|