/****************************************************************************
|
Copyright (c) 2013-2016 Chukong Technologies Inc.
|
Copyright (c) 2017-2018 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.
|
****************************************************************************/
|
const RenderComponent = require('../core/components/CCRenderComponent');
|
const renderEngine = require('../core/renderer/render-engine');
|
const SpriteMaterial = renderEngine.SpriteMaterial;
|
|
/**
|
* !#en Render the TMX layer.
|
* !#zh 渲染 TMX layer。
|
* @class TiledLayer
|
* @extends Component
|
*/
|
let TiledLayer = cc.Class({
|
name: 'cc.TiledLayer',
|
|
// Inherits from the abstract class directly,
|
// because TiledLayer not create or maintains the sgNode by itself.
|
extends: RenderComponent,
|
|
ctor () {
|
this._tiles = [];
|
this._texGrids = [];
|
this._textures = [];
|
this._spriteTiles = {};
|
|
this._tiledTiles = [];
|
|
this._layerName = '';
|
this._layerOrientation = null;
|
},
|
|
/**
|
* !#en Gets the layer name.
|
* !#zh 获取层的名称。
|
* @method getLayerName
|
* @return {String}
|
* @example
|
* let layerName = tiledLayer.getLayerName();
|
* cc.log(layerName);
|
*/
|
getLayerName () {
|
return this._layerName;
|
},
|
|
/**
|
* !#en Set the layer name.
|
* !#zh 设置层的名称
|
* @method SetLayerName
|
* @param {String} layerName
|
* @example
|
* tiledLayer.setLayerName("New Layer");
|
*/
|
setLayerName (layerName) {
|
this._layerName = layerName;
|
},
|
|
/**
|
* !#en Return the value for the specific property name.
|
* !#zh 获取指定属性名的值。
|
* @method getProperty
|
* @param {String} propertyName
|
* @return {*}
|
* @example
|
* let property = tiledLayer.getProperty("info");
|
* cc.log(property);
|
*/
|
getProperty (propertyName) {
|
return this._properties[propertyName];
|
},
|
|
|
/**
|
* !#en Returns the position in pixels of a given tile coordinate.
|
* !#zh 获取指定 tile 的像素坐标。
|
* @method getPositionAt
|
* @param {Vec2|Number} pos position or x
|
* @param {Number} [y]
|
* @return {Vec2}
|
* @example
|
* let pos = tiledLayer.getPositionAt(cc.v2(0, 0));
|
* cc.log("Pos: " + pos);
|
* let pos = tiledLayer.getPositionAt(0, 0);
|
* cc.log("Pos: " + pos);
|
*/
|
getPositionAt (pos, y) {
|
let x;
|
if (y !== undefined) {
|
x = Math.floor(pos);
|
y = Math.floor(y);
|
}
|
else {
|
x = Math.floor(pos.x);
|
y = Math.floor(pos.y);
|
}
|
|
let ret;
|
switch (this._layerOrientation) {
|
case cc.TiledMap.Orientation.ORTHO:
|
ret = this._positionForOrthoAt(x, y);
|
break;
|
case cc.TiledMap.Orientation.ISO:
|
ret = this._positionForIsoAt(x, y);
|
break;
|
case cc.TiledMap.Orientation.HEX:
|
ret = this._positionForHexAt(x, y);
|
break;
|
}
|
return ret;
|
},
|
|
_isInvalidPosition (x, y) {
|
if (x && typeof x === 'object') {
|
let pos = x;
|
y = pos.y;
|
x = pos.x;
|
}
|
return x >= this._layerSize.width || y >= this._layerSize.height || x < 0 || y < 0;
|
},
|
|
_positionForIsoAt (x, y) {
|
return cc.v2(
|
this._mapTileSize.width / 2 * ( this._layerSize.width + x - y - 1),
|
this._mapTileSize.height / 2 * (( this._layerSize.height * 2 - x - y) - 2)
|
);
|
},
|
|
_positionForOrthoAt (x, y) {
|
return cc.v2(
|
x * this._mapTileSize.width,
|
(this._layerSize.height - y - 1) * this._mapTileSize.height
|
);
|
},
|
|
_positionForHexAt (row, col) {
|
let tileWidth = this._mapTileSize.width;
|
let tileHeight = this._mapTileSize.height;
|
let cols = this._layerSize.height;
|
let offset = this._tileset.tileOffset;
|
let centerWidth = this.node.width / 2;
|
let centerHeight = this.node.height / 2;
|
let odd_even = (this._staggerIndex === cc.TiledMap.StaggerIndex.STAGGERINDEX_ODD) ? 1 : -1;
|
let x = 0, y = 0;
|
switch (this._staggerAxis) {
|
case cc.TiledMap.StaggerAxis.STAGGERAXIS_Y:
|
let diffX = 0;
|
let diffX1 = (this._staggerIndex === cc.TiledMap.StaggerIndex.STAGGERINDEX_ODD) ? 0 : tileWidth / 2;
|
if (col % 2 === 1) {
|
diffX = tileWidth / 2 * odd_even;
|
}
|
x = row * tileWidth + diffX + diffX1 + offset.x - centerWidth;
|
y = (cols - col - 1) * (tileHeight - (tileHeight - this._hexSideLength) / 2) - offset.y - centerHeight;
|
break;
|
case cc.TiledMap.StaggerAxis.STAGGERAXIS_X:
|
let diffY = 0;
|
let diffY1 = (this._staggerIndex === cc.TiledMap.StaggerIndex.STAGGERINDEX_ODD) ? tileHeight / 2 : 0;
|
if (row % 2 === 1) {
|
diffY = tileHeight / 2 * -odd_even;
|
}
|
x = row * (tileWidth - (tileWidth - this._hexSideLength) / 2) + offset.x - centerWidth;
|
y = (cols - col - 1) * tileHeight + diffY + diffY1 - offset.y - centerHeight;
|
break;
|
}
|
return cc.v2(x, y);
|
},
|
|
/**
|
* !#en
|
* Sets the tile gid (gid = tile global id) at a given tile coordinate.<br />
|
* The Tile GID can be obtained by using the method "tileGIDAt" or by using the TMX editor . Tileset Mgr +1.<br />
|
* If a tile is already placed at that position, then it will be removed.
|
* !#zh
|
* 设置给定坐标的 tile 的 gid (gid = tile 全局 id),
|
* tile 的 GID 可以使用方法 “tileGIDAt” 来获得。<br />
|
* 如果一个 tile 已经放在那个位置,那么它将被删除。
|
* @method setTileGIDAt
|
* @param {Number} gid
|
* @param {Vec2|Number} posOrX position or x
|
* @param {Number} flagsOrY flags or y
|
* @param {Number} [flags]
|
* @example
|
* tiledLayer.setTileGIDAt(1001, 10, 10, 1)
|
*/
|
setTileGIDAt (gid, posOrX, flagsOrY, flags) {
|
if (posOrX === undefined) {
|
throw new Error("cc.TiledLayer.setTileGIDAt(): pos should be non-null");
|
}
|
let pos;
|
if (flags !== undefined || !(posOrX instanceof cc.Vec2)) {
|
// four parameters or posOrX is not a Vec2 object
|
pos = cc.v2(posOrX, flagsOrY);
|
} else {
|
pos = posOrX;
|
flags = flagsOrY;
|
}
|
|
pos.x = Math.floor(pos.x);
|
pos.y = Math.floor(pos.y);
|
if (this._isInvalidPosition(pos)) {
|
throw new Error("cc.TiledLayer.setTileGIDAt(): invalid position");
|
}
|
if (!this._tiles) {
|
cc.logID(7238);
|
return;
|
}
|
if (gid !== 0 && gid < this._tileset.firstGid) {
|
cc.logID(7239, gid);
|
return;
|
}
|
|
flags = flags || 0;
|
let currentFlags = this.getTileFlagsAt(pos);
|
let currentGID = this.getTileGIDAt(pos);
|
|
if (currentGID === gid && currentFlags === flags) return;
|
|
let gidAndFlags = (gid | flags) >>> 0;
|
this._updateTileForGID(gidAndFlags, pos);
|
},
|
|
_updateTileForGID (gid, pos) {
|
if (gid !== 0 && !this._texGrids[gid]) {
|
return;
|
}
|
|
let idx = 0 | (pos.x + pos.y * this._layerSize.width);
|
if (idx < this._tiles.length) {
|
this._tiles[idx] = gid;
|
}
|
},
|
|
/**
|
* !#en
|
* Returns the tile gid at a given tile coordinate. <br />
|
* if it returns 0, it means that the tile is empty. <br />
|
* !#zh
|
* 通过给定的 tile 坐标、flags(可选)返回 tile 的 GID. <br />
|
* 如果它返回 0,则表示该 tile 为空。<br />
|
* @method getTileGIDAt
|
* @param {Vec2|Number} pos or x
|
* @param {Number} [y]
|
* @return {Number}
|
* @example
|
* let tileGid = tiledLayer.getTileGIDAt(0, 0);
|
*/
|
getTileGIDAt (pos, y) {
|
if (pos === undefined) {
|
throw new Error("cc.TiledLayer.getTileGIDAt(): pos should be non-null");
|
}
|
let x = pos;
|
if (y === undefined) {
|
x = pos.x;
|
y = pos.y;
|
}
|
if (this._isInvalidPosition(x, y)) {
|
throw new Error("cc.TiledLayer.getTileGIDAt(): invalid position");
|
}
|
if (!this._tiles) {
|
cc.logID(7237);
|
return null;
|
}
|
|
let index = Math.floor(x) + Math.floor(y) * this._layerSize.width;
|
// Bits on the far end of the 32-bit global tile ID are used for tile flags
|
let tile = this._tiles[index];
|
|
return (tile & cc.TiledMap.TileFlag.FLIPPED_MASK) >>> 0;
|
},
|
|
getTileFlagsAt (pos, y) {
|
if (!pos) {
|
throw new Error("TiledLayer.getTileFlagsAt: pos should be non-null");
|
}
|
if (y !== undefined) {
|
pos = cc.v2(pos, y);
|
}
|
if (this._isInvalidPosition(pos)) {
|
throw new Error("TiledLayer.getTileFlagsAt: invalid position");
|
}
|
if (!this._tiles) {
|
cc.logID(7240);
|
return null;
|
}
|
|
let idx = Math.floor(pos.x) + Math.floor(pos.y) * this._layerSize.width;
|
// Bits on the far end of the 32-bit global tile ID are used for tile flags
|
let tile = this._tiles[idx];
|
|
return (tile & cc.TiledMap.TileFlag.FLIPPED_ALL) >>> 0;
|
},
|
|
/**
|
* !#en
|
* Get the TiledTile with the tile coordinate.<br/>
|
* If there is no tile in the specified coordinate and forceCreate parameter is true, <br/>
|
* then will create a new TiledTile at the coordinate.
|
* The renderer will render the tile with the rotation, scale, position and color property of the TiledTile.
|
* !#zh
|
* 通过指定的 tile 坐标获取对应的 TiledTile。 <br/>
|
* 如果指定的坐标没有 tile,并且设置了 forceCreate 那么将会在指定的坐标创建一个新的 TiledTile 。<br/>
|
* 在渲染这个 tile 的时候,将会使用 TiledTile 的节点的旋转、缩放、位移、颜色属性。<br/>
|
* @method getTiledTileAt
|
* @param {Integer} x
|
* @param {Integer} y
|
* @param {Boolean} forceCreate
|
* @return {cc.TiledTile}
|
* @example
|
* let tile = tiledLayer.getTiledTileAt(100, 100, true);
|
* cc.log(tile);
|
*/
|
getTiledTileAt (x, y, forceCreate) {
|
if (this._isInvalidPosition(x, y)) {
|
throw new Error("TiledLayer.getTiledTileAt: invalid position");
|
}
|
if (!this._tiles) {
|
cc.logID(7236);
|
return null;
|
}
|
|
let index = Math.floor(x) + Math.floor(y) * this._layerSize.width;
|
let tile = this._tiledTiles[index];
|
if (!tile && forceCreate) {
|
let node = new cc.Node();
|
tile = node.addComponent(cc.TiledTile);
|
tile._x = x;
|
tile._y = y;
|
tile._layer = this;
|
tile._updateInfo();
|
node.parent = this.node;
|
return tile;
|
}
|
return tile;
|
},
|
|
/**
|
* !#en
|
* Change tile to TiledTile at the specified coordinate.
|
* !#zh
|
* 将指定的 tile 坐标替换为指定的 TiledTile。
|
* @method setTiledTileAt
|
* @param {Integer} x
|
* @param {Integer} y
|
* @param {cc.TiledTile} tiledTile
|
* @return {cc.TiledTile}
|
*/
|
setTiledTileAt (x, y, tiledTile) {
|
if (this._isInvalidPosition(x, y)) {
|
throw new Error("TiledLayer.setTiledTileAt: invalid position");
|
}
|
if (!this._tiles) {
|
cc.logID(7236);
|
return null;
|
}
|
|
let index = Math.floor(x) + Math.floor(y) * this._layerSize.width;
|
return this._tiledTiles[index] = tiledTile;
|
},
|
|
/**
|
* !#en Return texture of cc.SpriteBatchNode.
|
* !#zh 获取纹理。
|
* @method getTexture
|
* @return {Texture2D}
|
* @example
|
* let texture = tiledLayer.getTexture();
|
* cc.log("Texture: " + texture);
|
*/
|
getTexture () {
|
return this._texture;
|
},
|
|
/**
|
* !#en Set the texture of cc.SpriteBatchNode.
|
* !#zh 设置纹理。
|
* @method setTexture
|
* @param {Texture2D} texture
|
* @example
|
* tiledLayer.setTexture(texture);
|
*/
|
setTexture (texture){
|
this._texture = texture;
|
this._activateMaterial();
|
},
|
|
/**
|
* !#en Gets layer size.
|
* !#zh 获得层大小。
|
* @method getLayerSize
|
* @return {Size}
|
* @example
|
* let size = tiledLayer.getLayerSize();
|
* cc.log("layer size: " + size);
|
*/
|
getLayerSize () {
|
return this._layerSize;
|
},
|
|
/**
|
* !#en Size of the map's tile (could be different from the tile's size).
|
* !#zh 获取 tile 的大小( tile 的大小可能会有所不同)。
|
* @method getMapTileSize
|
* @return {Size}
|
* @example
|
* let mapTileSize = tiledLayer.getMapTileSize();
|
* cc.log("MapTile size: " + mapTileSize);
|
*/
|
getMapTileSize () {
|
return this._mapTileSize;
|
},
|
|
/**
|
* !#en Tile set information for the layer.
|
* !#zh 获取 layer 的 Tileset 信息。
|
* @method getTileSet
|
* @return {TMXTilesetInfo}
|
* @example
|
* let tileset = tiledLayer.getTileSet();
|
*/
|
getTileSet () {
|
return this._tileset;
|
},
|
|
/**
|
* !#en Tile set information for the layer.
|
* !#zh 设置 layer 的 Tileset 信息。
|
* @method setTileSet
|
* @param {TMXTilesetInfo} tileset
|
* @example
|
* tiledLayer.setTileSet(tileset);
|
*/
|
setTileSet (tileset) {
|
this._tileset = tileset;
|
},
|
|
/**
|
* !#en Layer orientation, which is the same as the map orientation.
|
* !#zh 获取 Layer 方向(同地图方向)。
|
* @method getLayerOrientation
|
* @return {Number}
|
* @example
|
* let orientation = tiledLayer.getLayerOrientation();
|
* cc.log("Layer Orientation: " + orientation);
|
*/
|
getLayerOrientation () {
|
return this._layerOrientation;
|
},
|
|
|
/**
|
* !#en properties from the layer. They can be added using Tiled.
|
* !#zh 获取 layer 的属性,可以使用 Tiled 编辑器添加属性。
|
* @method getProperties
|
* @return {Array}
|
* @example
|
* let properties = tiledLayer.getProperties();
|
* cc.log("Properties: " + properties);
|
*/
|
getProperties () {
|
return this._properties;
|
},
|
|
_init (tileset, layerInfo, mapInfo) {
|
let size = layerInfo._layerSize;
|
|
// layerInfo
|
this._layerName = layerInfo.name;
|
this._tiles = layerInfo._tiles;
|
this._properties = layerInfo.properties;
|
this._layerSize = size;
|
this._minGID = layerInfo._minGID;
|
this._maxGID = layerInfo._maxGID;
|
this._opacity = layerInfo._opacity;
|
this._staggerAxis = mapInfo.getStaggerAxis();
|
this._staggerIndex = mapInfo.getStaggerIndex();
|
this._hexSideLength = mapInfo.getHexSideLength();
|
|
// tilesetInfo
|
this._tileset = tileset;
|
|
// mapInfo
|
this._layerOrientation = mapInfo.orientation;
|
this._mapTileSize = mapInfo.getTileSize();
|
|
let tilesets = mapInfo._tilesets;
|
if (tilesets) {
|
this._textures.length = tilesets.length;
|
this._texGrids.length = 0;
|
for (let i = 0, l = tilesets.length; i < l; ++i) {
|
let tilesetInfo = tilesets[i];
|
let tex = tilesetInfo.sourceImage;
|
this._textures[i] = tex;
|
this._fillTextureGrids(tilesetInfo, i);
|
if (tileset === tilesetInfo) {
|
this._texture = tex;
|
}
|
}
|
}
|
|
// offset (after layer orientation is set);
|
this._offset = this._calculateLayerOffset(layerInfo.offset);
|
|
if (this._layerOrientation === cc.TiledMap.Orientation.HEX) {
|
let width = 0, height = 0;
|
if (this._staggerAxis === cc.TiledMap.StaggerAxis.STAGGERAXIS_X) {
|
height = mapInfo._tileSize.height * (this._layerSize.height + 0.5);
|
width = (mapInfo._tileSize.width + this._hexSideLength) * Math.floor(this._layerSize.width / 2) + mapInfo._tileSize.width * (this._layerSize.width % 2);
|
} else {
|
width = mapInfo._tileSize.width * (this._layerSize.width + 0.5);
|
height = (mapInfo._tileSize.height + this._hexSideLength) * Math.floor(this._layerSize.height / 2) + mapInfo._tileSize.height * (this._layerSize.height % 2);
|
}
|
this.node.setContentSize(width, height);
|
} else {
|
this.node.setContentSize(this._layerSize.width * this._mapTileSize.width,
|
this._layerSize.height * this._mapTileSize.height);
|
}
|
this._useAutomaticVertexZ = false;
|
this._vertexZvalue = 0;
|
|
this._activateMaterial();
|
},
|
|
_calculateLayerOffset (pos) {
|
let ret = cc.v2(0,0);
|
switch (this._layerOrientation) {
|
case cc.TiledMap.Orientation.ORTHO:
|
ret = cc.v2(pos.x * this._mapTileSize.width, -pos.y * this._mapTileSize.height);
|
break;
|
case cc.TiledMap.Orientation.ISO:
|
ret = cc.v2((this._mapTileSize.width / 2) * (pos.x - pos.y),
|
(this._mapTileSize.height / 2 ) * (-pos.x - pos.y));
|
break;
|
case cc.TiledMap.Orientation.HEX:
|
if(this._staggerAxis === cc.TiledMap.StaggerAxis.STAGGERAXIS_Y)
|
{
|
let diffX = (this._staggerIndex === cc.TiledMap.StaggerIndex.STAGGERINDEX_EVEN) ? this._mapTileSize.width/2 : 0;
|
ret = cc.v2(pos.x * this._mapTileSize.width + diffX,
|
-pos.y * (this._mapTileSize.height - (this._mapTileSize.width - this._hexSideLength) / 2));
|
}
|
else if(this._staggerAxis === cc.TiledMap.StaggerAxis.STAGGERAXIS_X)
|
{
|
let diffY = (this._staggerIndex === cc.TiledMap.StaggerIndex.STAGGERINDEX_ODD) ? this._mapTileSize.height/2 : 0;
|
ret = cc.v2(pos.x * (this._mapTileSize.width - (this._mapTileSize.width - this._hexSideLength) / 2),
|
-pos.y * this._mapTileSize.height + diffY);
|
}
|
break;
|
}
|
return ret;
|
},
|
|
_fillTextureGrids (tileset, texId) {
|
let tex = this._textures[texId];
|
if (!tex) {
|
return;
|
}
|
|
if (!tex.loaded) {
|
tex.once('load', function () {
|
this._fillTextureGrids(tileset, texId);
|
}, this);
|
return;
|
}
|
if (!tileset.imageSize.width || !tileset.imageSize.height) {
|
tileset.imageSize.width = tex.width;
|
tileset.imageSize.height = tex.height;
|
}
|
let tw = tileset._tileSize.width,
|
th = tileset._tileSize.height,
|
imageW = tex.width,
|
imageH = tex.height,
|
spacing = tileset.spacing,
|
margin = tileset.margin,
|
|
cols = Math.floor((imageW - margin*2 + spacing) / (tw + spacing)),
|
rows = Math.floor((imageH - margin*2 + spacing) / (th + spacing)),
|
count = rows * cols,
|
|
gid = tileset.firstGid,
|
maxGid = tileset.firstGid + count,
|
grids = this._texGrids,
|
grid = null,
|
override = grids[gid] ? true : false,
|
texelCorrect = cc.macro.FIX_ARTIFACTS_BY_STRECHING_TEXEL_TMX ? 0.5 : 0;
|
|
for (; gid < maxGid; ++gid) {
|
// Avoid overlapping
|
if (override && !grids[gid]) {
|
override = false;
|
}
|
if (!override && grids[gid]) {
|
break;
|
}
|
|
grid = {
|
texId: texId,
|
x: 0, y: 0, width: tw, height: th,
|
t: 0, l: 0, r: 0, b: 0
|
};
|
tileset.rectForGID(gid, grid);
|
grid.x += texelCorrect;
|
grid.y += texelCorrect;
|
grid.width -= texelCorrect*2;
|
grid.height -= texelCorrect*2;
|
grid.t = (grid.y) / imageH;
|
grid.l = (grid.x) / imageW;
|
grid.r = (grid.x + grid.width) / imageW;
|
grid.b = (grid.y + grid.height) / imageH;
|
grids[gid] = grid;
|
}
|
},
|
|
_activateMaterial () {
|
let material = this._material;
|
if (!material) {
|
material = this._material = new SpriteMaterial();
|
material.useColor = false;
|
}
|
|
if (this._texture) {
|
// TODO: old texture in material have been released by loader
|
material.texture = this._texture;
|
this.markForUpdateRenderData(true);
|
this.markForRender(true);
|
}
|
else {
|
this.disableRender();
|
}
|
|
this._updateMaterial(material);
|
},
|
});
|
|
cc.TiledLayer = module.exports = TiledLayer;
|