littlejsengine 1.24.0 → 1.24.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.
@@ -141,7 +141,7 @@ const tileLayersTiledFlips = [[0,0], [3,1], [2,1], [3,0], [0,1], [1,0], [2,0], [
141
141
  * @param {Object} tileMapData - Level data from exported data
142
142
  * @param {TileInfo} [tileInfo] - Default tile info (used for size and texture), tile() by default, none when no image is loaded
143
143
  * @param {number} [renderOrder] - Render order of the top layer
144
- * @param {number} [collisionLayer] - Layer to use for collision if any
144
+ * @param {number|string} [collisionLayer] - Layer to use for collision if any, by its index or its name
145
145
  * @param {boolean} [draw] - Should the layer be drawn automatically
146
146
  * @return {Array<TileCollisionLayer>}
147
147
  * @memberof TileLayers */
@@ -173,7 +173,8 @@ function tileLayersLoad(tileMapData, tileInfo=tileLayerDefaultTile(), renderOrde
173
173
  const margin = tileset.margin || 0, spacing = tileset.spacing || 0, size = tileInfo.size;
174
174
  const width = tileInfo.textureInfo?.size.x || 0;
175
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);
176
+ const origin = tileInfo.pos.subtract(vec2(tileInfo.padding)); // where the sheet starts, in an atlas too
177
+ tileInfo = new TileInfo(origin.add(vec2(margin)), size, tileInfo.textureInfo, spacing / 2, tileInfo.bleed, columns);
177
178
  }
178
179
 
179
180
  // flatten group layers in order, a group's color and visibility carry to the layers inside it
@@ -204,6 +205,14 @@ function tileLayersLoad(tileMapData, tileInfo=tileLayerDefaultTile(), renderOrde
204
205
  };
205
206
  addLayers(tileMapData.layers, WHITE, true);
206
207
 
208
+ // a layer named for collision is the first tile layer of that name, which stays the same when layers are added
209
+ if (typeof collisionLayer === 'string')
210
+ {
211
+ const name = collisionLayer;
212
+ collisionLayer = layers.findIndex((l)=> l.color && l.dataLayer.name === name);
213
+ ASSERT(collisionLayer >= 0, 'no tile layer named ' + name);
214
+ }
215
+
207
216
  // create tile layers and fill with data
208
217
  const tileLayers = [];
209
218
  const levelSize = vec2(tileMapData.width, tileMapData.height);
@@ -259,31 +268,38 @@ const tileLayersLDtkTypes = {Int: 'int', Float: 'float', Bool: 'bool', String: '
259
268
 
260
269
  /**
261
270
  * 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
271
+ * - Each Tiles, AutoLayer and IntGrid layer is a tile layer, the bottom one first as in Tiled; where LDtk stacks
272
+ * tiles in a cell, an edge over a fill, each tile over another goes in a layer of its own just above, named with
273
+ * (2), (3) and so on, and a tile LDtk draws see-through goes in one of its own with that opacity, times the
274
+ * layer's, named with it, like Ground .25
275
+ * - An IntGrid layer is a hidden layer of its values under its own name, for collision, whatever tiles its rules
276
+ * make, which are layers over it named with tiles, like Collisions tiles; pass the name as collisionLayer, since
277
+ * the stacked and see-through layers change the indices from level to level:
278
+ * tileLayersLoad(map, tile(0, 16), 0, 'Collisions')
267
279
  * - An Entities layer is an object layer: an entity's name is its type for objectLayersAddType, it is placed at
268
280
  * its middle, and its Int, Float, Bool, String, Color and FilePath fields are its properties (an enum is a string)
269
281
  * - The tileset is the first tile layer's, with its padding and spacing; give tileLayersLoad a tile info of its image
270
282
  * - 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
283
+ * another grid size or another tileset is left out, with a warning in debug builds, and a project with no tileset
284
+ * keeps its tiles for the tile info the game gives; a project of several worlds has its levels counted in order
285
+ * - Layer offsets are not read
272
286
  * - The level editor edits the map this returns, and saves it as a Tiled map
273
287
  * @param {Object} ldtk - The LDtk project, its JSON
274
288
  * @param {number|string} [level] - Which level, by its index or its identifier
275
289
  * @return {Object} - A Tiled map: width, height, tilewidth, tileheight, tilesets and layers
276
290
  * @example
277
291
  * const map = tileLayersFromLDtk(await fetchJSON('world.ldtk'), 'Level_0');
278
- * const layers = tileLayersLoad(map, tile(0, 16), 0, 1); // layer 1 is solid
292
+ * const layers = tileLayersLoad(map, tile(0, 16), 0, 'Collisions'); // its IntGrid layer is solid
279
293
  * objectLayersLoad(map);
280
294
  * @memberof TileLayers */
281
295
  function tileLayersFromLDtk(ldtk, level=0)
282
296
  {
283
- const levels = ldtk?.levels || [];
297
+ const levels = ldtk?.levels?.length ? ldtk.levels : (ldtk?.worlds || []).flatMap((w)=> w.levels || []);
284
298
  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');
299
+ if (!data)
300
+ throw new Error('tileLayersFromLDtk: no level ' + level);
301
+ if (!isArray(data.layerInstances))
302
+ throw new Error('tileLayersFromLDtk: level ' + data.identifier + ' has no layers, levels saved as separate files are not read');
287
303
  const instances = data.layerInstances;
288
304
  const tiled = (l)=> l.__type !== 'Entities';
289
305
  const first = instances.find((l)=> tiled(l) && l.__tilesetDefUid != undefined) || instances.find(tiled) || instances[0];
@@ -323,33 +339,53 @@ function tileLayersFromLDtk(ldtk, level=0)
323
339
  continue;
324
340
  }
325
341
  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)
342
+ const ownTiles = !tiles.length || !tileset ||
343
+ instance.__tilesetDefUid === tileset.uid && tileset.tileGridSize === grid;
344
+ if (instance.__gridSize !== grid || instance.__cWid !== width || instance.__cHei !== height || !ownTiles)
329
345
  {
330
346
  debug && console.warn(`tileLayersFromLDtk: layer ${name} has another grid or tileset, left out`);
331
347
  continue;
332
348
  }
333
- const empty = ()=> new Array(width * height).fill(0), stacks = [empty()];
334
- if (tiles.length)
349
+ // an IntGrid layer's values, hidden, under its own name, for collision whatever its tiles do
350
+ const empty = ()=> new Array(width * height).fill(0), opacity = instance.__opacity ?? 1;
351
+ const values = instance.__type === 'IntGrid';
352
+ if (values)
353
+ {
354
+ const data = empty();
355
+ (instance.intGridCsv || []).forEach((value, k)=> k < data.length && (data[k] = value));
356
+ map.layers.push({id, name, type: 'tilelayer', width, height, data, opacity, visible: false, x: 0, y: 0});
357
+ if (!tiles.length) continue;
358
+ }
359
+
360
+ // a layer for each depth in a cell and each opacity, the first tiles drawn solid first
361
+ const tilesName = values ? name + ' tiles' : name;
362
+ const stacks = [{depth: 0, alpha: 1, data: empty()}], stack = (depth, alpha)=>
363
+ {
364
+ let s = stacks.find((s)=> s.depth === depth && s.alpha === alpha);
365
+ s || stacks.push(s = {depth, alpha, data: empty()});
366
+ return s.data;
367
+ };
335
368
  {
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
369
+ // a tile's place in pixels, its tile in the sheet, its flips, bit 0 across and bit 1 down, which are
370
+ // Tiled's two top bits, and its opacity; LDtk lists them in the order it draws them, so the first in a
371
+ // cell is the bottom one and each after it goes a layer higher
339
372
  const depth = new Uint16Array(width * height);
340
373
  for (const t of tiles)
341
374
  {
342
375
  const x = floor(t.px[0] / grid), y = floor(t.px[1] / grid), cell = x + y * width;
343
376
  if (x >= 0 && x < width && y >= 0 && y < height)
344
- (stacks[depth[cell]++] ||= empty())[cell] =
377
+ stack(depth[cell]++, round(clamp(t.a ?? 1) * 1e3) / 1e3)[cell] =
345
378
  (t.t + 1 | (t.f & 1 ? 0x80000000 : 0) | (t.f & 2 ? 0x40000000 : 0)) >>> 0;
346
379
  }
347
380
  }
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}));
381
+ const visible = instance.visible !== false;
382
+ stacks.sort((a, b)=> a.depth - b.depth || b.alpha - a.alpha);
383
+ for (const {depth, alpha, data} of stacks)
384
+ {
385
+ const layerName = tilesName + (depth ? ` (${depth + 1})` : '') + (alpha < 1 ? ' ' + String(alpha).replace(/^0/, '') : '');
386
+ map.layers.push({id: values || depth || alpha < 1 ? map.nextlayerid++ : id, name: layerName, type: 'tilelayer',
387
+ width, height, data, opacity: opacity * alpha, visible, x: 0, y: 0});
388
+ }
353
389
  }
354
390
  return map;
355
391
  }