Feature/runtime cdn and plugin loader (#240)
* feat(ui): 完善 UI 布局系统和编辑器可视化工具 * refactor: 移除 ModuleRegistry,统一使用 PluginManager 插件系统 * fix: 修复 CodeQL 警告并提升测试覆盖率 * refactor: 分离运行时入口点,解决 runtime bundle 包含 React 的问题 * fix(ci): 添加 editor-core 和 editor-runtime 到 CI 依赖构建步骤 * docs: 完善 ServiceContainer 文档,新增 Symbol.for 模式和 @InjectProperty 说明 * fix(ci): 修复 type-check 失败问题 * fix(ci): 修复类型检查失败问题 * fix(ci): 修复类型检查失败问题 * fix(ci): behavior-tree 构建添加 @tauri-apps 外部依赖 * fix(ci): behavior-tree 添加 @tauri-apps/plugin-fs 类型依赖 * fix(ci): platform-web 添加缺失的 behavior-tree 依赖 * fix(lint): 移除正则表达式中不必要的转义字符
This commit is contained in:
244
packages/tilemap/src/editor/index.ts
Normal file
244
packages/tilemap/src/editor/index.ts
Normal file
@@ -0,0 +1,244 @@
|
||||
/**
|
||||
* Tilemap 编辑器模块入口
|
||||
* Tilemap Editor Module Entry
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import type { ServiceContainer, Entity } from '@esengine/ecs-framework';
|
||||
import { Core } from '@esengine/ecs-framework';
|
||||
import type {
|
||||
IEditorModuleLoader,
|
||||
PanelDescriptor,
|
||||
EntityCreationTemplate,
|
||||
ComponentAction,
|
||||
ComponentInspectorProviderDef,
|
||||
GizmoProviderRegistration
|
||||
} from '@esengine/editor-core';
|
||||
import {
|
||||
PanelPosition,
|
||||
InspectorRegistry,
|
||||
EntityStoreService,
|
||||
MessageHub,
|
||||
ComponentRegistry,
|
||||
IDialogService,
|
||||
IFileSystemService
|
||||
} from '@esengine/editor-core';
|
||||
import type { IDialog, IFileSystem } from '@esengine/editor-core';
|
||||
import { TransformComponent } from '@esengine/ecs-components';
|
||||
|
||||
// Local imports
|
||||
import { TilemapComponent } from '../TilemapComponent';
|
||||
import { TilemapEditorPanel } from './components/panels/TilemapEditorPanel';
|
||||
import { TilemapInspectorProvider } from './providers/TilemapInspectorProvider';
|
||||
import { registerTilemapGizmo } from './gizmos/TilemapGizmo';
|
||||
import { useTilemapEditorStore } from './stores/TilemapEditorStore';
|
||||
|
||||
// Re-exports
|
||||
export { TilemapEditorPanel } from './components/panels/TilemapEditorPanel';
|
||||
export { TilesetPanel } from './components/panels/TilesetPanel';
|
||||
export { TilemapCanvas } from './components/TilemapCanvas';
|
||||
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 { TilemapInspectorProvider } from './providers/TilemapInspectorProvider';
|
||||
|
||||
/**
|
||||
* 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'
|
||||
});
|
||||
}
|
||||
|
||||
// 订阅 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);
|
||||
}
|
||||
|
||||
// 注册 Tilemap Gizmo | Register Tilemap gizmo
|
||||
registerTilemapGizmo();
|
||||
}
|
||||
|
||||
async uninstall(): Promise<void> {
|
||||
this.unsubscribers.forEach(unsub => unsub());
|
||||
this.unsubscribers = [];
|
||||
}
|
||||
|
||||
getPanels(): PanelDescriptor[] {
|
||||
return [
|
||||
{
|
||||
id: 'tilemap-editor',
|
||||
title: 'Tilemap Editor',
|
||||
position: PanelPosition.Center,
|
||||
render: () => React.createElement(TilemapEditorPanel),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
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[] {
|
||||
return [
|
||||
{
|
||||
id: 'tilemap-edit',
|
||||
componentName: 'Tilemap',
|
||||
label: '编辑 Tilemap',
|
||||
icon: 'Edit3',
|
||||
execute: (_component: unknown, entity: Entity) => {
|
||||
const messageHub = Core.services.resolve(MessageHub);
|
||||
if (messageHub) {
|
||||
const entityIdStr = String(entity.id);
|
||||
useTilemapEditorStore.getState().setEntityId(entityIdStr);
|
||||
messageHub.publish('dynamic-panel:open', { panelId: 'tilemap-editor', title: 'Tilemap Editor' });
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
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.json'] }],
|
||||
defaultPath: 'new-tilemap.tilemap.json'
|
||||
});
|
||||
|
||||
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();
|
||||
|
||||
// Plugin exports
|
||||
export { TilemapPlugin, TilemapRuntimeModule } from './TilemapPlugin';
|
||||
export default tilemapEditorModule;
|
||||
Reference in New Issue
Block a user