* feat: 添加 Tilemap 编辑器插件和组件生命周期支持 * feat(editor-core): 添加声明式插件注册 API * feat(editor-core): 改进tiledmap结构合并tileset进tiledmapeditor * feat: 添加 editor-runtime SDK 和插件系统改进 * fix(ci): 修复SceneResourceManager里变量未使用问题
151 lines
4.9 KiB
TypeScript
151 lines
4.9 KiB
TypeScript
/**
|
||
* Tileset Panel - Display tileset for selection
|
||
*/
|
||
|
||
import React, { useEffect, useCallback } from 'react';
|
||
import { Core } from '@esengine/ecs-framework';
|
||
import { MessageHub } from '@esengine/editor-core';
|
||
import { TilemapComponent, type ITilesetData } from '@esengine/tilemap';
|
||
import { useTilemapEditorStore } from '../../stores/TilemapEditorStore';
|
||
import { TilesetPreview } from '../TilesetPreview';
|
||
import '../../styles/TilemapEditor.css';
|
||
|
||
// Helper to convert file path to URL
|
||
function convertFileSrc(path: string): string {
|
||
if (path.startsWith('http://') || path.startsWith('https://') || path.startsWith('asset://')) {
|
||
return path;
|
||
}
|
||
return `asset://localhost/${encodeURIComponent(path)}`;
|
||
}
|
||
|
||
interface TilesetPanelProps {
|
||
projectPath?: string | null;
|
||
}
|
||
|
||
export const TilesetPanel: React.FC<TilesetPanelProps> = () => {
|
||
const {
|
||
entityId,
|
||
tilesetImageUrl,
|
||
tilesetColumns,
|
||
tilesetRows,
|
||
tileWidth,
|
||
tileHeight,
|
||
selectedTiles,
|
||
setTileset
|
||
} = useTilemapEditorStore();
|
||
|
||
// Load tileset from component
|
||
const loadTilesetFromComponent = useCallback(() => {
|
||
if (!entityId) return;
|
||
|
||
const scene = Core.scene;
|
||
if (!scene) return;
|
||
|
||
const foundEntity = scene.findEntityById(parseInt(entityId, 10));
|
||
if (!foundEntity) return;
|
||
|
||
const tilemapComp = foundEntity.getComponent(TilemapComponent);
|
||
if (!tilemapComp) return;
|
||
|
||
// Get tileset source from first tileset
|
||
const tilesetRef = tilemapComp.tilesets[0];
|
||
if (!tilesetRef) return;
|
||
|
||
const tilesetPath = tilesetRef.source;
|
||
const imageUrl = convertFileSrc(tilesetPath);
|
||
const currentState = useTilemapEditorStore.getState();
|
||
|
||
// Check if URL or tile dimensions changed
|
||
const urlChanged = imageUrl !== currentState.tilesetImageUrl;
|
||
const dimensionsChanged =
|
||
tilemapComp.tileWidth !== currentState.tileWidth ||
|
||
tilemapComp.tileHeight !== currentState.tileHeight;
|
||
|
||
if (!urlChanged && !dimensionsChanged) return;
|
||
|
||
const img = new Image();
|
||
img.onload = () => {
|
||
const columns = Math.floor(img.width / tilemapComp.tileWidth);
|
||
const rows = Math.floor(img.height / tilemapComp.tileHeight);
|
||
|
||
// Create tileset data and set it
|
||
const tilesetData: ITilesetData = {
|
||
name: 'tileset',
|
||
version: 1,
|
||
image: tilesetPath,
|
||
imageWidth: img.width,
|
||
imageHeight: img.height,
|
||
tileWidth: tilemapComp.tileWidth,
|
||
tileHeight: tilemapComp.tileHeight,
|
||
tileCount: columns * rows,
|
||
columns,
|
||
rows
|
||
};
|
||
tilemapComp.setTilesetData(0, tilesetData);
|
||
setTileset(imageUrl, columns, rows, tilemapComp.tileWidth, tilemapComp.tileHeight);
|
||
};
|
||
img.src = imageUrl;
|
||
}, [entityId, setTileset]);
|
||
|
||
// Load tileset when entityId is set but tilesetImageUrl is not yet loaded
|
||
useEffect(() => {
|
||
if (!entityId || tilesetImageUrl) return;
|
||
loadTilesetFromComponent();
|
||
}, [entityId, tilesetImageUrl, loadTilesetFromComponent]);
|
||
|
||
// Listen for scene modifications to reload tileset when property changes
|
||
useEffect(() => {
|
||
if (!entityId) return;
|
||
|
||
const messageHub = Core.services.resolve(MessageHub);
|
||
if (!messageHub) return;
|
||
|
||
const unsubscribe = messageHub.subscribe('scene:modified', () => {
|
||
loadTilesetFromComponent();
|
||
});
|
||
return unsubscribe;
|
||
}, [entityId, loadTilesetFromComponent]);
|
||
|
||
if (!tilesetImageUrl) {
|
||
return (
|
||
<div className="tileset-panel">
|
||
<div className="tileset-panel-header">
|
||
<h3>Tileset</h3>
|
||
</div>
|
||
<div className="tileset-empty">
|
||
<p>
|
||
No tileset loaded.
|
||
<br />
|
||
Select a TilemapComponent to edit.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className="tileset-panel">
|
||
<div className="tileset-panel-header">
|
||
<h3>Tileset</h3>
|
||
</div>
|
||
<div className="tileset-canvas-container">
|
||
<TilesetPreview
|
||
imageUrl={tilesetImageUrl}
|
||
tileWidth={tileWidth}
|
||
tileHeight={tileHeight}
|
||
columns={tilesetColumns}
|
||
rows={tilesetRows}
|
||
/>
|
||
</div>
|
||
{selectedTiles && (
|
||
<div className="tilemap-info-bar">
|
||
<span>
|
||
Selected: {selectedTiles.width}×{selectedTiles.height}
|
||
</span>
|
||
<span>Tile: {selectedTiles.tiles[0]}</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|