Files
esengine/packages/tilemap/src/TilemapComponent.ts

1029 lines
33 KiB
TypeScript
Raw Normal View History

import { Component, ECSComponent, Serializable, Serialize, Property } from '@esengine/ecs-framework';
import type { IResourceComponent, ResourceReference } from '@esengine/asset-system';
import { UVHelper } from '@esengine/asset-system';
/**
* Resize anchor point for tilemap expansion
*
*/
export type ResizeAnchor =
| 'top-left' | 'top-center' | 'top-right'
| 'middle-left' | 'center' | 'middle-right'
| 'bottom-left' | 'bottom-center' | 'bottom-right';
/**
* Tileset data interface
*
*/
export interface ITilesetData {
/** Tileset name | 图块集名称 */
name: string;
/** Data format version | 数据格式版本 */
version: number;
/** Image file path | 图片文件路径 */
image: string;
/** Image width in pixels | 图片宽度(像素) */
imageWidth: number;
/** Image height in pixels | 图片高度(像素) */
imageHeight: number;
/** Single tile width in pixels | 单个图块宽度(像素) */
tileWidth: number;
/** Single tile height in pixels | 单个图块高度(像素) */
tileHeight: number;
/** Total number of tiles | 图块总数 */
tileCount: number;
/** Number of tile columns | 图块列数 */
columns: number;
/** Number of tile rows | 图块行数 */
rows: number;
/** Margin around tileset in pixels | 图块集边距(像素) */
margin?: number;
/** Spacing between tiles in pixels | 图块间距(像素) */
spacing?: number;
/** Individual tile metadata | 单个图块元数据 */
tiles?: Array<{
id: number;
type?: string;
properties?: Record<string, unknown>;
}>;
}
/**
* Layer data interface
*
*/
export interface ITilemapLayerData {
/** Unique layer identifier | 图层唯一标识符 */
id: string;
/** Layer display name | 图层显示名称 */
name: string;
/** Layer visibility | 图层可见性 */
visible: boolean;
/** Layer opacity (0-1) | 图层不透明度0-1 */
opacity: number;
/** Tile index data array (row-major order) | 图块索引数据数组(行优先顺序) */
data: number[];
/** Default tileset index for this layer | 此图层的默认图块集索引 */
tilesetIndex?: number;
/** Layer X offset in pixels | 图层X偏移像素 */
offsetX?: number;
/** Layer Y offset in pixels | 图层Y偏移像素 */
offsetY?: number;
/** Custom layer properties | 自定义图层属性 */
properties?: Record<string, unknown>;
}
/**
* Tileset reference info
*
*/
export interface ITilesetRef {
/** Tileset image source path | 图块集图片源路径 */
source: string;
/** First global tile ID for this tileset | 此图块集的第一个全局图块ID */
firstGid: number;
/** Loaded tileset data | 已加载的图块集数据 */
data?: ITilesetData;
/** GPU texture ID for rendering | 用于渲染的GPU纹理ID */
textureId?: number;
}
/**
* Tilemap data interface
*
*/
export interface ITilemapData {
/** Tilemap name | 瓦片地图名称 */
name: string;
/** Data format version | 数据格式版本 */
version: number;
/** Map width in tiles | 地图宽度(图块数) */
width: number;
/** Map height in tiles | 地图高度(图块数) */
height: number;
/** Single tile width in pixels | 单个图块宽度(像素) */
tileWidth: number;
/** Single tile height in pixels | 单个图块高度(像素) */
tileHeight: number;
/** Array of tileset references | 图块集引用数组 */
tilesets: ITilesetRef[];
/** Array of layer data | 图层数据数组 */
layers: ITilemapLayerData[];
/** Collision data array | 碰撞数据数组 */
collisionData?: number[];
/** Custom tilemap properties | 自定义瓦片地图属性 */
properties?: Record<string, unknown>;
}
/**
* Tilemap Component - Manages tile-based 2D map rendering
* - 2D地图渲染
*/
@ECSComponent('Tilemap')
@Serializable({ version: 2, typeId: 'Tilemap' })
export class TilemapComponent extends Component implements IResourceComponent {
/** Tilemap asset GUID reference | 瓦片地图资源GUID引用 */
@Serialize()
@Property({ type: 'asset', label: 'Tilemap', extensions: ['.tilemap.json'] })
public tilemapAssetGuid: string = '';
@Serialize()
private _width: number = 10;
@Serialize()
private _height: number = 10;
/** Map width in tiles | 地图宽度(图块数) */
@Property({ type: 'integer', label: 'Width (Tiles)', min: 1 })
public get width(): number {
return this._width;
}
public set width(value: number) {
if (value !== this._width && value > 0) {
this.resize(value, this._height);
}
}
/** Map height in tiles | 地图高度(图块数) */
@Property({ type: 'integer', label: 'Height (Tiles)', min: 1 })
public get height(): number {
return this._height;
}
public set height(value: number) {
if (value !== this._height && value > 0) {
this.resize(this._width, value);
}
}
/** Single tile width in pixels | 单个图块宽度(像素) */
@Serialize()
@Property({ type: 'integer', label: 'Tile Width', min: 1 })
public tileWidth: number = 32;
/** Single tile height in pixels | 单个图块高度(像素) */
@Serialize()
@Property({ type: 'integer', label: 'Tile Height', min: 1 })
public tileHeight: number = 32;
/** Component visibility | 组件可见性 */
@Serialize()
@Property({ type: 'boolean', label: 'Visible' })
public visible: boolean = true;
/** Rendering sort order | 渲染排序顺序 */
@Serialize()
@Property({ type: 'integer', label: 'Sorting Order' })
public sortingOrder: number = 0;
/** Tint color in hex format | 着色颜色(十六进制格式) */
@Serialize()
@Property({ type: 'color', label: 'Color' })
public color: string = '#ffffff';
/** Opacity value (0-1) | 不透明度0-1 */
@Serialize()
@Property({ type: 'number', label: 'Alpha', min: 0, max: 1, step: 0.01 })
public alpha: number = 1;
/** Flag indicating render data needs update | 标记渲染数据需要更新 */
public renderDirty: boolean = true;
// ===== 多Tileset =====
@Serialize()
private _tilesets: ITilesetRef[] = [];
private _tilesetsData: Map<number, ITilesetData> = new Map();
// ===== 多图层 =====
@Serialize()
private _layers: ITilemapLayerData[] = [];
private _layersData: Map<string, Uint32Array> = new Map();
@Serialize()
private _activeLayerIndex: number = 0;
// ===== 碰撞数据 =====
@Serialize()
private _collisionDataArray: number[] = [];
private _collisionData: Uint32Array = new Uint32Array(0);
// ===== Getters =====
/** All tileset references | 所有图块集引用 */
get tilesets(): readonly ITilesetRef[] {
return this._tilesets;
}
/** All layer data | 所有图层数据 */
get layers(): readonly ITilemapLayerData[] {
return this._layers;
}
/** Current active layer index | 当前活动图层索引 */
get activeLayerIndex(): number {
return this._activeLayerIndex;
}
set activeLayerIndex(value: number) {
if (value >= 0 && value < this._layers.length) {
this._activeLayerIndex = value;
}
}
/** Current active layer data | 当前活动图层数据 */
get activeLayer(): ITilemapLayerData | undefined {
return this._layers[this._activeLayerIndex];
}
/** Total map width in pixels | 地图总宽度(像素) */
get pixelWidth(): number {
return this._width * this.tileWidth;
}
/** Total map height in pixels | 地图总高度(像素) */
get pixelHeight(): number {
return this._height * this.tileHeight;
}
/** Raw collision data array | 原始碰撞数据数组 */
get collisionData(): Uint32Array {
return this._collisionData;
}
// ===== Initialization | 初始化 =====
/**
* Initialize an empty tilemap with default layer
*
* @param width Map width in tiles |
* @param height Map height in tiles |
*/
initializeEmpty(width: number, height: number): void {
this._width = width;
this._height = height;
const defaultLayer: ITilemapLayerData = {
id: 'default',
name: 'Layer 0',
visible: true,
opacity: 1,
data: new Array(width * height).fill(0)
};
this._layers = [defaultLayer];
this._layersData.set('default', new Uint32Array(width * height));
this._activeLayerIndex = 0;
this.renderDirty = true;
}
/**
* Apply tilemap data from external source
*
* @param data Tilemap data to apply |
*/
applyTilemapData(data: ITilemapData): void {
this._width = data.width;
this._height = data.height;
this.tileWidth = data.tileWidth;
this.tileHeight = data.tileHeight;
// 加载Tilesets
this._tilesets = data.tilesets.map((ts) => ({ ...ts }));
this._tilesetsData.clear();
// 加载图层
this._layers = data.layers.map((layer) => ({
...layer,
data: [...layer.data]
}));
this._layersData.clear();
for (const layer of this._layers) {
this._layersData.set(layer.id, new Uint32Array(layer.data));
}
// 加载碰撞数据
if (data.collisionData) {
this._collisionData = new Uint32Array(data.collisionData);
this._collisionDataArray = [...data.collisionData];
} else {
this._collisionData = new Uint32Array(0);
this._collisionDataArray = [];
}
this.renderDirty = true;
}
// ===== Tileset Methods | 图块集方法 =====
/**
* Add a new tileset reference
*
* @param source Tileset image source path |
* @param firstGid Optional first global tile ID | ID
* @returns Index of the added tileset |
*/
addTileset(source: string, firstGid?: number): number {
const gid = firstGid ?? this.calculateNextFirstGid();
this._tilesets.push({ source, firstGid: gid });
this.renderDirty = true;
return this._tilesets.length - 1;
}
/**
* Remove a tileset by index
*
* @param index Tileset index to remove |
*/
removeTileset(index: number): void {
if (index >= 0 && index < this._tilesets.length) {
this._tilesets.splice(index, 1);
this._tilesetsData.delete(index);
this.renderDirty = true;
}
}
/**
* Set tileset data for a specific index
*
* @param index Tileset index |
* @param data Tileset data to set |
*/
setTilesetData(index: number, data: ITilesetData): void {
if (index >= 0 && index < this._tilesets.length) {
this._tilesets[index].data = data;
this._tilesetsData.set(index, data);
this.renderDirty = true;
}
}
/**
* Get tileset data by index
*
* @param index Tileset index |
* @returns Tileset data or undefined | undefined
*/
getTilesetData(index: number): ITilesetData | undefined {
return this._tilesetsData.get(index) || this._tilesets[index]?.data;
}
/**
* Find tileset for a global tile ID
* ID查找图块集
* @param gid Global tile ID | ID
* @returns Tileset info with local ID, or null if not found | ID的图块集信息null
*/
getTilesetForGid(gid: number): { tileset: ITilesetRef; localId: number; index: number } | null {
if (gid <= 0) return null;
for (let i = this._tilesets.length - 1; i >= 0; i--) {
const tileset = this._tilesets[i];
if (gid >= tileset.firstGid) {
return {
tileset,
localId: gid - tileset.firstGid + 1,
index: i
};
}
}
return null;
}
private calculateNextFirstGid(): number {
if (this._tilesets.length === 0) return 1;
let maxGid = 1;
for (const tileset of this._tilesets) {
const tileCount = tileset.data?.tileCount || 256;
const nextGid = tileset.firstGid + tileCount;
if (nextGid > maxGid) maxGid = nextGid;
}
return maxGid;
}
// ===== Layer Methods | 图层方法 =====
/**
* Add a new layer to the tilemap
*
* @param name Optional layer name |
* @param index Optional insertion index |
* @returns The created layer data |
*/
addLayer(name?: string, index?: number): ITilemapLayerData {
const id = `layer_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
const layerName = name || `Layer ${this._layers.length}`;
const data = new Array(this._width * this._height).fill(0);
const layer: ITilemapLayerData = {
id,
name: layerName,
visible: true,
opacity: 1,
data
};
if (index !== undefined && index >= 0 && index <= this._layers.length) {
this._layers.splice(index, 0, layer);
} else {
this._layers.push(layer);
}
this._layersData.set(id, new Uint32Array(data));
this.renderDirty = true;
return layer;
}
/**
* Remove a layer by index (cannot remove last layer)
*
* @param index Layer index to remove |
*/
removeLayer(index: number): void {
if (index >= 0 && index < this._layers.length && this._layers.length > 1) {
const layer = this._layers[index];
this._layers.splice(index, 1);
this._layersData.delete(layer.id);
if (this._activeLayerIndex >= this._layers.length) {
this._activeLayerIndex = this._layers.length - 1;
}
this.renderDirty = true;
}
}
/**
* Move a layer from one position to another
*
* @param fromIndex Source index |
* @param toIndex Target index |
*/
moveLayer(fromIndex: number, toIndex: number): void {
if (
fromIndex >= 0 &&
fromIndex < this._layers.length &&
toIndex >= 0 &&
toIndex < this._layers.length &&
fromIndex !== toIndex
) {
const [layer] = this._layers.splice(fromIndex, 1);
this._layers.splice(toIndex, 0, layer);
this.renderDirty = true;
}
}
/**
* Get layer data by index
*
*/
getLayer(index: number): ITilemapLayerData | undefined {
return this._layers[index];
}
/**
* Get layer data by ID
* ID获取图层数据
*/
getLayerById(id: string): ITilemapLayerData | undefined {
return this._layers.find((l) => l.id === id);
}
/**
* Set layer visibility
*
*/
setLayerVisible(index: number, visible: boolean): void {
if (index >= 0 && index < this._layers.length) {
this._layers[index].visible = visible;
this.renderDirty = true;
}
}
/**
* Set layer opacity
*
*/
setLayerOpacity(index: number, opacity: number): void {
if (index >= 0 && index < this._layers.length) {
this._layers[index].opacity = Math.max(0, Math.min(1, opacity));
this.renderDirty = true;
}
}
/**
* Rename a layer
*
*/
renameLayer(index: number, name: string): void {
if (index >= 0 && index < this._layers.length) {
this._layers[index].name = name;
}
}
// ===== Tile Operations | 瓦片操作 =====
/**
* Get tile index at position
*
* @param layerIndex Layer index |
* @param col Column (X) | X
* @param row Row (Y) | Y
* @returns Tile index (0 = empty) | 0
*/
getTile(layerIndex: number, col: number, row: number): number {
if (col < 0 || col >= this._width || row < 0 || row >= this._height) {
return 0;
}
const layer = this._layers[layerIndex];
if (!layer) return 0;
const layerData = this._layersData.get(layer.id);
if (layerData) {
return layerData[row * this._width + col];
}
return layer.data[row * this._width + col] || 0;
}
/**
* Set tile index at position
*
* @param layerIndex Layer index |
* @param col Column (X) | X
* @param row Row (Y) | Y
* @param tileIndex Tile index to set (0 = clear) | 0
*/
setTile(layerIndex: number, col: number, row: number, tileIndex: number): void {
if (col < 0 || col >= this._width || row < 0 || row >= this._height) {
return;
}
const layer = this._layers[layerIndex];
if (!layer) return;
const index = row * this._width + col;
layer.data[index] = tileIndex;
let layerData = this._layersData.get(layer.id);
if (!layerData) {
layerData = new Uint32Array(layer.data);
this._layersData.set(layer.id, layerData);
}
layerData[index] = tileIndex;
this.renderDirty = true;
}
/**
* Get raw tile data array for a layer
*
* @param layerIndex Layer index |
* @returns Uint32Array of tile indices | Uint32Array
*/
getLayerData(layerIndex: number): Uint32Array | undefined {
const layer = this._layers[layerIndex];
if (!layer) return undefined;
return this._layersData.get(layer.id);
}
/**
* Get merged tile data from all visible layers
*
* @returns Merged tile data array |
*/
getMergedTileData(): Uint32Array {
const merged = new Uint32Array(this._width * this._height);
for (const layer of this._layers) {
if (!layer.visible) continue;
const layerData = this._layersData.get(layer.id);
if (!layerData) continue;
for (let i = 0; i < merged.length; i++) {
if (layerData[i] > 0) {
merged[i] = layerData[i];
}
}
}
return merged;
}
// ===== Collision | 碰撞 =====
/**
* Check if tile has collision
*
* @param col Column (X) | X
* @param row Row (Y) | Y
* @returns True if has collision | true
*/
hasCollision(col: number, row: number): boolean {
if (col < 0 || col >= this._width || row < 0 || row >= this._height) {
return true;
}
if (this._collisionData.length === 0) {
return false;
}
return this._collisionData[row * this._width + col] > 0;
}
/**
* Get collision type at tile position
*
*/
getCollisionType(col: number, row: number): number {
if (col < 0 || col >= this._width || row < 0 || row >= this._height) {
return 0;
}
if (this._collisionData.length === 0) {
return 0;
}
return this._collisionData[row * this._width + col];
}
/**
* Set collision type at tile position
*
* @param col Column (X) | X
* @param row Row (Y) | Y
* @param collisionType Collision type (0 = none) | 0
*/
setCollision(col: number, row: number, collisionType: number): void {
if (col < 0 || col >= this._width || row < 0 || row >= this._height) {
return;
}
if (this._collisionData.length === 0) {
this._collisionData = new Uint32Array(this._width * this._height);
this._collisionDataArray = new Array(this._width * this._height).fill(0);
}
const index = row * this._width + col;
this._collisionData[index] = collisionType;
this._collisionDataArray[index] = collisionType;
}
/**
* Check collision at world coordinates
*
*/
hasCollisionAt(worldX: number, worldY: number): boolean {
const [col, row] = this.worldToTile(worldX, worldY);
return this.hasCollision(col, row);
}
/**
* Get all collision tiles within bounds
*
* @returns Array of [col, row, type] | [, , ]
*/
getCollisionTilesInBounds(
left: number,
bottom: number,
right: number,
top: number
): Array<[number, number, number]> {
const result: Array<[number, number, number]> = [];
if (this._collisionData.length === 0) {
return result;
}
const startCol = Math.max(0, Math.floor(left / this.tileWidth));
const endCol = Math.min(this._width, Math.ceil(right / this.tileWidth));
const startRow = Math.max(0, Math.floor(bottom / this.tileHeight));
const endRow = Math.min(this._height, Math.ceil(top / this.tileHeight));
for (let row = startRow; row < endRow; row++) {
for (let col = startCol; col < endCol; col++) {
const type = this._collisionData[row * this._width + col];
if (type > 0) {
result.push([col, row, type]);
}
}
}
return result;
}
/**
* Generate collision rectangles for physics
*
* @returns Array of collision rectangles |
*/
generateCollisionRects(): Array<{
x: number;
y: number;
width: number;
height: number;
type: number;
}> {
const rects: Array<{
x: number;
y: number;
width: number;
height: number;
type: number;
}> = [];
if (this._collisionData.length === 0) {
return rects;
}
for (let row = 0; row < this._height; row++) {
for (let col = 0; col < this._width; col++) {
const type = this._collisionData[row * this._width + col];
if (type > 0) {
rects.push({
x: col * this.tileWidth,
y: row * this.tileHeight,
width: this.tileWidth,
height: this.tileHeight,
type
});
}
}
}
return rects;
}
// ===== Coordinate Conversion | 坐标转换 =====
/**
* Convert world coordinates to tile coordinates
*
* @returns [col, row] | [, ]
*/
worldToTile(worldX: number, worldY: number): [number, number] {
const col = Math.floor(worldX / this.tileWidth);
const row = Math.floor(worldY / this.tileHeight);
return [col, row];
}
/**
* Convert tile coordinates to world coordinates (center of tile)
*
* @returns [worldX, worldY] | [X, Y]
*/
tileToWorld(col: number, row: number): [number, number] {
const worldX = col * this.tileWidth + this.tileWidth / 2;
const worldY = row * this.tileHeight + this.tileHeight / 2;
return [worldX, worldY];
}
// ===== UV Calculation | UV计算 =====
/**
* Get UV coordinates for a tile
* tile UV
*
* 使 UVHelper OpenGL
* Uses UVHelper to calculate OpenGL texture coordinates.
*
* @see UVHelper.calculateTileUV for coordinate system documentation
* @param tilesetIndex Tileset index | Tileset
* @param localTileId Local tile ID (1-based) | tile ID1
* @returns [u0, v0, u1, v1] OpenGL UV coordinates, or null if invalid
*/
getTileUV(tilesetIndex: number, localTileId: number): [number, number, number, number] | null {
if (localTileId <= 0) return null;
const tilesetData = this.getTilesetData(tilesetIndex);
if (!tilesetData) {
console.warn('[TilemapComponent] getTileUV: No tileset data for index', tilesetIndex);
return null;
}
// Use UVHelper for coordinate calculation
// 使用 UVHelper 计算坐标
return UVHelper.calculateTileUV(localTileId - 1, {
columns: tilesetData.columns,
tileWidth: tilesetData.tileWidth,
tileHeight: tilesetData.tileHeight,
imageWidth: tilesetData.imageWidth,
imageHeight: tilesetData.imageHeight,
margin: tilesetData.margin,
spacing: tilesetData.spacing
});
}
// ===== Resize | 大小调整 =====
/**
* Calculate offset based on anchor point
*
*/
private calculateAnchorOffset(
oldSize: number,
newSize: number,
anchor: 'start' | 'center' | 'end'
): number {
const delta = newSize - oldSize;
switch (anchor) {
case 'start': return 0;
case 'center': return Math.floor(delta / 2);
case 'end': return delta;
}
}
/**
* Resize the tilemap, preserving existing data at the specified anchor position
*
* @param newWidth New width in tiles |
* @param newHeight New height in tiles |
* @param anchor Anchor point for preserving data (default: 'bottom-left' for Y-up coordinate system) | 'bottom-left'Y轴向上的坐标系
*/
resize(newWidth: number, newHeight: number, anchor: ResizeAnchor = 'bottom-left'): void {
if (newWidth === this._width && newHeight === this._height) {
return;
}
// Parse anchor to get X and Y alignment
// 解析锚点获取X和Y方向的对齐方式
let xAnchor: 'start' | 'center' | 'end' = 'start';
let yAnchor: 'start' | 'center' | 'end' = 'end'; // 'end' means bottom in Y-up system
if (anchor.includes('left')) xAnchor = 'start';
else if (anchor.includes('right')) xAnchor = 'end';
else xAnchor = 'center';
if (anchor.includes('bottom')) yAnchor = 'end';
else if (anchor.includes('top')) yAnchor = 'start';
else yAnchor = 'center';
// Calculate offsets for placing old data in new array
// 计算将旧数据放入新数组的偏移量
const offsetX = this.calculateAnchorOffset(this._width, newWidth, xAnchor);
const offsetY = this.calculateAnchorOffset(this._height, newHeight, yAnchor);
// 调整所有图层
for (const layer of this._layers) {
const oldLayerData = this._layersData.get(layer.id);
const newLayerData = new Uint32Array(newWidth * newHeight);
const newDataArray = new Array(newWidth * newHeight).fill(0);
if (oldLayerData) {
for (let y = 0; y < this._height; y++) {
for (let x = 0; x < this._width; x++) {
const newX = x + offsetX;
const newY = y + offsetY;
// Check bounds
if (newX >= 0 && newX < newWidth && newY >= 0 && newY < newHeight) {
const value = oldLayerData[y * this._width + x];
newLayerData[newY * newWidth + newX] = value;
newDataArray[newY * newWidth + newX] = value;
}
}
}
}
this._layersData.set(layer.id, newLayerData);
layer.data = newDataArray;
}
// 调整碰撞数据
if (this._collisionData.length > 0) {
const newCollisionData = new Uint32Array(newWidth * newHeight);
const newCollisionArray = new Array(newWidth * newHeight).fill(0);
for (let y = 0; y < this._height; y++) {
for (let x = 0; x < this._width; x++) {
const newX = x + offsetX;
const newY = y + offsetY;
if (newX >= 0 && newX < newWidth && newY >= 0 && newY < newHeight) {
const value = this._collisionData[y * this._width + x];
newCollisionData[newY * newWidth + newX] = value;
newCollisionArray[newY * newWidth + newX] = value;
}
}
}
this._collisionData = newCollisionData;
this._collisionDataArray = newCollisionArray;
}
this._width = newWidth;
this._height = newHeight;
this.renderDirty = true;
}
// ===== Serialization | 序列化 =====
/**
* Called after deserialization to restore runtime data
*
*/
override onDeserialized(): void {
// 恢复图层运行时数据
for (const layer of this._layers) {
if (layer.data && layer.data.length > 0) {
this._layersData.set(layer.id, new Uint32Array(layer.data));
}
}
// 恢复Tileset缓存数据
for (let i = 0; i < this._tilesets.length; i++) {
const tileset = this._tilesets[i];
if (tileset.data) {
this._tilesetsData.set(i, tileset.data);
}
}
// 恢复碰撞数据
if (this._collisionDataArray.length > 0) {
this._collisionData = new Uint32Array(this._collisionDataArray);
}
this.renderDirty = true;
}
/**
* Cleanup when component is destroyed
*
*/
onDestroy(): void {
this._tilesets = [];
this._tilesetsData.clear();
this._layers = [];
this._layersData.clear();
this._collisionData = new Uint32Array(0);
this._collisionDataArray = [];
}
/**
* Export tilemap to data format
*
* @returns Tilemap data object |
*/
exportToData(): ITilemapData {
return {
name: 'Tilemap',
version: 2,
width: this._width,
height: this._height,
tileWidth: this.tileWidth,
tileHeight: this.tileHeight,
tilesets: this._tilesets.map((ts) => ({ ...ts })),
layers: this._layers.map((layer) => ({
...layer,
data: [...layer.data]
})),
collisionData: this._collisionData.length > 0 ? Array.from(this._collisionData) : undefined
};
}
// ===== IResourceComponent 实现 =====
/**
*
* Get all resource references needed by this component
*/
getResourceReferences(): ResourceReference[] {
const refs: ResourceReference[] = [];
// 收集所有 tileset 纹理引用
// Collect all tileset texture references
for (const tileset of this._tilesets) {
if (tileset.source) {
refs.push({
path: tileset.source,
type: 'texture',
runtimeId: tileset.textureId
});
}
}
return refs;
}
/**
* ID
* Set runtime IDs for loaded resources
*/
setResourceIds(pathToId: Map<string, number>): void {
// 为每个 tileset 设置纹理 ID
// Set texture ID for each tileset
for (const tileset of this._tilesets) {
if (tileset.source) {
const textureId = pathToId.get(tileset.source);
if (textureId !== undefined) {
tileset.textureId = textureId;
}
}
}
// 标记渲染数据为脏,需要重新构建
// Mark render data as dirty, needs rebuild
this.renderDirty = true;
}
}