cocos-enhance-kit/engine/cocos2d/tilemap/CCTiledLayer.js

1448 lines
48 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/****************************************************************************
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 Material = require('../core/assets/material/CCMaterial');
const RenderFlow = require('../core/renderer/render-flow');
import { Mat4, Vec2 } from '../core/value-types';
import MaterialVariant from '../core/assets/material/material-variant';
let _mat4_temp = cc.mat4();
let _vec2_temp = cc.v2();
let _vec2_temp2 = cc.v2();
let _vec2_temp3 = cc.v2();
let _tempRowCol = {row:0, col:0};
let TiledUserNodeData = cc.Class({
name: 'cc.TiledUserNodeData',
extends: cc.Component,
ctor () {
this._index = -1;
this._row = -1;
this._col = -1;
this._tiledLayer = null;
}
});
/**
* !#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,
editor: {
inspector: 'packages://enhance-kit/inspectors/comps/tiled-layer.js',
},
properties: {
/**
* !#en Use the culling data of the specified layer, this reduces the performance consumption of culling but has limitations, please read the documentation for details.
* !#zh 使用指定 TiledLayer 的裁剪数据,这能降低裁剪的性能消耗,但该功能有所限制,详情请阅读文档。
* @property {TiledLayer} cullingLayer
*/
cullingLayer: {
default: null,
type: cc.Node,
tooltip: CC_DEV && '使用指定 TiledLayer 的裁剪数据,这能降低裁剪的性能消耗,但该功能有所限制,详情请阅读文档',
animatable: false,
notify(oldValue) {
if (this.cullingLayer) {
if (!this.cullingLayer.getComponent(cc.TiledLayer)) {
cc.warn("no cc.TiledLayer component on the cullingLayer node");
this.cullingLayer = undefined;
}
}
},
}
},
ctor () {
this._userNodeGrid = {};// [row][col] = {count: 0, nodesList: []};
this._userNodeMap = {};// [id] = node;
this._userNodeDirty = false;
// store the layer tiles node, index is caculated by 'x + width * y', format likes '[0]=tileNode0,[1]=tileNode1, ...'
this._tiledTiles = [];
// store the layer tilesets index array
this._tilesetIndexArr = [];
// tileset index to array index
this._tilesetIndexToArrIndex = {};
// texture id to material index
this._texIdToMatIndex = {};
this._viewPort = {x:-1, y:-1, width:-1, height:-1};
this._cullingRect = {
leftDown:{row:-1, col:-1},
rightTop:{row:-1, col:-1}
};
this._cullingDirty = true;
this._cullingDirtyForReuse = true;
this._rightTop = {row:-1, col:-1};
this._layerInfo = null;
this._mapInfo = null;
// record max or min tile texture offset,
// it will make culling rect more large, which insure culling rect correct.
this._topOffset = 0;
this._downOffset = 0;
this._leftOffset = 0;
this._rightOffset = 0;
// store the layer tiles, index is caculated by 'x + width * y', format likes '[0]=gid0,[1]=gid1, ...'
this._tiles = [];
// vertex array
this._vertices = [];
// vertices dirty
this._verticesDirty = true;
this._layerName = '';
this._layerOrientation = null;
// store all layer gid corresponding texture info, index is gid, format likes '[gid0]=tex-info,[gid1]=tex-info, ...'
this._texGrids = null;
// store all tileset texture, index is tileset index, format likes '[0]=texture0, [1]=texture1, ...'
this._textures = null;
this._tilesets = null;
this._leftDownToCenterX = 0;
this._leftDownToCenterY = 0;
this._hasTiledNodeGrid = false;
this._hasAniGrid = false;
this._animations = null;
// switch of culling
this._enableCulling = cc.macro.ENABLE_TILEDMAP_CULLING;
},
_hasTiledNode () {
return this._hasTiledNodeGrid;
},
_hasAnimation () {
return this._hasAniGrid;
},
/**
* !#en enable or disable culling
* !#zh 开启或关闭裁剪。
* @method enableCulling
* @param value
*/
enableCulling (value) {
if (this._enableCulling != value) {
this._enableCulling = value;
this._cullingDirty = true;
}
},
/**
* !#en Adds user's node into layer.
* !#zh 添加用户节点。
* @method addUserNode
* @param {cc.Node} node
* @return {Boolean}
*/
addUserNode (node) {
let dataComp = node.getComponent(TiledUserNodeData);
if (dataComp) {
cc.warn("CCTiledLayer:addUserNode node has been added");
return false;
}
dataComp = node.addComponent(TiledUserNodeData);
node.parent = this.node;
node._renderFlag |= RenderFlow.FLAG_BREAK_FLOW;
this._userNodeMap[node._id] = dataComp;
dataComp._row = -1;
dataComp._col = -1;
dataComp._tiledLayer = this;
this._nodeLocalPosToLayerPos(node, _vec2_temp);
this._positionToRowCol(_vec2_temp.x, _vec2_temp.y, _tempRowCol);
this._addUserNodeToGrid(dataComp, _tempRowCol);
this._updateCullingOffsetByUserNode(node);
node.on(cc.Node.EventType.POSITION_CHANGED, this._userNodePosChange, dataComp);
node.on(cc.Node.EventType.SIZE_CHANGED, this._userNodeSizeChange, dataComp);
return true;
},
/**
* !#en Removes user's node.
* !#zh 移除用户节点。
* @method removeUserNode
* @param {cc.Node} node
* @return {Boolean}
*/
removeUserNode (node) {
let dataComp = node.getComponent(TiledUserNodeData);
if (!dataComp) {
cc.warn("CCTiledLayer:removeUserNode node is not exist");
return false;
}
node.off(cc.Node.EventType.POSITION_CHANGED, this._userNodePosChange, dataComp);
node.off(cc.Node.EventType.SIZE_CHANGED, this._userNodeSizeChange, dataComp);
this._removeUserNodeFromGrid(dataComp);
delete this._userNodeMap[node._id];
node._removeComponent(dataComp);
dataComp.destroy();
node.removeFromParent(true);
node._renderFlag &= ~RenderFlow.FLAG_BREAK_FLOW;
return true;
},
/**
* !#en Destroy user's node.
* !#zh 销毁用户节点。
* @method destroyUserNode
* @param {cc.Node} node
*/
destroyUserNode (node) {
this.removeUserNode(node);
node.destroy();
},
// acording layer anchor point to calculate node layer pos
_nodeLocalPosToLayerPos (nodePos, out) {
out.x = nodePos.x + this._leftDownToCenterX;
out.y = nodePos.y + this._leftDownToCenterY;
},
_getNodesByRowCol (row, col) {
let rowData = this._userNodeGrid[row];
if (!rowData) return null;
return rowData[col];
},
_getNodesCountByRow (row) {
let rowData = this._userNodeGrid[row];
if (!rowData) return 0;
return rowData.count;
},
_updateAllUserNode () {
this._userNodeGrid = {};
for (let dataId in this._userNodeMap) {
let dataComp = this._userNodeMap[dataId];
this._nodeLocalPosToLayerPos(dataComp.node, _vec2_temp);
this._positionToRowCol(_vec2_temp.x, _vec2_temp.y, _tempRowCol);
this._addUserNodeToGrid(dataComp, _tempRowCol);
this._updateCullingOffsetByUserNode(dataComp.node);
}
},
_updateCullingOffsetByUserNode (node) {
if (this._topOffset < node.height) {
this._topOffset = node.height;
}
if (this._downOffset < node.height) {
this._downOffset = node.height;
}
if (this._leftOffset < node.width) {
this._leftOffset = node.width;
}
if (this._rightOffset < node.width) {
this._rightOffset = node.width;
}
},
_userNodeSizeChange () {
let dataComp = this;
let node = dataComp.node;
let self = dataComp._tiledLayer;
self._updateCullingOffsetByUserNode(node);
},
_userNodePosChange () {
let dataComp = this;
let node = dataComp.node;
let self = dataComp._tiledLayer;
self._nodeLocalPosToLayerPos(node, _vec2_temp);
self._positionToRowCol(_vec2_temp.x, _vec2_temp.y, _tempRowCol);
self._limitInLayer(_tempRowCol);
// users pos not change
if (_tempRowCol.row === dataComp._row && _tempRowCol.col === dataComp._col) return;
self._removeUserNodeFromGrid(dataComp);
self._addUserNodeToGrid(dataComp, _tempRowCol);
},
_removeUserNodeFromGrid (dataComp) {
let row = dataComp._row;
let col = dataComp._col;
let index = dataComp._index;
let rowData = this._userNodeGrid[row];
let colData = rowData && rowData[col];
if (colData) {
rowData.count --;
colData.count --;
colData.list[index] = null;
if (colData.count <= 0) {
colData.list.length = 0;
colData.count = 0;
}
}
dataComp._row = -1;
dataComp._col = -1;
dataComp._index = -1;
this._userNodeDirty = true;
},
_limitInLayer (rowCol) {
let row = rowCol.row;
let col = rowCol.col;
if (row < 0) rowCol.row = 0;
if (row > this._rightTop.row) rowCol.row = this._rightTop.row;
if (col < 0) rowCol.col = 0;
if (col > this._rightTop.col) rowCol.col = this._rightTop.col;
},
_addUserNodeToGrid (dataComp, tempRowCol) {
let row = tempRowCol.row;
let col = tempRowCol.col;
let rowData = this._userNodeGrid[row] = this._userNodeGrid[row] || {count : 0};
let colData = rowData[col] = rowData[col] || {count : 0, list: []};
dataComp._row = row;
dataComp._col = col;
dataComp._index = colData.list.length;
rowData.count++;
colData.count++;
colData.list.push(dataComp);
this._userNodeDirty = true;
},
_isUserNodeDirty () {
return this._userNodeDirty;
},
_setUserNodeDirty (value) {
this._userNodeDirty = value;
},
onEnable () {
this._super();
this.node.on(cc.Node.EventType.ANCHOR_CHANGED, this._syncAnchorPoint, this);
this._activateMaterial();
},
onDisable () {
this._super();
this.node.off(cc.Node.EventType.ANCHOR_CHANGED, this._syncAnchorPoint, this);
},
_syncAnchorPoint () {
let node = this.node;
this._leftDownToCenterX = node.width * node.anchorX * node.scaleX;
this._leftDownToCenterY = node.height * node.anchorY * node.scaleY;
this._cullingDirty = true;
},
onDestroy () {
this._super();
if (this._buffer) {
this._buffer.destroy();
this._buffer = null;
}
this._renderDataList = 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) {
let offsetX = 0, offsetY = 0;
let index = Math.floor(x) + Math.floor(y) * this._layerSize.width;
let gidAndFlags = this._tiles[index];
if (gidAndFlags) {
let gid = ((gidAndFlags & cc.TiledMap.TileFlag.FLIPPED_MASK) >>> 0);
let tileset = this._texGrids[gid].tileset;
let offset = tileset.tileOffset;
offsetX = offset.x;
offsetY = offset.y;
}
return cc.v2(
this._mapTileSize.width * 0.5 * (this._layerSize.height + x - y - 1) + offsetX,
this._mapTileSize.height * 0.5 * (this._layerSize.width - x + this._layerSize.height - y - 2) - offsetY
);
},
_positionForOrthoAt (x, y) {
let offsetX = 0, offsetY = 0;
let index = Math.floor(x) + Math.floor(y) * this._layerSize.width;
let gidAndFlags = this._tiles[index];
if (gidAndFlags) {
let gid = ((gidAndFlags & cc.TiledMap.TileFlag.FLIPPED_MASK) >>> 0);
let tileset = this._texGrids[gid].tileset;
let offset = tileset.tileOffset;
offsetX = offset.x;
offsetY = offset.y;
}
return cc.v2(
x * this._mapTileSize.width + offsetX,
(this._layerSize.height - y - 1) * this._mapTileSize.height - offsetY
);
},
_positionForHexAt (col, row) {
let tileWidth = this._mapTileSize.width;
let tileHeight = this._mapTileSize.height;
let rows = this._layerSize.height;
let index = Math.floor(col) + Math.floor(row) * this._layerSize.width;
let gid = this._tiles[index];
let offset;
if (this._texGrids[gid]) {
offset = this._texGrids[gid].tileset.tileOffset;
} else {
offset = { x: 0, y: 0 }
}
let odd_even = (this._staggerIndex === cc.TiledMap.StaggerIndex.STAGGERINDEX_ODD) ? 1 : -1;
let x = 0, y = 0;
let diffX = 0;
let diffY = 0;
switch (this._staggerAxis) {
case cc.TiledMap.StaggerAxis.STAGGERAXIS_Y:
diffX = 0;
if (row % 2 === 1) {
diffX = tileWidth / 2 * odd_even;
}
x = col * tileWidth + diffX + offset.x;
y = (rows - row - 1) * (tileHeight - (tileHeight - this._hexSideLength) / 2) - offset.y;
break;
case cc.TiledMap.StaggerAxis.STAGGERAXIS_X:
diffY = 0;
if (col % 2 === 1) {
diffY = tileHeight / 2 * -odd_even;
}
x = col * (tileWidth - (tileWidth - this._hexSideLength) / 2) + offset.x;
y = (rows - row - 1) * tileHeight + diffY - offset.y;
break;
}
return cc.v2(x, y);
},
/**
* !#en
* Sets the tiles gid (gid = tile global id) at a given tiles rect.
* !#zh
* 设置给定区域的 tile 的 gid (gid = tile 全局 id)
* @method setTilesGIDAt
* @param {Array} gids an array contains gid
* @param {Number} beginCol begin col number
* @param {Number} beginRow begin row number
* @param {Number} totalCols count of column
* @example
* tiledLayer.setTilesGIDAt([1, 1, 1, 1], 10, 10, 2)
*/
setTilesGIDAt (gids, beginCol, beginRow, totalCols) {
if (!gids || gids.length === 0 || totalCols <= 0) return;
if (beginRow < 0) beginRow = 0;
if (beginCol < 0) beginCol = 0;
let gidsIdx = 0;
let endCol = beginCol + totalCols;
for (let row = beginRow; ; row++) {
for (let col = beginCol; col < endCol; col++) {
if (gidsIdx >= gids.length) return;
this._updateTileForGID(gids[gidsIdx], col, row);
gidsIdx++;
}
}
},
/**
* !#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
_vec2_temp3.x = posOrX;
_vec2_temp3.y = flagsOrY;
pos = _vec2_temp3;
} else {
pos = posOrX;
flags = flagsOrY;
}
let ugid = gid & cc.TiledMap.TileFlag.FLIPPED_MASK;
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 || !this._tilesets || this._tilesets.length == 0) {
cc.logID(7238);
return;
}
if (ugid !== 0 && ugid < this._tilesets[0].firstGid) {
cc.logID(7239, gid);
return;
}
flags = flags || 0;
this._updateTileForGID( (gid | flags) >>> 0, pos.x, pos.y);
},
_updateTileForGID (gidAndFlags, x, y) {
let idx = 0 | (x + y * this._layerSize.width);
if (idx >= this._tiles.length) return;
let oldGIDAndFlags = this._tiles[idx];
if (gidAndFlags === oldGIDAndFlags) return;
let gid = ((gidAndFlags & cc.TiledMap.TileFlag.FLIPPED_MASK) >>> 0);
let grid = this._texGrids[gid];
let tilesetIdx = grid && grid.texId;
if (grid) {
this._tiles[idx] = gidAndFlags;
this._updateVertex(x, y);
this._buildMaterial(tilesetIdx);
} else {
this._tiles[idx] = 0;
}
this._cullingDirty = true;
},
/**
* !#en
* Returns the tiles data.An array fill with GIDs. <br />
* !#zh
* 返回 tiles 数据. 由GID构成的一个数组. <br />
* @method getTiles
* @return {Number[]}
*/
getTiles() {
return this._tiles;
},
/**
* !#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;
},
_setCullingDirty (value) {
this._cullingDirty = value;
},
_isCullingDirty () {
return this._cullingDirty;
},
// 'x, y' is the position of viewPort, which's anchor point is at the center of rect.
// 'width, height' is the size of viewPort.
_updateViewPort (x, y, width, height) {
if (this._viewPort.width === width &&
this._viewPort.height === height &&
this._viewPort.x === x &&
this._viewPort.y === y) {
return;
}
this._viewPort.x = x;
this._viewPort.y = y;
this._viewPort.width = width;
this._viewPort.height = height;
// if map's type is iso, reserve bottom line is 2 to avoid show empty grid because of iso grid arithmetic
let reserveLine = 1;
if (this._layerOrientation === cc.TiledMap.Orientation.ISO) {
reserveLine = 2;
}
let vpx = this._viewPort.x - this._offset.x + this._leftDownToCenterX;
let vpy = this._viewPort.y - this._offset.y + this._leftDownToCenterY;
let leftDownX = vpx - this._leftOffset;
let leftDownY = vpy - this._downOffset;
let rightTopX = vpx + width + this._rightOffset;
let rightTopY = vpy + height + this._topOffset;
let leftDown = this._cullingRect.leftDown;
let rightTop = this._cullingRect.rightTop;
if (leftDownX < 0) leftDownX = 0;
if (leftDownY < 0) leftDownY = 0;
// calc left down
this._positionToRowCol(leftDownX, leftDownY, _tempRowCol);
// make range large
_tempRowCol.row-=reserveLine;
_tempRowCol.col-=reserveLine;
// insure left down row col greater than 0
_tempRowCol.row = _tempRowCol.row > 0 ? _tempRowCol.row : 0;
_tempRowCol.col = _tempRowCol.col > 0 ? _tempRowCol.col : 0;
if (_tempRowCol.row !== leftDown.row || _tempRowCol.col !== leftDown.col) {
leftDown.row = _tempRowCol.row;
leftDown.col = _tempRowCol.col;
this._cullingDirty = true;
}
// show nothing
if (rightTopX < 0 || rightTopY < 0) {
_tempRowCol.row = -1;
_tempRowCol.col = -1;
} else {
// calc right top
this._positionToRowCol(rightTopX, rightTopY, _tempRowCol);
// make range large
_tempRowCol.row++;
_tempRowCol.col++;
}
// avoid range out of max rect
if (_tempRowCol.row > this._rightTop.row) _tempRowCol.row = this._rightTop.row;
if (_tempRowCol.col > this._rightTop.col) _tempRowCol.col = this._rightTop.col;
if (_tempRowCol.row !== rightTop.row || _tempRowCol.col !== rightTop.col) {
rightTop.row = _tempRowCol.row;
rightTop.col = _tempRowCol.col;
this._cullingDirty = true;
}
},
// the result may not precise, but it dose't matter, it just uses to be got range
_positionToRowCol (x, y, result) {
const TiledMap = cc.TiledMap;
const Orientation = TiledMap.Orientation;
const StaggerAxis = TiledMap.StaggerAxis;
let maptw = this._mapTileSize.width,
mapth = this._mapTileSize.height,
maptw2 = maptw * 0.5,
mapth2 = mapth * 0.5;
let row = 0, col = 0, diffX2 = 0, diffY2 = 0, axis = this._staggerAxis;
let cols = this._layerSize.width;
switch (this._layerOrientation) {
// left top to right dowm
case Orientation.ORTHO:
col = Math.floor(x / maptw);
row = Math.floor(y / mapth);
break;
// right top to left down
// iso can be treat as special hex whose hex side length is 0
case Orientation.ISO:
col = Math.floor(x / maptw2);
row = Math.floor(y / mapth2);
break;
// left top to right dowm
case Orientation.HEX:
if (axis === StaggerAxis.STAGGERAXIS_Y) {
row = Math.floor(y / (mapth - this._diffY1));
diffX2 = row % 2 === 1 ? maptw2 * this._odd_even : 0;
col = Math.floor((x - diffX2) / maptw);
} else {
col = Math.floor(x / (maptw - this._diffX1));
diffY2 = col % 2 === 1 ? mapth2 * -this._odd_even : 0;
row = Math.floor((y - diffY2) / mapth);
}
break;
}
result.row = row;
result.col = col;
return result;
},
_updateCulling () {
if (CC_EDITOR) {
this.enableCulling(false);
} else if (this._enableCulling) {
if (this.cullingLayer) {
const _cullingRect = this._cullingRect;
const layerComp = this.cullingLayer.getComponent(cc.TiledLayer);
if (layerComp) {
const __cullingRect = layerComp._cullingRect;
_cullingRect.leftDown.row = __cullingRect.leftDown.row;
_cullingRect.leftDown.col = __cullingRect.leftDown.col;
_cullingRect.rightTop.row = __cullingRect.rightTop.row;
_cullingRect.rightTop.col = __cullingRect.rightTop.col;
this._cullingDirty = this._cullingDirtyForReuse;
}
} else {
this.node._updateWorldMatrix();
Mat4.invert(_mat4_temp, this.node._worldMatrix);
let rect = cc.visibleRect;
let camera = cc.Camera.findCamera(this.node);
if (camera) {
_vec2_temp.x = 0;
_vec2_temp.y = 0;
_vec2_temp2.x = _vec2_temp.x + rect.width;
_vec2_temp2.y = _vec2_temp.y + rect.height;
camera.getScreenToWorldPoint(_vec2_temp, _vec2_temp);
camera.getScreenToWorldPoint(_vec2_temp2, _vec2_temp2);
Vec2.transformMat4(_vec2_temp, _vec2_temp, _mat4_temp);
Vec2.transformMat4(_vec2_temp2, _vec2_temp2, _mat4_temp);
this._updateViewPort(_vec2_temp.x, _vec2_temp.y, _vec2_temp2.x - _vec2_temp.x, _vec2_temp2.y - _vec2_temp.y);
// fillBuffers 之后会修改 _cullingDirty 导致无法复用裁剪数据
this._cullingDirtyForReuse = this._cullingDirty;
}
}
}
},
/**
* !#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 {Object}
* @example
* let properties = tiledLayer.getProperties();
* cc.log("Properties: " + properties);
*/
getProperties () {
return this._properties;
},
_updateVertex (col, row) {
const TiledMap = cc.TiledMap;
const TileFlag = TiledMap.TileFlag;
const FLIPPED_MASK = TileFlag.FLIPPED_MASK;
const StaggerAxis = TiledMap.StaggerAxis;
const Orientation = TiledMap.Orientation;
let vertices = this._vertices;
let layerOrientation = this._layerOrientation,
tiles = this._tiles;
if (!tiles) {
return;
}
let rightTop = this._rightTop;
let maptw = this._mapTileSize.width,
mapth = this._mapTileSize.height,
maptw2 = maptw * 0.5,
mapth2 = mapth * 0.5,
rows = this._layerSize.height,
cols = this._layerSize.width,
grids = this._texGrids;
let gid, grid, left, bottom,
axis, diffX1, diffY1, odd_even, diffX2, diffY2;
if (layerOrientation === Orientation.HEX) {
axis = this._staggerAxis;
diffX1 = this._diffX1;
diffY1 = this._diffY1;
odd_even = this._odd_even;
}
let cullingCol = 0, cullingRow = 0;
let tileOffset = null, gridGID = 0;
// grid border
let topBorder = 0, downBorder = 0, leftBorder = 0, rightBorder = 0;
let index = row * cols + col;
gid = tiles[index];
gridGID = ((gid & FLIPPED_MASK) >>> 0);
grid = grids[gridGID];
if (!grid) {
return;
}
// if has animation, grid must be updated per frame
if (this._animations[gridGID]) {
this._hasAniGrid = this._hasAniGrid || true;
}
switch (layerOrientation) {
// left top to right dowm
case Orientation.ORTHO:
cullingCol = col;
cullingRow = rows - row - 1;
left = cullingCol * maptw;
bottom = cullingRow * mapth;
break;
// right top to left down
case Orientation.ISO:
// if not consider about col, then left is 'w/2 * (rows - row - 1)'
// if consider about col then left must add 'w/2 * col'
// so left is 'w/2 * (rows - row - 1) + w/2 * col'
// combine expression is 'w/2 * (rows - row + col -1)'
cullingCol = rows + col - row - 1;
// if not consider about row, then bottom is 'h/2 * (cols - col -1)'
// if consider about row then bottom must add 'h/2 * (rows - row - 1)'
// so bottom is 'h/2 * (cols - col -1) + h/2 * (rows - row - 1)'
// combine expressionn is 'h/2 * (rows + cols - col - row - 2)'
cullingRow = rows + cols - col - row - 2;
left = maptw2 * cullingCol;
bottom = mapth2 * cullingRow;
break;
// left top to right dowm
case Orientation.HEX:
diffX2 = (axis === StaggerAxis.STAGGERAXIS_Y && row % 2 === 1) ? maptw2 * odd_even : 0;
diffY2 = (axis === StaggerAxis.STAGGERAXIS_X && col % 2 === 1) ? mapth2 * -odd_even : 0;
left = col * (maptw - diffX1) + diffX2;
bottom = (rows - row - 1) * (mapth - diffY1) + diffY2;
cullingCol = col;
cullingRow = rows - row - 1;
break;
}
let rowData = vertices[cullingRow] = vertices[cullingRow] || {minCol:0, maxCol:0};
let colData = rowData[cullingCol] = rowData[cullingCol] || {};
// record each row range, it will faster when culling grid
if (rowData.minCol > cullingCol) {
rowData.minCol = cullingCol;
}
if (rowData.maxCol < cullingCol) {
rowData.maxCol = cullingCol;
}
// record max rect, when viewPort is bigger than layer, can make it smaller
if (rightTop.row < cullingRow) {
rightTop.row = cullingRow;
}
if (rightTop.col < cullingCol) {
rightTop.col = cullingCol;
}
// _offset is whole layer offset
// tileOffset is tileset offset which is related to each grid
// tileOffset coordinate system's y axis is opposite with engine's y axis.
tileOffset = grid.tileset.tileOffset;
left += this._offset.x + tileOffset.x;
bottom += this._offset.y - tileOffset.y;
topBorder = -tileOffset.y + grid.tileset._tileSize.height - mapth;
topBorder = topBorder < 0 ? 0 : topBorder;
downBorder = tileOffset.y < 0 ? 0 : tileOffset.y;
leftBorder = -tileOffset.x < 0 ? 0 : -tileOffset.x;
rightBorder = tileOffset.x + grid.tileset._tileSize.width - maptw;
rightBorder = rightBorder < 0 ? 0 : rightBorder;
if (this._rightOffset < leftBorder) {
this._rightOffset = leftBorder;
}
if (this._leftOffset < rightBorder) {
this._leftOffset = rightBorder;
}
if (this._topOffset < downBorder) {
this._topOffset = downBorder;
}
if (this._downOffset < topBorder) {
this._downOffset = topBorder;
}
colData.left = left;
colData.bottom = bottom;
// this index is tiledmap grid index
colData.index = index;
this._cullingDirty = true;
},
_updateVertices () {
let vertices = this._vertices;
vertices.length = 0;
let tiles = this._tiles;
if (!tiles) {
return;
}
let rightTop = this._rightTop;
rightTop.row = -1;
rightTop.col = -1;
let rows = this._layerSize.height,
cols = this._layerSize.width;
this._topOffset = 0;
this._downOffset = 0;
this._leftOffset = 0;
this._rightOffset = 0;
this._hasAniGrid = false;
for (let row = 0; row < rows; ++row) {
for (let col = 0; col < cols; ++col) {
this._updateVertex(col, row);
}
}
this._verticesDirty = false;
},
/**
* !#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;
this._tiledTiles[index] = tiledTile;
this._cullingDirty = true;
if (tiledTile) {
this._hasTiledNodeGrid = true;
} else {
this._hasTiledNodeGrid = this._tiledTiles.some(function (tiledNode, index) {
return !!tiledNode;
});
}
return tiledTile;
},
/**
* !#en Return texture.
* !#zh 获取纹理。
* @method getTexture
* @param index The index of textures
* @return {Texture2D}
*/
getTexture (index) {
index = index || 0;
if (this._textures && index >= 0 && this._textures.length > index) {
return this._textures[index];
}
return null;
},
/**
* !#en Return texture.
* !#zh 获取纹理。
* @method getTextures
* @return {Texture2D}
*/
getTextures () {
return this._textures;
},
/**
* !#en Set the texture.
* !#zh 设置纹理。
* @method setTexture
* @param {Texture2D} texture
*/
setTexture (texture){
this.setTextures([texture]);
},
/**
* !#en Set the texture.
* !#zh 设置纹理。
* @method setTexture
* @param {Texture2D} textures
*/
setTextures (textures) {
this._textures = textures;
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 Gets Tile set first information for the layer.
* !#zh 获取 layer 索引位置为0的 Tileset 信息。
* @method getTileSet
* @param index The index of tilesets
* @return {TMXTilesetInfo}
*/
getTileSet (index) {
index = index || 0;
if (this._tilesets && index >= 0 && this._tilesets.length > index) {
return this._tilesets[index];
}
return null;
},
/**
* !#en Gets tile set all information for the layer.
* !#zh 获取 layer 所有的 Tileset 信息。
* @method getTileSet
* @return {TMXTilesetInfo}
*/
getTileSets () {
return this._tilesets;
},
/**
* !#en Sets tile set information for the layer.
* !#zh 设置 layer 的 tileset 信息。
* @method setTileSet
* @param {TMXTilesetInfo} tileset
*/
setTileSet (tileset) {
this.setTileSets([tileset]);
},
/**
* !#en Sets Tile set information for the layer.
* !#zh 设置 layer 的 Tileset 信息。
* @method setTileSets
* @param {TMXTilesetInfo} tilesets
*/
setTileSets (tilesets) {
this._tilesets = tilesets;
let textures = this._textures = [];
let texGrids = this._texGrids = [];
for (let i = 0; i < tilesets.length; i++) {
let tileset = tilesets[i];
if (tileset) {
textures[i] = tileset.sourceImage;
}
}
cc.TiledMap.loadAllTextures (textures, function () {
for (let i = 0, l = tilesets.length; i < l; ++i) {
let tilesetInfo = tilesets[i];
if (!tilesetInfo) continue;
cc.TiledMap.fillTextureGrids(tilesetInfo, texGrids, i);
}
this._prepareToRender();
}.bind(this));
},
_traverseAllGrid () {
let tiles = this._tiles;
let texGrids = this._texGrids;
let tilesetIndexArr = this._tilesetIndexArr;
let tilesetIndexToArrIndex = this._tilesetIndexToArrIndex = {};
const TiledMap = cc.TiledMap;
const TileFlag = TiledMap.TileFlag;
const FLIPPED_MASK = TileFlag.FLIPPED_MASK;
tilesetIndexArr.length = 0;
for (let i = 0; i < tiles.length; i++) {
let gid = tiles[i];
if (gid === 0) continue;
gid = ((gid & FLIPPED_MASK) >>> 0);
let grid = texGrids[gid];
if (!grid) {
cc.error("CCTiledLayer:_traverseAllGrid grid is null, gid is:", gid);
continue;
}
let tilesetIdx = grid.texId;
if (tilesetIndexToArrIndex[tilesetIdx] !== undefined) continue;
tilesetIndexToArrIndex[tilesetIdx] = tilesetIndexArr.length;
tilesetIndexArr.push(tilesetIdx);
}
},
_init (layerInfo, mapInfo, tilesets, textures, texGrids) {
this._cullingDirty = true;
this._layerInfo = layerInfo;
this._mapInfo = 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._renderOrder = mapInfo.renderOrder;
this._staggerAxis = mapInfo.getStaggerAxis();
this._staggerIndex = mapInfo.getStaggerIndex();
this._hexSideLength = mapInfo.getHexSideLength();
this._animations = mapInfo.getTileAnimations();
// tilesets
this._tilesets = tilesets;
// textures
this._textures = textures;
// grid texture
this._texGrids = texGrids;
// mapInfo
this._layerOrientation = mapInfo.orientation;
this._mapTileSize = mapInfo.getTileSize();
let maptw = this._mapTileSize.width;
let mapth = this._mapTileSize.height;
let layerW = this._layerSize.width;
let layerH = this._layerSize.height;
if (this._layerOrientation === cc.TiledMap.Orientation.HEX) {
// handle hex map
const TiledMap = cc.TiledMap;
const StaggerAxis = TiledMap.StaggerAxis;
const StaggerIndex = TiledMap.StaggerIndex;
let width = 0, height = 0;
this._odd_even = (this._staggerIndex === StaggerIndex.STAGGERINDEX_ODD) ? 1 : -1;
if (this._staggerAxis === StaggerAxis.STAGGERAXIS_X) {
this._diffX1 = (maptw - this._hexSideLength) / 2;
this._diffY1 = 0;
height = mapth * (layerH + 0.5);
width = (maptw + this._hexSideLength) * Math.floor(layerW / 2) + maptw * (layerW % 2);
} else {
this._diffX1 = 0;
this._diffY1 = (mapth - this._hexSideLength) / 2;
width = maptw * (layerW + 0.5);
height = (mapth + this._hexSideLength) * Math.floor(layerH / 2) + mapth * (layerH % 2);
}
this.node.setContentSize(width, height);
} else if (this._layerOrientation === cc.TiledMap.Orientation.ISO) {
let wh = layerW + layerH;
this.node.setContentSize(maptw * 0.5 * wh, mapth * 0.5 * wh);
} else {
this.node.setContentSize(layerW * maptw, layerH * mapth);
}
// offset (after layer orientation is set);
this._offset = cc.v2(layerInfo.offset.x, -layerInfo.offset.y);
this._useAutomaticVertexZ = false;
this._vertexZvalue = 0;
this._syncAnchorPoint();
this._prepareToRender();
},
_prepareToRender () {
this._updateVertices();
this._traverseAllGrid();
this._updateAllUserNode();
this._activateMaterial();
},
_buildMaterial (tilesetIdx) {
let texIdMatIdx = this._texIdToMatIndex;
if (texIdMatIdx[tilesetIdx] !== undefined) {
return null;
}
let tilesetIndexArr = this._tilesetIndexArr;
let tilesetIndexToArrIndex = this._tilesetIndexToArrIndex;
let index = tilesetIndexToArrIndex[tilesetIdx];
if (index === undefined) {
tilesetIndexToArrIndex[tilesetIdx] = index = tilesetIndexArr.length;
tilesetIndexArr.push(tilesetIdx);
}
let texture = this._textures[tilesetIdx];
let material = this._materials[index];
if (!material) {
material = Material.getBuiltinMaterial('2d-sprite');
}
material = MaterialVariant.create(material, this);
material.define('CC_USE_MODEL', true);
material.setProperty('texture', texture);
this._materials[index] = material;
texIdMatIdx[tilesetIdx] = index;
return material;
},
_activateMaterial () {
let tilesetIndexArr = this._tilesetIndexArr;
if (tilesetIndexArr.length === 0) {
this.disableRender();
return;
}
let matLen = tilesetIndexArr.length;
for (let i = 0; i < matLen; i++) {
this._buildMaterial(tilesetIndexArr[i]);
}
this._materials.length = matLen;
this.markForRender(true);
}
});
cc.TiledLayer = module.exports = TiledLayer;