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:
@@ -1,48 +0,0 @@
|
||||
{
|
||||
"name": "@esengine/behavior-tree-editor",
|
||||
"version": "1.0.0",
|
||||
"description": "Behavior Tree Editor Plugin for ECS Framework",
|
||||
"type": "module",
|
||||
"main": "dist/index.esm.js",
|
||||
"module": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"clean": "rimraf bin dist tsconfig.tsbuildinfo",
|
||||
"prebuild": "npm run clean",
|
||||
"build": "npm run build:tsc && npm run copy:css && npm run build:rollup",
|
||||
"build:tsc": "tsc",
|
||||
"copy:css": "node scripts/copy-css.js",
|
||||
"build:rollup": "rollup -c",
|
||||
"dev": "rollup -c -w"
|
||||
},
|
||||
"keywords": [
|
||||
"ecs",
|
||||
"behavior-tree",
|
||||
"editor",
|
||||
"plugin"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@esengine/behavior-tree": "workspace:*",
|
||||
"@esengine/editor-runtime": "workspace:*",
|
||||
"@rollup/plugin-commonjs": "^28.0.1",
|
||||
"@rollup/plugin-node-resolve": "^15.3.0",
|
||||
"@rollup/plugin-replace": "^6.0.3",
|
||||
"@types/react": "^18.3.18",
|
||||
"rimraf": "^6.0.1",
|
||||
"rollup": "^4.28.1",
|
||||
"rollup-plugin-copy": "^3.5.0",
|
||||
"rollup-plugin-dts": "^6.1.1",
|
||||
"rollup-plugin-postcss": "^4.0.2",
|
||||
"typescript": "^5.8.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@esengine/behavior-tree": "*",
|
||||
"@esengine/editor-runtime": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"mobx": "^6.15.0",
|
||||
"mobx-react-lite": "^4.1.1"
|
||||
}
|
||||
}
|
||||
2039
packages/behavior-tree-editor/pnpm-lock.yaml
generated
2039
packages/behavior-tree-editor/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,67 +0,0 @@
|
||||
const resolve = require('@rollup/plugin-node-resolve');
|
||||
const commonjs = require('@rollup/plugin-commonjs');
|
||||
const replace = require('@rollup/plugin-replace');
|
||||
const dts = require('rollup-plugin-dts').default;
|
||||
const postcss = require('rollup-plugin-postcss');
|
||||
|
||||
const external = [
|
||||
'@esengine/editor-runtime',
|
||||
'@esengine/behavior-tree',
|
||||
];
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
input: 'bin/index.js',
|
||||
output: {
|
||||
file: 'dist/index.esm.js',
|
||||
format: 'es',
|
||||
sourcemap: true,
|
||||
exports: 'named',
|
||||
inlineDynamicImports: true
|
||||
},
|
||||
plugins: [
|
||||
replace({
|
||||
preventAssignment: true,
|
||||
'process.env.NODE_ENV': JSON.stringify('production')
|
||||
}),
|
||||
resolve({
|
||||
extensions: ['.js', '.jsx']
|
||||
}),
|
||||
postcss({
|
||||
inject: true,
|
||||
minimize: false
|
||||
}),
|
||||
commonjs()
|
||||
],
|
||||
external,
|
||||
onwarn(warning, warn) {
|
||||
if (warning.code === 'CIRCULAR_DEPENDENCY' || warning.code === 'THIS_IS_UNDEFINED') {
|
||||
return;
|
||||
}
|
||||
warn(warning);
|
||||
}
|
||||
},
|
||||
|
||||
// 类型定义构建
|
||||
{
|
||||
input: 'bin/index.d.ts',
|
||||
output: {
|
||||
file: 'dist/index.d.ts',
|
||||
format: 'es'
|
||||
},
|
||||
plugins: [
|
||||
dts({
|
||||
respectExternal: true
|
||||
})
|
||||
],
|
||||
external: [
|
||||
...external,
|
||||
/\.css$/,
|
||||
// 排除 React 相关类型,避免 rollup-plugin-dts 解析问题
|
||||
'react',
|
||||
'react-dom',
|
||||
/^@types\//,
|
||||
/^@esengine\//
|
||||
]
|
||||
}
|
||||
];
|
||||
@@ -1,25 +0,0 @@
|
||||
import { readdirSync, statSync, copyFileSync, mkdirSync } from 'fs';
|
||||
import { join, dirname, relative } from 'path';
|
||||
|
||||
function copyCSS(srcDir, destDir) {
|
||||
const files = readdirSync(srcDir);
|
||||
|
||||
for (const file of files) {
|
||||
const srcPath = join(srcDir, file);
|
||||
const stat = statSync(srcPath);
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
copyCSS(srcPath, destDir);
|
||||
} else if (file.endsWith('.css')) {
|
||||
const relativePath = relative('src', srcPath);
|
||||
const destPath = join(destDir, relativePath);
|
||||
|
||||
mkdirSync(dirname(destPath), { recursive: true });
|
||||
copyFileSync(srcPath, destPath);
|
||||
console.log(`Copied: ${relativePath}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
copyCSS('src', 'bin');
|
||||
console.log('CSS files copied successfully!');
|
||||
@@ -1,105 +0,0 @@
|
||||
import { singleton } from 'tsyringe';
|
||||
import { Core, createLogger } from '@esengine/ecs-framework';
|
||||
import { CompilerRegistry, IEditorModule, IModuleContext, PanelPosition } from '@esengine/editor-core';
|
||||
import { BehaviorTreeService } from './services/BehaviorTreeService';
|
||||
import { BehaviorTreeCompiler } from './compiler/BehaviorTreeCompiler';
|
||||
import { BehaviorTreeNodeInspectorProvider } from './providers/BehaviorTreeNodeInspectorProvider';
|
||||
import { BehaviorTreeEditorPanel } from './components/panels/BehaviorTreeEditorPanel';
|
||||
|
||||
const logger = createLogger('BehaviorTreeModule');
|
||||
|
||||
@singleton()
|
||||
export class BehaviorTreeModule implements IEditorModule {
|
||||
readonly id = 'behavior-tree';
|
||||
readonly name = 'Behavior Tree Editor';
|
||||
readonly version = '1.0.0';
|
||||
|
||||
async load(context: IModuleContext): Promise<void> {
|
||||
logger.info('[BehaviorTreeModule] Loading behavior tree editor module...');
|
||||
|
||||
this.registerServices(context);
|
||||
this.registerCompilers();
|
||||
this.registerInspectors(context);
|
||||
this.registerCommands(context);
|
||||
this.registerPanels(context);
|
||||
this.subscribeEvents(context);
|
||||
|
||||
logger.info('[BehaviorTreeModule] Behavior tree editor module loaded');
|
||||
}
|
||||
|
||||
private registerServices(context: IModuleContext): void {
|
||||
context.container.register(BehaviorTreeService, { useClass: BehaviorTreeService });
|
||||
logger.info('[BehaviorTreeModule] Services registered');
|
||||
}
|
||||
|
||||
private registerCompilers(): void {
|
||||
const compilerRegistry = Core.services.resolve(CompilerRegistry);
|
||||
if (compilerRegistry) {
|
||||
const compiler = new BehaviorTreeCompiler();
|
||||
compilerRegistry.register(compiler);
|
||||
logger.info('[BehaviorTreeModule] Compiler registered');
|
||||
}
|
||||
}
|
||||
|
||||
private registerInspectors(context: IModuleContext): void {
|
||||
const provider = new BehaviorTreeNodeInspectorProvider();
|
||||
context.inspectorRegistry.register(provider);
|
||||
logger.info('[BehaviorTreeModule] Inspector provider registered');
|
||||
}
|
||||
|
||||
async unload(): Promise<void> {
|
||||
logger.info('[BehaviorTreeModule] Unloading behavior tree editor module...');
|
||||
}
|
||||
|
||||
private registerCommands(context: IModuleContext): void {
|
||||
context.commands.register({
|
||||
id: 'behavior-tree.new',
|
||||
label: 'New Behavior Tree',
|
||||
icon: 'file-plus',
|
||||
execute: async () => {
|
||||
const service = context.container.resolve(BehaviorTreeService);
|
||||
await service.createNew();
|
||||
}
|
||||
});
|
||||
|
||||
context.commands.register({
|
||||
id: 'behavior-tree.open',
|
||||
label: 'Open Behavior Tree',
|
||||
icon: 'folder-open',
|
||||
execute: async () => {
|
||||
logger.info('Open behavior tree');
|
||||
}
|
||||
});
|
||||
|
||||
context.commands.register({
|
||||
id: 'behavior-tree.save',
|
||||
label: 'Save Behavior Tree',
|
||||
icon: 'save',
|
||||
keybinding: { key: 'S', ctrl: true },
|
||||
execute: async () => {
|
||||
logger.info('Save behavior tree');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private registerPanels(context: IModuleContext): void {
|
||||
logger.info('[BehaviorTreeModule] Registering panels...');
|
||||
|
||||
context.panels.register({
|
||||
id: 'behavior-tree-editor',
|
||||
title: '行为树编辑器',
|
||||
icon: 'GitBranch',
|
||||
component: BehaviorTreeEditorPanel,
|
||||
position: PanelPosition.Center,
|
||||
defaultSize: 400,
|
||||
closable: true,
|
||||
isDynamic: true
|
||||
});
|
||||
|
||||
logger.info('[BehaviorTreeModule] Panel registered: behavior-tree-editor');
|
||||
}
|
||||
|
||||
private subscribeEvents(_context: IModuleContext): void {
|
||||
// 文件加载由 BehaviorTreeEditorPanel 处理
|
||||
}
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
import {
|
||||
type Core,
|
||||
type ServiceContainer,
|
||||
type IService,
|
||||
type ServiceType,
|
||||
type IEditorPlugin,
|
||||
EditorPluginCategory,
|
||||
CompilerRegistry,
|
||||
ICompilerRegistry,
|
||||
InspectorRegistry,
|
||||
IInspectorRegistry,
|
||||
PanelPosition,
|
||||
type FileCreationTemplate,
|
||||
type FileActionHandler,
|
||||
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 { createRootNode } from './domain/constants/RootNode';
|
||||
import { PluginContext } from './PluginContext';
|
||||
|
||||
const { GitBranch } = Icons;
|
||||
|
||||
const logger = createLogger('BehaviorTreePlugin');
|
||||
|
||||
export class BehaviorTreePlugin implements IEditorPlugin {
|
||||
readonly name = '@esengine/behavior-tree-editor';
|
||||
readonly version = '1.0.0';
|
||||
readonly displayName = 'Behavior Tree Editor';
|
||||
readonly category = EditorPluginCategory.Tool;
|
||||
readonly description = 'Visual behavior tree editor for game AI development';
|
||||
readonly icon = 'GitBranch';
|
||||
|
||||
private services?: ServiceContainer;
|
||||
private registeredServices: Set<ServiceType<IService>> = new Set();
|
||||
private fileActionHandler?: FileActionHandler;
|
||||
private fileCreationTemplate?: FileCreationTemplate;
|
||||
|
||||
async install(core: Core, services: ServiceContainer): Promise<void> {
|
||||
this.services = services;
|
||||
// 设置插件上下文,让内部服务可以访问服务容器
|
||||
PluginContext.setServices(services);
|
||||
this.registerServices(services);
|
||||
this.registerCompilers(services);
|
||||
this.registerInspectors(services);
|
||||
this.registerFileActions(services);
|
||||
}
|
||||
|
||||
async uninstall(): Promise<void> {
|
||||
if (this.services) {
|
||||
for (const serviceType of this.registeredServices) {
|
||||
this.services.unregister(serviceType);
|
||||
}
|
||||
}
|
||||
|
||||
this.registeredServices.clear();
|
||||
useBehaviorTreeDataStore.getState().reset();
|
||||
PluginContext.clear();
|
||||
this.services = undefined;
|
||||
}
|
||||
|
||||
registerPanels(): PanelDescriptor[] {
|
||||
return [
|
||||
{
|
||||
id: 'behavior-tree-editor',
|
||||
title: 'Behavior Tree Editor',
|
||||
position: PanelPosition.Center,
|
||||
closable: true,
|
||||
component: BehaviorTreeEditorPanel,
|
||||
order: 100,
|
||||
isDynamic: true // 标记为动态面板
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
private registerServices(services: ServiceContainer): void {
|
||||
// 先注册 FileSystemService(BehaviorTreeService 依赖它)
|
||||
if (services.isRegistered(FileSystemService)) {
|
||||
services.unregister(FileSystemService);
|
||||
}
|
||||
services.registerSingleton(FileSystemService);
|
||||
this.registeredServices.add(FileSystemService);
|
||||
|
||||
// 再注册 BehaviorTreeService
|
||||
if (services.isRegistered(BehaviorTreeService)) {
|
||||
services.unregister(BehaviorTreeService);
|
||||
}
|
||||
services.registerSingleton(BehaviorTreeService);
|
||||
this.registeredServices.add(BehaviorTreeService);
|
||||
}
|
||||
|
||||
private registerCompilers(services: ServiceContainer): void {
|
||||
try {
|
||||
const compilerRegistry = services.resolve<CompilerRegistry>(ICompilerRegistry);
|
||||
const compiler = new BehaviorTreeCompiler();
|
||||
compilerRegistry.register(compiler);
|
||||
logger.info('Successfully registered BehaviorTreeCompiler');
|
||||
} catch (error) {
|
||||
logger.error('Failed to register compiler:', error);
|
||||
}
|
||||
}
|
||||
|
||||
private registerInspectors(services: ServiceContainer): void {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private registerFileActions(services: ServiceContainer): void {
|
||||
this.fileCreationTemplate = {
|
||||
label: 'Behavior Tree',
|
||||
extension: 'btree',
|
||||
defaultFileName: 'NewBehaviorTree',
|
||||
icon: createElement(GitBranch, { size: 16 }),
|
||||
createContent: (fileName: string) => {
|
||||
// 创建根节点
|
||||
const rootNode = createRootNode();
|
||||
const rootNodeData = {
|
||||
id: rootNode.id,
|
||||
type: rootNode.template.type,
|
||||
displayName: rootNode.template.displayName,
|
||||
data: rootNode.data,
|
||||
position: {
|
||||
x: rootNode.position.x,
|
||||
y: rootNode.position.y
|
||||
},
|
||||
children: []
|
||||
};
|
||||
|
||||
const emptyTree = {
|
||||
name: fileName.replace('.btree', ''),
|
||||
nodes: [rootNodeData],
|
||||
connections: [],
|
||||
variables: {}
|
||||
};
|
||||
|
||||
return JSON.stringify(emptyTree, null, 2);
|
||||
}
|
||||
};
|
||||
|
||||
this.fileActionHandler = {
|
||||
extensions: ['btree'],
|
||||
onDoubleClick: async (filePath: string) => {
|
||||
const service = services.resolve(BehaviorTreeService);
|
||||
if (service) {
|
||||
await service.loadFromFile(filePath);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
registerFileActionHandlers(): FileActionHandler[] {
|
||||
return this.fileActionHandler ? [this.fileActionHandler] : [];
|
||||
}
|
||||
|
||||
registerFileCreationTemplates(): FileCreationTemplate[] {
|
||||
return this.fileCreationTemplate ? [this.fileCreationTemplate] : [];
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
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';
|
||||
export * from './providers/BehaviorTreeNodeInspectorProvider';
|
||||
|
||||
export * from './domain';
|
||||
export * from './application/commands/tree';
|
||||
export * from './application/use-cases';
|
||||
export * from './application/services/BlackboardManager';
|
||||
export * from './application/services/ExecutionController';
|
||||
export * from './application/services/GlobalBlackboardService';
|
||||
export * from './application/interfaces/IExecutionHooks';
|
||||
export * from './application/state/BehaviorTreeDataStore';
|
||||
export * from './hooks';
|
||||
export * from './stores';
|
||||
// Re-export specific items to avoid conflicts
|
||||
export {
|
||||
EditorConfig
|
||||
} from './types';
|
||||
export * from './infrastructure/factories/NodeFactory';
|
||||
export * from './infrastructure/serialization/BehaviorTreeSerializer';
|
||||
export * from './infrastructure/validation/BehaviorTreeValidator';
|
||||
export * from './infrastructure/events/EditorEventBus';
|
||||
export * from './infrastructure/services/NodeRegistryService';
|
||||
export * from './utils/BehaviorTreeExecutor';
|
||||
export * from './utils/DOMCache';
|
||||
export * from './utils/portUtils';
|
||||
export * from './utils/RuntimeLoader';
|
||||
export * from './compiler/BehaviorTreeCompiler';
|
||||
// Export everything except DEFAULT_EDITOR_CONFIG from editorConstants
|
||||
export {
|
||||
ICON_MAP,
|
||||
ROOT_NODE_TEMPLATE,
|
||||
DEFAULT_EDITOR_CONFIG
|
||||
} from './config/editorConstants';
|
||||
export * from './interfaces/IEditorExtensions';
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ES2020",
|
||||
"moduleResolution": "node",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"downlevelIteration": true,
|
||||
"outDir": "./bin",
|
||||
"rootDir": "./src",
|
||||
"composite": true,
|
||||
"jsx": "react-jsx",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"lib": ["ES2020", "DOM"]
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "bin"],
|
||||
"references": [
|
||||
{ "path": "../core" },
|
||||
{ "path": "../editor-core" },
|
||||
{ "path": "../behavior-tree" }
|
||||
]
|
||||
}
|
||||
@@ -1,21 +1,29 @@
|
||||
{
|
||||
"name": "@esengine/behavior-tree",
|
||||
"version": "1.0.1",
|
||||
"description": "完全ECS化的行为树系统,基于组件和实体的行为树实现",
|
||||
"main": "bin/index.js",
|
||||
"types": "bin/index.d.ts",
|
||||
"description": "ECS-based AI behavior tree system with visual editor and runtime execution",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./bin/index.d.ts",
|
||||
"import": "./bin/index.js",
|
||||
"development": {
|
||||
"types": "./src/index.ts",
|
||||
"import": "./src/index.ts"
|
||||
}
|
||||
}
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
},
|
||||
"./runtime": {
|
||||
"types": "./dist/runtime.d.ts",
|
||||
"import": "./dist/runtime.js"
|
||||
},
|
||||
"./editor": {
|
||||
"types": "./dist/editor/index.d.ts",
|
||||
"import": "./dist/editor/index.js"
|
||||
},
|
||||
"./plugin.json": "./plugin.json"
|
||||
},
|
||||
"files": [
|
||||
"bin/**/*"
|
||||
"dist",
|
||||
"plugin.json"
|
||||
],
|
||||
"keywords": [
|
||||
"ecs",
|
||||
@@ -25,40 +33,52 @@
|
||||
"entity-component-system"
|
||||
],
|
||||
"scripts": {
|
||||
"clean": "rimraf bin dist tsconfig.tsbuildinfo",
|
||||
"build:ts": "tsc",
|
||||
"prebuild": "npm run clean",
|
||||
"build": "npm run build:ts",
|
||||
"build:esm": "vite build",
|
||||
"build:watch": "tsc --watch",
|
||||
"rebuild": "npm run clean && npm run build",
|
||||
"build:npm": "npm run build && node build-rollup.cjs",
|
||||
"clean": "rimraf dist tsconfig.tsbuildinfo",
|
||||
"build": "vite build",
|
||||
"build:watch": "vite build --watch",
|
||||
"type-check": "tsc --noEmit",
|
||||
"test": "jest --config jest.config.cjs",
|
||||
"test:watch": "jest --watch --config jest.config.cjs"
|
||||
},
|
||||
"author": "yhh",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@esengine/ecs-framework": "^2.2.8"
|
||||
"@esengine/ecs-framework": ">=2.0.0",
|
||||
"@esengine/ecs-components": "workspace:*",
|
||||
"@esengine/editor-runtime": "workspace:*",
|
||||
"lucide-react": "^0.545.0",
|
||||
"react": "^18.3.1",
|
||||
"zustand": "^4.5.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@esengine/ecs-components": {
|
||||
"optional": true
|
||||
},
|
||||
"@esengine/editor-runtime": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"lucide-react": {
|
||||
"optional": true
|
||||
},
|
||||
"zustand": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.28.3",
|
||||
"@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1",
|
||||
"@babel/plugin-transform-optional-chaining": "^7.27.1",
|
||||
"@babel/preset-env": "^7.28.3",
|
||||
"@rollup/plugin-babel": "^6.0.4",
|
||||
"@rollup/plugin-commonjs": "^28.0.3",
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@tauri-apps/plugin-fs": "^2.4.2",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^20.19.17",
|
||||
"@types/react": "^18.3.12",
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"jest": "^29.7.0",
|
||||
"rimraf": "^5.0.0",
|
||||
"rollup": "^4.42.0",
|
||||
"rollup-plugin-dts": "^6.2.1",
|
||||
"ts-jest": "^29.4.0",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^6.0.7"
|
||||
"vite": "^6.0.7",
|
||||
"vite-plugin-dts": "^3.7.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": "^2.8.1"
|
||||
|
||||
30
packages/behavior-tree/plugin.json
Normal file
30
packages/behavior-tree/plugin.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"id": "@esengine/behavior-tree",
|
||||
"name": "Behavior Tree System",
|
||||
"version": "1.0.0",
|
||||
"description": "AI behavior tree system with visual editor and runtime execution",
|
||||
"category": "ai",
|
||||
"loadingPhase": "default",
|
||||
"enabledByDefault": true,
|
||||
"canContainContent": false,
|
||||
"isEnginePlugin": false,
|
||||
"modules": [
|
||||
{
|
||||
"name": "BehaviorTreeRuntime",
|
||||
"type": "runtime",
|
||||
"entry": "./src/index.ts"
|
||||
},
|
||||
{
|
||||
"name": "BehaviorTreeEditor",
|
||||
"type": "editor",
|
||||
"entry": "./src/editor/index.ts"
|
||||
}
|
||||
],
|
||||
"dependencies": [
|
||||
{
|
||||
"id": "@esengine/core",
|
||||
"version": ">=1.0.0"
|
||||
}
|
||||
],
|
||||
"icon": "GitBranch"
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BehaviorTreeData, BehaviorNodeData } from './Runtime/BehaviorTreeData';
|
||||
import { BehaviorTreeData, BehaviorNodeData } from './execution/BehaviorTreeData';
|
||||
import { NodeType } from './Types/TaskStatus';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
import type { Core } from '@esengine/ecs-framework';
|
||||
import type { ServiceContainer, IPlugin, IScene } from '@esengine/ecs-framework';
|
||||
import { WorldManager } from '@esengine/ecs-framework';
|
||||
import { BehaviorTreeExecutionSystem } from './Runtime/BehaviorTreeExecutionSystem';
|
||||
import { GlobalBlackboardService } from './Services/GlobalBlackboardService';
|
||||
import { BehaviorTreeAssetManager } from './Runtime/BehaviorTreeAssetManager';
|
||||
|
||||
/**
|
||||
* 行为树插件
|
||||
*
|
||||
* 提供便捷方法向场景添加行为树系统
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const core = Core.create();
|
||||
* const plugin = new BehaviorTreePlugin();
|
||||
* await core.pluginManager.install(plugin);
|
||||
*
|
||||
* // 为场景添加行为树系统
|
||||
* const scene = new Scene();
|
||||
* plugin.setupScene(scene);
|
||||
* ```
|
||||
*/
|
||||
export class BehaviorTreePlugin implements IPlugin {
|
||||
readonly name = '@esengine/behavior-tree';
|
||||
readonly version = '1.0.0';
|
||||
|
||||
private worldManager: WorldManager | null = null;
|
||||
private services: ServiceContainer | null = null;
|
||||
|
||||
/**
|
||||
* 安装插件
|
||||
*/
|
||||
async install(_core: Core, services: ServiceContainer): Promise<void> {
|
||||
this.services = services;
|
||||
|
||||
// 注册全局服务
|
||||
services.registerSingleton(GlobalBlackboardService);
|
||||
services.registerSingleton(BehaviorTreeAssetManager);
|
||||
|
||||
this.worldManager = services.resolve(WorldManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* 卸载插件
|
||||
*/
|
||||
async uninstall(): Promise<void> {
|
||||
if (this.services) {
|
||||
this.services.unregister(GlobalBlackboardService);
|
||||
this.services.unregister(BehaviorTreeAssetManager);
|
||||
}
|
||||
|
||||
this.worldManager = null;
|
||||
this.services = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为场景设置行为树系统
|
||||
*
|
||||
* 向场景添加行为树执行系统
|
||||
*
|
||||
* @param scene 目标场景
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const scene = new Scene();
|
||||
* behaviorTreePlugin.setupScene(scene);
|
||||
* ```
|
||||
*/
|
||||
public setupScene(scene: IScene): void {
|
||||
scene.addSystem(new BehaviorTreeExecutionSystem());
|
||||
}
|
||||
|
||||
/**
|
||||
* 为所有现有场景设置行为树系统
|
||||
*/
|
||||
public setupAllScenes(): void {
|
||||
if (!this.worldManager) {
|
||||
throw new Error('Plugin not installed');
|
||||
}
|
||||
|
||||
const worlds = this.worldManager.getAllWorlds();
|
||||
for (const world of worlds) {
|
||||
for (const scene of world.getAllScenes()) {
|
||||
this.setupScene(scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
43
packages/behavior-tree/src/BehaviorTreeRuntimeModule.ts
Normal file
43
packages/behavior-tree/src/BehaviorTreeRuntimeModule.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Behavior Tree Runtime Module (Pure runtime, no editor dependencies)
|
||||
* 行为树运行时模块(纯运行时,无编辑器依赖)
|
||||
*/
|
||||
|
||||
import type { IScene, ServiceContainer } from '@esengine/ecs-framework';
|
||||
import { ComponentRegistry, Core } from '@esengine/ecs-framework';
|
||||
import type { IRuntimeModuleLoader, SystemContext } from '@esengine/ecs-components';
|
||||
|
||||
import { BehaviorTreeRuntimeComponent } from './execution/BehaviorTreeRuntimeComponent';
|
||||
import { BehaviorTreeExecutionSystem } from './execution/BehaviorTreeExecutionSystem';
|
||||
import { BehaviorTreeAssetManager } from './execution/BehaviorTreeAssetManager';
|
||||
import { GlobalBlackboardService } from './Services/GlobalBlackboardService';
|
||||
|
||||
/**
|
||||
* Behavior Tree Runtime Module
|
||||
* 行为树运行时模块
|
||||
*/
|
||||
export class BehaviorTreeRuntimeModule implements IRuntimeModuleLoader {
|
||||
registerComponents(registry: typeof ComponentRegistry): void {
|
||||
registry.register(BehaviorTreeRuntimeComponent);
|
||||
}
|
||||
|
||||
registerServices(services: ServiceContainer): void {
|
||||
if (!services.isRegistered(GlobalBlackboardService)) {
|
||||
services.registerSingleton(GlobalBlackboardService);
|
||||
}
|
||||
if (!services.isRegistered(BehaviorTreeAssetManager)) {
|
||||
services.registerSingleton(BehaviorTreeAssetManager);
|
||||
}
|
||||
}
|
||||
|
||||
createSystems(scene: IScene, context: SystemContext): void {
|
||||
const behaviorTreeSystem = new BehaviorTreeExecutionSystem(Core);
|
||||
|
||||
if (context.isEditor) {
|
||||
behaviorTreeSystem.enabled = false;
|
||||
}
|
||||
|
||||
scene.addSystem(behaviorTreeSystem);
|
||||
context.behaviorTreeSystem = behaviorTreeSystem;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Entity, Core } from '@esengine/ecs-framework';
|
||||
import { BehaviorTreeData } from './Runtime/BehaviorTreeData';
|
||||
import { BehaviorTreeRuntimeComponent } from './Runtime/BehaviorTreeRuntimeComponent';
|
||||
import { BehaviorTreeAssetManager } from './Runtime/BehaviorTreeAssetManager';
|
||||
import { BehaviorTreeData } from './execution/BehaviorTreeData';
|
||||
import { BehaviorTreeRuntimeComponent } from './execution/BehaviorTreeRuntimeComponent';
|
||||
import { BehaviorTreeAssetManager } from './execution/BehaviorTreeAssetManager';
|
||||
|
||||
/**
|
||||
* 行为树启动辅助类
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BehaviorTreeData, BehaviorNodeData } from '../Runtime/BehaviorTreeData';
|
||||
import { BehaviorTreeData, BehaviorNodeData } from '../execution/BehaviorTreeData';
|
||||
import { NodeType, AbortType } from '../Types/TaskStatus';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NodeType } from '../Types/TaskStatus';
|
||||
import { NodeMetadataRegistry, ConfigFieldDefinition, NodeMetadata } from '../Runtime/NodeMetadata';
|
||||
import { NodeMetadataRegistry, ConfigFieldDefinition, NodeMetadata } from '../execution/NodeMetadata';
|
||||
|
||||
/**
|
||||
* 节点数据JSON格式
|
||||
|
||||
98
packages/behavior-tree/src/editor/BehaviorTreePlugin.ts
Normal file
98
packages/behavior-tree/src/editor/BehaviorTreePlugin.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Behavior Tree Unified Plugin
|
||||
* 行为树统一插件
|
||||
*/
|
||||
|
||||
import type { IScene, ServiceContainer } from '@esengine/ecs-framework';
|
||||
import { ComponentRegistry, Core } from '@esengine/ecs-framework';
|
||||
import type {
|
||||
IPluginLoader,
|
||||
IRuntimeModuleLoader,
|
||||
PluginDescriptor,
|
||||
SystemContext
|
||||
} from '@esengine/editor-runtime';
|
||||
|
||||
// Runtime imports
|
||||
import { BehaviorTreeRuntimeComponent } from '../execution/BehaviorTreeRuntimeComponent';
|
||||
import { BehaviorTreeExecutionSystem } from '../execution/BehaviorTreeExecutionSystem';
|
||||
import { BehaviorTreeAssetManager } from '../execution/BehaviorTreeAssetManager';
|
||||
import { GlobalBlackboardService } from '../Services/GlobalBlackboardService';
|
||||
|
||||
/**
|
||||
* 插件描述符
|
||||
*/
|
||||
export const descriptor: PluginDescriptor = {
|
||||
id: '@esengine/behavior-tree',
|
||||
name: 'Behavior Tree System',
|
||||
version: '1.0.0',
|
||||
description: 'AI 行为树系统,支持可视化编辑和运行时执行',
|
||||
category: 'ai',
|
||||
enabledByDefault: true,
|
||||
canContainContent: false,
|
||||
isEnginePlugin: false,
|
||||
modules: [
|
||||
{
|
||||
name: 'BehaviorTreeRuntime',
|
||||
type: 'runtime',
|
||||
loadingPhase: 'default',
|
||||
entry: './src/index.ts'
|
||||
},
|
||||
{
|
||||
name: 'BehaviorTreeEditor',
|
||||
type: 'editor',
|
||||
loadingPhase: 'default',
|
||||
entry: './src/editor/index.ts'
|
||||
}
|
||||
],
|
||||
dependencies: [
|
||||
{ id: '@esengine/core', version: '>=1.0.0' }
|
||||
],
|
||||
icon: 'GitBranch'
|
||||
};
|
||||
|
||||
/**
|
||||
* Behavior Tree Runtime Module
|
||||
* 行为树运行时模块
|
||||
*/
|
||||
export class BehaviorTreeRuntimeModule implements IRuntimeModuleLoader {
|
||||
registerComponents(registry: typeof ComponentRegistry): void {
|
||||
registry.register(BehaviorTreeRuntimeComponent);
|
||||
}
|
||||
|
||||
registerServices(services: ServiceContainer): void {
|
||||
if (!services.isRegistered(GlobalBlackboardService)) {
|
||||
services.registerSingleton(GlobalBlackboardService);
|
||||
}
|
||||
if (!services.isRegistered(BehaviorTreeAssetManager)) {
|
||||
services.registerSingleton(BehaviorTreeAssetManager);
|
||||
}
|
||||
}
|
||||
|
||||
createSystems(scene: IScene, context: SystemContext): void {
|
||||
const behaviorTreeSystem = new BehaviorTreeExecutionSystem(Core);
|
||||
|
||||
// 编辑器模式下默认禁用
|
||||
if (context.isEditor) {
|
||||
behaviorTreeSystem.enabled = false;
|
||||
}
|
||||
|
||||
scene.addSystem(behaviorTreeSystem);
|
||||
|
||||
// 保存引用
|
||||
context.behaviorTreeSystem = behaviorTreeSystem;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Behavior Tree Plugin Loader
|
||||
* 行为树插件加载器
|
||||
*
|
||||
* 注意:editorModule 在 ./index.ts 中通过 createBehaviorTreePlugin() 设置
|
||||
*/
|
||||
export const BehaviorTreePlugin: IPluginLoader = {
|
||||
descriptor,
|
||||
runtimeModule: new BehaviorTreeRuntimeModule(),
|
||||
// editorModule 将在 index.ts 中设置
|
||||
};
|
||||
|
||||
export default BehaviorTreePlugin;
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Connection } from '../../../domain/models/Connection';
|
||||
import { BaseCommand } from '@esengine/editor-core';
|
||||
import { BaseCommand } from '@esengine/editor-runtime';
|
||||
import { ITreeState } from '../ITreeState';
|
||||
|
||||
/**
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Node } from '../../../domain/models/Node';
|
||||
import { BaseCommand } from '@esengine/editor-core';
|
||||
import { BaseCommand } from '@esengine/editor-runtime';
|
||||
import { ITreeState } from '../ITreeState';
|
||||
|
||||
/**
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Node } from '../../../domain/models/Node';
|
||||
import { BaseCommand } from '@esengine/editor-core';
|
||||
import { BaseCommand } from '@esengine/editor-runtime';
|
||||
import { ITreeState } from '../ITreeState';
|
||||
|
||||
/**
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Position } from '../../../domain/value-objects/Position';
|
||||
import { BaseCommand, ICommand } from '@esengine/editor-core';
|
||||
import { BaseCommand, ICommand } from '@esengine/editor-runtime';
|
||||
import { ITreeState } from '../ITreeState';
|
||||
|
||||
/**
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Connection } from '../../../domain/models/Connection';
|
||||
import { BaseCommand } from '@esengine/editor-core';
|
||||
import { BaseCommand } from '@esengine/editor-runtime';
|
||||
import { ITreeState } from '../ITreeState';
|
||||
|
||||
/**
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BaseCommand } from '@esengine/editor-core';
|
||||
import { BaseCommand } from '@esengine/editor-runtime';
|
||||
import { ITreeState } from '../ITreeState';
|
||||
|
||||
/**
|
||||
@@ -1,4 +1,4 @@
|
||||
import { GlobalBlackboardConfig, BlackboardValueType, BlackboardVariable } from '@esengine/behavior-tree';
|
||||
import { GlobalBlackboardConfig, BlackboardValueType, BlackboardVariable } from '../../..';
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
|
||||
const logger = createLogger('GlobalBlackboardService');
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createStore } from '@esengine/editor-runtime';
|
||||
|
||||
const create = createStore;
|
||||
import { NodeTemplates, NodeTemplate } from '@esengine/behavior-tree';
|
||||
import { NodeTemplates, NodeTemplate } from '../../..';
|
||||
import { BehaviorTree } from '../../domain/models/BehaviorTree';
|
||||
import { Node } from '../../domain/models/Node';
|
||||
import { Connection, ConnectionType } from '../../domain/models/Connection';
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Connection, ConnectionType } from '../../domain/models/Connection';
|
||||
import { CommandManager } from '@esengine/editor-core';
|
||||
import { CommandManager } from '@esengine/editor-runtime';
|
||||
import { AddConnectionCommand } from '../commands/tree/AddConnectionCommand';
|
||||
import { ITreeState } from '../commands/ITreeState';
|
||||
import { IValidator } from '../../domain/interfaces/IValidator';
|
||||
@@ -1,8 +1,8 @@
|
||||
import { NodeTemplate } from '@esengine/behavior-tree';
|
||||
import { NodeTemplate } from '../../..';
|
||||
import { Node } from '../../domain/models/Node';
|
||||
import { Position } from '../../domain/value-objects/Position';
|
||||
import { INodeFactory } from '../../domain/interfaces/INodeFactory';
|
||||
import { CommandManager } from '@esengine/editor-core';
|
||||
import { CommandManager } from '@esengine/editor-runtime';
|
||||
import { CreateNodeCommand } from '../commands/tree/CreateNodeCommand';
|
||||
import { ITreeState } from '../commands/ITreeState';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CommandManager, ICommand } from '@esengine/editor-core';
|
||||
import { CommandManager, ICommand } from '@esengine/editor-runtime';
|
||||
import { DeleteNodeCommand } from '../commands/tree/DeleteNodeCommand';
|
||||
import { RemoveConnectionCommand } from '../commands/tree/RemoveConnectionCommand';
|
||||
import { ITreeState } from '../commands/ITreeState';
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Position } from '../../domain/value-objects/Position';
|
||||
import { CommandManager } from '@esengine/editor-core';
|
||||
import { CommandManager } from '@esengine/editor-runtime';
|
||||
import { MoveNodeCommand } from '../commands/tree/MoveNodeCommand';
|
||||
import { ITreeState } from '../commands/ITreeState';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CommandManager } from '@esengine/editor-core';
|
||||
import { CommandManager } from '@esengine/editor-runtime';
|
||||
import { RemoveConnectionCommand } from '../commands/tree/RemoveConnectionCommand';
|
||||
import { ITreeState } from '../commands/ITreeState';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CommandManager } from '@esengine/editor-core';
|
||||
import { CommandManager } from '@esengine/editor-runtime';
|
||||
import { UpdateNodeDataCommand } from '../commands/tree/UpdateNodeDataCommand';
|
||||
import { ITreeState } from '../commands/ITreeState';
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
createLogger,
|
||||
} from '@esengine/editor-runtime';
|
||||
import { GlobalBlackboardTypeGenerator } from '../generators/GlobalBlackboardTypeGenerator';
|
||||
import { EditorFormatConverter, BehaviorTreeAssetSerializer } from '@esengine/behavior-tree';
|
||||
import { EditorFormatConverter, BehaviorTreeAssetSerializer } from '../..';
|
||||
import { useBehaviorTreeDataStore } from '../application/state/BehaviorTreeDataStore';
|
||||
|
||||
const { File, FolderTree, FolderOpen } = Icons;
|
||||
@@ -1,5 +1,5 @@
|
||||
import { React, useEffect, useMemo, useRef, useState, useCallback } from '@esengine/editor-runtime';
|
||||
import { NodeTemplate, BlackboardValueType } from '@esengine/behavior-tree';
|
||||
import { NodeTemplate, BlackboardValueType } from '../..';
|
||||
import { useBehaviorTreeDataStore, BehaviorTreeNode, ROOT_NODE_ID } from '../stores';
|
||||
import { useUIStore } from '../stores';
|
||||
import { showToast as notificationShowToast } from '../services/NotificationService';
|
||||
@@ -1,6 +1,6 @@
|
||||
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 { NodeTemplate } from '../../..';
|
||||
import { NodeFactory } from '../../infrastructure/factories/NodeFactory';
|
||||
|
||||
const { Search, X, ChevronDown, ChevronRight } = Icons;
|
||||
@@ -1,6 +1,6 @@
|
||||
import { React, Icons } from '@esengine/editor-runtime';
|
||||
import type { LucideIcon } from '@esengine/editor-runtime';
|
||||
import { PropertyDefinition } from '@esengine/behavior-tree';
|
||||
import { PropertyDefinition } from '../../..';
|
||||
|
||||
import { Node as BehaviorTreeNodeType } from '../../domain/models/Node';
|
||||
import { Connection } from '../../domain/models/Connection';
|
||||
@@ -3,12 +3,11 @@ import {
|
||||
useState,
|
||||
useCallback,
|
||||
useEffect,
|
||||
Core,
|
||||
createLogger,
|
||||
MessageHub,
|
||||
open,
|
||||
save,
|
||||
Icons,
|
||||
PluginAPI,
|
||||
} from '@esengine/editor-runtime';
|
||||
import { useBehaviorTreeDataStore } from '../../stores';
|
||||
import { BehaviorTreeEditor } from '../BehaviorTreeEditor';
|
||||
@@ -77,11 +76,19 @@ export const BehaviorTreeEditorPanel: React.FC<BehaviorTreeEditorPanelProps> = (
|
||||
}, [tree, lastSavedSnapshot, isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
// 检查 PluginAPI 是否可用
|
||||
if (!PluginAPI.isAvailable) {
|
||||
return;
|
||||
}
|
||||
|
||||
let unsubscribeFileOpened: (() => void) | undefined;
|
||||
let unsubscribePropertyChanged: (() => void) | undefined;
|
||||
|
||||
try {
|
||||
const messageHub = Core.services.resolve(MessageHub);
|
||||
const messageHub = PluginAPI.messageHub;
|
||||
|
||||
// 订阅文件打开事件
|
||||
const unsubscribeFileOpened = messageHub.subscribe('behavior-tree:file-opened', (data: { filePath: string; fileName: string }) => {
|
||||
unsubscribeFileOpened = messageHub.subscribe('behavior-tree:file-opened', (data: { filePath: string; fileName: string }) => {
|
||||
setCurrentFilePath(data.filePath);
|
||||
setCurrentFileName(data.fileName);
|
||||
const loadedTree = useBehaviorTreeDataStore.getState().tree;
|
||||
@@ -90,7 +97,7 @@ export const BehaviorTreeEditorPanel: React.FC<BehaviorTreeEditorPanelProps> = (
|
||||
});
|
||||
|
||||
// 订阅节点属性更改事件
|
||||
const unsubscribePropertyChanged = messageHub.subscribe('behavior-tree:node-property-changed',
|
||||
unsubscribePropertyChanged = messageHub.subscribe('behavior-tree:node-property-changed',
|
||||
(data: { nodeId: string; propertyName: string; value: any }) => {
|
||||
const state = useBehaviorTreeDataStore.getState();
|
||||
const node = state.getNode(data.nodeId);
|
||||
@@ -127,19 +134,22 @@ export const BehaviorTreeEditorPanel: React.FC<BehaviorTreeEditorPanelProps> = (
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
unsubscribeFileOpened();
|
||||
unsubscribePropertyChanged();
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('Failed to subscribe to events:', error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return () => {
|
||||
unsubscribeFileOpened?.();
|
||||
unsubscribePropertyChanged?.();
|
||||
};
|
||||
}, [isOpen]);
|
||||
|
||||
const handleNodeSelect = useCallback((node: BehaviorTreeNode) => {
|
||||
try {
|
||||
const messageHub = Core.services.resolve(MessageHub);
|
||||
if (!PluginAPI.isAvailable) {
|
||||
return;
|
||||
}
|
||||
const messageHub = PluginAPI.messageHub;
|
||||
messageHub.publish('behavior-tree:node-selected', { data: node });
|
||||
} catch (error) {
|
||||
logger.error('Failed to publish node selection:', error);
|
||||
@@ -161,7 +171,7 @@ export const BehaviorTreeEditorPanel: React.FC<BehaviorTreeEditorPanelProps> = (
|
||||
filePath = selected;
|
||||
}
|
||||
|
||||
const service = Core.services.resolve(BehaviorTreeService);
|
||||
const service = PluginAPI.resolve<BehaviorTreeService>(BehaviorTreeService);
|
||||
await service.saveToFile(filePath);
|
||||
|
||||
setCurrentFilePath(filePath);
|
||||
@@ -195,7 +205,7 @@ export const BehaviorTreeEditorPanel: React.FC<BehaviorTreeEditorPanelProps> = (
|
||||
if (!selected) return;
|
||||
|
||||
const filePath = selected as string;
|
||||
const service = Core.services.resolve(BehaviorTreeService);
|
||||
const service = PluginAPI.resolve<BehaviorTreeService>(BehaviorTreeService);
|
||||
await service.loadFromFile(filePath);
|
||||
|
||||
setCurrentFilePath(filePath);
|
||||
@@ -220,7 +230,7 @@ export const BehaviorTreeEditorPanel: React.FC<BehaviorTreeEditorPanelProps> = (
|
||||
}
|
||||
|
||||
try {
|
||||
const messageHub = Core.services.resolve(MessageHub);
|
||||
const messageHub = PluginAPI.messageHub;
|
||||
messageHub.publish('compiler:open-dialog', {
|
||||
compilerId: 'behavior-tree',
|
||||
currentFileName: currentFileName || undefined,
|
||||
@@ -1,4 +1,4 @@
|
||||
import { NodeTemplate, NodeType } from '@esengine/behavior-tree';
|
||||
import { NodeTemplate, NodeType } from '../..';
|
||||
import { Icons } from '@esengine/editor-runtime';
|
||||
import type { LucideIcon } from '@esengine/editor-runtime';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Node } from '../models/Node';
|
||||
import { Position } from '../value-objects/Position';
|
||||
import { NodeTemplate } from '@esengine/behavior-tree';
|
||||
import { NodeTemplate } from '../../..';
|
||||
|
||||
export const ROOT_NODE_ID = 'root-node';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { NodeTemplate } from '@esengine/behavior-tree';
|
||||
import { NodeTemplate } from '../../..';
|
||||
import { Node } from '../models/Node';
|
||||
import { Position } from '../value-objects';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { NodeTemplate } from '@esengine/behavior-tree';
|
||||
import { NodeTemplate } from '../../..';
|
||||
import { Position, NodeType } from '../value-objects';
|
||||
import { ValidationError } from '../errors';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { GlobalBlackboardConfig, BlackboardValueType } from '@esengine/behavior-tree';
|
||||
import { GlobalBlackboardConfig, BlackboardValueType } from '../..';
|
||||
|
||||
/**
|
||||
* 类型生成配置选项
|
||||
@@ -70,7 +70,7 @@ export class GlobalBlackboardTypeGenerator {
|
||||
typeAliasName: 'GlobalVariableName',
|
||||
wrapperClassName: 'TypedGlobalBlackboard',
|
||||
defaultsName: 'GlobalBlackboardDefaults',
|
||||
importPath: '@esengine/behavior-tree',
|
||||
importPath: '../..',
|
||||
includeConstants: true,
|
||||
includeInterface: true,
|
||||
includeTypeAlias: true,
|
||||
@@ -150,6 +150,9 @@ export class GlobalBlackboardTypeGenerator {
|
||||
|
||||
/**
|
||||
* 生成文件头部注释
|
||||
*
|
||||
* 注意:生成的代码字符串会被打包到 IIFE 中,如果 import/export 出现在行首
|
||||
* 会被浏览器误解析为 ES module 语法。因此使用字符串拼接确保不在行首。
|
||||
*/
|
||||
private static generateHeader(timestamp: string, opts: Required<TypeGenerationOptions>): string {
|
||||
const customHeader = opts.customHeader || `/**
|
||||
@@ -159,22 +162,25 @@ export class GlobalBlackboardTypeGenerator {
|
||||
* 生成时间: ${timestamp}
|
||||
*/`;
|
||||
|
||||
return `${customHeader}
|
||||
|
||||
import { GlobalBlackboardService } from '${opts.importPath}';`;
|
||||
// 使用字符串拼接避免 import 出现在源代码行首(打包后可能被误解析)
|
||||
const importStatement = 'im' + 'port { GlobalBlackboardService } from \'' + opts.importPath + '\';';
|
||||
return `${customHeader}\n\n${importStatement}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成常量对象
|
||||
* 注意:使用 EXP + 'ort' 拼接避免打包后 export 在行首被误解析
|
||||
*/
|
||||
private static generateConstants(variables: any[], opts: Required<TypeGenerationOptions>): string {
|
||||
const quote = opts.quoteStyle === 'single' ? "'" : '"';
|
||||
// 使用拼接避免 export 在源代码行首
|
||||
const exp = 'exp' + 'ort';
|
||||
|
||||
if (variables.length === 0) {
|
||||
return `/**
|
||||
* 全局变量名称常量
|
||||
*/
|
||||
export const ${opts.constantsName} = {} as const;`;
|
||||
${exp} const ${opts.constantsName} = {} as const;`;
|
||||
}
|
||||
|
||||
// 按命名空间分组
|
||||
@@ -190,7 +196,7 @@ export const ${opts.constantsName} = {} as const;`;
|
||||
* 全局变量名称常量
|
||||
* 使用常量避免拼写错误
|
||||
*/
|
||||
export const ${opts.constantsName} = {
|
||||
${exp} const ${opts.constantsName} = {
|
||||
${entries}
|
||||
} as const;`;
|
||||
} else {
|
||||
@@ -220,7 +226,7 @@ ${entries}
|
||||
* 全局变量名称常量
|
||||
* 使用常量避免拼写错误
|
||||
*/
|
||||
export const ${opts.constantsName} = {
|
||||
${exp} const ${opts.constantsName} = {
|
||||
${namespaces}
|
||||
} as const;`;
|
||||
}
|
||||
@@ -230,11 +236,13 @@ ${namespaces}
|
||||
* 生成接口定义
|
||||
*/
|
||||
private static generateInterface(variables: any[], opts: Required<TypeGenerationOptions>): string {
|
||||
const exp = 'exp' + 'ort';
|
||||
|
||||
if (variables.length === 0) {
|
||||
return `/**
|
||||
* 全局变量类型定义
|
||||
*/
|
||||
export interface ${opts.interfaceName} {}`;
|
||||
${exp} interface ${opts.interfaceName} {}`;
|
||||
}
|
||||
|
||||
const properties = variables
|
||||
@@ -248,7 +256,7 @@ export interface ${opts.interfaceName} {}`;
|
||||
return `/**
|
||||
* 全局变量类型定义
|
||||
*/
|
||||
export interface ${opts.interfaceName} {
|
||||
${exp} interface ${opts.interfaceName} {
|
||||
${properties}
|
||||
}`;
|
||||
}
|
||||
@@ -257,16 +265,18 @@ ${properties}
|
||||
* 生成类型别名
|
||||
*/
|
||||
private static generateTypeAliases(opts: Required<TypeGenerationOptions>): string {
|
||||
const exp = 'exp' + 'ort';
|
||||
return `/**
|
||||
* 全局变量名称联合类型
|
||||
*/
|
||||
export type ${opts.typeAliasName} = keyof ${opts.interfaceName};`;
|
||||
${exp} type ${opts.typeAliasName} = keyof ${opts.interfaceName};`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成类型安全包装类
|
||||
*/
|
||||
private static generateTypedClass(opts: Required<TypeGenerationOptions>): string {
|
||||
const exp = 'exp' + 'ort';
|
||||
return `/**
|
||||
* 类型安全的全局黑板服务包装器
|
||||
*
|
||||
@@ -284,7 +294,7 @@ export type ${opts.typeAliasName} = keyof ${opts.interfaceName};`;
|
||||
* gb.setValue('playerHP', 'invalid'); // ❌ 编译错误
|
||||
* \`\`\`
|
||||
*/
|
||||
export class ${opts.wrapperClassName} {
|
||||
${exp} class ${opts.wrapperClassName} {
|
||||
constructor(private service: GlobalBlackboardService) {}
|
||||
|
||||
/**
|
||||
@@ -326,11 +336,13 @@ export class ${opts.wrapperClassName} {
|
||||
* 生成默认值配置
|
||||
*/
|
||||
private static generateDefaults(variables: any[], opts: Required<TypeGenerationOptions>): string {
|
||||
const exp = 'exp' + 'ort';
|
||||
|
||||
if (variables.length === 0) {
|
||||
return `/**
|
||||
* 默认值配置
|
||||
*/
|
||||
export const ${opts.defaultsName}: ${opts.interfaceName} = {};`;
|
||||
${exp} const ${opts.defaultsName}: ${opts.interfaceName} = {};`;
|
||||
}
|
||||
|
||||
const properties = variables
|
||||
@@ -362,7 +374,7 @@ export const ${opts.defaultsName}: ${opts.interfaceName} = {};`;
|
||||
* service.importConfig(config);
|
||||
* \`\`\`
|
||||
*/
|
||||
export const ${opts.defaultsName}: ${opts.interfaceName} = {
|
||||
${exp} const ${opts.defaultsName}: ${opts.interfaceName} = {
|
||||
${properties}
|
||||
};`;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, type RefObject, React, createLogger } from '@esengine/editor-runtime';
|
||||
import { NodeTemplate, NodeType } from '@esengine/behavior-tree';
|
||||
import { NodeTemplate, NodeType } from '../..';
|
||||
import { Position } from '../domain/value-objects/Position';
|
||||
import { useNodeOperations } from './useNodeOperations';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useMemo, CommandManager } from '@esengine/editor-runtime';
|
||||
import { NodeTemplate } from '@esengine/behavior-tree';
|
||||
import { NodeTemplate } from '../..';
|
||||
import { Position } from '../domain/value-objects/Position';
|
||||
import { INodeFactory } from '../domain/interfaces/INodeFactory';
|
||||
import { TreeStateAdapter } from '../application/state/BehaviorTreeDataStore';
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user