Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/melonjs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
- Tiled: a tile object from an image collection is drawn on its own box when it is resized or flipped in Tiled. A resized one was drawn above and to the left of it, and a flipped one a whole tile width to the right
- Tiled: tile objects are placed by their tileset's object alignment, so a tileset that anchors its objects at the top left, the center or any other point places them there. They were always anchored at the bottom left, or the bottom center on isometric maps
- Tiled: a rotated tile object turns about the point its tileset aligns, as in Tiled, which is its bottom left (bottom center on isometric maps) unless the tileset sets another. It turned about another point, so it was drawn and collided away from where Tiled shows it
- Tiled: the collision shapes of rectangle, polygon and polyline objects on isometric maps are projected with the map's tile ratio, as Tiled draws them. They were projected as if the tiles were 2:1, so on a map with wider or narrower tiles they were squeezed or stretched across and no longer lined up with the walls they outline (thanks @vtxe02023274)
- Tiled: a map with an animated tile object loads, and the object plays its animation. One from a tileset image threw "string parameters for addAnimation are not allowed for standard spritesheet based Texture", and one from an image collection threw a TypeError
- Tiled: in a tile layer, tiles from an image collection whose images are not all the same height each sit on the bottom of their cell, as in Tiled. Every tile was placed as if it were as tall as the tallest image of the collection
- Tiled: a collision object whose shape reaches left of or above its position, like every rectangle on an isometric map, is found by collision checks, `queryAABB` and `raycast` wherever its shape is. Parts of it were missed, so a character could walk through walls; such an object's `pos` is now the top left corner of its shape
Expand Down
16 changes: 15 additions & 1 deletion packages/melonjs/src/level/tiled/TMXObject.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ export default class TMXObject {
*/
#properties = Object.create(null);

/**
* how much wider than 2:1 the map's tiles are, see parseTMXShapes
* @type {number}
*/
#isoRatio = 1;

/**
* @param {TMXTileMap} map - the map the object belongs to
* @param {object} settings - the object as parsed from the map
Expand Down Expand Up @@ -196,6 +202,10 @@ export default class TMXObject {
*/
this.orientation = map.orientation;

if (map.tilewidth > 0 && map.tileheight > 0) {
this.#isoRatio = map.tilewidth / (2 * map.tileheight);
}

/**
* the collision shapes defined for this object
* @type {object[]}
Expand Down Expand Up @@ -436,11 +446,15 @@ export default class TMXObject {
}

// Apply isometric projection, except to a tile object: Tiled draws
// it upright, so its box is already in screen space
// it upright, so its box is already in screen space. toIso() is the
// projection of 2:1 tiles, Tiled uses the map's tile ratio
if (this.orientation === "isometric" && this.gid === null) {
for (let i = 0; i < shapes.length; i++) {
if (typeof shapes[i].toIso === "function") {
shapes[i].toIso();
if (this.#isoRatio !== 1) {
shapes[i].scale(this.#isoRatio, 1);
}
}
}
}
Expand Down
89 changes: 89 additions & 0 deletions packages/melonjs/tests/tmxobject.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,95 @@ describe("TMXObject", () => {
expect(pts[1].x).toBeCloseTo(0, 0);
expect(pts[1].y).toBeCloseTo(100, 0);
});

describe("on isometric maps", () => {
// where Tiled draws a point (x, y) of an object relative to the
// object position: (x - y) * tilewidth / (2 * tileheight), (x + y) / 2
const project = (tw, th, x, y) => {
return [((x - y) * tw) / (2 * th), (x + y) / 2];
};
const isoMap = (tilewidth, tileheight) => {
return { ...mockMap("isometric"), tilewidth, tileheight };
};
const expectPoints = (shape, expected) => {
const pts = shape.points.map((p) => {
return [p.x + shape.pos.x, p.y + shape.pos.y];
});
expect(pts.length).toEqual(expected.length);
for (let i = 0; i < pts.length; i++) {
expect(pts[i][0]).toBeCloseTo(expected[i][0], 6);
expect(pts[i][1]).toBeCloseTo(expected[i][1], 6);
}
};

for (const [tw, th] of [
[64, 32],
[128, 32],
[186, 108],
[32, 32],
]) {
it(`should project a rectangle with ${tw}x${th} tiles`, () => {
const obj = new TMXObject(
isoMap(tw, th),
{ name: "", x: 0, y: 0, width: 96, height: 48 },
0,
);
expectPoints(obj.shapes[0], [
project(tw, th, 0, 0),
project(tw, th, 96, 0),
project(tw, th, 96, 48),
project(tw, th, 0, 48),
]);
});

it(`should project a polygon with ${tw}x${th} tiles`, () => {
const obj = new TMXObject(
isoMap(tw, th),
{
name: "",
x: 0,
y: 0,
polygon: [
{ x: 0, y: 0 },
{ x: 60, y: 10 },
{ x: 20, y: 70 },
],
},
0,
);
expectPoints(obj.shapes[0], [
project(tw, th, 0, 0),
project(tw, th, 60, 10),
project(tw, th, 20, 70),
]);
});

it(`should project polyline segments with ${tw}x${th} tiles`, () => {
const obj = new TMXObject(
isoMap(tw, th),
{
name: "",
x: 0,
y: 0,
polyline: [
{ x: 0, y: 0 },
{ x: 50, y: 0 },
{ x: 50, y: 40 },
],
},
0,
);
expectPoints(obj.shapes[0], [
project(tw, th, 0, 0),
project(tw, th, 50, 0),
]);
expectPoints(obj.shapes[1], [
project(tw, th, 50, 0),
project(tw, th, 50, 40),
]);
});
}
});
});

describe("basic properties", () => {
Expand Down
Loading