mirror of
https://github.com/cocos/cocos-engine.git
synced 2026-09-03 06:15:06 +08:00
1591 lines
55 KiB
TypeScript
1591 lines
55 KiB
TypeScript
/* eslint-disable default-case */
|
||
/*
|
||
Copyright (c) 2013-2016 Chukong Technologies Inc.
|
||
Copyright (c) 2017-2023 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 documentation files (the "Software"), to deal
|
||
in the Software without restriction, including without limitation the rights to
|
||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||
subject to the following conditions:
|
||
|
||
The above copyright notice and this permission notice shall be included in
|
||
all copies or substantial portions of the Software.
|
||
|
||
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 { ccclass } from 'cc.decorator';
|
||
|
||
import { EDITOR_NOT_IN_PREVIEW } from 'internal:constants';
|
||
import { screenAdapter } from 'pal/screen-adapter';
|
||
import { UIRenderer } from '../2d/framework/ui-renderer';
|
||
import { SpriteFrame } from '../2d/assets/sprite-frame';
|
||
import { Component, Node } from '../scene-graph';
|
||
import { TMXMapInfo } from './tmx-xml-parser';
|
||
import { Color, IVec2Like, Mat4, Size, Vec2, Vec3, logID, warnID } from '../core';
|
||
import { TiledTile } from './tiled-tile';
|
||
import { RenderData } from '../2d/renderer/render-data';
|
||
import { IBatcher } from '../2d/renderer/i-batcher';
|
||
import {
|
||
MixedGID, GID, Orientation, TiledTextureGrids, TMXTilesetInfo, RenderOrder, StaggerAxis, StaggerIndex, TileFlag,
|
||
GIDFlags, TiledAnimationType, PropertiesInfo, TMXLayerInfo,
|
||
} from './tiled-types';
|
||
import { fillTextureGrids } from './tiled-utils';
|
||
import { NodeEventType } from '../scene-graph/node-event';
|
||
import { RenderEntity, RenderEntityType } from '../2d/renderer/render-entity';
|
||
import { RenderDrawInfo, RenderDrawInfoType } from '../2d/renderer/render-draw-info';
|
||
import { Texture2D } from '../asset/assets';
|
||
import { director } from '../game';
|
||
import { Camera } from '../render-scene/scene';
|
||
import { View } from '../ui/view';
|
||
|
||
const _mat4_temp = new Mat4();
|
||
const _vec2_temp = new Vec2();
|
||
const _vec3_temp = new Vec3();
|
||
const _vec3_temp2 = new Vec3();
|
||
const _tempRowCol = { row: 0, col: 0 };
|
||
|
||
function isTiledSubNodeData (data: TiledRenderData | TiledSubNodeData): data is TiledSubNodeData {
|
||
return 'subNodes' in data;
|
||
}
|
||
|
||
@ccclass('cc.TiledUserNodeData')
|
||
export class TiledUserNodeData extends Component {
|
||
_index = -1;
|
||
_row = -1;
|
||
_col = -1;
|
||
_tiledLayer: TiledLayer | null = null;
|
||
constructor () {
|
||
super();
|
||
}
|
||
}
|
||
|
||
export interface TiledRenderData {
|
||
renderData: RenderData | null;
|
||
texture: Texture2D | null;
|
||
}
|
||
|
||
interface TiledSubNodeData {
|
||
subNodes: (null | TiledUserNodeData)[];
|
||
}
|
||
|
||
type TiledDataArray = (TiledRenderData | TiledSubNodeData)[];
|
||
|
||
export interface ITiledLayerCullingRect {
|
||
leftDown: {
|
||
row: number;
|
||
col: number;
|
||
};
|
||
rightTop: {
|
||
row: number;
|
||
col: number;
|
||
};
|
||
}
|
||
|
||
/**
|
||
* @en Render the TMX layer.
|
||
* @zh 渲染 TMX layer。
|
||
* @class TiledLayer
|
||
* @extends Component
|
||
*/
|
||
@ccclass('cc.TiledLayer')
|
||
export class TiledLayer extends UIRenderer {
|
||
// [row][col] = {count: 0, nodesList: []};
|
||
protected _userNodeGrid: SafeRecord<number, { count: number; } & SafeRecord<number, { count: number, list: (TiledUserNodeData | null)[] } >> = {};
|
||
protected _userNodeMap: { [key: string]: TiledUserNodeData } = {};// [id] = node;
|
||
protected _userNodeDirty = false;
|
||
|
||
// store the layer tiles node, index is caculated by 'x + width * y', format likes '[0]=tileNode0,[1]=tileNode1, ...'
|
||
public tiledTiles: (TiledTile | null)[] = [];
|
||
|
||
// // store the layer tilesets index array
|
||
// _tilesetIndexArr: number[] = [];
|
||
// // tileset index to array index
|
||
// _tilesetIndexToArrIndex: { [key: number]: number } = {};
|
||
|
||
protected _viewPort = { x: -1, y: -1, width: -1, height: -1 };
|
||
protected _cullingRect: ITiledLayerCullingRect = {
|
||
leftDown: { row: -1, col: -1 },
|
||
rightTop: { row: -1, col: -1 },
|
||
};
|
||
get cullingRect (): ITiledLayerCullingRect {
|
||
return this._cullingRect;
|
||
}
|
||
|
||
protected _cullingDirty = true;
|
||
protected _rightTop = { row: -1, col: -1 };
|
||
get rightTop (): {
|
||
row: number;
|
||
col: number;
|
||
} { return this._rightTop; }
|
||
|
||
protected _layerInfo: TMXLayerInfo | null = null;
|
||
protected _mapInfo: TMXMapInfo | null = null;
|
||
|
||
// record max or min tile texture offset,
|
||
// it will make culling rect more large, which insure culling rect correct.
|
||
protected _topOffset = 0;
|
||
protected _downOffset = 0;
|
||
protected _leftOffset = 0;
|
||
protected _rightOffset = 0;
|
||
|
||
// store the layer tiles, index is caculated by 'x + width * y', format likes '[0]=gid0,[1]=gid1, ...'
|
||
public tiles: MixedGID[] = [];
|
||
|
||
// vertex array
|
||
public vertices: SafeArray<{ minCol: number, maxCol: number } & SafeRecord<number, { left: number, bottom: number, index: number }>> = [];
|
||
// vertices dirty
|
||
protected _verticesDirty = true;
|
||
|
||
protected _layerName = '';
|
||
protected _layerSize?: Size;
|
||
|
||
get layerSize (): Size { return this._layerSize!; }
|
||
|
||
protected _minGID?: GID;
|
||
protected _maxGID?: GID;
|
||
protected _layerOrientation: null | Orientation = null;
|
||
|
||
protected _opacity?: number;
|
||
protected _tintColor?: Color;
|
||
|
||
// store all layer gid corresponding texture info, index is gid, format likes '[gid0]=tex-info,[gid1]=tex-info, ...'
|
||
public texGrids: TiledTextureGrids | null = null;
|
||
// store all tileset texture, index is tileset index, format likes '[0]=texture0, [1]=texture1, ...'
|
||
protected _textures: SpriteFrame[] = [];
|
||
protected _tilesets: TMXTilesetInfo[] = [];
|
||
|
||
protected _leftDownToCenterX = 0;
|
||
protected _leftDownToCenterY = 0;
|
||
|
||
protected _hasTiledNodeGrid = false;
|
||
protected _hasAniGrid = false;
|
||
protected _animations: TiledAnimationType | null = null;
|
||
|
||
// switch of culling
|
||
protected _enableCulling?: boolean;
|
||
|
||
public colorChanged = false;
|
||
|
||
protected _properties?: PropertiesInfo;
|
||
public renderOrder?: RenderOrder;
|
||
protected _staggerAxis?: StaggerAxis;
|
||
protected _staggerIndex?: StaggerIndex;
|
||
protected _hexSideLength?: number;
|
||
|
||
protected _mapTileSize?: Size;
|
||
protected _odd_even?: number;
|
||
protected _diffX1?: number;
|
||
protected _diffY1?: number;
|
||
protected _useAutomaticVertexZ?: boolean;
|
||
protected _vertexZvalue?: number;
|
||
protected _offset?: Vec2;
|
||
|
||
protected _tiledDataArray: TiledDataArray = [];
|
||
|
||
protected _cameraNode?: Node;
|
||
|
||
get tiledDataArray (): TiledDataArray { return this._tiledDataArray; }
|
||
get leftDownToCenterX (): number { return this._leftDownToCenterX; }
|
||
get leftDownToCenterY (): number { return this._leftDownToCenterY; }
|
||
|
||
private _drawInfoList: RenderDrawInfo[] = [];
|
||
private requestDrawInfo (idx: number): RenderDrawInfo {
|
||
if (!this._drawInfoList[idx]) {
|
||
this._drawInfoList[idx] = new RenderDrawInfo();
|
||
this._drawInfoList[idx].setDrawInfoType(RenderDrawInfoType.MIDDLEWARE);
|
||
}
|
||
return this._drawInfoList[idx];
|
||
}
|
||
|
||
constructor () {
|
||
super();
|
||
}
|
||
|
||
public hasTiledNode (): boolean {
|
||
return this._hasTiledNodeGrid;
|
||
}
|
||
|
||
public hasAnimation (): boolean {
|
||
return this._hasAniGrid;
|
||
}
|
||
|
||
/**
|
||
* @en enable or disable culling
|
||
* @zh 开启或关闭裁剪。
|
||
* @method enableCulling
|
||
* @param value
|
||
*/
|
||
set enableCulling (value: boolean) {
|
||
if (this._enableCulling !== value) {
|
||
this._enableCulling = value;
|
||
this._cullingDirty = true;
|
||
this._markForUpdateRenderData();
|
||
}
|
||
}
|
||
get enableCulling (): boolean { return this._enableCulling!; }
|
||
|
||
/**
|
||
* @en Adds user's node into layer.
|
||
* @zh 添加用户节点。
|
||
* @method addUserNode
|
||
* @param {cc.Node} node
|
||
* @return {Boolean}
|
||
*/
|
||
public addUserNode (node: Node): boolean {
|
||
let dataComp = node.getComponent(TiledUserNodeData);
|
||
if (dataComp) {
|
||
warnID(7242);
|
||
return false;
|
||
}
|
||
|
||
dataComp = node.addComponent(TiledUserNodeData);
|
||
node.parent = this.node;
|
||
this._userNodeMap[node.uuid] = dataComp;
|
||
|
||
dataComp._row = -1;
|
||
dataComp._col = -1;
|
||
dataComp._tiledLayer = this;
|
||
|
||
this._nodeLocalPosToLayerPos(node.getPosition(), _vec2_temp);
|
||
this._positionToRowCol(_vec2_temp.x, _vec2_temp.y, _tempRowCol);
|
||
this._addUserNodeToGrid(dataComp, _tempRowCol);
|
||
this._updateCullingOffsetByUserNode(node);
|
||
node.on(NodeEventType.TRANSFORM_CHANGED, this._userNodePosChange, dataComp);
|
||
node.on(NodeEventType.SIZE_CHANGED, this._userNodeSizeChange, dataComp);
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* @en Removes user's node.
|
||
* @zh 移除用户节点。
|
||
* @method removeUserNode
|
||
* @param {cc.Node} node
|
||
* @return {Boolean}
|
||
*/
|
||
public removeUserNode (node: Node): boolean {
|
||
const dataComp = node.getComponent(TiledUserNodeData);
|
||
if (!dataComp) {
|
||
warnID(7243);
|
||
return false;
|
||
}
|
||
node.off(NodeEventType.TRANSFORM_CHANGED, this._userNodePosChange, dataComp);
|
||
node.off(NodeEventType.SIZE_CHANGED, this._userNodeSizeChange, dataComp);
|
||
this._removeUserNodeFromGrid(dataComp);
|
||
delete this._userNodeMap[node.uuid];
|
||
node._removeComponent(dataComp);
|
||
dataComp.destroy();
|
||
node.removeFromParent();
|
||
// node._renderFlag &= ~RenderFlow.FLAG_BREAK_FLOW;
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* @en Destroy user's node.
|
||
* @zh 销毁用户节点。
|
||
* @method destroyUserNode
|
||
* @param {cc.Node} node
|
||
*/
|
||
public destroyUserNode (node: Node): void {
|
||
this.removeUserNode(node);
|
||
node.destroy();
|
||
}
|
||
|
||
// acording layer anchor point to calculate node layer pos
|
||
protected _nodeLocalPosToLayerPos (nodePos: IVec2Like, out: IVec2Like): void {
|
||
out.x = nodePos.x + this._leftDownToCenterX;
|
||
out.y = nodePos.y + this._leftDownToCenterY;
|
||
}
|
||
|
||
public getNodesByRowCol (row: number, col: number): {
|
||
count: number;
|
||
list: (TiledUserNodeData | null)[];
|
||
} | null | undefined {
|
||
const rowData = this._userNodeGrid[row];
|
||
if (!rowData) return null;
|
||
return rowData[col];
|
||
}
|
||
|
||
public getNodesCountByRow (row): number {
|
||
const rowData = this._userNodeGrid[row];
|
||
if (!rowData) return 0;
|
||
return rowData.count;
|
||
}
|
||
|
||
protected _updateAllUserNode (): void {
|
||
this._userNodeGrid = {};
|
||
for (const dataId in this._userNodeMap) {
|
||
const dataComp = this._userNodeMap[dataId];
|
||
this._nodeLocalPosToLayerPos(dataComp.node.getPosition(), _vec2_temp);
|
||
this._positionToRowCol(_vec2_temp.x, _vec2_temp.y, _tempRowCol);
|
||
this._addUserNodeToGrid(dataComp, _tempRowCol);
|
||
this._updateCullingOffsetByUserNode(dataComp.node);
|
||
}
|
||
}
|
||
|
||
protected _updateCullingOffsetByUserNode (node_: Node): void {
|
||
const node = node_._getUITransformComp()!.contentSize;
|
||
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;
|
||
}
|
||
}
|
||
|
||
protected _userNodeSizeChange (): void {
|
||
const dataComp: TiledUserNodeData = this as unknown as any;
|
||
const node = dataComp.node;
|
||
const self = dataComp._tiledLayer!;
|
||
self._updateCullingOffsetByUserNode(node);
|
||
self._userNodeDirty = true;
|
||
self._markForUpdateRenderData();
|
||
}
|
||
|
||
protected _userNodePosChange (): void {
|
||
const dataComp: TiledUserNodeData = this as unknown as any;
|
||
const node = dataComp.node;
|
||
const self = dataComp._tiledLayer!;
|
||
self._nodeLocalPosToLayerPos(node.getPosition(), _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);
|
||
}
|
||
|
||
protected _removeUserNodeFromGrid (dataComp: TiledUserNodeData): void {
|
||
const row = dataComp._row;
|
||
const col = dataComp._col;
|
||
const index = dataComp._index;
|
||
|
||
const rowData = this._userNodeGrid[row];
|
||
const 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;
|
||
this._markForUpdateRenderData();
|
||
}
|
||
|
||
protected _limitInLayer (rowCol: { row: number, col: number }): void {
|
||
const row = rowCol.row;
|
||
const 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;
|
||
}
|
||
|
||
protected _addUserNodeToGrid (dataComp: TiledUserNodeData, tempRowCol: { col: number, row: number }): void {
|
||
const row = tempRowCol.row;
|
||
const col = tempRowCol.col;
|
||
const rowData = this._userNodeGrid[row] = this._userNodeGrid[row] || { count: 0 };
|
||
const 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;
|
||
this._markForUpdateRenderData();
|
||
}
|
||
|
||
public isUserNodeDirty (): boolean {
|
||
return this._userNodeDirty;
|
||
}
|
||
|
||
public setUserNodeDirty (value): void {
|
||
this._userNodeDirty = value;
|
||
}
|
||
|
||
protected _reinstallCamera (): Camera | null {
|
||
const camera = director.root!.batcher2D.getFirstRenderCamera(this.node);
|
||
const cameraNode = camera?.node;
|
||
if (this._cameraNode !== cameraNode) {
|
||
this._uninstallCamera();
|
||
if (cameraNode) {
|
||
cameraNode.on(NodeEventType.TRANSFORM_CHANGED, this.updateCulling, this);
|
||
cameraNode.on(NodeEventType.SIZE_CHANGED, this.updateCulling, this);
|
||
this._cameraNode = cameraNode;
|
||
}
|
||
}
|
||
return camera;
|
||
}
|
||
|
||
protected _uninstallCamera (): void {
|
||
if (this._cameraNode) {
|
||
this._cameraNode.off(NodeEventType.TRANSFORM_CHANGED, this.updateCulling, this);
|
||
this._cameraNode.off(NodeEventType.SIZE_CHANGED, this.updateCulling, this);
|
||
delete this._cameraNode;
|
||
}
|
||
}
|
||
|
||
onEnable (): void {
|
||
super.onEnable();
|
||
this.node.on(NodeEventType.ANCHOR_CHANGED, this._syncAnchorPoint, this);
|
||
this.node.on(NodeEventType.TRANSFORM_CHANGED, this.updateCulling, this);
|
||
this.node.on(NodeEventType.SIZE_CHANGED, this.updateCulling, this);
|
||
this.node.parent!.on(NodeEventType.TRANSFORM_CHANGED, this.updateCulling, this);
|
||
this.node.parent!.on(NodeEventType.SIZE_CHANGED, this.updateCulling, this);
|
||
View.instance.on('canvas-resize', this._resize, this);
|
||
screenAdapter.on('window-resize', this._resize, this);
|
||
this._markForUpdateRenderData();
|
||
// delay 1 frame, since camera's matrix data is dirty
|
||
this.scheduleOnce(this.updateCulling.bind(this));
|
||
}
|
||
|
||
onDisable (): void {
|
||
super.onDisable();
|
||
this.node.parent?.off(NodeEventType.SIZE_CHANGED, this.updateCulling, this);
|
||
this.node.parent?.off(NodeEventType.TRANSFORM_CHANGED, this.updateCulling, this);
|
||
this.node.off(NodeEventType.SIZE_CHANGED, this.updateCulling, this);
|
||
this.node.off(NodeEventType.TRANSFORM_CHANGED, this.updateCulling, this);
|
||
this.node.off(NodeEventType.ANCHOR_CHANGED, this._syncAnchorPoint, this);
|
||
View.instance.off('canvas-resize', this._resize, this);
|
||
screenAdapter.off('window-resize', this._resize, this);
|
||
this._uninstallCamera();
|
||
}
|
||
|
||
protected _syncAnchorPoint (): void {
|
||
const node = this.node;
|
||
const trans = node._getUITransformComp()!;
|
||
const scale = node.getScale();
|
||
this._leftDownToCenterX = trans.width * trans.anchorX * scale.x;
|
||
this._leftDownToCenterY = trans.height * trans.anchorY * scale.y;
|
||
this._cullingDirty = true;
|
||
this._markForUpdateRenderData();
|
||
}
|
||
|
||
protected _resize(): void {
|
||
this._markForUpdateRenderData();
|
||
}
|
||
|
||
/**
|
||
* @en Gets the layer name.
|
||
* @zh 获取层的名称。
|
||
* @method getLayerName
|
||
* @return {String}
|
||
* @example
|
||
* let layerName = tiledLayer.getLayerName();
|
||
* cc.log(layerName);
|
||
*/
|
||
public getLayerName (): string {
|
||
return this._layerName;
|
||
}
|
||
|
||
/**
|
||
* @en Set the layer name.
|
||
* @zh 设置层的名称
|
||
* @method setLayerName
|
||
* @param {String} layerName
|
||
* @example
|
||
* tiledLayer.setLayerName("New Layer");
|
||
*/
|
||
public setLayerName (layerName: string): void {
|
||
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);
|
||
*/
|
||
public getProperty (propertyName: string): string | number {
|
||
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);
|
||
*/
|
||
public getPositionAt (pos: IVec2Like | number, y?: number): Vec2 | null {
|
||
let x: number;
|
||
if (y !== undefined) {
|
||
x = Math.floor(pos as number);
|
||
y = Math.floor(y);
|
||
} else {
|
||
x = Math.floor((pos as IVec2Like).x);
|
||
y = Math.floor((pos as IVec2Like).y);
|
||
}
|
||
|
||
switch (this._layerOrientation) {
|
||
case Orientation.ORTHO:
|
||
return this._positionForOrthoAt(x, y);
|
||
case Orientation.ISO:
|
||
return this._positionForIsoAt(x, y);
|
||
case Orientation.HEX:
|
||
return this._positionForHexAt(x, y);
|
||
}
|
||
return null;
|
||
}
|
||
|
||
public isInvalidPosition (x: number, y: number): boolean {
|
||
return x >= this._layerSize!.width || y >= this._layerSize!.height || x < 0 || y < 0;
|
||
}
|
||
|
||
protected _positionForIsoAt (x: number, y: number): Vec2 {
|
||
let offsetX = 0;
|
||
let offsetY = 0;
|
||
const index = Math.floor(x) + Math.floor(y) * this._layerSize!.width;
|
||
const gidAndFlags = this.tiles[index];
|
||
if (gidAndFlags) {
|
||
const gid = (((gidAndFlags as unknown as number) & TileFlag.FLIPPED_MASK) >>> 0);
|
||
const tileset = this.texGrids!.get(gid as unknown as GID)!.tileset;
|
||
const offset = tileset.tileOffset;
|
||
offsetX = offset.x;
|
||
offsetY = offset.y;
|
||
}
|
||
|
||
return new Vec2(
|
||
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,
|
||
);
|
||
}
|
||
|
||
protected _positionForOrthoAt (x: number, y: number): Vec2 {
|
||
let offsetX = 0;
|
||
let offsetY = 0;
|
||
const index = Math.floor(x) + Math.floor(y) * this._layerSize!.width;
|
||
const gidAndFlags = this.tiles[index];
|
||
if (gidAndFlags) {
|
||
const gid = (((gidAndFlags as unknown as number) & TileFlag.FLIPPED_MASK) >>> 0) as unknown as GID;
|
||
const tileset = this.texGrids!.get(gid)!.tileset;
|
||
const offset = tileset.tileOffset;
|
||
offsetX = offset.x;
|
||
offsetY = offset.y;
|
||
}
|
||
|
||
return new Vec2(
|
||
x * this._mapTileSize!.width + offsetX,
|
||
(this._layerSize!.height - y - 1) * this._mapTileSize!.height - offsetY,
|
||
);
|
||
}
|
||
|
||
protected _positionForHexAt (col: number, row: number): Vec2 {
|
||
const tileWidth = this._mapTileSize!.width;
|
||
const tileHeight = this._mapTileSize!.height;
|
||
const rows = this._layerSize!.height;
|
||
|
||
const index = Math.floor(col) + Math.floor(row) * this._layerSize!.width;
|
||
const gid = ((this.tiles[index] as unknown as number) & TileFlag.FLIPPED_MASK) >>> 0;
|
||
let offset: IVec2Like;
|
||
if (this.texGrids!.get(gid as unknown as GID)) {
|
||
offset = this.texGrids!.get(gid as unknown as GID)!.tileset.tileOffset;
|
||
} else {
|
||
offset = { x: 0, y: 0 };
|
||
}
|
||
|
||
const odd_even = (this._staggerIndex === StaggerIndex.STAGGERINDEX_ODD) ? 1 : -1;
|
||
let x = 0;
|
||
let y = 0;
|
||
let diffX = 0;
|
||
let diffY = 0;
|
||
switch (this._staggerAxis) {
|
||
case 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 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 new Vec2(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)
|
||
*/
|
||
public setTilesGIDAt (gids: number[], beginCol: number, beginRow: number, totalCols: number): void {
|
||
if (!gids || gids.length === 0 || totalCols <= 0) return;
|
||
if (beginRow < 0) beginRow = 0;
|
||
if (beginCol < 0) beginCol = 0;
|
||
let gidsIdx = 0;
|
||
const endCol = beginCol + totalCols;
|
||
for (let row = beginRow; ; row++) {
|
||
for (let col = beginCol; col < endCol; col++) {
|
||
if (gidsIdx >= gids.length) return;
|
||
this._updateTileForGID(gids[gidsIdx] as unknown as MixedGID, 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)
|
||
*/
|
||
public setTileGIDAt (gid: MixedGID, x: number, y: number, flags?: number): void {
|
||
const ugid = (gid & TileFlag.FLIPPED_MASK) >>> 0;
|
||
|
||
x = Math.floor(x);
|
||
y = Math.floor(y);
|
||
if (this.isInvalidPosition(x, y)) {
|
||
throw new Error('cc.TiledLayer.setTileGIDAt(): invalid position');
|
||
}
|
||
if (!this.tiles || !this._tilesets || this._tilesets.length === 0) {
|
||
logID(7238);
|
||
return;
|
||
}
|
||
if (ugid !== 0 && ugid < this._tilesets[0].firstGid) {
|
||
logID(7239, gid);
|
||
return;
|
||
}
|
||
|
||
flags = flags || 0;
|
||
this._updateTileForGID(((ugid | flags) >>> 0) as unknown as MixedGID, x, y);
|
||
}
|
||
|
||
protected _updateTileForGID (gidAndFlags: MixedGID, x: number, y: number): void {
|
||
const idx = 0 | (x + y * this._layerSize!.width);
|
||
if (idx >= this.tiles.length) return;
|
||
|
||
const oldGIDAndFlags = this.tiles[idx];
|
||
if (gidAndFlags === oldGIDAndFlags) return;
|
||
|
||
const gid = ((gidAndFlags & TileFlag.FLIPPED_MASK) >>> 0);
|
||
const grid = this.texGrids!.get(gid);
|
||
|
||
if (grid) {
|
||
this.tiles[idx] = gidAndFlags;
|
||
this._updateVertex(x, y);
|
||
} else {
|
||
this.tiles[idx] = 0;
|
||
}
|
||
this._cullingDirty = true;
|
||
}
|
||
|
||
/**
|
||
* @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} pos
|
||
* @return {Number}
|
||
* @example
|
||
* let tileGid = tiledLayer.getTileGIDAt(0, 0);
|
||
*/
|
||
public getTileGIDAt (x: number, y: number): number | null {
|
||
if (this.isInvalidPosition(x, y)) {
|
||
throw new Error('cc.TiledLayer.getTileGIDAt(): invalid position');
|
||
}
|
||
if (!this.tiles) {
|
||
logID(7237);
|
||
return null;
|
||
}
|
||
|
||
const 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
|
||
const tile = this.tiles[index] as unknown as number;
|
||
|
||
return ((tile & TileFlag.FLIPPED_MASK) >>> 0);
|
||
}
|
||
/**
|
||
* @en
|
||
* Returns the tile flags at a given tile coordinate. <br />
|
||
* @zh
|
||
* 通过给定的 tile 坐标, 返回 tile 的 flags. <br />
|
||
* 如果它返回 null,则表示该 tile 为空。<br />
|
||
* @method getTileGIDAt
|
||
* @param {number}} x
|
||
* @param {number}} y
|
||
* @return {Number}
|
||
* @example
|
||
* let tileGid = tiledLayer.getTileGIDAt(0, 0);
|
||
*/
|
||
public getTileFlagsAt (x: number, y: number): number | null {
|
||
if (this.isInvalidPosition(x, y)) {
|
||
throw new Error('TiledLayer.getTileFlagsAt: invalid position');
|
||
}
|
||
if (!this.tiles) {
|
||
logID(7240);
|
||
return null;
|
||
}
|
||
|
||
const idx = 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
|
||
const tile = this.tiles[idx] as unknown as number;
|
||
|
||
return ((tile & TileFlag.FLIPPED_ALL) >>> 0) as unknown as GIDFlags;
|
||
}
|
||
|
||
public setCullingDirty (value: boolean): void {
|
||
this._cullingDirty = value;
|
||
}
|
||
|
||
public isCullingDirty (): boolean {
|
||
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.
|
||
public updateViewPort (x: number, y: number, width: number, height: number): void {
|
||
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 === Orientation.ISO) {
|
||
reserveLine = 2;
|
||
}
|
||
|
||
const vpx = this._viewPort.x - this._offset!.x + this._leftDownToCenterX;
|
||
const vpy = this._viewPort.y - this._offset!.y + this._leftDownToCenterY;
|
||
|
||
let leftDownX = vpx - this._leftOffset;
|
||
let leftDownY = vpy - this._downOffset;
|
||
const rightTopX = vpx + width + this._rightOffset;
|
||
const rightTopY = vpy + height + this._topOffset;
|
||
|
||
const leftDown = this._cullingRect.leftDown;
|
||
const 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++;
|
||
}
|
||
|
||
if (_tempRowCol.row !== rightTop.row || _tempRowCol.col !== rightTop.col) {
|
||
rightTop.row = _tempRowCol.row;
|
||
rightTop.col = _tempRowCol.col;
|
||
this._cullingDirty = true;
|
||
}
|
||
|
||
if (this._cullingDirty) this._markForUpdateRenderData();
|
||
}
|
||
|
||
// the result may not precise, but it dose't matter, it just uses to be got range
|
||
protected _positionToRowCol (x: number, y: number, result: { col: number, row: number }): { col: number, row: number } {
|
||
const maptw = this._mapTileSize!.width;
|
||
const mapth = this._mapTileSize!.height;
|
||
const maptw2 = maptw * 0.5;
|
||
const mapth2 = mapth * 0.5;
|
||
let row = 0;
|
||
let col = 0;
|
||
let diffX2 = 0;
|
||
let diffY2 = 0;
|
||
const axis = this._staggerAxis;
|
||
|
||
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;
|
||
}
|
||
|
||
public updateCulling (): void {
|
||
if (EDITOR_NOT_IN_PREVIEW) {
|
||
this.enableCulling = false;
|
||
} else if (this._enableCulling) {
|
||
this.node.updateWorldTransform();
|
||
Mat4.invert(_mat4_temp, this.node.getWorldMatrix());
|
||
const camera = this._reinstallCamera(); // developer should call updateCalling if the camera has changed
|
||
if (camera) {
|
||
_vec3_temp.x = 0;
|
||
_vec3_temp.y = 0;
|
||
_vec3_temp.z = 0;
|
||
_vec3_temp2.x = camera.width;
|
||
_vec3_temp2.y = camera.height;
|
||
_vec3_temp2.z = 0;
|
||
camera.screenToWorld(_vec3_temp, _vec3_temp);
|
||
camera.screenToWorld(_vec3_temp2, _vec3_temp2);
|
||
// camera.getScreenToWorldPoint(_vec2_temp, _vec2_temp);
|
||
// camera.getScreenToWorldPoint(_vec2_temp2, _vec2_temp2);
|
||
Vec3.transformMat4(_vec3_temp, _vec3_temp, _mat4_temp);
|
||
Vec3.transformMat4(_vec3_temp2, _vec3_temp2, _mat4_temp);
|
||
this.updateViewPort(_vec3_temp.x, _vec3_temp.y, _vec3_temp2.x - _vec3_temp.x, _vec3_temp2.y - _vec3_temp.y);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @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);
|
||
*/
|
||
public getLayerOrientation (): Orientation | null {
|
||
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);
|
||
*/
|
||
public getProperties (): PropertiesInfo | undefined {
|
||
return this._properties;
|
||
}
|
||
|
||
protected _updateVertex (col: number, row: number): void {
|
||
const FLIPPED_MASK = TileFlag.FLIPPED_MASK;
|
||
|
||
const vertices = this.vertices;
|
||
|
||
const layerOrientation = this._layerOrientation;
|
||
const tiles = this.tiles;
|
||
|
||
if (!tiles) {
|
||
return;
|
||
}
|
||
|
||
const rightTop = this._rightTop;
|
||
const maptw = this._mapTileSize!.width;
|
||
const mapth = this._mapTileSize!.height;
|
||
const maptw2 = maptw * 0.5;
|
||
const mapth2 = mapth * 0.5;
|
||
const rows = this._layerSize!.height;
|
||
const cols = this._layerSize!.width;
|
||
const grids = this.texGrids!;
|
||
|
||
let left = 0;
|
||
let bottom = 0;
|
||
let axis: StaggerAxis;
|
||
let diffX1: number;
|
||
let diffY1: number;
|
||
let odd_even: number;
|
||
let diffX2: number;
|
||
let diffY2: number;
|
||
|
||
if (layerOrientation === Orientation.HEX) {
|
||
axis = this._staggerAxis!;
|
||
diffX1 = this._diffX1!;
|
||
diffY1 = this._diffY1!;
|
||
odd_even = this._odd_even!;
|
||
}
|
||
|
||
let cullingCol = 0;
|
||
let cullingRow = 0;
|
||
let gridGID: GID = 0 as unknown as GID;
|
||
|
||
// grid border
|
||
let topBorder = 0;
|
||
let downBorder = 0; let leftBorder = 0;
|
||
let rightBorder = 0;
|
||
const index = row * cols + col;
|
||
const gid = tiles[index];
|
||
gridGID = (((gid as unknown as number) & FLIPPED_MASK) >>> 0) as unknown as GID;
|
||
const grid = grids.get(gridGID)!;
|
||
if (!grid) {
|
||
return;
|
||
}
|
||
|
||
// if has animation, grid must be updated per frame
|
||
if (this._animations!.get(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;
|
||
}
|
||
|
||
const rowData = vertices[cullingRow] = vertices[cullingRow] || { minCol: 0, maxCol: 0 };
|
||
const colData = rowData[cullingCol] = rowData[cullingCol] || { left: 0, bottom: 0, index: 0 };
|
||
|
||
// 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 (layerOrientation === Orientation.ISO) {
|
||
rightTop.row += 1;
|
||
}
|
||
}
|
||
|
||
if (rightTop.col < cullingCol) {
|
||
rightTop.col = cullingCol;
|
||
if (layerOrientation === Orientation.ISO) {
|
||
rightTop.col += 1;
|
||
}
|
||
}
|
||
|
||
// _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.
|
||
const tileOffset = grid.tileset.tileOffset;
|
||
left += this._offset!.x + tileOffset.x + grid.offsetX;
|
||
bottom += this._offset!.y - tileOffset.y - grid.offsetY;
|
||
|
||
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;
|
||
}
|
||
|
||
protected _updateVertices (): void {
|
||
const vertices = this.vertices;
|
||
vertices.length = 0;
|
||
|
||
const tiles = this.tiles;
|
||
if (!tiles) {
|
||
return;
|
||
}
|
||
|
||
const rightTop = this._rightTop;
|
||
rightTop.row = -1;
|
||
rightTop.col = -1;
|
||
|
||
const rows = this._layerSize!.height;
|
||
const 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);
|
||
*/
|
||
public getTiledTileAt (x: number, y: number, forceCreate?: boolean): TiledTile | null {
|
||
if (this.isInvalidPosition(x, y)) {
|
||
throw new Error('TiledLayer.getTiledTileAt: invalid position');
|
||
}
|
||
if (!this.tiles) {
|
||
logID(7236);
|
||
return null;
|
||
}
|
||
|
||
const index = Math.floor(x) + Math.floor(y) * this._layerSize!.width;
|
||
let tile = this.tiledTiles[index];
|
||
if (!tile && forceCreate) {
|
||
const node = new Node();
|
||
tile = node.addComponent(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}
|
||
*/
|
||
public setTiledTileAt (x: number, y: number, tiledTile: TiledTile | null): TiledTile | null {
|
||
if (this.isInvalidPosition(x, y)) {
|
||
throw new Error('TiledLayer.setTiledTileAt: invalid position');
|
||
}
|
||
if (!this.tiles) {
|
||
logID(7236);
|
||
return null;
|
||
}
|
||
|
||
const 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((tiledNode) => !!tiledNode);
|
||
}
|
||
|
||
return tiledTile;
|
||
}
|
||
|
||
/**
|
||
* @en Return texture.
|
||
* @zh 获取纹理。
|
||
* @method getTexture
|
||
* @param index The index of textures
|
||
* @return {Texture2D}
|
||
*/
|
||
public getTexture (index?: number): SpriteFrame | null {
|
||
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}
|
||
*/
|
||
public getTextures (): SpriteFrame[] {
|
||
return this._textures;
|
||
}
|
||
|
||
/**
|
||
* @en Set the texture.
|
||
* @zh 设置纹理。
|
||
* @method setTexture
|
||
* @param {SpriteFrame} texture
|
||
*/
|
||
public setTexture (texture: SpriteFrame): void {
|
||
this.setTextures([texture]);
|
||
}
|
||
|
||
/**
|
||
* @en Set the texture.
|
||
* @zh 设置纹理。
|
||
* @method setTexture
|
||
* @param {SpriteFrame} textures
|
||
*/
|
||
public setTextures (textures: SpriteFrame[]): void {
|
||
this._textures = textures;
|
||
this._markForUpdateRenderData();
|
||
}
|
||
|
||
/**
|
||
* @en Gets layer size.
|
||
* @zh 获得层大小。
|
||
* @method getLayerSize
|
||
* @return {Size}
|
||
* @example
|
||
* let size = tiledLayer.getLayerSize();
|
||
* cc.log("layer size: " + size);
|
||
*/
|
||
public getLayerSize (): Size {
|
||
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);
|
||
*/
|
||
public getMapTileSize (): Size {
|
||
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}
|
||
*/
|
||
public getTileSet (index: number): TMXTilesetInfo | null {
|
||
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}
|
||
*/
|
||
public getTileSets (): TMXTilesetInfo[] {
|
||
return this._tilesets;
|
||
}
|
||
|
||
/**
|
||
* @en Sets tile set information for the layer.
|
||
* @zh 设置 layer 的 tileset 信息。
|
||
* @method setTileSet
|
||
* @param {TMXTilesetInfo} tileset
|
||
*/
|
||
public setTileSet (tileset: TMXTilesetInfo): void {
|
||
this.setTileSets([tileset]);
|
||
}
|
||
|
||
/**
|
||
* @en Sets Tile set information for the layer.
|
||
* @zh 设置 layer 的 Tileset 信息。
|
||
* @method setTileSets
|
||
* @param {TMXTilesetInfo} tilesets
|
||
*/
|
||
public setTileSets (tilesets: TMXTilesetInfo[]): void {
|
||
this._tilesets = tilesets;
|
||
const textures: SpriteFrame[] = this._textures = [];
|
||
const texGrids = this.texGrids!;
|
||
texGrids.clear();
|
||
for (let i = 0; i < tilesets.length; i++) {
|
||
const tileset = tilesets[i];
|
||
if (tileset) {
|
||
textures[i] = tileset.sourceImage!;
|
||
}
|
||
}
|
||
|
||
for (let i = 0, l = tilesets.length; i < l; ++i) {
|
||
const tilesetInfo = tilesets[i];
|
||
if (!tilesetInfo) continue;
|
||
fillTextureGrids(tilesetInfo, texGrids, tilesetInfo.sourceImage);
|
||
}
|
||
this._prepareToRender();
|
||
}
|
||
|
||
public init (
|
||
layerInfo: TMXLayerInfo,
|
||
mapInfo: TMXMapInfo,
|
||
tilesets: TMXTilesetInfo[],
|
||
textures: SpriteFrame[],
|
||
texGrids: TiledTextureGrids,
|
||
): void {
|
||
const self = this;
|
||
self._cullingDirty = true;
|
||
self._layerInfo = layerInfo;
|
||
self._mapInfo = mapInfo;
|
||
|
||
const size = layerInfo.layerSize!;
|
||
|
||
// layerInfo
|
||
self._layerName = layerInfo.name;
|
||
self.tiles = layerInfo.tiles as unknown as any;
|
||
self._properties = layerInfo.properties;
|
||
self._layerSize = size;
|
||
self._minGID = layerInfo.minGID;
|
||
self._maxGID = layerInfo.maxGID;
|
||
self._opacity = layerInfo.opacity;
|
||
|
||
if (layerInfo.tintColor) {
|
||
self._tintColor = layerInfo.tintColor;
|
||
// this.node.color = this._tintColor;
|
||
}
|
||
|
||
self.renderOrder = mapInfo.renderOrder;
|
||
self._staggerAxis = mapInfo.getStaggerAxis()!;
|
||
self._staggerIndex = mapInfo.getStaggerIndex()!;
|
||
self._hexSideLength = mapInfo.getHexSideLength();
|
||
self._animations = mapInfo.getTileAnimations();
|
||
|
||
// tilesets
|
||
self._tilesets = tilesets;
|
||
// textures
|
||
self._textures = textures;
|
||
// grid texture
|
||
self.texGrids = texGrids;
|
||
|
||
// mapInfo
|
||
self._layerOrientation = mapInfo.orientation;
|
||
self._mapTileSize = mapInfo.getTileSize();
|
||
|
||
const maptw = self._mapTileSize.width;
|
||
const mapth = self._mapTileSize.height;
|
||
const layerW = self._layerSize.width;
|
||
const layerH = self._layerSize.height;
|
||
const uiTransformComp = self.node._getUITransformComp()!;
|
||
|
||
if (self._layerOrientation === Orientation.HEX) {
|
||
let width = 0;
|
||
let height = 0;
|
||
const tileWidth = maptw & ~1;
|
||
const tileHeight = mapth & ~1;
|
||
|
||
self._odd_even = (self._staggerIndex === StaggerIndex.STAGGERINDEX_ODD) ? 1 : -1;
|
||
if (self._staggerAxis === StaggerAxis.STAGGERAXIS_X) {
|
||
self._diffX1 = (tileWidth - self._hexSideLength) / 2;
|
||
self._diffY1 = 0;
|
||
width = (self._diffX1 + self._hexSideLength) * layerW + self._diffX1;
|
||
height = (tileHeight * layerH) + tileHeight / 2;
|
||
} else {
|
||
self._diffX1 = 0;
|
||
self._diffY1 = (tileHeight - self._hexSideLength) / 2;
|
||
width = (tileWidth * layerW) + tileWidth / 2;
|
||
height = (self._diffY1 + self._hexSideLength) * layerH + self._diffY1;
|
||
}
|
||
uiTransformComp.setContentSize(width, height);
|
||
} else if (self._layerOrientation === Orientation.ISO) {
|
||
const wh = layerW + layerH;
|
||
uiTransformComp.setContentSize(maptw * 0.5 * wh, mapth * 0.5 * wh);
|
||
} else {
|
||
uiTransformComp.setContentSize(layerW * maptw, layerH * mapth);
|
||
}
|
||
|
||
// offset (after layer orientation is set);
|
||
self._offset = new Vec2(layerInfo.offset.x, -layerInfo.offset.y);
|
||
self._useAutomaticVertexZ = false;
|
||
self._vertexZvalue = 0;
|
||
self._syncAnchorPoint();
|
||
self._prepareToRender();
|
||
}
|
||
|
||
protected _prepareToRender (): void {
|
||
this._updateVertices();
|
||
this._updateAllUserNode();
|
||
}
|
||
|
||
public requestTiledRenderData (): TiledRenderData {
|
||
const arr = this._tiledDataArray as any[];
|
||
while (arr.length > 0 && arr[arr.length - 1].subNodes && arr[arr.length - 1].subNodes.length === 0) {
|
||
arr.pop();
|
||
}
|
||
if (arr.length > 0) {
|
||
const last = arr[arr.length - 1];
|
||
if (last.renderData && last.renderData.vertexCount === 0) {
|
||
return last as TiledRenderData;
|
||
}
|
||
}
|
||
const comb = { renderData: null, texture: null };
|
||
this._tiledDataArray.push(comb);
|
||
return (comb as TiledRenderData);
|
||
}
|
||
|
||
public requestSubNodesData (): TiledSubNodeData {
|
||
const arr = this._tiledDataArray;
|
||
if (arr.length > 0) {
|
||
const last = arr[arr.length - 1];
|
||
if (isTiledSubNodeData(last) && last.subNodes.length === 0) {
|
||
return last;
|
||
}
|
||
}
|
||
const renderData: (TiledUserNodeData | null)[] = [];
|
||
const comb = { subNodes: renderData };
|
||
this._tiledDataArray.push(comb);
|
||
return comb;
|
||
}
|
||
|
||
public destroyRenderData (): void {
|
||
this._tiledDataArray.forEach((rd) => {
|
||
const renderData = (rd as TiledRenderData).renderData;
|
||
if (renderData) RenderData.remove(renderData);
|
||
});
|
||
this._tiledDataArray.length = 0;
|
||
super.destroyRenderData();
|
||
}
|
||
|
||
protected _flushAssembler (): void {
|
||
const assembler = TiledLayer.Assembler.getAssembler(this);
|
||
if (this._assembler !== assembler) {
|
||
this._assembler = assembler;
|
||
this._assembler.createData!(this);
|
||
}
|
||
if (this._tiledDataArray.length === 0) {
|
||
this._markForUpdateRenderData();
|
||
this._updateColor();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @en
|
||
* Index of mesh render data array
|
||
* @zh
|
||
* 网格渲染数据数组的索引
|
||
* @deprecated since v3.5.0, this is an engine private interface that will be removed in the future.
|
||
*/
|
||
public _tiledDataArrayIdx = 0;
|
||
protected _render (ui: IBatcher): void {
|
||
for (let i = 0; i < this._tiledDataArray.length; i++) {
|
||
this._tiledDataArrayIdx = i;
|
||
const m = this._tiledDataArray[i];
|
||
if ((m as TiledSubNodeData).subNodes) {
|
||
// 提前处理 User Nodes
|
||
(m as TiledSubNodeData).subNodes.forEach((c) => {
|
||
if (c) ui.walk(c.node);
|
||
});
|
||
} else {
|
||
const td = m as TiledRenderData;
|
||
if (td.texture) {
|
||
// NOTE: 由于 commitComp 只支持单张纹理, 故分多次提交
|
||
ui.commitComp(this, td.renderData, td.texture, this._assembler, null);
|
||
}
|
||
}
|
||
}
|
||
this.node._static = true;
|
||
}
|
||
|
||
protected createRenderEntity (): RenderEntity {
|
||
return new RenderEntity(RenderEntityType.CROSSED);
|
||
}
|
||
|
||
private fillIndicesBuffer (renderData: RenderData, drawInfo: RenderDrawInfo): void {
|
||
const iBuf = renderData.chunk.meshBuffer.iData;
|
||
let vertexId = renderData.chunk.vertexOffset;
|
||
const quadCount = renderData.vertexCount / 4;
|
||
let indexOffset = (vertexId / 4) * 6;
|
||
drawInfo.setIndexOffset(indexOffset);
|
||
for (let i = 0; i < quadCount; i += 1) {
|
||
iBuf[indexOffset] = vertexId;
|
||
iBuf[indexOffset + 1] = vertexId + 1;
|
||
iBuf[indexOffset + 2] = vertexId + 2;
|
||
iBuf[indexOffset + 3] = vertexId + 2;
|
||
iBuf[indexOffset + 4] = vertexId + 1;
|
||
iBuf[indexOffset + 5] = vertexId + 3;
|
||
indexOffset += 6;
|
||
vertexId += 4;
|
||
}
|
||
renderData.chunk.meshBuffer.indexOffset = indexOffset;
|
||
drawInfo.setIBCount(quadCount * 6);
|
||
}
|
||
|
||
public prepareDrawData (): void {
|
||
this._drawInfoList.length = 0;
|
||
const entity = this.renderEntity;
|
||
entity.clearDynamicRenderDrawInfos();
|
||
const tiledDataArray = this._tiledDataArray;
|
||
let idx = 0;
|
||
tiledDataArray.forEach((m) => {
|
||
if (isTiledSubNodeData(m)) {
|
||
// 提前处理 User Nodes
|
||
m.subNodes.forEach((c) => {
|
||
if (c) {
|
||
if (!this._drawInfoList[idx]) {
|
||
this._drawInfoList[idx] = new RenderDrawInfo();
|
||
}
|
||
const drawInfo = this._drawInfoList[idx];
|
||
drawInfo.setDrawInfoType(RenderDrawInfoType.SUB_NODE);
|
||
drawInfo.setSubNode(c.node);
|
||
entity.setDynamicRenderDrawInfo(drawInfo, idx);
|
||
idx++;
|
||
}
|
||
});
|
||
} else {
|
||
const td = m;
|
||
if (td.texture && td.renderData) {
|
||
if (!this._drawInfoList[idx]) {
|
||
this._drawInfoList[idx] = new RenderDrawInfo();
|
||
}
|
||
const drawInfo = this._drawInfoList[idx];
|
||
td.renderData.fillDrawInfoAttributes(drawInfo);
|
||
drawInfo.setTexture(td.texture.getGFXTexture());
|
||
drawInfo.setSampler(td.texture.getGFXSampler());
|
||
drawInfo.setMaterial(this.getRenderMaterial(0)!);
|
||
this.fillIndicesBuffer(td.renderData, drawInfo);
|
||
entity.setDynamicRenderDrawInfo(drawInfo, idx);
|
||
idx++;
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|