Files
esengine/packages/tilemap-editor/src/index.ts
2025-12-03 18:15:34 +08:00

393 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Tilemap 编辑器模块入口
* Tilemap Editor Module Entry
*/
import type { ServiceContainer, Entity } from '@esengine/ecs-framework';
import { Core } from '@esengine/ecs-framework';
import type {
IEditorModuleLoader,
PanelDescriptor,
EntityCreationTemplate,
ComponentAction,
ComponentInspectorProviderDef,
GizmoProviderRegistration,
FileActionHandler,
FileCreationTemplate
} from '@esengine/editor-core';
import {
PanelPosition,
InspectorRegistry,
EntityStoreService,
MessageHub,
ComponentRegistry,
IDialogService,
IFileSystemService,
FileActionRegistry
} from '@esengine/editor-core';
import type { IDialog, IFileSystem } from '@esengine/editor-core';
import { TransformComponent } from '@esengine/engine-core';
// Runtime imports from @esengine/tilemap
import { TilemapComponent, TilemapCollider2DComponent, TilemapRuntimeModule } from '@esengine/tilemap';
import type { IPlugin, ModuleManifest } from '@esengine/editor-core';
import { TilemapEditorPanel } from './components/panels/TilemapEditorPanel';
import { TilemapInspectorProvider } from './providers/TilemapInspectorProvider';
import { registerTilemapGizmo } from './gizmos/TilemapGizmo';
import { useTilemapEditorStore } from './stores/TilemapEditorStore';
// 导入编辑器 CSS 样式(会被 vite 自动处理并注入到 DOM
// Import editor CSS styles (automatically handled and injected by vite)
import './styles/TilemapEditor.css';
// Re-exports
export { TilemapEditorPanel } from './components/panels/TilemapEditorPanel';
export { TilesetPanel } from './components/panels/TilesetPanel';
export { TilemapCanvas } from './components/TilemapCanvas';
export { TilemapViewport } from './components/TilemapViewport';
export { TilesetPreview } from './components/TilesetPreview';
export { useTilemapEditorStore } from './stores/TilemapEditorStore';
export type { TilemapEditorState, TilemapToolType, TileSelection } from './stores/TilemapEditorStore';
export type { ITilemapTool, ToolContext } from './tools/ITilemapTool';
export { BrushTool } from './tools/BrushTool';
export { EraserTool } from './tools/EraserTool';
export { FillTool } from './tools/FillTool';
export { RectangleTool } from './tools/RectangleTool';
export { SelectTool } from './tools/SelectTool';
export { TilemapInspectorProvider } from './providers/TilemapInspectorProvider';
export { TileAnimationEditor } from './components/panels/TileAnimationEditor';
/**
* Tilemap 编辑器模块
* Tilemap Editor Module
*/
export class TilemapEditorModule implements IEditorModuleLoader {
private unsubscribers: Array<() => void> = [];
async install(services: ServiceContainer): Promise<void> {
// 注册检视器提供者 | Register inspector provider
const inspectorRegistry = services.resolve(InspectorRegistry);
if (inspectorRegistry) {
inspectorRegistry.register(new TilemapInspectorProvider());
}
// 注册组件到编辑器组件注册表 | Register to editor component registry
const componentRegistry = services.resolve(ComponentRegistry);
if (componentRegistry) {
componentRegistry.register({
name: 'Tilemap',
type: TilemapComponent,
category: 'components.category.tilemap',
description: 'Tilemap component for tile-based levels',
icon: 'Grid3X3'
});
componentRegistry.register({
name: 'TilemapCollider2D',
type: TilemapCollider2DComponent,
category: 'components.category.physics',
description: 'Generates physics colliders from tilemap collision data',
icon: 'Shield'
});
}
// 订阅 tilemap:create-asset 消息 | Subscribe to tilemap:create-asset message
const messageHub = services.resolve(MessageHub);
if (messageHub) {
const unsubscribe = messageHub.subscribe('tilemap:create-asset', async (payload: {
entityId?: string;
onChange?: (value: string | null) => void;
}) => {
await this.handleCreateTilemapAsset(services, payload);
});
this.unsubscribers.push(unsubscribe);
}
// 注册资产创建消息映射 | Register asset creation message mappings
const fileActionRegistry = services.resolve(FileActionRegistry);
if (fileActionRegistry) {
fileActionRegistry.registerAssetCreationMapping({
extension: '.tilemap',
createMessage: 'tilemap:create-asset'
});
fileActionRegistry.registerAssetCreationMapping({
extension: '.tileset',
createMessage: 'tileset:create-asset'
});
}
// 注册 Tilemap Gizmo | Register Tilemap gizmo
registerTilemapGizmo();
}
async uninstall(): Promise<void> {
// 清理订阅 | Clean up subscriptions
this.unsubscribers.forEach(unsub => unsub());
this.unsubscribers = [];
}
getPanels(): PanelDescriptor[] {
return [
{
id: 'tilemap-editor',
title: 'Tilemap Editor',
position: PanelPosition.Center,
closable: true,
component: TilemapEditorPanel,
isDynamic: true
},
];
}
getInspectorProviders(): ComponentInspectorProviderDef[] {
return [
{
componentType: 'Tilemap',
priority: 100,
render: (component, entity, onChange) => {
const provider = new TilemapInspectorProvider();
return provider.render(
{ entityId: String(entity.id), component },
{ target: component, onChange }
);
}
}
];
}
getEntityCreationTemplates(): EntityCreationTemplate[] {
return [
{
id: 'create-tilemap-entity',
label: '创建 Tilemap',
icon: 'Grid3X3',
category: 'rendering',
order: 100,
create: (): number => {
const scene = Core.scene;
if (!scene) {
throw new Error('Scene not available');
}
const entityStore = Core.services.resolve(EntityStoreService);
const messageHub = Core.services.resolve(MessageHub);
if (!entityStore || !messageHub) {
throw new Error('EntityStoreService or MessageHub not available');
}
const tilemapCount = entityStore.getAllEntities()
.filter((e: Entity) => e.name.startsWith('Tilemap ')).length;
const entityName = `Tilemap ${tilemapCount + 1}`;
const entity = scene.createEntity(entityName);
entity.addComponent(new TransformComponent());
const tilemapComponent = new TilemapComponent();
tilemapComponent.tileWidth = 16;
tilemapComponent.tileHeight = 16;
tilemapComponent.initializeEmpty(20, 15);
entity.addComponent(tilemapComponent);
entityStore.addEntity(entity);
messageHub.publish('entity:added', { entity });
messageHub.publish('scene:modified', {});
entityStore.selectEntity(entity);
return entity.id;
}
}
];
}
getComponentActions(): ComponentAction[] {
// 移除编辑按钮,改为双击 tilemap 文件打开编辑器
return [];
}
getFileActionHandlers(): FileActionHandler[] {
return [
{
extensions: ['tilemap', 'json'],
onDoubleClick: (filePath: string) => {
// 只处理 .tilemap 和 .tilemap.json 文件
const lowerPath = filePath.toLowerCase();
if (!lowerPath.endsWith('.tilemap') && !lowerPath.endsWith('.tilemap.json')) {
return;
}
// 先设置待打开的文件路径到 store
useTilemapEditorStore.getState().setPendingFilePath(filePath);
const messageHub = Core.services.resolve(MessageHub);
if (messageHub) {
// 打开 tilemap 编辑器面板(面板挂载后会从 store 读取 pendingFilePath
messageHub.publish('dynamic-panel:open', {
panelId: 'tilemap-editor',
title: `Tilemap Editor - ${filePath.split(/[\\/]/).pop()}`
});
}
}
},
{
extensions: ['tileset', 'json'],
onDoubleClick: (filePath: string) => {
// 只处理 .tileset 和 .tileset.json 文件
const lowerPath = filePath.toLowerCase();
if (!lowerPath.endsWith('.tileset') && !lowerPath.endsWith('.tileset.json')) {
return;
}
const messageHub = Core.services.resolve(MessageHub);
if (messageHub) {
// 发送消息打开 tileset 预览
messageHub.publish('tileset:open-file', { filePath });
messageHub.publish('dynamic-panel:open', {
panelId: 'tilemap-editor',
title: `Tileset - ${filePath.split(/[\\/]/).pop()}`
});
}
}
}
];
}
getFileCreationTemplates(): FileCreationTemplate[] {
return [
{
id: 'create-tilemap',
label: 'Tilemap',
extension: 'tilemap',
icon: 'Grid3X3',
category: 'tilemap',
getContent: (_fileName: string) => {
const defaultTilemapData = {
width: 20,
height: 15,
tileWidth: 16,
tileHeight: 16,
layers: [
{
name: 'Layer 1',
visible: true,
opacity: 1,
data: new Array(20 * 15).fill(0)
}
],
tilesets: []
};
return JSON.stringify(defaultTilemapData, null, 2);
}
},
{
id: 'create-tileset',
label: 'Tileset',
extension: 'tileset',
icon: 'LayoutGrid',
category: 'tilemap',
getContent: (_fileName: string) => {
const defaultTilesetData = {
name: 'New Tileset',
tileWidth: 16,
tileHeight: 16,
spacing: 0,
margin: 0,
imageSource: '',
tiles: []
};
return JSON.stringify(defaultTilesetData, null, 2);
}
}
];
}
private async handleCreateTilemapAsset(
_services: ServiceContainer,
payload: { entityId?: string; onChange?: (value: string | null) => void }
): Promise<void> {
const dialog = Core.services.tryResolve(IDialogService) as IDialog | null;
const fileSystem = Core.services.tryResolve(IFileSystemService) as IFileSystem | null;
const messageHub = Core.services.tryResolve(MessageHub);
if (!dialog || !fileSystem) {
console.error('[TilemapEditorModule] Dialog or FileSystem service not available');
return;
}
const filePath = await dialog.saveDialog({
title: '创建 Tilemap 资产',
filters: [{ name: 'Tilemap', extensions: ['tilemap'] }],
defaultPath: 'new-tilemap.tilemap'
});
if (!filePath) {
return;
}
const defaultTilemapData = {
width: 20,
height: 15,
tileWidth: 16,
tileHeight: 16,
layers: [
{
name: 'Layer 1',
visible: true,
opacity: 1,
data: new Array(20 * 15).fill(0)
}
],
tilesets: []
};
await fileSystem.writeFile(filePath, JSON.stringify(defaultTilemapData, null, 2));
if (payload.onChange) {
payload.onChange(filePath);
}
if (messageHub && payload.entityId) {
useTilemapEditorStore.getState().setEntityId(payload.entityId);
messageHub.publish('dynamic-panel:open', { panelId: 'tilemap-editor', title: 'Tilemap Editor' });
messageHub.publish('dynamic-panel:open', { panelId: 'tileset-panel', title: 'Tileset' });
}
}
}
export const tilemapEditorModule = new TilemapEditorModule();
/**
* Tilemap 插件清单
* Tilemap Plugin Manifest
*/
const manifest: ModuleManifest = {
id: '@esengine/tilemap',
name: '@esengine/tilemap',
displayName: 'Tilemap',
version: '1.0.0',
description: 'Tilemap system with Tiled editor support',
category: 'Rendering',
isCore: false,
defaultEnabled: false,
isEngineModule: true,
canContainContent: true,
dependencies: ['engine-core'],
exports: {
components: ['TilemapComponent', 'TilemapCollider2DComponent'],
systems: ['TilemapRenderingSystem'],
loaders: ['TilemapLoader', 'TilesetLoader']
}
};
/**
* 完整的 Tilemap 插件(运行时 + 编辑器)
* Complete Tilemap Plugin (runtime + editor)
*/
export const TilemapPlugin: IPlugin = {
manifest,
runtimeModule: new TilemapRuntimeModule(),
editorModule: tilemapEditorModule
};
export default tilemapEditorModule;