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>
|
|||
|
|
);
|
|||
|
|
};
|