Feature/tilemap editor (#237)

* feat: 添加 Tilemap 编辑器插件和组件生命周期支持

* feat(editor-core): 添加声明式插件注册 API

* feat(editor-core): 改进tiledmap结构合并tileset进tiledmapeditor

* feat: 添加 editor-runtime SDK 和插件系统改进

* fix(ci): 修复SceneResourceManager里变量未使用问题
This commit is contained in:
YHH
2025-11-25 22:23:19 +08:00
committed by GitHub
parent 551ca7805d
commit 3fb6f919f8
166 changed files with 54691 additions and 8674 deletions

View File

@@ -1,25 +1,32 @@
import type { Core, ServiceContainer } from '@esengine/ecs-framework';
import {
IEditorPlugin,
type Core,
type ServiceContainer,
type IService,
type ServiceType,
type IEditorPlugin,
EditorPluginCategory,
CompilerRegistry,
ICompilerRegistry,
InspectorRegistry,
IInspectorRegistry,
PanelPosition,
type FileCreationTemplate,
type FileActionHandler,
type PanelDescriptor
} from '@esengine/editor-core';
type PanelDescriptor,
createElement,
Icons,
createLogger,
} from '@esengine/editor-runtime';
import { BehaviorTreeService } from './services/BehaviorTreeService';
import { FileSystemService } from './services/FileSystemService';
import { BehaviorTreeCompiler } from './compiler/BehaviorTreeCompiler';
import { BehaviorTreeNodeInspectorProvider } from './providers/BehaviorTreeNodeInspectorProvider';
import { BehaviorTreeEditorPanel } from './components/panels/BehaviorTreeEditorPanel';
import { useBehaviorTreeDataStore } from './stores';
import { createElement } from 'react';
import { GitBranch } from 'lucide-react';
import { createRootNode } from './domain/constants/RootNode';
import type { IService, ServiceType } from '@esengine/ecs-framework';
import { createLogger } from '@esengine/ecs-framework';
import { PluginContext } from './PluginContext';
const { GitBranch } = Icons;
const logger = createLogger('BehaviorTreePlugin');
@@ -38,6 +45,8 @@ export class BehaviorTreePlugin implements IEditorPlugin {
async install(core: Core, services: ServiceContainer): Promise<void> {
this.services = services;
// 设置插件上下文,让内部服务可以访问服务容器
PluginContext.setServices(services);
this.registerServices(services);
this.registerCompilers(services);
this.registerInspectors(services);
@@ -53,6 +62,7 @@ export class BehaviorTreePlugin implements IEditorPlugin {
this.registeredServices.clear();
useBehaviorTreeDataStore.getState().reset();
PluginContext.clear();
this.services = undefined;
}
@@ -88,7 +98,7 @@ export class BehaviorTreePlugin implements IEditorPlugin {
private registerCompilers(services: ServiceContainer): void {
try {
const compilerRegistry = services.resolve(CompilerRegistry);
const compilerRegistry = services.resolve<CompilerRegistry>(ICompilerRegistry);
const compiler = new BehaviorTreeCompiler();
compilerRegistry.register(compiler);
logger.info('Successfully registered BehaviorTreeCompiler');
@@ -98,10 +108,14 @@ export class BehaviorTreePlugin implements IEditorPlugin {
}
private registerInspectors(services: ServiceContainer): void {
const inspectorRegistry = services.resolve(InspectorRegistry);
if (inspectorRegistry) {
const provider = new BehaviorTreeNodeInspectorProvider();
inspectorRegistry.register(provider);
try {
const inspectorRegistry = services.resolve<InspectorRegistry>(IInspectorRegistry);
if (inspectorRegistry) {
const provider = new BehaviorTreeNodeInspectorProvider();
inspectorRegistry.register(provider);
}
} catch (error) {
logger.error('Failed to register inspector:', error);
}
}

View File

@@ -0,0 +1,26 @@
import type { ServiceContainer } from '@esengine/editor-runtime';
/**
* 插件上下文
* 存储插件安装时传入的服务容器引用
*/
class PluginContextClass {
private _services: ServiceContainer | null = null;
setServices(services: ServiceContainer): void {
this._services = services;
}
getServices(): ServiceContainer {
if (!this._services) {
throw new Error('PluginContext not initialized. Make sure the plugin is properly installed.');
}
return this._services;
}
clear(): void {
this._services = null;
}
}
export const PluginContext = new PluginContextClass();

View File

@@ -1,4 +1,6 @@
import { create } from 'zustand';
import { createStore } from '@esengine/editor-runtime';
const create = createStore;
import { NodeTemplates, NodeTemplate } from '@esengine/behavior-tree';
import { BehaviorTree } from '../../domain/models/BehaviorTree';
import { Node } from '../../domain/models/Node';

View File

@@ -1,10 +1,19 @@
import React, { useState, useEffect } from 'react';
import { ICompiler, CompileResult, CompilerContext, IFileSystem } from '@esengine/editor-core';
import { File, FolderTree, FolderOpen } from 'lucide-react';
import {
React,
useState,
useEffect,
type ICompiler,
type CompileResult,
type CompilerContext,
type IFileSystem,
Icons,
createLogger,
} from '@esengine/editor-runtime';
import { GlobalBlackboardTypeGenerator } from '../generators/GlobalBlackboardTypeGenerator';
import { EditorFormatConverter, BehaviorTreeAssetSerializer } from '@esengine/behavior-tree';
import { useBehaviorTreeDataStore } from '../application/state/BehaviorTreeDataStore';
import { createLogger } from '@esengine/ecs-framework';
const { File, FolderTree, FolderOpen } = Icons;
const logger = createLogger('BehaviorTreeCompiler');

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useRef, useState, useCallback } from 'react';
import { React, useEffect, useMemo, useRef, useState, useCallback } from '@esengine/editor-runtime';
import { NodeTemplate, BlackboardValueType } from '@esengine/behavior-tree';
import { useBehaviorTreeDataStore, BehaviorTreeNode, ROOT_NODE_ID } from '../stores';
import { useUIStore } from '../stores';

View File

@@ -1,5 +1,6 @@
import React, { useState } from 'react';
import { Clipboard, Edit2, Trash2, ChevronDown, ChevronRight, Globe, GripVertical, ChevronLeft, Plus, Copy } from 'lucide-react';
import { React, useState, Icons } from '@esengine/editor-runtime';
const { Clipboard, Edit2, Trash2, ChevronDown, ChevronRight, Globe, GripVertical, ChevronLeft, Plus, Copy } = Icons;
type SimpleBlackboardType = 'number' | 'string' | 'boolean' | 'object';

View File

@@ -1,4 +1,4 @@
import React, { useRef, useCallback, forwardRef, useState, useEffect } from 'react';
import { React, useRef, useCallback, forwardRef, useState, useEffect } from '@esengine/editor-runtime';
import { useCanvasInteraction } from '../../hooks/useCanvasInteraction';
import { EditorConfig } from '../../types';
import { GridBackground } from './GridBackground';

View File

@@ -1,4 +1,4 @@
import React, { useMemo } from 'react';
import { React, useMemo } from '@esengine/editor-runtime';
interface GridBackgroundProps {
canvasOffset: { x: number; y: number };

View File

@@ -1,5 +1,6 @@
import React, { useState, useRef, useEffect, ReactNode } from 'react';
import { GripVertical } from 'lucide-react';
import { React, useState, useRef, useEffect, type ReactNode, Icons } from '@esengine/editor-runtime';
const { GripVertical } = Icons;
interface DraggablePanelProps {
title: string | ReactNode;

View File

@@ -1,4 +1,4 @@
import React, { useMemo } from 'react';
import { React, useMemo } from '@esengine/editor-runtime';
import { ConnectionRenderer } from './ConnectionRenderer';
import { ConnectionViewData } from '../../types';
import { Node } from '../../domain/models/Node';

View File

@@ -1,4 +1,4 @@
import React, { useMemo } from 'react';
import { React, useMemo } from '@esengine/editor-runtime';
import { ConnectionViewData } from '../../types';
import { Node } from '../../domain/models/Node';

View File

@@ -1,5 +1,6 @@
import React from 'react';
import { Trash2, Replace, Plus } from 'lucide-react';
import { React, Icons } from '@esengine/editor-runtime';
const { Trash2, Replace, Plus } = Icons;
interface NodeContextMenuProps {
visible: boolean;

View File

@@ -1,8 +1,10 @@
import React, { useRef, useEffect, useState, useMemo } from 'react';
import { React, useRef, useEffect, useState, useMemo, Icons } from '@esengine/editor-runtime';
import type { LucideIcon } from '@esengine/editor-runtime';
import { NodeTemplate } from '@esengine/behavior-tree';
import { Search, X, LucideIcon, ChevronDown, ChevronRight } from 'lucide-react';
import { NodeFactory } from '../../infrastructure/factories/NodeFactory';
const { Search, X, ChevronDown, ChevronRight } = Icons;
interface QuickCreateMenuProps {
visible: boolean;
position: { x: number; y: number };

View File

@@ -1,19 +1,17 @@
import React from 'react';
import {
TreePine,
Database,
AlertTriangle,
AlertCircle,
LucideIcon
} from 'lucide-react';
import { React, Icons } from '@esengine/editor-runtime';
import type { LucideIcon } from '@esengine/editor-runtime';
import { PropertyDefinition } from '@esengine/behavior-tree';
import { Node as BehaviorTreeNodeType } from '../../domain/models/Node';
import { Connection } from '../../domain/models/Connection';
import { ROOT_NODE_ID } from '../../domain/constants/RootNode';
import type { NodeExecutionStatus } from '../../stores';
import { BehaviorTreeExecutor } from '../../utils/BehaviorTreeExecutor';
import { BlackboardValue } from '../../domain/models/Blackboard';
const { TreePine, Database, AlertTriangle, AlertCircle } = Icons;
type BlackboardVariables = Record<string, BlackboardValue>;
interface BehaviorTreeNodeProps {

View File

@@ -1,8 +1,10 @@
import React, { useMemo } from 'react';
import * as LucideIcons from 'lucide-react';
import type { LucideIcon } from 'lucide-react';
import { React, useMemo, Icons } from '@esengine/editor-runtime';
import type { LucideIcon } from '@esengine/editor-runtime';
import { NodeViewData } from '../../types';
const LucideIcons = Icons;
/**
* 图标映射
*/

View File

@@ -1,16 +1,25 @@
import React, { useState, useCallback, useEffect } from 'react';
import { Core, createLogger } from '@esengine/ecs-framework';
import { MessageHub } from '@esengine/editor-core';
import { open, save } from '@tauri-apps/plugin-dialog';
import {
React,
useState,
useCallback,
useEffect,
Core,
createLogger,
MessageHub,
open,
save,
Icons,
} from '@esengine/editor-runtime';
import { useBehaviorTreeDataStore } from '../../stores';
import { BehaviorTreeEditor } from '../BehaviorTreeEditor';
import { BehaviorTreeService } from '../../services/BehaviorTreeService';
import { showToast } from '../../services/NotificationService';
import { FolderOpen } from 'lucide-react';
import { Node as BehaviorTreeNode } from '../../domain/models/Node';
import { BehaviorTree } from '../../domain/models/BehaviorTree';
import './BehaviorTreeEditorPanel.css';
const { FolderOpen } = Icons;
const logger = createLogger('BehaviorTreeEditorPanel');
/**

View File

@@ -1,5 +1,6 @@
import React from 'react';
import { Play, Pause, Square, SkipForward, Undo, Redo, ZoomIn, Save, FolderOpen, Download, Clipboard, Home } from 'lucide-react';
import { React, Icons } from '@esengine/editor-runtime';
const { Play, Pause, Square, SkipForward, Undo, Redo, ZoomIn, Save, FolderOpen, Download, Clipboard, Home } = Icons;
type ExecutionMode = 'idle' | 'running' | 'paused';

View File

@@ -1,12 +1,14 @@
import { NodeTemplate, NodeType } from '@esengine/behavior-tree';
import {
import { Icons } from '@esengine/editor-runtime';
import type { LucideIcon } from '@esengine/editor-runtime';
const {
List, GitBranch, Layers, Shuffle, RotateCcw,
Repeat, CheckCircle, XCircle, CheckCheck, HelpCircle, Snowflake, Timer,
Clock, FileText, Edit, Calculator, Code,
Equal, Dices, Settings,
Database, TreePine,
LucideIcon
} from 'lucide-react';
Database, TreePine
} = Icons;
export const ICON_MAP: Record<string, LucideIcon> = {
List,

View File

@@ -1,4 +1,4 @@
import { useCallback, useMemo } from 'react';
import { useCallback, useMemo, React } from '@esengine/editor-runtime';
import { useBehaviorTreeDataStore, useUIStore } from '../stores';
/**

View File

@@ -1,4 +1,4 @@
import { RefObject, useEffect, useRef } from 'react';
import { type RefObject, useEffect, useRef, React } from '@esengine/editor-runtime';
import { BehaviorTreeNode, ROOT_NODE_ID } from '../stores';
interface QuickCreateMenuState {

View File

@@ -1,5 +1,4 @@
import { useRef, useCallback, useMemo, useEffect } from 'react';
import { CommandManager } from '@esengine/editor-core';
import { useRef, useCallback, useMemo, useEffect, CommandManager } from '@esengine/editor-runtime';
/**
* 撤销/重做功能 Hook

View File

@@ -1,11 +1,9 @@
import { useCallback, useMemo } from 'react';
import { CommandManager } from '@esengine/editor-core';
import { useCallback, useMemo, CommandManager, createLogger } from '@esengine/editor-runtime';
import { ConnectionType } from '../domain/models/Connection';
import { IValidator } from '../domain/interfaces/IValidator';
import { TreeStateAdapter } from '../application/state/BehaviorTreeDataStore';
import { AddConnectionUseCase } from '../application/use-cases/AddConnectionUseCase';
import { RemoveConnectionUseCase } from '../application/use-cases/RemoveConnectionUseCase';
import { createLogger } from '@esengine/ecs-framework';
const logger = createLogger('useConnectionOperations');

View File

@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState, React } from '@esengine/editor-runtime';
import { BehaviorTreeNode, ROOT_NODE_ID } from '../stores';
interface ContextMenuState {

View File

@@ -1,8 +1,7 @@
import { useState, RefObject } from 'react';
import { useState, type RefObject, React, createLogger } from '@esengine/editor-runtime';
import { NodeTemplate, NodeType } from '@esengine/behavior-tree';
import { Position } from '../domain/value-objects/Position';
import { useNodeOperations } from './useNodeOperations';
import { createLogger } from '@esengine/ecs-framework';
const logger = createLogger('useDropHandler');

View File

@@ -1,5 +1,4 @@
import { useCallback } from 'react';
import { ask } from '@tauri-apps/plugin-dialog';
import { useCallback, React, ask } from '@esengine/editor-runtime';
import { BehaviorTreeNode } from '../stores';
interface UseEditorHandlersParams {

View File

@@ -1,4 +1,4 @@
import { useRef, useState } from 'react';
import { useRef, useState } from '@esengine/editor-runtime';
import { BehaviorTreeExecutor } from '../utils/BehaviorTreeExecutor';
export function useEditorState() {

View File

@@ -1,10 +1,9 @@
import { useState, useEffect, useMemo, useRef } from 'react';
import { useState, useEffect, useMemo, useRef, createLogger } from '@esengine/editor-runtime';
import { ExecutionController, ExecutionMode } from '../application/services/ExecutionController';
import { BlackboardManager } from '../application/services/BlackboardManager';
import { BehaviorTreeNode, Connection, useBehaviorTreeDataStore } from '../stores';
import { ExecutionLog } from '../utils/BehaviorTreeExecutor';
import { BlackboardValue } from '../domain/models/Blackboard';
import { createLogger } from '@esengine/ecs-framework';
const logger = createLogger('useExecutionController');

View File

@@ -1,4 +1,4 @@
import { useEffect } from 'react';
import { useEffect } from '@esengine/editor-runtime';
import { Connection, ROOT_NODE_ID } from '../stores';
import { useNodeOperations } from './useNodeOperations';
import { useConnectionOperations } from './useConnectionOperations';

View File

@@ -1,4 +1,4 @@
import { useRef, useCallback, RefObject } from 'react';
import { useRef, useCallback, type RefObject, React } from '@esengine/editor-runtime';
import { BehaviorTreeNode, ROOT_NODE_ID } from '../stores';
import { Position } from '../domain/value-objects/Position';
import { useNodeOperations } from './useNodeOperations';

View File

@@ -1,6 +1,5 @@
import { useCallback, useMemo } from 'react';
import { useCallback, useMemo, CommandManager } from '@esengine/editor-runtime';
import { NodeTemplate } from '@esengine/behavior-tree';
import { CommandManager } from '@esengine/editor-core';
import { Position } from '../domain/value-objects/Position';
import { INodeFactory } from '../domain/interfaces/INodeFactory';
import { TreeStateAdapter } from '../application/state/BehaviorTreeDataStore';

View File

@@ -1,4 +1,4 @@
import { useState, useEffect, useRef } from 'react';
import { useState, useEffect, useRef } from '@esengine/editor-runtime';
import { BehaviorTreeNode } from '../stores';
import { ExecutionMode } from '../application/services/ExecutionController';

View File

@@ -1,4 +1,4 @@
import { RefObject } from 'react';
import { type RefObject, React } from '@esengine/editor-runtime';
import { BehaviorTreeNode, Connection, ROOT_NODE_ID, useUIStore } from '../stores';
import { PropertyDefinition } from '@esengine/behavior-tree';
import { useConnectionOperations } from './useConnectionOperations';

View File

@@ -1,4 +1,4 @@
import { useState, RefObject } from 'react';
import { useState, type RefObject } from '@esengine/editor-runtime';
import { NodeTemplate } from '@esengine/behavior-tree';
import { BehaviorTreeNode, Connection, useBehaviorTreeDataStore } from '../stores';
import { Node } from '../domain/models/Node';

View File

@@ -3,6 +3,7 @@ import { BehaviorTreePlugin } from './BehaviorTreePlugin';
export default new BehaviorTreePlugin();
export { BehaviorTreePlugin } from './BehaviorTreePlugin';
export { PluginContext } from './PluginContext';
export { BehaviorTreeEditorPanel } from './components/panels/BehaviorTreeEditorPanel';
export * from './BehaviorTreeModule';
export * from './services/BehaviorTreeService';

View File

@@ -1,8 +1,7 @@
import { React, createLogger } from '@esengine/editor-runtime';
import type { LucideIcon } from '@esengine/editor-runtime';
import { NodeTemplate } from '@esengine/behavior-tree';
import { Node as BehaviorTreeNode } from '../domain/models/Node';
import { LucideIcon } from 'lucide-react';
import React from 'react';
import { createLogger } from '@esengine/ecs-framework';
const logger = createLogger('IEditorExtensions');

View File

@@ -1,6 +1,14 @@
import React, { useState, useCallback } from 'react';
import { IInspectorProvider, InspectorContext, MessageHub, FieldEditorRegistry, FieldEditorContext } from '@esengine/editor-core';
import { Core } from '@esengine/ecs-framework';
import {
React,
useState,
useCallback,
type IInspectorProvider,
type InspectorContext,
MessageHub,
FieldEditorRegistry,
type FieldEditorContext,
Core,
} from '@esengine/editor-runtime';
import { Node as BehaviorTreeNode } from '../domain/models/Node';
import { PropertyDefinition } from '@esengine/behavior-tree';

View File

@@ -1,9 +1,14 @@
import { singleton } from 'tsyringe';
import { Core, IService, createLogger } from '@esengine/ecs-framework';
import { MessageHub } from '@esengine/editor-core';
import {
singleton,
type IService,
createLogger,
MessageHub,
IMessageHub,
} from '@esengine/editor-runtime';
import { useBehaviorTreeDataStore } from '../application/state/BehaviorTreeDataStore';
import type { BehaviorTree } from '../domain/models/BehaviorTree';
import { FileSystemService } from './FileSystemService';
import { PluginContext } from '../PluginContext';
const logger = createLogger('BehaviorTreeService');
@@ -15,8 +20,10 @@ export class BehaviorTreeService implements IService {
async loadFromFile(filePath: string): Promise<void> {
try {
const services = PluginContext.getServices();
// 运行时解析 FileSystemService
const fileSystem = Core.services.resolve(FileSystemService);
const fileSystem = services.resolve(FileSystemService);
if (!fileSystem) {
throw new Error('FileSystemService not found. Please ensure the BehaviorTreePlugin is properly installed.');
}
@@ -29,7 +36,7 @@ export class BehaviorTreeService implements IService {
// 在 store 中保存文件信息Panel 挂载时读取
store.setCurrentFile(filePath, fileName);
const messageHub = Core.services.resolve(MessageHub);
const messageHub = services.resolve<MessageHub>(IMessageHub);
if (messageHub) {
messageHub.publish('dynamic-panel:open', {
panelId: 'behavior-tree-editor',
@@ -50,8 +57,10 @@ export class BehaviorTreeService implements IService {
async saveToFile(filePath: string, metadata?: { name: string; description: string }): Promise<void> {
try {
const services = PluginContext.getServices();
// 运行时解析 FileSystemService
const fileSystem = Core.services.resolve(FileSystemService);
const fileSystem = services.resolve(FileSystemService);
if (!fileSystem) {
throw new Error('FileSystemService not found. Please ensure the BehaviorTreePlugin is properly installed.');
}

View File

@@ -1,6 +1,4 @@
import { singleton } from 'tsyringe';
import { invoke } from '@tauri-apps/api/core';
import { IService } from '@esengine/ecs-framework';
import { singleton, invoke, type IService } from '@esengine/editor-runtime';
/**
* 文件系统服务

View File

@@ -1,5 +1,4 @@
import { Core, createLogger } from '@esengine/ecs-framework';
import { MessageHub } from '@esengine/editor-core';
import { Core, createLogger, MessageHub } from '@esengine/editor-runtime';
const logger = createLogger('NotificationService');

View File

@@ -1,4 +1,6 @@
import { create } from 'zustand';
import { createStore } from '@esengine/editor-runtime';
const create = createStore;
/**
* 节点执行统计信息

View File

@@ -1,4 +1,6 @@
import { create } from 'zustand';
import { createStore } from '@esengine/editor-runtime';
const create = createStore;
/**
* UI 状态 Store

View File

@@ -1,6 +1,5 @@
import { RefObject } from 'react';
import { type RefObject, createLogger } from '@esengine/editor-runtime';
import { Node as BehaviorTreeNode } from '../domain/models/Node';
import { createLogger } from '@esengine/ecs-framework';
const logger = createLogger('portUtils');