@codexo/exojs-ldtk 0.15.3 → 0.16.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/README.md +38 -20
- package/dist/esm/LdtkData.d.ts +59 -14
- package/dist/esm/LdtkData.d.ts.map +1 -0
- package/dist/esm/LdtkData.js +8 -16
- package/dist/esm/LdtkData.js.map +1 -1
- package/dist/esm/LdtkMap.d.ts +25 -6
- package/dist/esm/LdtkMap.d.ts.map +1 -0
- package/dist/esm/LdtkMap.js +88 -63
- package/dist/esm/LdtkMap.js.map +1 -1
- package/dist/esm/LdtkProject.d.ts +107 -0
- package/dist/esm/LdtkProject.d.ts.map +1 -0
- package/dist/esm/LdtkProject.js +136 -0
- package/dist/esm/LdtkProject.js.map +1 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +11 -7
- package/dist/esm/ldtkExtension.d.ts +9 -5
- package/dist/esm/ldtkExtension.d.ts.map +1 -0
- package/dist/esm/ldtkExtension.js +25 -22
- package/dist/esm/ldtkExtension.js.map +1 -1
- package/dist/esm/ldtkLevelEntries.d.ts +4 -3
- package/dist/esm/ldtkLevelEntries.d.ts.map +1 -0
- package/dist/esm/ldtkLevelEntries.js +30 -26
- package/dist/esm/ldtkLevelEntries.js.map +1 -1
- package/dist/esm/ldtkParallax.d.ts +22 -0
- package/dist/esm/ldtkParallax.d.ts.map +1 -0
- package/dist/esm/ldtkParallax.js +22 -0
- package/dist/esm/ldtkParallax.js.map +1 -0
- package/dist/esm/ldtkToMapWorld.d.ts +20 -0
- package/dist/esm/ldtkToMapWorld.d.ts.map +1 -0
- package/dist/esm/ldtkToMapWorld.js +76 -0
- package/dist/esm/ldtkToMapWorld.js.map +1 -0
- package/dist/esm/ldtkToTileMap.d.ts +48 -10
- package/dist/esm/ldtkToTileMap.d.ts.map +1 -0
- package/dist/esm/ldtkToTileMap.js +410 -387
- package/dist/esm/ldtkToTileMap.js.map +1 -1
- package/dist/esm/ldtkTypes.d.ts +47 -0
- package/dist/esm/ldtkTypes.d.ts.map +1 -0
- package/dist/esm/ldtkTypes.js +57 -0
- package/dist/esm/ldtkTypes.js.map +1 -0
- package/dist/esm/loadLdtkMap.d.ts +30 -5
- package/dist/esm/loadLdtkMap.d.ts.map +1 -0
- package/dist/esm/loadLdtkMap.js +139 -133
- package/dist/esm/loadLdtkMap.js.map +1 -1
- package/dist/esm/loadLdtkProject.d.ts +17 -0
- package/dist/esm/loadLdtkProject.d.ts.map +1 -0
- package/dist/esm/loadLdtkProject.js +31 -0
- package/dist/esm/loadLdtkProject.js.map +1 -0
- package/dist/esm/public.d.ts +24 -9
- package/dist/esm/public.d.ts.map +1 -0
- package/dist/esm/public.js +11 -0
- package/dist/esm/url.d.ts +16 -0
- package/dist/esm/url.d.ts.map +1 -0
- package/dist/esm/url.js +31 -0
- package/dist/esm/url.js.map +1 -0
- package/dist/esm/validate.d.ts +27 -0
- package/dist/esm/validate.d.ts.map +1 -0
- package/dist/esm/validate.js +327 -0
- package/dist/esm/validate.js.map +1 -0
- package/package.json +10 -18
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/ldtkBinding.d.ts +0 -22
- package/dist/esm/ldtkBinding.js +0 -31
- package/dist/esm/ldtkBinding.js.map +0 -1
- package/dist/esm/register.d.ts +0 -1
- package/dist/esm/register.js +0 -17
- package/dist/esm/register.js.map +0 -1
|
@@ -1,400 +1,423 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import { getLdtkLevelEntries } from "./ldtkLevelEntries.js";
|
|
2
|
+
import { LDTK_FLIP_X, LDTK_FLIP_Y } from "./LdtkData.js";
|
|
3
|
+
import { resolveLdtkLayerParallax } from "./ldtkParallax.js";
|
|
4
|
+
import { LdtkMap } from "./LdtkMap.js";
|
|
5
|
+
import { ObjectLayer, TileLayer, TileMap, TilePropertyKind } from "@codexo/exojs-tilemap";
|
|
6
6
|
|
|
7
|
+
//#region src/ldtkToTileMap.ts
|
|
7
8
|
/**
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
// ── Level conversion ──────────────────────────────────────────────────────────
|
|
31
|
-
// eslint-disable-next-line complexity
|
|
32
|
-
function convertLevel(level, worldIid, levelIndex, data, tilesets) {
|
|
33
|
-
// Derive map-level tile size from the first non-entity layer, or fall back.
|
|
34
|
-
const gridSize = pickLevelGridSize(level, data.defaultGridSize ?? 16);
|
|
35
|
-
const mapWidth = Math.max(1, Math.ceil(level.pxWid / gridSize));
|
|
36
|
-
const mapHeight = Math.max(1, Math.ceil(level.pxHei / gridSize));
|
|
37
|
-
const runtimeTilesets = [...tilesets.values()];
|
|
38
|
-
const runtimeLayers = [];
|
|
39
|
-
const runtimeObjectLayers = [];
|
|
40
|
-
// LDtk stores layers top-to-bottom (first = top-most); preserve that order.
|
|
41
|
-
const layerInstances = level.layerInstances ?? [];
|
|
42
|
-
let entityCounter = 0;
|
|
43
|
-
for (const layerInst of layerInstances) {
|
|
44
|
-
const layerGridSize = layerInst.__gridSize;
|
|
45
|
-
// Layer IDs must be unique within a TileMap (= one level).
|
|
46
|
-
// Use layerDefUid directly — it is unique per layer definition in the file.
|
|
47
|
-
const layerId = layerInst.layerDefUid;
|
|
48
|
-
switch (layerInst.__type) {
|
|
49
|
-
case 'Tiles':
|
|
50
|
-
case 'AutoLayer': {
|
|
51
|
-
const rLayer = makeTileLayer(layerInst, layerId, runtimeTilesets);
|
|
52
|
-
const tiles = layerInst.__type === 'Tiles'
|
|
53
|
-
? (layerInst.gridTiles ?? [])
|
|
54
|
-
: (layerInst.autoLayerTiles ?? []);
|
|
55
|
-
const tsUid = layerInst.__tilesetDefUid;
|
|
56
|
-
if (tsUid !== undefined) {
|
|
57
|
-
const rts = tilesets.get(tsUid);
|
|
58
|
-
if (rts)
|
|
59
|
-
populateTileLayer(rLayer, tiles, rts, layerGridSize);
|
|
60
|
-
}
|
|
61
|
-
runtimeLayers.push(rLayer);
|
|
62
|
-
break;
|
|
63
|
-
}
|
|
64
|
-
case 'IntGrid': {
|
|
65
|
-
const intGridProperties = buildIntGridProperties(layerInst, data);
|
|
66
|
-
const rLayer = makeTileLayer(layerInst, layerId, runtimeTilesets, intGridProperties);
|
|
67
|
-
// IntGrid layers may carry auto-tiles when "Auto-layer" rules are
|
|
68
|
-
// configured. Use those for rendering; raw intGridCsv is exposed as
|
|
69
|
-
// data-only layer properties (see buildIntGridProperties).
|
|
70
|
-
const autoTiles = layerInst.autoLayerTiles ?? [];
|
|
71
|
-
const tsUid = layerInst.__tilesetDefUid;
|
|
72
|
-
if (autoTiles.length > 0 && tsUid !== undefined) {
|
|
73
|
-
const rts = tilesets.get(tsUid);
|
|
74
|
-
if (rts)
|
|
75
|
-
populateTileLayer(rLayer, autoTiles, rts, layerGridSize);
|
|
76
|
-
}
|
|
77
|
-
runtimeLayers.push(rLayer);
|
|
78
|
-
break;
|
|
79
|
-
}
|
|
80
|
-
case 'Entities': {
|
|
81
|
-
const objects = convertEntityLayer(layerInst, layerGridSize, levelIndex, entityCounter);
|
|
82
|
-
entityCounter += layerInst.entityInstances?.length ?? 0;
|
|
83
|
-
runtimeObjectLayers.push(new ObjectLayer({
|
|
84
|
-
id: layerId,
|
|
85
|
-
name: layerInst.__identifier,
|
|
86
|
-
visible: layerInst.visible,
|
|
87
|
-
opacity: layerInst.opacity ?? 1,
|
|
88
|
-
offsetX: layerInst.pxOffsetX ?? 0,
|
|
89
|
-
offsetY: layerInst.pxOffsetY ?? 0,
|
|
90
|
-
objects,
|
|
91
|
-
}));
|
|
92
|
-
break;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return new TileMap({
|
|
97
|
-
name: level.identifier,
|
|
98
|
-
width: mapWidth,
|
|
99
|
-
height: mapHeight,
|
|
100
|
-
tileWidth: gridSize,
|
|
101
|
-
tileHeight: gridSize,
|
|
102
|
-
tilesets: runtimeTilesets,
|
|
103
|
-
layers: runtimeLayers,
|
|
104
|
-
objectLayers: runtimeObjectLayers,
|
|
105
|
-
// Convert user-defined level fields first, then apply the reserved keys
|
|
106
|
-
// last so a same-named user field can never clobber them. ldtkWorldIid is
|
|
107
|
-
// only added for multi-world documents (worldIid !== undefined) — a
|
|
108
|
-
// single-world document's properties must stay exactly as they were
|
|
109
|
-
// before multi-world support existed.
|
|
110
|
-
properties: {
|
|
111
|
-
...convertFieldInstances(level.fieldInstances ?? []),
|
|
112
|
-
ldtkUid: level.uid,
|
|
113
|
-
ldtkIid: level.iid,
|
|
114
|
-
worldX: level.worldX,
|
|
115
|
-
worldY: level.worldY,
|
|
116
|
-
...(worldIid !== undefined && { ldtkWorldIid: worldIid }),
|
|
117
|
-
},
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
// ── Helpers: TileLayer ────────────────────────────────────────────────────────
|
|
121
|
-
function makeTileLayer(layerInst, layerId, tilesets, properties) {
|
|
122
|
-
return new TileLayer({
|
|
123
|
-
id: layerId,
|
|
124
|
-
name: layerInst.__identifier,
|
|
125
|
-
width: layerInst.__cWid,
|
|
126
|
-
height: layerInst.__cHei,
|
|
127
|
-
tilesets,
|
|
128
|
-
tileWidth: layerInst.__gridSize,
|
|
129
|
-
tileHeight: layerInst.__gridSize,
|
|
130
|
-
visible: layerInst.visible,
|
|
131
|
-
opacity: layerInst.opacity ?? 1,
|
|
132
|
-
offsetX: layerInst.pxOffsetX ?? 0,
|
|
133
|
-
offsetY: layerInst.pxOffsetY ?? 0,
|
|
134
|
-
...(properties && { properties }),
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
function populateTileLayer(layer, tiles, tileset, gridSize) {
|
|
138
|
-
for (const tile of tiles) {
|
|
139
|
-
const tx = Math.floor(tile.px[0] / gridSize);
|
|
140
|
-
const ty = Math.floor(tile.px[1] / gridSize);
|
|
141
|
-
if (!layer.inBounds(tx, ty))
|
|
142
|
-
continue;
|
|
143
|
-
const localTileId = tile.t;
|
|
144
|
-
if (localTileId < 0 || localTileId >= tileset.tileCount)
|
|
145
|
-
continue;
|
|
146
|
-
const f = tile.f;
|
|
147
|
-
layer.setTileAt(tx, ty, {
|
|
148
|
-
tileset,
|
|
149
|
-
localTileId,
|
|
150
|
-
transform: {
|
|
151
|
-
flipX: (f & ldtkFlipX) !== 0,
|
|
152
|
-
flipY: (f & ldtkFlipY) !== 0,
|
|
153
|
-
diagonal: false,
|
|
154
|
-
},
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
// ── Helpers: IntGrid ──────────────────────────────────────────────────────────
|
|
9
|
+
* Convert a raw {@link LdtkData} document into an {@link LdtkMap} containing
|
|
10
|
+
* one runtime {@link TileMap} per LDtk level.
|
|
11
|
+
*
|
|
12
|
+
* Tile layers (`Tiles` / `AutoLayer`) become renderable `TileLayer`s. IntGrid
|
|
13
|
+
* layers become dimension-correct `TileLayer`s - tile data is placed only when
|
|
14
|
+
* the layer carries `autoLayerTiles`. Entity layers become data-only
|
|
15
|
+
* `ObjectLayer`s with entity position, size, and scalar field properties.
|
|
16
|
+
*
|
|
17
|
+
* Pass `options.tilesets` to populate tile data; omit it for structure-only
|
|
18
|
+
* conversion (useful in unit tests that do not need textures).
|
|
19
|
+
*
|
|
20
|
+
* Transparently handles both LDtk root shapes via {@link getLdtkLevelEntries}:
|
|
21
|
+
* single-world (`data.levels`) and multi-world (`data.worlds[].levels`) - in
|
|
22
|
+
* the latter case every converted level's `TileMap.properties` is additionally
|
|
23
|
+
* tagged with its owning world's iid under the reserved `ldtkWorldIid` key.
|
|
24
|
+
*/
|
|
25
|
+
const ldtkToTileMap = (data, options) => {
|
|
26
|
+
const source = options?.source ?? "";
|
|
27
|
+
const tilesets = options?.tilesets ?? /* @__PURE__ */ new Map();
|
|
28
|
+
const levels = getLdtkLevelEntries(data).map((entry, levelIndex) => convertLevel(entry.level, entry.worldIid, levelIndex, data, tilesets));
|
|
29
|
+
return new LdtkMap(source, data, levels);
|
|
30
|
+
};
|
|
159
31
|
/**
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
32
|
+
* Convert one already-resolved LDtk level into its runtime {@link TileMap}.
|
|
33
|
+
*
|
|
34
|
+
* `level.layerInstances` must be present: an externalized level has to have its
|
|
35
|
+
* `.ldtkl` payload merged in first, or the result is an empty map.
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
const ldtkLevelToTileMap = (level, worldIid, levelIndex, data, tilesets) => convertLevel(level, worldIid, levelIndex, data, tilesets);
|
|
39
|
+
const convertLevel = (level, worldIid, levelIndex, data, tilesets) => {
|
|
40
|
+
const gridSize = pickLevelGridSize(level, data.defaultGridSize ?? 16);
|
|
41
|
+
const mapWidth = Math.max(1, Math.ceil(level.pxWid / gridSize));
|
|
42
|
+
const mapHeight = Math.max(1, Math.ceil(level.pxHei / gridSize));
|
|
43
|
+
const runtimeTilesets = [...tilesets.values()];
|
|
44
|
+
const runtimeLayers = [];
|
|
45
|
+
const runtimeObjectLayers = [];
|
|
46
|
+
const layerInstances = level.layerInstances ?? [];
|
|
47
|
+
let entityCounter = 0;
|
|
48
|
+
for (const layerInst of layerInstances) {
|
|
49
|
+
const layerId = layerInst.layerDefUid;
|
|
50
|
+
const parallax = resolveLdtkLayerParallax(data, layerInst);
|
|
51
|
+
switch (layerInst.__type) {
|
|
52
|
+
case "Tiles":
|
|
53
|
+
case "AutoLayer":
|
|
54
|
+
runtimeLayers.push(convertTilesOrAutoLayer(layerInst, layerId, runtimeTilesets, tilesets, parallax));
|
|
55
|
+
break;
|
|
56
|
+
case "IntGrid":
|
|
57
|
+
runtimeLayers.push(convertIntGridLayer(layerInst, layerId, runtimeTilesets, tilesets, data, parallax));
|
|
58
|
+
break;
|
|
59
|
+
case "Entities":
|
|
60
|
+
runtimeObjectLayers.push(convertEntitiesLayer(layerInst, layerId, levelIndex, entityCounter, parallax));
|
|
61
|
+
entityCounter += layerInst.entityInstances?.length ?? 0;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return new TileMap({
|
|
65
|
+
name: level.identifier,
|
|
66
|
+
width: mapWidth,
|
|
67
|
+
height: mapHeight,
|
|
68
|
+
tileWidth: gridSize,
|
|
69
|
+
tileHeight: gridSize,
|
|
70
|
+
tilesets: runtimeTilesets,
|
|
71
|
+
layers: runtimeLayers,
|
|
72
|
+
objectLayers: runtimeObjectLayers,
|
|
73
|
+
properties: buildLdtkLevelProperties(level, worldIid)
|
|
74
|
+
});
|
|
75
|
+
};
|
|
166
76
|
/**
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
77
|
+
* The property bag a level contributes to its runtime representation - user
|
|
78
|
+
* fields first, then the reserved LDtk keys, so a same-named user field can
|
|
79
|
+
* never clobber them. `ldtkWorldIid` is added only for multi-world documents;
|
|
80
|
+
* a single-world document's properties stay exactly as they were before
|
|
81
|
+
* multi-world support existed.
|
|
82
|
+
* @internal
|
|
83
|
+
*/
|
|
84
|
+
const buildLdtkLevelProperties = (level, worldIid) => ({
|
|
85
|
+
...convertFieldInstances(level.fieldInstances ?? []),
|
|
86
|
+
ldtkUid: level.uid,
|
|
87
|
+
ldtkIid: level.iid,
|
|
88
|
+
worldX: level.worldX,
|
|
89
|
+
worldY: level.worldY,
|
|
90
|
+
...worldIid !== void 0 && { ldtkWorldIid: worldIid }
|
|
91
|
+
});
|
|
92
|
+
/** Convert a `Tiles`/`AutoLayer` LDtk layer instance into a runtime {@link TileLayer}. */
|
|
93
|
+
const convertTilesOrAutoLayer = (layerInst, layerId, runtimeTilesets, tilesets, parallax) => {
|
|
94
|
+
const rLayer = makeTileLayer(layerInst, layerId, runtimeTilesets, parallax);
|
|
95
|
+
const tiles = layerInst.__type === "Tiles" ? layerInst.gridTiles ?? [] : layerInst.autoLayerTiles ?? [];
|
|
96
|
+
const tsUid = layerInst.__tilesetDefUid;
|
|
97
|
+
if (tsUid !== void 0 && tsUid !== null) {
|
|
98
|
+
const rts = tilesets.get(tsUid);
|
|
99
|
+
if (rts) populateTileLayer(rLayer, tiles, rts, layerInst.__gridSize);
|
|
100
|
+
}
|
|
101
|
+
return rLayer;
|
|
102
|
+
};
|
|
103
|
+
/** Convert an `IntGrid` LDtk layer instance into a runtime {@link TileLayer}. */
|
|
104
|
+
const convertIntGridLayer = (layerInst, layerId, runtimeTilesets, tilesets, data, parallax) => {
|
|
105
|
+
const intGridProperties = buildIntGridProperties(layerInst, data);
|
|
106
|
+
const rLayer = makeTileLayer(layerInst, layerId, runtimeTilesets, parallax, intGridProperties);
|
|
107
|
+
const autoTiles = layerInst.autoLayerTiles ?? [];
|
|
108
|
+
const tsUid = layerInst.__tilesetDefUid;
|
|
109
|
+
if (autoTiles.length > 0 && tsUid !== void 0 && tsUid !== null) {
|
|
110
|
+
const rts = tilesets.get(tsUid);
|
|
111
|
+
if (rts) populateTileLayer(rLayer, autoTiles, rts, layerInst.__gridSize);
|
|
112
|
+
}
|
|
113
|
+
return rLayer;
|
|
114
|
+
};
|
|
115
|
+
/** Convert an `Entities` LDtk layer instance into a runtime {@link ObjectLayer}. */
|
|
116
|
+
const convertEntitiesLayer = (layerInst, layerId, levelIndex, entityCounter, parallax) => {
|
|
117
|
+
const objects = convertEntityLayer(layerInst, layerInst.__gridSize, levelIndex, entityCounter);
|
|
118
|
+
return new ObjectLayer({
|
|
119
|
+
id: layerId,
|
|
120
|
+
name: layerInst.__identifier,
|
|
121
|
+
visible: layerInst.visible,
|
|
122
|
+
opacity: layerInst.opacity ?? 1,
|
|
123
|
+
offsetX: parallax.offsetX + (layerInst.pxOffsetX ?? 0),
|
|
124
|
+
offsetY: parallax.offsetY + (layerInst.pxOffsetY ?? 0),
|
|
125
|
+
parallaxX: parallax.parallaxX,
|
|
126
|
+
parallaxY: parallax.parallaxY,
|
|
127
|
+
parallaxScale: parallax.parallaxScale,
|
|
128
|
+
objects
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
const makeTileLayer = (layerInst, layerId, tilesets, parallax, properties) => new TileLayer({
|
|
132
|
+
id: layerId,
|
|
133
|
+
name: layerInst.__identifier,
|
|
134
|
+
width: layerInst.__cWid,
|
|
135
|
+
height: layerInst.__cHei,
|
|
136
|
+
tilesets,
|
|
137
|
+
tileWidth: layerInst.__gridSize,
|
|
138
|
+
tileHeight: layerInst.__gridSize,
|
|
139
|
+
visible: layerInst.visible,
|
|
140
|
+
opacity: layerInst.opacity ?? 1,
|
|
141
|
+
offsetX: parallax.offsetX + (layerInst.pxOffsetX ?? 0),
|
|
142
|
+
offsetY: parallax.offsetY + (layerInst.pxOffsetY ?? 0),
|
|
143
|
+
parallaxX: parallax.parallaxX,
|
|
144
|
+
parallaxY: parallax.parallaxY,
|
|
145
|
+
parallaxScale: parallax.parallaxScale,
|
|
146
|
+
...properties && { properties }
|
|
147
|
+
});
|
|
148
|
+
const populateTileLayer = (layer, tiles, tileset, gridSize) => {
|
|
149
|
+
for (const tile of tiles) {
|
|
150
|
+
const tx = Math.floor(tile.px[0] / gridSize);
|
|
151
|
+
const ty = Math.floor(tile.px[1] / gridSize);
|
|
152
|
+
if (!layer.inBounds(tx, ty)) continue;
|
|
153
|
+
const localTileId = tile.t;
|
|
154
|
+
if (localTileId < 0 || localTileId >= tileset.tileCount) continue;
|
|
155
|
+
const f = tile.f;
|
|
156
|
+
layer.setTileAt(tx, ty, {
|
|
157
|
+
tileset,
|
|
158
|
+
localTileId,
|
|
159
|
+
transform: {
|
|
160
|
+
flipX: (f & 1) !== 0,
|
|
161
|
+
flipY: (f & 2) !== 0,
|
|
162
|
+
diagonal: false
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
};
|
|
174
167
|
/**
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
* properties rather than stored as nested structures — the same
|
|
182
|
-
* `properties`-bag mechanism the Tiled adapter already uses for per-layer
|
|
183
|
-
* metadata, just serialized to fit its scalar-only value type.
|
|
184
|
-
*/
|
|
185
|
-
function buildIntGridProperties(layerInst, data) {
|
|
186
|
-
const csv = layerInst.intGridCsv;
|
|
187
|
-
if (!csv || csv.length === 0)
|
|
188
|
-
return undefined;
|
|
189
|
-
const layerDef = data.defs.layers.find(def => def.uid === layerInst.layerDefUid);
|
|
190
|
-
const values = layerDef?.intGridValues ?? [];
|
|
191
|
-
return Object.freeze({
|
|
192
|
-
[ldtkIntGridCsvProperty]: JSON.stringify(csv),
|
|
193
|
-
[ldtkIntGridValuesProperty]: JSON.stringify(values),
|
|
194
|
-
});
|
|
195
|
-
}
|
|
168
|
+
* Reserved {@link TileLayer.properties} key holding the JSON-encoded raw
|
|
169
|
+
* IntGrid CSV array (`readonly number[]`) for a `TileLayer` converted from an
|
|
170
|
+
* LDtk `IntGrid` layer instance. Index = `y * layer.width + x`; `0` = empty.
|
|
171
|
+
* Prefer {@link getLdtkIntGridValueAt} over reading this directly.
|
|
172
|
+
*/
|
|
173
|
+
const ldtkIntGridCsvProperty = "ldtkIntGridCsv";
|
|
196
174
|
/**
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
* can never change afterwards.
|
|
205
|
-
*/
|
|
206
|
-
const intGridCache = new WeakMap();
|
|
175
|
+
* Reserved {@link TileLayer.properties} key holding the JSON-encoded
|
|
176
|
+
* {@link LdtkIntGridValueDef} array for a `TileLayer` converted from an LDtk
|
|
177
|
+
* `IntGrid` layer instance - the raw-int → named/coloured mapping declared on
|
|
178
|
+
* the owning layer definition (`data.defs.layers[].intGridValues`).
|
|
179
|
+
* Prefer {@link getLdtkIntGridValueAt} over reading this directly.
|
|
180
|
+
*/
|
|
181
|
+
const ldtkIntGridValuesProperty = "ldtkIntGridValues";
|
|
207
182
|
/**
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
183
|
+
* Build the reserved IntGrid properties for a `TileLayer` from an LDtk
|
|
184
|
+
* `IntGrid` layer instance, or `undefined` when the layer carries no
|
|
185
|
+
* `intGridCsv` data (nothing to expose).
|
|
186
|
+
*
|
|
187
|
+
* {@link TileLayer.properties} values are scalar-only, so the raw CSV array
|
|
188
|
+
* and the value-definition mapping are JSON-encoded into two reserved string
|
|
189
|
+
* properties rather than stored as nested structures - the same
|
|
190
|
+
* `properties`-bag mechanism the Tiled adapter already uses for per-layer
|
|
191
|
+
* metadata, just serialized to fit its scalar-only value type.
|
|
192
|
+
*/
|
|
193
|
+
const buildIntGridProperties = (layerInst, data) => {
|
|
194
|
+
const csv = layerInst.intGridCsv;
|
|
195
|
+
if (!csv || csv.length === 0) return void 0;
|
|
196
|
+
const values = data.defs.layers.find((def) => def.uid === layerInst.layerDefUid)?.intGridValues ?? [];
|
|
197
|
+
return Object.freeze({
|
|
198
|
+
[ldtkIntGridCsvProperty]: JSON.stringify(csv),
|
|
199
|
+
[ldtkIntGridValuesProperty]: JSON.stringify(values)
|
|
200
|
+
});
|
|
201
|
+
};
|
|
227
202
|
/**
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
* hot path (e.g. per-cell or per-frame collision/classification checks).
|
|
239
|
-
*/
|
|
240
|
-
function getLdtkIntGridValueAt(layer, x, y) {
|
|
241
|
-
if (!layer.inBounds(x, y))
|
|
242
|
-
return undefined;
|
|
243
|
-
const parsed = getParsedIntGridData(layer);
|
|
244
|
-
if (!parsed)
|
|
245
|
-
return undefined;
|
|
246
|
-
const raw = parsed.csv[y * layer.width + x];
|
|
247
|
-
if (raw === undefined || raw === 0)
|
|
248
|
-
return undefined;
|
|
249
|
-
return parsed.values.find(v => v.value === raw);
|
|
250
|
-
}
|
|
251
|
-
// ── Helpers: ObjectLayer ──────────────────────────────────────────────────────
|
|
252
|
-
function convertEntityLayer(layerInst, _gridSize, levelIndex, baseCounter) {
|
|
253
|
-
const instances = layerInst.entityInstances ?? [];
|
|
254
|
-
const objects = [];
|
|
255
|
-
for (let i = 0; i < instances.length; i++) {
|
|
256
|
-
const entity = instances[i];
|
|
257
|
-
if (entity === undefined)
|
|
258
|
-
continue;
|
|
259
|
-
// Build a deterministic numeric id: (levelIndex * 1_000_000) + counter.
|
|
260
|
-
const id = levelIndex * 1_000_000 + baseCounter + i;
|
|
261
|
-
objects.push(convertEntity(entity, id));
|
|
262
|
-
}
|
|
263
|
-
return objects;
|
|
264
|
-
}
|
|
265
|
-
function convertEntity(entity, id) {
|
|
266
|
-
// entity.px is the pivot-adjusted anchor, not the bounding box's top-left
|
|
267
|
-
// corner — undo the pivot offset to recover the corner TileMapObject expects.
|
|
268
|
-
const x = entity.px[0] - entity.width * entity.__pivot[0];
|
|
269
|
-
const y = entity.px[1] - entity.height * entity.__pivot[1];
|
|
270
|
-
return {
|
|
271
|
-
kind: 'rectangle',
|
|
272
|
-
id,
|
|
273
|
-
name: entity.__identifier,
|
|
274
|
-
type: entity.__identifier,
|
|
275
|
-
x,
|
|
276
|
-
y,
|
|
277
|
-
width: entity.width,
|
|
278
|
-
height: entity.height,
|
|
279
|
-
rotation: 0,
|
|
280
|
-
visible: true,
|
|
281
|
-
properties: convertFieldInstances(entity.fieldInstances),
|
|
282
|
-
};
|
|
283
|
-
}
|
|
203
|
+
* Per-`TileLayer` cache of parsed IntGrid CSV/value-defs data, populated
|
|
204
|
+
* lazily on first {@link getLdtkIntGridValueAt} lookup for a given layer.
|
|
205
|
+
*
|
|
206
|
+
* Keyed by the `TileLayer` instance itself (`WeakMap`), so an entry is
|
|
207
|
+
* naturally garbage-collected once the layer it was derived from is no
|
|
208
|
+
* longer referenced - no manual invalidation needed since
|
|
209
|
+
* {@link TileLayer.properties} is frozen and copied at construction time and
|
|
210
|
+
* can never change afterwards.
|
|
211
|
+
*/
|
|
212
|
+
const intGridCache = /* @__PURE__ */ new WeakMap();
|
|
284
213
|
/**
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
}
|
|
303
|
-
return Object.freeze(out);
|
|
304
|
-
}
|
|
214
|
+
* Parse (or retrieve from {@link intGridCache}) the IntGrid CSV/value-defs
|
|
215
|
+
* data attached to `layer`, or `undefined` when `layer` carries no such data
|
|
216
|
+
* (not converted from an IntGrid layer instance).
|
|
217
|
+
*/
|
|
218
|
+
const getParsedIntGridData = (layer) => {
|
|
219
|
+
const cached = intGridCache.get(layer);
|
|
220
|
+
if (cached) return cached;
|
|
221
|
+
const csvRaw = layer.properties[ldtkIntGridCsvProperty];
|
|
222
|
+
const valuesRaw = layer.properties[ldtkIntGridValuesProperty];
|
|
223
|
+
if (typeof csvRaw !== "string" || typeof valuesRaw !== "string") return void 0;
|
|
224
|
+
const parsed = {
|
|
225
|
+
csv: JSON.parse(csvRaw),
|
|
226
|
+
values: JSON.parse(valuesRaw)
|
|
227
|
+
};
|
|
228
|
+
intGridCache.set(layer, parsed);
|
|
229
|
+
return parsed;
|
|
230
|
+
};
|
|
305
231
|
/**
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
}
|
|
232
|
+
* Look up the named/coloured {@link LdtkIntGridValueDef} at a tile coordinate
|
|
233
|
+
* on a `TileLayer` converted from an LDtk `IntGrid` layer instance.
|
|
234
|
+
*
|
|
235
|
+
* Returns `undefined` when the coordinate is out of bounds, the cell's raw
|
|
236
|
+
* value is `0` (empty), the raw value has no matching definition, or `layer`
|
|
237
|
+
* was not converted from an IntGrid layer instance (no IntGrid data attached
|
|
238
|
+
* via {@link ldtkIntGridCsvProperty} / {@link ldtkIntGridValuesProperty}).
|
|
239
|
+
*
|
|
240
|
+
* The underlying JSON-encoded CSV/value-defs properties are parsed once per
|
|
241
|
+
* layer and cached (see {@link getParsedIntGridData}) - safe to call from a
|
|
242
|
+
* hot path (e.g. per-cell or per-frame collision/classification checks).
|
|
243
|
+
*/
|
|
244
|
+
const getLdtkIntGridValueAt = (layer, x, y) => {
|
|
245
|
+
if (!layer.inBounds(x, y) || layer.width === void 0) return void 0;
|
|
246
|
+
const parsed = getParsedIntGridData(layer);
|
|
247
|
+
if (!parsed) return void 0;
|
|
248
|
+
const raw = parsed.csv[y * layer.width + x];
|
|
249
|
+
if (raw === void 0 || raw === 0) return void 0;
|
|
250
|
+
return parsed.values.find((v) => v.value === raw);
|
|
251
|
+
};
|
|
252
|
+
/**
|
|
253
|
+
* Per-cell collision classification for a `TileLayer` converted from an LDtk
|
|
254
|
+
* `IntGrid` layer instance, or `undefined` when the layer carries no IntGrid
|
|
255
|
+
* data (not converted from an IntGrid layer instance, or unbounded).
|
|
256
|
+
*
|
|
257
|
+
* The classification is the value's LDtk-authored identifier, falling back to
|
|
258
|
+
* the raw integer as a string when the value definition declares none. Cells
|
|
259
|
+
* whose raw value is `0` (empty) classify as `null`. Distinct IntGrid values
|
|
260
|
+
* stay distinct strings: what they mean - solid, water, hazard - is the
|
|
261
|
+
* caller's to decide, and an unrecognised value is data, not an error.
|
|
262
|
+
*
|
|
263
|
+
* The result reads the layer's frozen IntGrid data, so it is stable for the
|
|
264
|
+
* lifetime of the layer and safe to hand to a consumer that samples it while
|
|
265
|
+
* building geometry.
|
|
266
|
+
*
|
|
267
|
+
* ```ts
|
|
268
|
+
* const geometry = buildTileCollisionGeometry(layer, { cells: createLdtkIntGridCellSource(layer) });
|
|
269
|
+
* ```
|
|
270
|
+
*/
|
|
271
|
+
const createLdtkIntGridCellSource = (layer) => {
|
|
272
|
+
const parsed = getParsedIntGridData(layer);
|
|
273
|
+
const width = layer.width;
|
|
274
|
+
if (!parsed || width === void 0) return void 0;
|
|
275
|
+
const identifiers = /* @__PURE__ */ new Map();
|
|
276
|
+
for (const value of parsed.values) identifiers.set(value.value, value.identifier ?? String(value.value));
|
|
277
|
+
return (tx, ty) => {
|
|
278
|
+
if (!layer.inBounds(tx, ty)) return null;
|
|
279
|
+
const raw = parsed.csv[ty * width + tx];
|
|
280
|
+
if (raw === void 0 || raw === 0) return null;
|
|
281
|
+
return identifiers.get(raw) ?? String(raw);
|
|
282
|
+
};
|
|
283
|
+
};
|
|
284
|
+
const convertEntityLayer = (layerInst, _gridSize, levelIndex, baseCounter) => {
|
|
285
|
+
const instances = layerInst.entityInstances ?? [];
|
|
286
|
+
const objects = [];
|
|
287
|
+
for (let i = 0; i < instances.length; i++) {
|
|
288
|
+
const entity = instances[i];
|
|
289
|
+
if (entity === void 0) continue;
|
|
290
|
+
const id = levelIndex * 1e6 + baseCounter + i;
|
|
291
|
+
objects.push(convertEntity(entity, id));
|
|
292
|
+
}
|
|
293
|
+
return objects;
|
|
294
|
+
};
|
|
295
|
+
const convertEntity = (entity, id) => {
|
|
296
|
+
const x = entity.px[0] - entity.width * entity.__pivot[0];
|
|
297
|
+
const y = entity.px[1] - entity.height * entity.__pivot[1];
|
|
298
|
+
return {
|
|
299
|
+
kind: "rectangle",
|
|
300
|
+
id,
|
|
301
|
+
sourceId: entity.iid,
|
|
302
|
+
name: entity.__identifier,
|
|
303
|
+
type: entity.__identifier,
|
|
304
|
+
x,
|
|
305
|
+
y,
|
|
306
|
+
width: entity.width,
|
|
307
|
+
height: entity.height,
|
|
308
|
+
rotation: 0,
|
|
309
|
+
visible: true,
|
|
310
|
+
properties: convertFieldInstances(entity.fieldInstances),
|
|
311
|
+
source: entity
|
|
312
|
+
};
|
|
313
|
+
};
|
|
314
|
+
/**
|
|
315
|
+
* Project LDtk field instances to a flat {@link TileProperties} bag.
|
|
316
|
+
* Every LDtk field type maps to a canonical {@link TilePropertyValue}
|
|
317
|
+
* (scalars pass through; `Point`/`EntityRef`/`Tile` become their tagged
|
|
318
|
+
* structured variants; `Array<T>` fields recursively convert each element).
|
|
319
|
+
* A field whose raw `__value` is `null` (LDtk's "not set" convention) is
|
|
320
|
+
* omitted from the bag entirely, matching the property-absent case rather
|
|
321
|
+
* than a present-but-null value.
|
|
322
|
+
*/
|
|
323
|
+
const convertFieldInstances = (fields) => {
|
|
324
|
+
if (fields.length === 0) return Object.freeze({});
|
|
325
|
+
const out = {};
|
|
326
|
+
for (const field of fields) {
|
|
327
|
+
const value = convertField(field);
|
|
328
|
+
if (value !== void 0) out[field.__identifier] = value;
|
|
329
|
+
}
|
|
330
|
+
return Object.freeze(out);
|
|
331
|
+
};
|
|
332
|
+
/**
|
|
333
|
+
* Type guard narrowing to the `Array<T>` member of {@link LdtkFieldInstance}.
|
|
334
|
+
* `String.prototype.startsWith` alone does not narrow a template-literal
|
|
335
|
+
* union member for the compiler; a predicate on `field` itself does.
|
|
336
|
+
*/
|
|
337
|
+
const isLdtkArrayField = (field) => field.__type.startsWith("Array<");
|
|
313
338
|
/** Convert one {@link LdtkFieldInstance} to its canonical {@link TilePropertyValue}, or `undefined` for a `null` (unset) field. */
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
341
|
-
throw new Error(`convertFieldInstances: unrecognised LDtk field type "${field.__type}".`);
|
|
342
|
-
}
|
|
339
|
+
const convertField = (field) => {
|
|
340
|
+
switch (field.__type) {
|
|
341
|
+
case "Int":
|
|
342
|
+
case "Float":
|
|
343
|
+
case "Bool":
|
|
344
|
+
case "String":
|
|
345
|
+
case "Multilines":
|
|
346
|
+
case "Color":
|
|
347
|
+
case "FilePath":
|
|
348
|
+
case "Enum":
|
|
349
|
+
case "Point":
|
|
350
|
+
case "EntityRef":
|
|
351
|
+
case "Tile": return mapLdtkFieldValue(field.__type, field.__value);
|
|
352
|
+
}
|
|
353
|
+
if (isLdtkArrayField(field)) {
|
|
354
|
+
if (field.__value === null) return void 0;
|
|
355
|
+
const elementType = field.__type.slice(6, -1);
|
|
356
|
+
const elements = [];
|
|
357
|
+
for (const raw of field.__value) {
|
|
358
|
+
const converted = mapLdtkFieldValue(elementType, raw);
|
|
359
|
+
if (converted !== void 0) elements.push(converted);
|
|
360
|
+
}
|
|
361
|
+
return Object.freeze(elements);
|
|
362
|
+
}
|
|
363
|
+
throw new Error(`convertFieldInstances: unrecognised LDtk field type "${field.__type}".`);
|
|
364
|
+
};
|
|
343
365
|
/**
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
}
|
|
366
|
+
* Map a single raw LDtk field value (or array element) to its canonical
|
|
367
|
+
* {@link TilePropertyValue}, given the LDtk type name it was declared with.
|
|
368
|
+
* Shared by {@link convertField} (top-level fields) and array-element
|
|
369
|
+
* conversion (`typeName` is the `T` extracted from an `Array<T>` field).
|
|
370
|
+
* Returns `undefined` for a `null` value or an unrecognised `typeName`.
|
|
371
|
+
*/
|
|
372
|
+
const mapLdtkFieldValue = (typeName, value) => {
|
|
373
|
+
if (value === null || value === void 0) return void 0;
|
|
374
|
+
switch (typeName) {
|
|
375
|
+
case "Int":
|
|
376
|
+
case "Float":
|
|
377
|
+
case "Bool":
|
|
378
|
+
case "String":
|
|
379
|
+
case "Multilines":
|
|
380
|
+
case "Color":
|
|
381
|
+
case "FilePath":
|
|
382
|
+
case "Enum": return value;
|
|
383
|
+
case "Point": {
|
|
384
|
+
const v = value;
|
|
385
|
+
return {
|
|
386
|
+
kind: TilePropertyKind.Point,
|
|
387
|
+
cx: v.cx,
|
|
388
|
+
cy: v.cy
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
case "EntityRef": {
|
|
392
|
+
const v = value;
|
|
393
|
+
return {
|
|
394
|
+
kind: TilePropertyKind.ObjectRef,
|
|
395
|
+
id: v.entityIid,
|
|
396
|
+
layerIid: v.layerIid,
|
|
397
|
+
levelIid: v.levelIid,
|
|
398
|
+
worldIid: v.worldIid
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
case "Tile": {
|
|
402
|
+
const v = value;
|
|
403
|
+
return {
|
|
404
|
+
kind: TilePropertyKind.TileRef,
|
|
405
|
+
tilesetUid: v.tilesetUid,
|
|
406
|
+
x: v.x,
|
|
407
|
+
y: v.y,
|
|
408
|
+
w: v.w,
|
|
409
|
+
h: v.h
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
default: return;
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
const pickLevelGridSize = (level, fallback) => {
|
|
416
|
+
const instances = level.layerInstances ?? [];
|
|
417
|
+
for (const layer of instances) if (layer.__type !== "Entities" && layer.__gridSize > 0) return layer.__gridSize;
|
|
418
|
+
return fallback;
|
|
419
|
+
};
|
|
398
420
|
|
|
399
|
-
|
|
400
|
-
|
|
421
|
+
//#endregion
|
|
422
|
+
export { buildLdtkLevelProperties, createLdtkIntGridCellSource, getLdtkIntGridValueAt, ldtkIntGridCsvProperty, ldtkIntGridValuesProperty, ldtkLevelToTileMap, ldtkToTileMap };
|
|
423
|
+
//# sourceMappingURL=ldtkToTileMap.js.map
|