phaser
Version:
A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.
702 lines (586 loc) • 23.7 kB
JavaScript
vi.mock('../../../../src/tilemaps/Tile', function ()
{
function MockTile (layer, index, x, y, width, height)
{
this.layer = layer;
this.index = index;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.flipX = false;
this.rotation = 0;
}
return MockTile;
});
var ParseTileLayers = require('../../../../src/tilemaps/parsers/tiled/ParseTileLayers');
describe('Phaser.Tilemaps.Parsers.Tiled.ParseTileLayers', function ()
{
function makeFiniteJSON (layers, overrides)
{
return Object.assign({
infinite: false,
orientation: 'orthogonal',
tilewidth: 32,
tileheight: 32,
layers: layers
}, overrides);
}
function makeTileLayer (overrides)
{
return Object.assign({
type: 'tilelayer',
name: 'layer1',
id: 1,
x: 0,
y: 0,
width: 2,
height: 2,
opacity: 1,
visible: true,
data: [1, 2, 3, 4]
}, overrides);
}
function makeInfiniteJSON (layers, overrides)
{
return Object.assign({
infinite: true,
orientation: 'orthogonal',
tilewidth: 32,
tileheight: 32,
layers: layers
}, overrides);
}
function makeInfiniteTileLayer (overrides)
{
return Object.assign({
type: 'tilelayer',
name: 'layer1',
id: 1,
x: 0,
y: 0,
startx: 0,
starty: 0,
width: 2,
height: 2,
opacity: 1,
visible: true,
chunks: [
{
x: 0, y: 0,
width: 2, height: 2,
data: [1, 2, 3, 4]
}
]
}, overrides);
}
// -------------------------------------------------------------------------
// Return value structure
// -------------------------------------------------------------------------
it('should return an empty array when json has no layers', function ()
{
var json = makeFiniteJSON([]);
var result = ParseTileLayers(json, false);
expect(result).toEqual([]);
});
it('should return an empty array when no tilelayer type layers exist', function ()
{
var json = makeFiniteJSON([
{ type: 'objectgroup', name: 'objects', id: 1 },
{ type: 'imagelayer', name: 'bg', id: 2 }
]);
var result = ParseTileLayers(json, false);
expect(result).toEqual([]);
});
it('should return one LayerData for a single tilelayer', function ()
{
var json = makeFiniteJSON([ makeTileLayer() ]);
var result = ParseTileLayers(json, false);
expect(result.length).toBe(1);
});
it('should return one LayerData per tilelayer when multiple exist', function ()
{
var json = makeFiniteJSON([
makeTileLayer({ name: 'a', id: 1 }),
makeTileLayer({ name: 'b', id: 2 })
]);
var result = ParseTileLayers(json, false);
expect(result.length).toBe(2);
});
// -------------------------------------------------------------------------
// Finite map — LayerData properties
// -------------------------------------------------------------------------
it('should set the layer name from the JSON', function ()
{
var json = makeFiniteJSON([ makeTileLayer({ name: 'myLayer' }) ]);
var result = ParseTileLayers(json, false);
expect(result[0].name).toBe('myLayer');
});
it('should set the layer id from the JSON', function ()
{
var json = makeFiniteJSON([ makeTileLayer({ id: 7 }) ]);
var result = ParseTileLayers(json, false);
expect(result[0].id).toBe(7);
});
it('should set layer width and height from the JSON', function ()
{
var json = makeFiniteJSON([ makeTileLayer({ width: 4, height: 3, data: new Array(12).fill(1) }) ]);
var result = ParseTileLayers(json, false);
expect(result[0].width).toBe(4);
expect(result[0].height).toBe(3);
});
it('should set tile dimensions from the JSON map tilewidth and tileheight', function ()
{
var json = makeFiniteJSON([ makeTileLayer() ], { tilewidth: 16, tileheight: 16 });
var result = ParseTileLayers(json, false);
expect(result[0].tileWidth).toBe(16);
expect(result[0].tileHeight).toBe(16);
});
it('should set layer alpha as product of layer opacity and group opacity', function ()
{
var json = makeFiniteJSON([ makeTileLayer({ opacity: 0.5 }) ]);
var result = ParseTileLayers(json, false);
expect(result[0].alpha).toBeCloseTo(0.5);
});
it('should set layer visible from the JSON', function ()
{
var json = makeFiniteJSON([ makeTileLayer({ visible: false }) ]);
var result = ParseTileLayers(json, false);
expect(result[0].visible).toBe(false);
});
it('should set layer x position including offsetx and layer x', function ()
{
var json = makeFiniteJSON([ makeTileLayer({ x: 5, offsetx: 10 }) ]);
var result = ParseTileLayers(json, false);
expect(result[0].x).toBe(15);
});
it('should set layer y position including offsety and layer y', function ()
{
var json = makeFiniteJSON([ makeTileLayer({ y: 3, offsety: 7 }) ]);
var result = ParseTileLayers(json, false);
expect(result[0].y).toBe(10);
});
it('should set layer properties from the JSON', function ()
{
var props = [ { name: 'speed', type: 'int', value: 5 } ];
var json = makeFiniteJSON([ makeTileLayer({ properties: props }) ]);
var result = ParseTileLayers(json, false);
expect(result[0].properties).toEqual(props);
});
it('should default properties to empty array when not present', function ()
{
var layer = makeTileLayer();
delete layer.properties;
var json = makeFiniteJSON([ layer ]);
var result = ParseTileLayers(json, false);
expect(result[0].properties).toEqual([]);
});
// -------------------------------------------------------------------------
// Finite map — tile data (output grid)
// -------------------------------------------------------------------------
it('should produce a 2D data array with correct row and column counts', function ()
{
var json = makeFiniteJSON([ makeTileLayer({ width: 2, height: 2, data: [1, 2, 3, 4] }) ]);
var result = ParseTileLayers(json, false);
expect(result[0].data.length).toBe(2);
expect(result[0].data[0].length).toBe(2);
});
it('should create Tile objects for gids greater than zero', function ()
{
var json = makeFiniteJSON([ makeTileLayer({ width: 2, height: 1, data: [1, 2] }) ]);
var result = ParseTileLayers(json, false);
expect(result[0].data[0][0]).not.toBeNull();
expect(result[0].data[0][0].index).toBe(1);
expect(result[0].data[0][1].index).toBe(2);
});
it('should place null for empty gids (0) when insertNull is true', function ()
{
var json = makeFiniteJSON([ makeTileLayer({ width: 2, height: 1, data: [0, 1] }) ]);
var result = ParseTileLayers(json, true);
expect(result[0].data[0][0]).toBeNull();
});
it('should place a Tile with index -1 for empty gids when insertNull is false', function ()
{
var json = makeFiniteJSON([ makeTileLayer({ width: 2, height: 1, data: [0, 1] }) ]);
var result = ParseTileLayers(json, false);
expect(result[0].data[0][0]).not.toBeNull();
expect(result[0].data[0][0].index).toBe(-1);
});
it('should set tile x and y coordinates based on position in data array', function ()
{
var json = makeFiniteJSON([ makeTileLayer({ width: 2, height: 2, data: [1, 2, 3, 4] }) ]);
var result = ParseTileLayers(json, false);
// first row: x=0,y=0 and x=1,y=0
expect(result[0].data[0][0].x).toBe(0);
expect(result[0].data[0][0].y).toBe(0);
expect(result[0].data[0][1].x).toBe(1);
expect(result[0].data[0][1].y).toBe(0);
// second row: x=0,y=1 and x=1,y=1
expect(result[0].data[1][0].x).toBe(0);
expect(result[0].data[1][0].y).toBe(1);
});
it('should apply flipX=true on a horizontally flipped tile', function ()
{
// FLIPPED_HORIZONTAL = 0x80000000, no other flags => rotation=0, flipped=true
var gid = 0x80000000 | 1;
var json = makeFiniteJSON([ makeTileLayer({ width: 1, height: 1, data: [gid] }) ]);
var result = ParseTileLayers(json, false);
expect(result[0].data[0][0].flipX).toBe(true);
expect(result[0].data[0][0].rotation).toBe(0);
});
it('should apply rotation PI on a tile flipped both horizontally and vertically', function ()
{
// H+V => rotation=Math.PI, flipped=false
var gid = (0x80000000 | 0x40000000) | 1;
var json = makeFiniteJSON([ makeTileLayer({ width: 1, height: 1, data: [gid] }) ]);
var result = ParseTileLayers(json, false);
expect(result[0].data[0][0].rotation).toBeCloseTo(Math.PI);
expect(result[0].data[0][0].flipX).toBe(false);
});
// -------------------------------------------------------------------------
// Compressed layers
// -------------------------------------------------------------------------
it('should skip compressed layers and emit a console warning', function ()
{
var warnSpy = vi.spyOn(console, 'warn').mockImplementation(function () {});
var compressedLayer = makeTileLayer({ name: 'compressed', compression: 'zlib' });
delete compressedLayer.data;
var json = makeFiniteJSON([ compressedLayer ]);
var result = ParseTileLayers(json, false);
expect(result.length).toBe(0);
expect(warnSpy).toHaveBeenCalled();
warnSpy.mockRestore();
});
it('should include the layer name in the compression warning message', function ()
{
var warnings = [];
var warnSpy = vi.spyOn(console, 'warn').mockImplementation(function (msg)
{
warnings.push(msg);
});
var compressedLayer = makeTileLayer({ name: 'secretLayer', compression: 'gzip' });
delete compressedLayer.data;
var json = makeFiniteJSON([ compressedLayer ]);
ParseTileLayers(json, false);
expect(warnings[0]).toContain('secretLayer');
warnSpy.mockRestore();
});
// -------------------------------------------------------------------------
// Base64 encoded layers
// -------------------------------------------------------------------------
it('should decode a base64-encoded layer and remove the encoding property', function ()
{
// Set up window.atob for the node environment
if (typeof global.window === 'undefined')
{
global.window = { atob: function (str) { return Buffer.from(str, 'base64').toString('binary'); } };
}
// [1, 2, 3, 4] encoded as little-endian uint32 bytes in base64
// bytes: 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00
var base64Data = Buffer.from(new Uint8Array([1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0])).toString('base64');
var layer = makeTileLayer({ encoding: 'base64', width: 2, height: 2 });
delete layer.data;
layer.data = base64Data;
var json = makeFiniteJSON([ layer ]);
var result = ParseTileLayers(json, false);
expect(result.length).toBe(1);
expect(result[0].data[0][0].index).toBe(1);
expect(layer.encoding).toBeUndefined();
delete global.window;
});
it('should decode base64-encoded chunks for infinite maps', function ()
{
if (typeof global.window === 'undefined')
{
global.window = { atob: function (str) { return Buffer.from(str, 'base64').toString('binary'); } };
}
var base64Data = Buffer.from(new Uint8Array([1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0])).toString('base64');
var layer = makeInfiniteTileLayer({ encoding: 'base64' });
layer.chunks[0].data = base64Data;
var json = makeInfiniteJSON([ layer ]);
var result = ParseTileLayers(json, false);
expect(result.length).toBe(1);
expect(result[0].data[0][0].index).toBe(1);
expect(layer.encoding).toBeUndefined();
delete global.window;
});
// -------------------------------------------------------------------------
// Group layers
// -------------------------------------------------------------------------
it('should process tilelayers nested inside a group layer', function ()
{
var childLayer = makeTileLayer({ name: 'child', id: 10 });
var groupLayer = {
type: 'group',
name: 'myGroup',
id: 99,
x: 0,
y: 0,
opacity: 1,
visible: true,
layers: [ childLayer ]
};
var json = makeFiniteJSON([ groupLayer ]);
var result = ParseTileLayers(json, false);
expect(result.length).toBe(1);
});
it('should prefix child layer names with the group name and a slash', function ()
{
var childLayer = makeTileLayer({ name: 'child', id: 10 });
var groupLayer = {
type: 'group',
name: 'myGroup',
id: 99,
x: 0,
y: 0,
opacity: 1,
visible: true,
layers: [ childLayer ]
};
var json = makeFiniteJSON([ groupLayer ]);
var result = ParseTileLayers(json, false);
expect(result[0].name).toBe('myGroup/child');
});
it('should multiply child layer opacity by the group opacity', function ()
{
var childLayer = makeTileLayer({ name: 'child', id: 10, opacity: 0.5 });
var groupLayer = {
type: 'group',
name: 'g',
id: 99,
x: 0,
y: 0,
opacity: 0.4,
visible: true,
layers: [ childLayer ]
};
var json = makeFiniteJSON([ groupLayer ]);
var result = ParseTileLayers(json, false);
expect(result[0].alpha).toBeCloseTo(0.2);
});
it('should hide child layer when group is not visible', function ()
{
var childLayer = makeTileLayer({ name: 'child', id: 10, visible: true });
var groupLayer = {
type: 'group',
name: 'g',
id: 99,
x: 0,
y: 0,
opacity: 1,
visible: false,
layers: [ childLayer ]
};
var json = makeFiniteJSON([ groupLayer ]);
var result = ParseTileLayers(json, false);
expect(result[0].visible).toBe(false);
});
it('should not include group layers themselves in the output', function ()
{
var childLayer = makeTileLayer({ name: 'child', id: 10 });
var groupLayer = {
type: 'group',
name: 'g',
id: 99,
x: 0,
y: 0,
opacity: 1,
visible: true,
layers: [ childLayer ]
};
// top-level also has a direct tile layer
var topLayer = makeTileLayer({ name: 'top', id: 1 });
var json = makeFiniteJSON([ groupLayer, topLayer ]);
var result = ParseTileLayers(json, false);
expect(result.length).toBe(2);
var names = result.map(function (l) { return l.name; });
expect(names).toContain('g/child');
expect(names).toContain('top');
});
it('should process layers in a doubly nested group', function ()
{
var innerChild = makeTileLayer({ name: 'deep', id: 20 });
var innerGroup = {
type: 'group',
name: 'inner',
id: 10,
x: 0,
y: 0,
opacity: 1,
visible: true,
layers: [ innerChild ]
};
var outerGroup = {
type: 'group',
name: 'outer',
id: 9,
x: 0,
y: 0,
opacity: 1,
visible: true,
layers: [ innerGroup ]
};
var json = makeFiniteJSON([ outerGroup ]);
var result = ParseTileLayers(json, false);
expect(result.length).toBe(1);
expect(result[0].name).toBe('outer/inner/deep');
});
// -------------------------------------------------------------------------
// Non-tilelayer types are ignored at the top level
// -------------------------------------------------------------------------
it('should skip objectgroup layers', function ()
{
var json = makeFiniteJSON([
{ type: 'objectgroup', name: 'objects', id: 1, objects: [] },
makeTileLayer({ name: 'tiles', id: 2 })
]);
var result = ParseTileLayers(json, false);
expect(result.length).toBe(1);
expect(result[0].name).toBe('tiles');
});
// -------------------------------------------------------------------------
// Infinite maps
// -------------------------------------------------------------------------
it('should return a LayerData for an infinite tilelayer', function ()
{
var json = makeInfiniteJSON([ makeInfiniteTileLayer() ]);
var result = ParseTileLayers(json, false);
expect(result.length).toBe(1);
});
it('should set layer dimensions correctly for an infinite map', function ()
{
var json = makeInfiniteJSON([ makeInfiniteTileLayer({ width: 2, height: 2 }) ]);
var result = ParseTileLayers(json, false);
expect(result[0].width).toBe(2);
expect(result[0].height).toBe(2);
});
it('should populate tile data from chunks in an infinite map', function ()
{
var json = makeInfiniteJSON([ makeInfiniteTileLayer() ]);
var result = ParseTileLayers(json, false);
expect(result[0].data[0][0]).not.toBeNull();
expect(result[0].data[0][0].index).toBe(1);
expect(result[0].data[0][1].index).toBe(2);
expect(result[0].data[1][0].index).toBe(3);
expect(result[0].data[1][1].index).toBe(4);
});
it('should place null for empty gids in infinite map when insertNull is true', function ()
{
var layer = makeInfiniteTileLayer({
chunks: [ { x: 0, y: 0, width: 2, height: 1, data: [0, 1] } ],
width: 2,
height: 1
});
var json = makeInfiniteJSON([ layer ]);
var result = ParseTileLayers(json, true);
expect(result[0].data[0][0]).toBeNull();
});
it('should use x/y offsets from layer startx/starty on infinite maps', function ()
{
// Chunk must be placed at (startx, starty) so tile offsets compute to 0,0 within the output array
var layer = makeInfiniteTileLayer({
startx: 2, starty: 1, x: 0, y: 0,
chunks: [ { x: 2, y: 1, width: 2, height: 2, data: [1, 2, 3, 4] } ]
});
var json = makeInfiniteJSON([ layer ], { tilewidth: 32, tileheight: 32 });
var result = ParseTileLayers(json, false);
// x = curGroupState.x(0) + offsetx(0) + layerOffsetX(startx=2 + x=0) * tilewidth(32) = 64
expect(result[0].x).toBe(64);
});
// -------------------------------------------------------------------------
// Hexagonal maps
// -------------------------------------------------------------------------
it('should set hexSideLength, staggerAxis, staggerIndex on hexagonal layers', function ()
{
var json = makeFiniteJSON(
[ makeTileLayer() ],
{
orientation: 'hexagonal',
hexsidelength: 18,
staggeraxis: 'y',
staggerindex: 'odd'
}
);
var result = ParseTileLayers(json, false);
expect(result[0].hexSideLength).toBe(18);
expect(result[0].staggerAxis).toBe('y');
expect(result[0].staggerIndex).toBe('odd');
});
it('should calculate widthInPixels and heightInPixels for hexagonal staggerAxis y', function ()
{
// tileWidth=32, tileHeight=32, hexSideLength=16, width=4, height=3
// triangleHeight = (32 - 16) / 2 = 8
// widthInPixels = 32 * (4 + 0.5) = 144
// heightInPixels = 3 * (16 + 8) + 8 = 80
var json = makeFiniteJSON(
[ makeTileLayer({ width: 4, height: 3, data: new Array(12).fill(1) }) ],
{
orientation: 'hexagonal',
tilewidth: 32,
tileheight: 32,
hexsidelength: 16,
staggeraxis: 'y',
staggerindex: 'odd'
}
);
var result = ParseTileLayers(json, false);
expect(result[0].widthInPixels).toBeCloseTo(144);
expect(result[0].heightInPixels).toBeCloseTo(80);
});
it('should calculate widthInPixels and heightInPixels for hexagonal staggerAxis x', function ()
{
// tileWidth=32, tileHeight=32, hexSideLength=16, width=4, height=3
// triangleWidth = (32 - 16) / 2 = 8
// widthInPixels = 4 * (16 + 8) + 8 = 104
// heightInPixels = 32 * (3 + 0.5) = 112
var json = makeFiniteJSON(
[ makeTileLayer({ width: 4, height: 3, data: new Array(12).fill(1) }) ],
{
orientation: 'hexagonal',
tilewidth: 32,
tileheight: 32,
hexsidelength: 16,
staggeraxis: 'x',
staggerindex: 'even'
}
);
var result = ParseTileLayers(json, false);
expect(result[0].widthInPixels).toBeCloseTo(104);
expect(result[0].heightInPixels).toBeCloseTo(112);
});
it('should not set hexSideLength on orthogonal layers', function ()
{
var json = makeFiniteJSON([ makeTileLayer() ]);
var result = ParseTileLayers(json, false);
expect(result[0].hexSideLength).toBe(0);
});
// -------------------------------------------------------------------------
// Orientation
// -------------------------------------------------------------------------
it('should set ORTHOGONAL orientation constant for orthogonal maps', function ()
{
var json = makeFiniteJSON([ makeTileLayer() ], { orientation: 'orthogonal' });
var result = ParseTileLayers(json, false);
expect(result[0].orientation).toBe(0); // CONST.ORTHOGONAL
});
it('should set ISOMETRIC orientation constant for isometric maps', function ()
{
var json = makeFiniteJSON([ makeTileLayer() ], { orientation: 'isometric' });
var result = ParseTileLayers(json, false);
expect(result[0].orientation).toBe(1); // CONST.ISOMETRIC
});
it('should set HEXAGONAL orientation constant for hexagonal maps', function ()
{
var json = makeFiniteJSON(
[ makeTileLayer() ],
{
orientation: 'hexagonal',
hexsidelength: 16,
staggeraxis: 'y',
staggerindex: 'odd'
}
);
var result = ParseTileLayers(json, false);
expect(result[0].orientation).toBe(3); // CONST.HEXAGONAL
});
});