Feature/editor optimization (#251)
* refactor: 编辑器/运行时架构拆分与构建系统升级 * feat(core): 层级系统重构与UI变换矩阵修复 * refactor: 移除 ecs-components 聚合包并修复跨包组件查找问题 * fix(physics): 修复跨包组件类引用问题 * feat: 统一运行时架构与浏览器运行支持 * feat(asset): 实现浏览器运行时资产加载系统 * fix: 修复文档、CodeQL安全问题和CI类型检查错误 * fix: 修复文档、CodeQL安全问题和CI类型检查错误 * fix: 修复文档、CodeQL安全问题、CI类型检查和测试错误 * test: 补齐核心模块测试用例,修复CI构建配置 * fix: 修复测试用例中的类型错误和断言问题 * fix: 修复 turbo build:npm 任务的依赖顺序问题 * fix: 修复 CI 构建错误并优化构建性能
This commit is contained in:
150
packages/tilemap-editor/src/components/panels/TilesetPanel.tsx
Normal file
150
packages/tilemap-editor/src/components/panels/TilesetPanel.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
/**
|
||||
* 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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user