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:
YHH
2025-11-27 20:42:46 +08:00
committed by GitHub
parent 71869b1a58
commit 107439d70c
367 changed files with 10661 additions and 12473 deletions

View File

@@ -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"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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\//
]
}
];

View File

@@ -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!');

View File

@@ -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 处理
}
}

View File

@@ -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 {
// 先注册 FileSystemServiceBehaviorTreeService 依赖它)
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] : [];
}
}

View File

@@ -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';

View File

@@ -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" }
]
}

View File

@@ -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"

View 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"
}

View File

@@ -1,4 +1,4 @@
import { BehaviorTreeData, BehaviorNodeData } from './Runtime/BehaviorTreeData';
import { BehaviorTreeData, BehaviorNodeData } from './execution/BehaviorTreeData';
import { NodeType } from './Types/TaskStatus';
/**

View File

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

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

View File

@@ -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';
/**
* 行为树启动辅助类

View File

@@ -1,4 +1,4 @@
import { BehaviorTreeData, BehaviorNodeData } from '../Runtime/BehaviorTreeData';
import { BehaviorTreeData, BehaviorNodeData } from '../execution/BehaviorTreeData';
import { NodeType, AbortType } from '../Types/TaskStatus';
/**

View File

@@ -1,5 +1,5 @@
import { NodeType } from '../Types/TaskStatus';
import { NodeMetadataRegistry, ConfigFieldDefinition, NodeMetadata } from '../Runtime/NodeMetadata';
import { NodeMetadataRegistry, ConfigFieldDefinition, NodeMetadata } from '../execution/NodeMetadata';
/**
* 节点数据JSON格式

View 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;

View File

@@ -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';
/**

View File

@@ -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';
/**

View File

@@ -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';
/**

View File

@@ -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';
/**

View File

@@ -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';
/**

View File

@@ -1,4 +1,4 @@
import { BaseCommand } from '@esengine/editor-core';
import { BaseCommand } from '@esengine/editor-runtime';
import { ITreeState } from '../ITreeState';
/**

View File

@@ -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');

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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;

View File

@@ -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';

View File

@@ -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;

View File

@@ -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';

View File

@@ -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,

View File

@@ -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';

View File

@@ -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';

View File

@@ -1,4 +1,4 @@
import { NodeTemplate } from '@esengine/behavior-tree';
import { NodeTemplate } from '../../..';
import { Node } from '../models/Node';
import { Position } from '../value-objects';

View File

@@ -1,4 +1,4 @@
import { NodeTemplate } from '@esengine/behavior-tree';
import { NodeTemplate } from '../../..';
import { Position, NodeType } from '../value-objects';
import { ValidationError } from '../errors';

View File

@@ -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}
};`;
}

View File

@@ -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';

View File

@@ -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