Files
cocos-engine/cocos/tiledmap/tmx-xml-parser.ts
2022-05-17 11:56:06 +08:00

1077 lines
37 KiB
TypeScript

/*
Copyright (c) 2020 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
import { Label, HorizontalTextAlignment, VerticalTextAlignment } from '../2d/components/label';
import codec from '../../external/compression/ZipUtils.js';
import zlib from '../../external/compression/zlib.min.js';
import { SAXParser } from '../core/asset-manager/plist-parser';
import {
GID, MixedGID, Orientation, PropertiesInfo, RenderOrder, StaggerAxis, StaggerIndex, TiledAnimation, TiledAnimationType,
TileFlag, TMXImageLayerInfo, TMXLayerInfo, TMXObject, TMXObjectGroupInfo, TMXObjectType, TMXTilesetInfo,
} from './tiled-types';
import { Color, errorID, logID, Size, Vec2 } from '../core';
import { SpriteFrame } from '../2d/assets';
function uint8ArrayToUint32Array (uint8Arr: Uint8Array): null | Uint32Array | number[] {
if (uint8Arr.length % 4 !== 0) return null;
const arrLen = uint8Arr.length / 4;
const retArr = window.Uint32Array ? new Uint32Array(arrLen) : [];
for (let i = 0; i < arrLen; i++) {
const offset = i * 4;
retArr[i] = uint8Arr[offset] + uint8Arr[offset + 1] * (1 << 8) + uint8Arr[offset + 2] * (1 << 16) + uint8Arr[offset + 3] * (1 << 24);
}
return retArr;
}
function strToHAlign (value): HorizontalTextAlignment {
const hAlign = Label.HorizontalAlign;
switch (value) {
case 'center':
return hAlign.CENTER;
case 'right':
return hAlign.RIGHT;
default:
return hAlign.LEFT;
}
}
function strToVAlign (value): VerticalTextAlignment {
const vAlign = Label.VerticalAlign;
switch (value) {
case 'center':
return vAlign.CENTER;
case 'bottom':
return vAlign.BOTTOM;
default:
return vAlign.TOP;
}
}
function strToColor (value: string): Color {
if (!value) {
return new Color(0, 0, 0, 255);
}
value = (value.indexOf('#') !== -1) ? value.substring(1) : value;
if (value.length === 8) {
const a = parseInt(value.substr(0, 2), 16) || 255;
const r = parseInt(value.substr(2, 2), 16) || 0;
const g = parseInt(value.substr(4, 2), 16) || 0;
const b = parseInt(value.substr(6, 2), 16) || 0;
return new Color(r, g, b, a);
} else {
const r = parseInt(value.substr(0, 2), 16) || 0;
const g = parseInt(value.substr(2, 2), 16) || 0;
const b = parseInt(value.substr(4, 2), 16) || 0;
return new Color(r, g, b, 255);
}
}
function getPropertyList (node: Element, map?: PropertiesInfo): PropertiesInfo {
const res: any[] = [];
const properties = node.getElementsByTagName('properties');
for (let i = 0; i < properties.length; ++i) {
const property = properties[i].getElementsByTagName('property');
for (let j = 0; j < property.length; ++j) {
res.push(property[j]);
}
}
map = map || ({} as any);
for (let i = 0; i < res.length; i++) {
const element = res[i];
const name = element.getAttribute('name');
const type = element.getAttribute('type') || 'string';
let value = element.getAttribute('value');
if (type === 'int') {
value = parseInt(value);
} else if (type === 'float') {
value = parseFloat(value);
} else if (type === 'bool') {
value = value === 'true';
} else if (type === 'color') {
value = strToColor(value);
}
map![name] = value;
}
return map!;
}
/**
* <p>cc.TMXMapInfo contains the information about the map like: <br/>
* - Map orientation (hexagonal, isometric or orthogonal)<br/>
* - Tile size<br/>
* - Map size</p>
*
* <p>And it also contains: <br/>
* - Layers (an array of TMXLayerInfo objects)<br/>
* - Tilesets (an array of TMXTilesetInfo objects) <br/>
* - ObjectGroups (an array of TMXObjectGroupInfo objects) </p>
*
* <p>This information is obtained from the TMX file. </p>
* @class TMXMapInfo
*/
export class TMXMapInfo {
/**
* Properties of the map info.
* @property {Array} properties
*/
properties: PropertiesInfo = {} as any;
/**
* Map orientation.
* @property {Number} orientation
*/
orientation: Orientation | null = null;
/**
* Parent element.
* @property {Object} parentElement
*/
parentElement: Record<string, unknown> | null = null;
/**
* Parent GID.
* @property {Number} parentGID
*/
parentGID: MixedGID = 0 as unknown as any;
/**
* Layer attributes.
* @property {Object} layerAttrs
*/
layerAttrs = 0;
/**
* Is reading storing characters stream.
* @property {Boolean} storingCharacters
*/
storingCharacters = false;
/**
* Current string stored from characters stream.
* @property {String} currentString
*/
currentString: string | null = null;
renderOrder: RenderOrder = RenderOrder.RightDown;
protected _supportVersion = [1, 4, 0];
protected _objectGroups: TMXObjectGroupInfo[] = [];
protected _allChildren: (TMXLayerInfo | TMXImageLayerInfo | TMXObjectGroupInfo)[] = [];
protected _mapSize = new Size(0, 0);
get mapSize () { return this._mapSize; }
protected _tileSize = new Size(0, 0);
get tileSize () { return this._tileSize; }
protected _layers: TMXLayerInfo[] = [];
protected _tilesets: TMXTilesetInfo[] = [];
protected _imageLayers: TMXImageLayerInfo[] = [];
protected _tileProperties: Map<GID, PropertiesInfo> = new Map();
protected _tileAnimations: TiledAnimationType = {} as any;
protected _tsxContentMap: { [key: string]: string } | null = null;
// map of textures indexed by name
protected _spriteFrameMap: { [key: string]: SpriteFrame } | null = null;
protected _spfSizeMap: { [key: string]: Size } = {};
// hex map values
protected _staggerAxis: StaggerAxis | null = null;
protected _staggerIndex: StaggerIndex | null = null;
protected _hexSideLength = 0;
protected _imageLayerSPF: { [key: string]: SpriteFrame } | null = null;
constructor (tmxFile: string, tsxContentMap: { [key: string]: string }, spfTexturesMap: { [key: string]: SpriteFrame },
textureSizes: { [key: string]: Size }, imageLayerTextures: { [key: string]: SpriteFrame }) {
this.initWithXML(tmxFile, tsxContentMap, spfTexturesMap, textureSizes, imageLayerTextures);
}
/* Gets Map orientation.
* @return {Number}
*/
getOrientation () {
return this.orientation;
}
/**
* Set the Map orientation.
* @param {Number} value
*/
setOrientation (value: Orientation) {
this.orientation = value;
}
/**
* Gets the staggerAxis of map.
* @return {TiledMap.StaggerAxis}
*/
getStaggerAxis () {
return this._staggerAxis;
}
/**
* Set the staggerAxis of map.
* @param {TiledMap.StaggerAxis} value
*/
setStaggerAxis (value: StaggerAxis) {
this._staggerAxis = value;
}
/**
* Gets stagger index
* @return {TiledMap.StaggerIndex}
*/
getStaggerIndex () {
return this._staggerIndex;
}
/**
* Set the stagger index.
* @param {TiledMap.StaggerIndex} value
*/
setStaggerIndex (value) {
this._staggerIndex = value;
}
/**
* Gets Hex side length.
* @return {Number}
*/
getHexSideLength () {
return this._hexSideLength;
}
/**
* Set the Hex side length.
* @param {Number} value
*/
setHexSideLength (value: number) {
this._hexSideLength = value;
}
/**
* Map width & height
* @return {Size}
*/
getMapSize () {
return new Size(this._mapSize.width, this._mapSize.height);
}
/**
* Map width & height
* @param {Size} value
*/
setMapSize (value: Size) {
this._mapSize.width = value.width;
this._mapSize.height = value.height;
}
get mapWidth () {
return this._mapSize.width;
}
set mapWidth (width: number) {
this._mapSize.width = width;
}
get mapHeight () {
return this._mapSize.height;
}
set mapHeight (height: number) {
this._mapSize.height = height;
}
/**
* Tiles width & height
* @return {Size}
*/
getTileSize () {
return new Size(this._tileSize.width, this._tileSize.height);
}
/**
* Tiles width & height
* @param {Size} value
*/
setTileSize (value: Size) {
this._tileSize.width = value.width;
this._tileSize.height = value.height;
}
get tileWidth () {
return this._tileSize.width;
}
set tileWidth (width) {
this._tileSize.width = width;
}
/**
* Height of a tile
*/
get tileHeight () {
return this._tileSize.height;
}
set tileHeight (height: number) {
this._tileSize.height = height;
}
/**
* Layers
* @return {Array}
*/
getLayers () {
return this._layers;
}
/**
* Layers
* @param {cc.TMXLayerInfo} value
*/
setLayers (value: TMXLayerInfo) {
this._allChildren.push(value);
this._layers.push(value);
}
/**
* ImageLayers
* @return {Array}
*/
getImageLayers () {
return this._imageLayers;
}
/**
* ImageLayers
* @param {cc.TMXImageLayerInfo} value
*/
setImageLayers (value: TMXImageLayerInfo) {
this._allChildren.push(value);
this._imageLayers.push(value);
}
/**
* tilesets
* @return {Array}
*/
getTilesets () {
return this._tilesets;
}
/**
* tilesets
* @param {cc.TMXTilesetInfo} value
*/
setTilesets (value: TMXTilesetInfo) {
this._tilesets.push(value);
}
/**
* ObjectGroups
* @return {Array}
*/
getObjectGroups () {
return this._objectGroups;
}
/**
* ObjectGroups
* @param {cc.TMXObjectGroup} value
*/
setObjectGroups (value: TMXObjectGroupInfo) {
this._allChildren.push(value);
this._objectGroups.push(value);
}
getAllChildren () {
return this._allChildren;
}
/**
* parent element
* @return {Object}
*/
getParentElement () {
return this.parentElement;
}
/**
* parent element
* @param {Object} value
*/
setParentElement (value) {
this.parentElement = value;
}
/**
* parent GID
* @return {Number}
*/
getParentGID () {
return this.parentGID;
}
/**
* parent GID
* @param {Number} value
*/
setParentGID (value) {
this.parentGID = value;
}
/**
* Layer attribute
* @return {Object}
*/
getLayerAttribs () {
return this.layerAttrs;
}
/**
* Layer attribute
* @param {Object} value
*/
setLayerAttribs (value) {
this.layerAttrs = value;
}
/**
* Is reading storing characters stream
* @return {Boolean}
*/
getStoringCharacters () {
return this.storingCharacters;
}
/**
* Is reading storing characters stream
* @param {Boolean} value
*/
setStoringCharacters (value) {
this.storingCharacters = value;
}
/**
* Properties
* @return {Array}
*/
getProperties () {
return this.properties;
}
/**
* Properties
* @param {object} value
*/
setProperties (value) {
this.properties = value;
}
/**
* initializes a TMX format with an XML string and a TMX resource path
* @param {String} tmxString
* @param {Object} tsxMap
* @param {Object} spfTextureMap
* @return {Boolean}
*/
initWithXML (tmxString: string, tsxMap: { [key: string]: string }, spfTextureMap: { [key: string]: SpriteFrame },
textureSizes: { [key: string]: Size }, imageLayerTextures: { [key: string]: SpriteFrame }) {
this._tilesets.length = 0;
this._layers.length = 0;
this._imageLayers.length = 0;
this._tsxContentMap = tsxMap;
this._spriteFrameMap = spfTextureMap;
this._imageLayerSPF = imageLayerTextures;
this._spfSizeMap = textureSizes;
this._objectGroups.length = 0;
this._allChildren.length = 0;
this.properties = {} as any;
this._tileProperties = new Map();
this._tileAnimations = new Map();
// tmp vars
this.currentString = '';
this.storingCharacters = false;
this.layerAttrs = TMXLayerInfo.ATTRIB_NONE;
this.parentElement = null;
return this.parseXMLString(tmxString);
}
/**
* Initializes parsing of an XML string, either a tmx (Map) string or tsx (Tileset) string
* @param {String} xmlString
* @param {Number} tilesetFirstGid
* @return {Element}
*/
parseXMLString (xmlStr: string, tilesetFirstGid?: number) {
const parser = new SAXParser();
const mapXML: Document = parser.parse(xmlStr);
let i: number;
// PARSE <map>
const map = mapXML.documentElement;
const orientationStr = map.getAttribute('orientation');
const staggerAxisStr = map.getAttribute('staggeraxis');
const staggerIndexStr = map.getAttribute('staggerindex');
const hexSideLengthStr = map.getAttribute('hexsidelength');
const renderorderStr = map.getAttribute('renderorder');
const version = map.getAttribute('version') || '1.0.0';
if (map.nodeName === 'map') {
const versionArr = version.split('.');
const supportVersion = this._supportVersion;
for (i = 0; i < supportVersion.length; i++) {
const v = parseInt(versionArr[i]) || 0;
const sv = supportVersion[i];
if (sv < v) {
logID(7216, version);
break;
}
}
if (orientationStr === 'orthogonal') this.orientation = Orientation.ORTHO;
else if (orientationStr === 'isometric') this.orientation = Orientation.ISO;
else if (orientationStr === 'hexagonal') this.orientation = Orientation.HEX;
else if (orientationStr !== null) logID(7217, orientationStr);
if (renderorderStr === 'right-up') {
this.renderOrder = RenderOrder.RightUp;
} else if (renderorderStr === 'left-up') {
this.renderOrder = RenderOrder.LeftUp;
} else if (renderorderStr === 'left-down') {
this.renderOrder = RenderOrder.LeftDown;
} else {
this.renderOrder = RenderOrder.RightDown;
}
if (staggerAxisStr === 'x') {
this.setStaggerAxis(StaggerAxis.STAGGERAXIS_X);
} else if (staggerAxisStr === 'y') {
this.setStaggerAxis(StaggerAxis.STAGGERAXIS_Y);
}
if (staggerIndexStr === 'odd') {
this.setStaggerIndex(StaggerIndex.STAGGERINDEX_ODD);
} else if (staggerIndexStr === 'even') {
this.setStaggerIndex(StaggerIndex.STAGGERINDEX_EVEN);
}
if (hexSideLengthStr) {
this.setHexSideLength(parseFloat(hexSideLengthStr));
}
let mapSize = new Size(0, 0);
mapSize.width = parseFloat(map.getAttribute('width')!);
mapSize.height = parseFloat(map.getAttribute('height')!);
this.setMapSize(mapSize);
mapSize = new Size(0, 0);
mapSize.width = parseFloat(map.getAttribute('tilewidth')!);
mapSize.height = parseFloat(map.getAttribute('tileheight')!);
this.setTileSize(mapSize);
// The parent element is the map
this.properties = getPropertyList(map);
}
// PARSE <tileset>
let tilesets: Element[] = map.getElementsByTagName('tileset') as unknown as Element[];
if (map.nodeName !== 'map') {
tilesets = [];
tilesets.push(map);
}
for (i = 0; i < tilesets.length; i++) {
const curTileset = tilesets[i];
// If this is an external tileset then start parsing that
const tsxName = curTileset.getAttribute('source');
if (tsxName) {
const currentFirstGID = parseInt(curTileset.getAttribute('firstgid')!);
const tsxXmlString = this._tsxContentMap![tsxName];
if (tsxXmlString) {
this.parseXMLString(tsxXmlString, currentFirstGID);
}
} else {
const images = curTileset.getElementsByTagName('image');
const collection = images.length > 1;
const firstImage = images[0];
let firstImageName: string = firstImage.getAttribute('source')!;
firstImageName = firstImageName.replace(/\\/g, '/');
const tiles = curTileset.getElementsByTagName('tile');
const tileCount = tiles && tiles.length || 1;
let tile: Element | null = null;
const tilesetName = curTileset.getAttribute('name') || '';
const tilesetSpacing = parseInt(curTileset.getAttribute('spacing')!) || 0;
const tilesetMargin = parseInt(curTileset.getAttribute('margin')!) || 0;
const fgid = tilesetFirstGid || (parseInt(curTileset.getAttribute('firstgid')!) || 0);
const tilesetSize = new Size(0, 0);
tilesetSize.width = parseFloat(curTileset.getAttribute('tilewidth')!);
tilesetSize.height = parseFloat(curTileset.getAttribute('tileheight')!);
// parse tile offset
const curTileOffset = curTileset.getElementsByTagName('tileoffset')[0];
let tileOffsetX = 0;
let tileOffsetY = 0;
if (curTileOffset) {
tileOffsetX = parseFloat(curTileOffset.getAttribute('x')!) || 0;
tileOffsetY = parseFloat(curTileOffset.getAttribute('y')!) || 0;
}
let tileset: TMXTilesetInfo | null = null;
for (let tileIdx = 0; tileIdx < tileCount; tileIdx++) {
const curImage = images[tileIdx] ? images[tileIdx] : firstImage;
if (!curImage) continue;
let curImageName: string = curImage.getAttribute('source')!;
curImageName = curImageName.replace(/\\/g, '/');
if (!tileset || collection) {
tileset = new TMXTilesetInfo();
tileset.name = tilesetName;
tileset.firstGid = ((fgid as unknown as number) & TileFlag.FLIPPED_MASK) as unknown as GID;
tileset.tileOffset.x = tileOffsetX;
tileset.tileOffset.y = tileOffsetY;
tileset.collection = collection;
if (!collection) {
tileset.imageName = curImageName;
tileset.imageSize.width = parseFloat(curImage.getAttribute('width')!) || 0;
tileset.imageSize.height = parseFloat(curImage.getAttribute('height')!) || 0;
tileset.sourceImage = this._spriteFrameMap![curImageName];
if (!tileset.sourceImage) {
const nameWithPostfix = TMXMapInfo.getNameWithPostfix(curImageName);
tileset.imageName = nameWithPostfix;
tileset.sourceImage = this._spriteFrameMap![nameWithPostfix];
if (!tileset.sourceImage) {
const shortName = TMXMapInfo.getShortName(curImageName);
tileset.imageName = shortName;
tileset.sourceImage = this._spriteFrameMap![shortName];
if (!tileset.sourceImage) {
console.error(`[error]: ${shortName} not find in [${Object.keys(this._spriteFrameMap!).join(', ')}]`);
errorID(7221, curImageName);
console.warn(`Please try asset type of ${curImageName} to 'sprite-frame'`);
}
}
}
}
tileset.spacing = tilesetSpacing;
tileset.margin = tilesetMargin;
tileset._tileSize.width = tilesetSize.width;
tileset._tileSize.height = tilesetSize.height;
this.setTilesets(tileset);
}
// parse tiles by tileIdx
tile = tiles && tiles[tileIdx];
if (!tile) {
continue;
}
this.parentGID = (fgid + (parseInt(tile.getAttribute('id')!) || 0)) as any;
const tileImages = tile.getElementsByTagName('image');
if (tileImages && tileImages.length > 0) {
const image = tileImages[0];
let imageName = image.getAttribute('source')!;
imageName = imageName.replace(/\\/g, '/');
tileset.imageName = imageName;
tileset.imageSize.width = parseFloat(image.getAttribute('width')!) || 0;
tileset.imageSize.height = parseFloat(image.getAttribute('height')!) || 0;
tileset._tileSize.width = tileset.imageSize.width;
tileset._tileSize.height = tileset.imageSize.height;
tileset.sourceImage = this._spriteFrameMap![imageName];
if (!tileset.sourceImage) {
const nameWithPostfix = TMXMapInfo.getNameWithPostfix(imageName);
tileset.imageName = nameWithPostfix;
tileset.sourceImage = this._spriteFrameMap![nameWithPostfix];
if (!tileset.sourceImage) {
const shortName = TMXMapInfo.getShortName(imageName);
tileset.imageName = shortName;
tileset.sourceImage = this._spriteFrameMap![shortName];
if (!tileset.sourceImage) {
errorID(7221, imageName);
console.warn(`Please try asset type of ${imageName} to 'sprite-frame'`);
}
}
}
tileset.firstGid = ((this.parentGID as unknown as number) & TileFlag.FLIPPED_MASK) as unknown as GID;
}
const pid = ((TileFlag.FLIPPED_MASK & this.parentGID as unknown as number) >>> 0) as unknown as GID;
this._tileProperties.set(pid, getPropertyList(tile));
const animations = tile.getElementsByTagName('animation');
if (animations && animations.length > 0) {
const animation = animations[0];
const framesData = animation.getElementsByTagName('frame');
const animationProp: TiledAnimation = { frames: [], dt: 0, frameIdx: 0 };
this._tileAnimations.set(pid, animationProp);
const frames = animationProp.frames;
for (let frameIdx = 0; frameIdx < framesData.length; frameIdx++) {
const frame = framesData[frameIdx];
const tileid = fgid + (parseInt(frame.getAttribute('tileid')!) || 0);
const duration = parseFloat(frame.getAttribute('duration')!) || 0;
frames.push({ tileid: tileid as unknown as GID, duration: duration / 1000, grid: null });
}
}
}
}
}
// PARSE <layer> & <objectgroup> in order
const childNodes = map.childNodes;
for (i = 0; i < childNodes.length; i++) {
const childNode = childNodes[i];
if (this._shouldIgnoreNode(childNode)) {
continue;
}
if (childNode.nodeName === 'imagelayer') {
const imageLayer = this._parseImageLayer(childNode as Element);
if (imageLayer) {
this.setImageLayers(imageLayer);
}
}
if (childNode.nodeName === 'layer') {
const layer = this._parseLayer(childNode as Element);
this.setLayers(layer!);
}
if (childNode.nodeName === 'objectgroup') {
const objectGroup = this._parseObjectGroup(childNode as Element);
this.setObjectGroups(objectGroup);
}
}
return map;
}
protected _shouldIgnoreNode (node: ChildNode): boolean {
return node.nodeType === 3 // text
|| node.nodeType === 8 // comment
|| node.nodeType === 4; // cdata
}
protected _parseImageLayer (selLayer: Element) {
const datas = selLayer.getElementsByTagName('image');
if (!datas || datas.length === 0) return null;
const imageLayer = new TMXImageLayerInfo();
imageLayer.name = selLayer.getAttribute('name')!;
imageLayer.offset.x = parseFloat(selLayer.getAttribute('offsetx')!) || 0;
imageLayer.offset.y = parseFloat(selLayer.getAttribute('offsety')!) || 0;
const visible = selLayer.getAttribute('visible');
imageLayer.visible = !(visible === '0');
const opacity = selLayer.getAttribute('opacity');
imageLayer.opacity = opacity ? Math.round(255 * parseFloat(opacity)) : 255;
const tintColor = selLayer.getAttribute('tintcolor');
imageLayer.tintColor = tintColor ? strToColor(tintColor) : null;
const data = datas[0];
const source = data.getAttribute('source');
imageLayer.sourceImage = this._imageLayerSPF![source!];
imageLayer.width = parseInt(data.getAttribute('width')!) || 0;
imageLayer.height = parseInt(data.getAttribute('height')!) || 0;
imageLayer.trans = strToColor(data.getAttribute('trans')!);
if (!imageLayer.sourceImage) {
errorID(7221, source);
console.warn(`Please try asset type of ${source} to 'sprite-frame'`);
return null;
}
return imageLayer;
}
protected _parseLayer (selLayer: Element) {
const data = selLayer.getElementsByTagName('data')[0];
const layer = new TMXLayerInfo();
layer.name = selLayer.getAttribute('name')!;
const layerSize = new Size(0, 0);
layerSize.width = parseFloat(selLayer.getAttribute('width')!);
layerSize.height = parseFloat(selLayer.getAttribute('height')!);
layer.layerSize = layerSize;
const visible = selLayer.getAttribute('visible');
layer.visible = !(visible === '0');
const opacity = selLayer.getAttribute('opacity');
if (opacity) layer.opacity = Math.round(255 * parseFloat(opacity));
else layer.opacity = 255;
layer.offset = new Vec2(parseFloat(selLayer.getAttribute('offsetx')!) || 0, parseFloat(selLayer.getAttribute('offsety')!) || 0);
const tintColor = selLayer.getAttribute('tintcolor');
layer.tintColor = tintColor ? strToColor(tintColor) : null;
let nodeValue = '';
for (let j = 0; j < data.childNodes.length; j++) {
nodeValue += data.childNodes[j].nodeValue;
}
nodeValue = nodeValue.trim();
// Unpack the tilemap data
const compression = data.getAttribute('compression');
const encoding = data.getAttribute('encoding');
if (compression && compression !== 'gzip' && compression !== 'zlib') {
logID(7218);
return null;
}
let tiles;
switch (compression) {
case 'gzip':
tiles = codec.unzipBase64AsArray(nodeValue, 4);
break;
case 'zlib': {
const inflator = new zlib.Inflate(codec.Base64.decodeAsArray(nodeValue, 1));
tiles = uint8ArrayToUint32Array(inflator.decompress());
break;
}
case null:
case '':
// Uncompressed
if (encoding === 'base64') tiles = codec.Base64.decodeAsArray(nodeValue, 4);
else if (encoding === 'csv') {
tiles = [];
const csvTiles = nodeValue.split(',');
for (let csvIdx = 0; csvIdx < csvTiles.length; csvIdx++) tiles.push(parseInt(csvTiles[csvIdx]));
} else {
// XML format
const selDataTiles = data.getElementsByTagName('tile');
tiles = [];
for (let xmlIdx = 0; xmlIdx < selDataTiles.length; xmlIdx++) tiles.push(parseInt(selDataTiles[xmlIdx].getAttribute('gid')!));
}
break;
default:
if (this.layerAttrs === TMXLayerInfo.ATTRIB_NONE) logID(7219);
break;
}
if (tiles) {
layer.tiles = new Uint32Array(tiles);
}
// The parent element is the last layer
layer.properties = getPropertyList(selLayer);
return layer;
}
protected _parseObjectGroup (selGroup: Element) {
const objectGroup = new TMXObjectGroupInfo();
objectGroup.name = selGroup.getAttribute('name') || '';
objectGroup.offset = new Vec2(parseFloat(selGroup.getAttribute('offsetx')!), parseFloat(selGroup.getAttribute('offsety')!));
const opacity = selGroup.getAttribute('opacity');
if (opacity) objectGroup.opacity = Math.round(255 * parseFloat(opacity));
else objectGroup.opacity = 255;
const tintColor = selGroup.getAttribute('tintcolor');
objectGroup.tintColor = tintColor ? strToColor(tintColor) : null;
const visible = selGroup.getAttribute('visible');
if (visible && parseInt(visible) === 0) objectGroup.visible = false;
const color = selGroup.getAttribute('color');
if (color) objectGroup.color.fromHEX(color);
const draworder = selGroup.getAttribute('draworder');
if (draworder) objectGroup.draworder = draworder as any;
// set the properties to the group
objectGroup.setProperties(getPropertyList(selGroup));
const objects = selGroup.getElementsByTagName('object');
if (objects) {
for (let j = 0; j < objects.length; j++) {
const selObj = objects[j];
// The value for "type" was blank or not a valid class name
// Create an instance of TMXObjectInfo to store the object and its properties
const objectProp: TMXObject = {} as any;
// Set the id of the object
objectProp.id = selObj.getAttribute('id') || j;
// Set the name of the object to the value for "name"
objectProp.name = selObj.getAttribute('name') || '';
// Assign all the attributes as key/name pairs in the properties dictionary
objectProp.width = parseFloat(selObj.getAttribute('width')!) || 0;
objectProp.height = parseFloat(selObj.getAttribute('height')!) || 0;
objectProp.x = parseFloat(selObj.getAttribute('x')!) || 0;
objectProp.y = parseFloat(selObj.getAttribute('y')!) || 0;
objectProp.rotation = parseFloat(selObj.getAttribute('rotation')!) || 0;
getPropertyList(selObj, objectProp as any);
// visible
const visibleAttr = selObj.getAttribute('visible');
objectProp.visible = !(visibleAttr && parseInt(visibleAttr) === 0);
// text
const texts = selObj.getElementsByTagName('text');
if (texts && texts.length > 0) {
const text = texts[0];
objectProp.type = TMXObjectType.TEXT;
objectProp.wrap = text.getAttribute('wrap') === '1';
objectProp.color = strToColor(text.getAttribute('color')!);
objectProp.halign = strToHAlign(text.getAttribute('halign'));
objectProp.valign = strToVAlign(text.getAttribute('valign'));
objectProp.pixelsize = parseInt(text.getAttribute('pixelsize')!) || 16;
objectProp.text = text.childNodes[0].nodeValue!;
}
// image
const gid = selObj.getAttribute('gid');
if (gid) {
objectProp.gid = parseInt(gid) as any;
objectProp.type = TMXObjectType.IMAGE;
}
// ellipse
const ellipse = selObj.getElementsByTagName('ellipse');
if (ellipse && ellipse.length > 0) {
objectProp.type = TMXObjectType.ELLIPSE;
}
// polygon
const polygonProps = selObj.getElementsByTagName('polygon');
if (polygonProps && polygonProps.length > 0) {
objectProp.type = TMXObjectType.POLYGON;
const selPgPointStr = polygonProps[0].getAttribute('points');
if (selPgPointStr) objectProp.points = this._parsePointsString(selPgPointStr)!;
}
// polyline
const polylineProps = selObj.getElementsByTagName('polyline');
if (polylineProps && polylineProps.length > 0) {
objectProp.type = TMXObjectType.POLYLINE;
const selPlPointStr = polylineProps[0].getAttribute('points');
if (selPlPointStr) objectProp.polylinePoints = this._parsePointsString(selPlPointStr)!;
}
if (!objectProp.type) {
objectProp.type = TMXObjectType.RECT;
}
// Add the object to the objectGroup
objectGroup.objects.push(objectProp);
}
if (draworder !== 'index') {
objectGroup.objects.sort((a, b) => a.y - b.y);
}
}
return objectGroup;
}
protected _parsePointsString (pointsString?: string) {
if (!pointsString) return null;
const points: { x: number, y: number }[] = [];
const pointsStr = pointsString.split(' ');
for (let i = 0; i < pointsStr.length; i++) {
const selPointStr = pointsStr[i].split(',');
points.push({ x: parseFloat(selPointStr[0]), y: parseFloat(selPointStr[1]) });
}
return points;
}
/**
* Sets the tile animations.
* @return {Object}
*/
setTileAnimations (animations: TiledAnimationType) {
this._tileAnimations = animations;
}
/**
* Gets the tile animations.
* @return {Object}
*/
getTileAnimations (): TiledAnimationType {
return this._tileAnimations;
}
/**
* Gets the tile properties.
* @return {Object}
*/
getTileProperties () {
return this._tileProperties;
}
/**
* Set the tile properties.
* @param {Object} tileProperties
*/
setTileProperties (tileProperties: Map<GID, PropertiesInfo>) {
this._tileProperties = tileProperties;
}
/**
* Gets the currentString
* @return {String}
*/
getCurrentString () {
return this.currentString;
}
/**
* Set the currentString
* @param {String} currentString
*/
setCurrentString (currentString: string) {
this.currentString = currentString;
}
static getNameWithPostfix (name: string) {
name = name.replace(/\\/g, '/');
const slashIndex = name.lastIndexOf('/') + 1;
const strLen = name.length;
return name.substring(slashIndex, strLen);
}
static getShortName (name: string) {
name = name.replace(/\\/g, '/');
const slashIndex = name.lastIndexOf('/') + 1;
let dotIndex = name.lastIndexOf('.');
dotIndex = dotIndex < 0 ? name.length : dotIndex;
return name.substring(slashIndex, dotIndex);
}
}