littlejsengine 1.23.0 → 1.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +234 -0
- package/COPYRIGHT.txt +38 -38
- package/EDITOR.md +359 -0
- package/FAQ.md +4 -9
- package/README.md +14 -8
- package/REFERENCE.md +2327 -0
- package/dist/littlejs.d.ts +149 -104
- package/dist/littlejs.esm.js +1652 -1332
- package/dist/littlejs.esm.min.js +3 -3
- package/dist/littlejs.js +1651 -1328
- package/dist/littlejs.min.js +2 -2
- package/dist/littlejs.release.js +1553 -1294
- package/package.json +5 -3
- package/plugins/gltf.js +29 -13
- package/plugins/pluginExport.js +0 -4
- package/plugins/postProcess.js +5 -2
- package/plugins/render3d.js +136 -31
- package/plugins/render3dEditor.js +51 -17
- package/plugins/render3dExtras.js +166 -70
- package/plugins/render3dLevel.js +52 -6
- package/plugins/textureSheet.js +1 -0
- package/plugins/tweenSystem.js +39 -34
- package/plugins/uiSystem.js +7 -3
- package/src/engine.js +1 -1
- package/src/engineAudio.js +9 -3
- package/src/engineBuild.mjs +17 -3
- package/src/engineDraw.js +11 -7
- package/src/engineEditor.js +52 -18
- package/src/engineExport.js +1 -0
- package/src/engineObject.js +675 -675
- package/src/engineRelease.js +5 -1
- package/src/engineTileLayer.js +119 -1
- package/src/engineUtilities.js +291 -291
- package/src/engineWebGL.js +12 -3
- package/plugins/zzfxm.js +0 -160
package/src/engineRelease.js
CHANGED
|
@@ -54,7 +54,11 @@ function tweakDivider(){}
|
|
|
54
54
|
function tweakEngineDefaults(){}
|
|
55
55
|
class LevelEditor
|
|
56
56
|
{
|
|
57
|
-
constructor()
|
|
57
|
+
constructor()
|
|
58
|
+
{
|
|
59
|
+
this.paletteTiles = this.use3D = this.tool = undefined;
|
|
60
|
+
this.keys = {}, this.buttons = [], this.tools = {};
|
|
61
|
+
}
|
|
58
62
|
get isOpen() { return false; }
|
|
59
63
|
get is3D() { return false; }
|
|
60
64
|
get edit2D() { return undefined; }
|
package/src/engineTileLayer.js
CHANGED
|
@@ -128,6 +128,10 @@ const tileLayersTiledFlips = [[0,0], [3,1], [2,1], [3,0], [0,1], [1,0], [2,0], [
|
|
|
128
128
|
* Load tile layers from exported data
|
|
129
129
|
* - Tiled maps come in as they are, flipped and turned tiles included, from one tileset image (a second tileset's
|
|
130
130
|
* tiles continue its numbering), finite maps in the CSV or array layer format; layer offsets and parallax are not read
|
|
131
|
+
* - A tileset kept in the map with a margin or a spacing, a sheet with gaps between its tiles, is read where its
|
|
132
|
+
* tiles are, whatever padding the tile info has; one in a file of its own (a tsx) is not read, pass a tile info
|
|
133
|
+
* with its padding
|
|
134
|
+
* - An LDtk level loads through tileLayersFromLDtk, which makes it a map like these
|
|
131
135
|
* - Group layers are flattened in order, each replaced by the layers inside it, so the layer indices
|
|
132
136
|
* (collisionLayer and the returned array) count that flattened list; a group's tint, opacity and
|
|
133
137
|
* visibility carry to the layers inside it
|
|
@@ -160,6 +164,18 @@ function tileLayersLoad(tileMapData, tileInfo=tileLayerDefaultTile(), renderOrde
|
|
|
160
164
|
ASSERT(tileMapData.width && tileMapData.height);
|
|
161
165
|
ASSERT(tileMapData.layers && tileMapData.layers.length);
|
|
162
166
|
|
|
167
|
+
// a sheet with a margin around its tiles or a spacing between them: the first tile is at the margin and each
|
|
168
|
+
// cell is a tile and a spacing, which is a padding of half the spacing counted from there, by the tileset's
|
|
169
|
+
// own count of columns, since the image's width does not say with a spacing that is not all around
|
|
170
|
+
const tileset = tileMapData.tilesets?.[0];
|
|
171
|
+
if (tileInfo && tileset && (tileset.margin > 0 || tileset.spacing > 0))
|
|
172
|
+
{
|
|
173
|
+
const margin = tileset.margin || 0, spacing = tileset.spacing || 0, size = tileInfo.size;
|
|
174
|
+
const width = tileInfo.textureInfo?.size.x || 0;
|
|
175
|
+
const columns = tileset.columns || max(1, floor((width - margin*2 + spacing) / (size.x + spacing)));
|
|
176
|
+
tileInfo = new TileInfo(vec2(margin), size, tileInfo.textureInfo, spacing / 2, tileInfo.bleed, columns);
|
|
177
|
+
}
|
|
178
|
+
|
|
163
179
|
// flatten group layers in order, a group's color and visibility carry to the layers inside it
|
|
164
180
|
/** @type {Array<{dataLayer: Object, color?: Color, visible?: boolean}>} */
|
|
165
181
|
const layers = [];
|
|
@@ -237,6 +253,107 @@ function tileLayersLoad(tileMapData, tileInfo=tileLayerDefaultTile(), renderOrde
|
|
|
237
253
|
function tileLayersColor(hex)
|
|
238
254
|
{ return new Color().setHex(hex.length === 9 ? '#' + hex.slice(3) + hex.slice(1, 3) : hex); }
|
|
239
255
|
|
|
256
|
+
// the Tiled property type of an LDtk field type, for the ones Tiled has a property for
|
|
257
|
+
const tileLayersLDtkTypes = {Int: 'int', Float: 'float', Bool: 'bool', String: 'string', Multilines: 'string',
|
|
258
|
+
Color: 'color', FilePath: 'file'};
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Make a Tiled map of a level of an LDtk project, to load with tileLayersLoad and objectLayersLoad
|
|
262
|
+
* - Each Tiles, AutoLayer and IntGrid layer is a tile layer, the bottom one first as in Tiled, so the last layer
|
|
263
|
+
* of the LDtk file is layer 0; where LDtk stacks tiles in a cell, an edge over a fill, each tile over another
|
|
264
|
+
* goes in a layer of its own just above, named with (2), (3) and so on, so a layer of the first tiles has one
|
|
265
|
+
* wherever the LDtk layer has any, which makes it the one for collision
|
|
266
|
+
* - An IntGrid layer with no tiles is a hidden layer of its values, for collision: pass its index as collisionLayer
|
|
267
|
+
* - An Entities layer is an object layer: an entity's name is its type for objectLayersAddType, it is placed at
|
|
268
|
+
* its middle, and its Int, Float, Bool, String, Color and FilePath fields are its properties (an enum is a string)
|
|
269
|
+
* - The tileset is the first tile layer's, with its padding and spacing; give tileLayersLoad a tile info of its image
|
|
270
|
+
* - The level is in the project file (not saved as separate level files), its layers of one grid size; a layer of
|
|
271
|
+
* another grid size or another tileset is left out, with a warning in debug builds
|
|
272
|
+
* - The level editor edits the map this returns, and saves it as a Tiled map
|
|
273
|
+
* @param {Object} ldtk - The LDtk project, its JSON
|
|
274
|
+
* @param {number|string} [level] - Which level, by its index or its identifier
|
|
275
|
+
* @return {Object} - A Tiled map: width, height, tilewidth, tileheight, tilesets and layers
|
|
276
|
+
* @example
|
|
277
|
+
* const map = tileLayersFromLDtk(await fetchJSON('world.ldtk'), 'Level_0');
|
|
278
|
+
* const layers = tileLayersLoad(map, tile(0, 16), 0, 1); // layer 1 is solid
|
|
279
|
+
* objectLayersLoad(map);
|
|
280
|
+
* @memberof TileLayers */
|
|
281
|
+
function tileLayersFromLDtk(ldtk, level=0)
|
|
282
|
+
{
|
|
283
|
+
const levels = ldtk?.levels || [];
|
|
284
|
+
const data = typeof level === 'string' ? levels.find((l)=> l.identifier === level) : levels[level];
|
|
285
|
+
ASSERT(data, 'LDtk level not found', level);
|
|
286
|
+
ASSERT(isArray(data.layerInstances), 'LDtk level has no layers: levels saved as separate files are not read');
|
|
287
|
+
const instances = data.layerInstances;
|
|
288
|
+
const tiled = (l)=> l.__type !== 'Entities';
|
|
289
|
+
const first = instances.find((l)=> tiled(l) && l.__tilesetDefUid != undefined) || instances.find(tiled) || instances[0];
|
|
290
|
+
const grid = first?.__gridSize || 16;
|
|
291
|
+
const width = first?.__cWid || ceil(data.pxWid / grid), height = first?.__cHei || ceil(data.pxHei / grid);
|
|
292
|
+
const map = {width, height, tilewidth: grid, tileheight: grid, orientation: 'orthogonal', renderorder: 'right-down',
|
|
293
|
+
infinite: false, layers: [], nextlayerid: 1, nextobjectid: 1};
|
|
294
|
+
|
|
295
|
+
// the tileset of the first layer that has one, where its tiles are in its image
|
|
296
|
+
const tileset = (ldtk?.defs?.tilesets || []).find((t)=> t.uid === first?.__tilesetDefUid);
|
|
297
|
+
if (tileset)
|
|
298
|
+
map.tilesets = [{firstgid: 1, name: String(tileset.relPath || '').replace(/^.*[\\/]/, '').replace(/\.\w+$/, ''),
|
|
299
|
+
image: tileset.relPath, imagewidth: tileset.pxWid, imageheight: tileset.pxHei,
|
|
300
|
+
tilewidth: tileset.tileGridSize, tileheight: tileset.tileGridSize, margin: tileset.padding || 0,
|
|
301
|
+
spacing: tileset.spacing || 0, columns: tileset.__cWid, tilecount: tileset.__cWid * tileset.__cHei}];
|
|
302
|
+
|
|
303
|
+
// LDtk lists the top layer first, Tiled the bottom one
|
|
304
|
+
for (let i = instances.length; i--;)
|
|
305
|
+
{
|
|
306
|
+
const instance = instances[i], name = instance.__identifier, id = map.nextlayerid++;
|
|
307
|
+
if (!tiled(instance))
|
|
308
|
+
{
|
|
309
|
+
const objects = (instance.entityInstances || []).map((entity)=>
|
|
310
|
+
{
|
|
311
|
+
// an entity is placed by its pivot, an object here by its middle
|
|
312
|
+
const [pivotX=0, pivotY=0] = entity.__pivot || [], w = entity.width || 0, h = entity.height || 0;
|
|
313
|
+
const properties = [];
|
|
314
|
+
for (const field of entity.fieldInstances || [])
|
|
315
|
+
{
|
|
316
|
+
const type = tileLayersLDtkTypes[field.__type] || (/^(Local|Extern)Enum\./.test(field.__type) && 'string');
|
|
317
|
+
type && field.__value != undefined && properties.push({name: field.__identifier, type, value: field.__value});
|
|
318
|
+
}
|
|
319
|
+
return {id: map.nextobjectid++, name: '', type: entity.__identifier, x: entity.px[0] + (.5 - pivotX) * w,
|
|
320
|
+
y: entity.px[1] + (.5 - pivotY) * h, width: w, height: h, rotation: 0, visible: true, properties};
|
|
321
|
+
});
|
|
322
|
+
map.layers.push({id, name, type: 'objectgroup', objects, opacity: 1, visible: true, x: 0, y: 0});
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
const tiles = [...(instance.autoLayerTiles || []), ...(instance.gridTiles || [])];
|
|
326
|
+
const usable = instance.__gridSize === grid && instance.__cWid === width && instance.__cHei === height &&
|
|
327
|
+
(!tiles.length || instance.__tilesetDefUid === tileset?.uid);
|
|
328
|
+
if (!usable)
|
|
329
|
+
{
|
|
330
|
+
debug && console.warn(`tileLayersFromLDtk: layer ${name} has another grid or tileset, left out`);
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
const empty = ()=> new Array(width * height).fill(0), stacks = [empty()];
|
|
334
|
+
if (tiles.length)
|
|
335
|
+
{
|
|
336
|
+
// a tile's place in pixels, its tile in the sheet, and its flips: bit 0 across, bit 1 down, which are
|
|
337
|
+
// Tiled's two top bits; LDtk lists them in the order it draws them, so the first in a cell is the
|
|
338
|
+
// bottom one and each after it goes a layer higher
|
|
339
|
+
const depth = new Uint16Array(width * height);
|
|
340
|
+
for (const t of tiles)
|
|
341
|
+
{
|
|
342
|
+
const x = floor(t.px[0] / grid), y = floor(t.px[1] / grid), cell = x + y * width;
|
|
343
|
+
if (x >= 0 && x < width && y >= 0 && y < height)
|
|
344
|
+
(stacks[depth[cell]++] ||= empty())[cell] =
|
|
345
|
+
(t.t + 1 | (t.f & 1 ? 0x80000000 : 0) | (t.f & 2 ? 0x40000000 : 0)) >>> 0;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
else
|
|
349
|
+
(instance.intGridCsv || []).forEach((value, k)=> k < stacks[0].length && (stacks[0][k] = value));
|
|
350
|
+
const visible = instance.__type === 'IntGrid' && !tiles.length ? false : instance.visible !== false;
|
|
351
|
+
stacks.forEach((data, k)=> map.layers.push({id: k ? map.nextlayerid++ : id, name: k ? `${name} (${k + 1})` : name,
|
|
352
|
+
type: 'tilelayer', width, height, data, opacity: instance.__opacity ?? 1, visible, x: 0, y: 0}));
|
|
353
|
+
}
|
|
354
|
+
return map;
|
|
355
|
+
}
|
|
356
|
+
|
|
240
357
|
///////////////////////////////////////////////////////////////////////////////
|
|
241
358
|
// Object layers
|
|
242
359
|
|
|
@@ -331,7 +448,8 @@ function objectLayersMake(tileMapData, object)
|
|
|
331
448
|
const name = object.type || object.class, type = objectLayersTypes.get(name);
|
|
332
449
|
if (!type)
|
|
333
450
|
{
|
|
334
|
-
|
|
451
|
+
// an object with no type, a shape or a text someone drew in Tiled, is not one a game makes
|
|
452
|
+
debug && name && console.warn(`objectLayersLoad: no type added for ${name}, skipped`);
|
|
335
453
|
return;
|
|
336
454
|
}
|
|
337
455
|
const {height=0, tilewidth=1, tileheight=1} = tileMapData;
|