diff --git a/extensions/cocos/cocos-ecs/assets/behavior_tree.json.bt.json b/extensions/cocos/cocos-ecs/assets/behavior_tree.json.bt.json deleted file mode 100644 index a5ba8f67..00000000 --- a/extensions/cocos/cocos-ecs/assets/behavior_tree.json.bt.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "nodes": [ - { - "id": "node_b0bpuk8ei", - "type": "Sequence", - "name": "序列器", - "icon": "→", - "description": "按顺序执行子节点,任一失败则整体失败", - "x": 207.39999389648438, - "y": 145.59999084472656, - "children": [ - "node_pgmfxi7ho" - ], - "properties": { - "abortType": { - "name": "中止类型", - "type": "select", - "value": "None", - "description": "决定节点在何种情况下会被中止", - "options": [ - "None", - "LowerPriority", - "Self", - "Both" - ], - "required": false - } - }, - "canHaveChildren": true, - "canHaveParent": true, - "hasError": false - }, - { - "id": "node_pgmfxi7ho", - "type": "Inverter", - "name": "反转器", - "icon": "⚡", - "description": "反转子节点的执行结果", - "x": 163.39999389648438, - "y": 436.59999084472656, - "children": [], - "properties": {}, - "canHaveChildren": true, - "canHaveParent": true, - "hasError": false, - "parent": "node_b0bpuk8ei" - } - ], - "connections": [ - { - "id": "node_b0bpuk8ei-node_pgmfxi7ho", - "sourceId": "node_b0bpuk8ei", - "targetId": "node_pgmfxi7ho", - "path": "M 307.3999938964844 265.59999084472656 C 307.3999938964844 351.09999084472656 263.3999938964844 351.09999084472656 263.3999938964844 436.59999084472656", - "active": false - } - ], - "metadata": { - "name": "untitled", - "created": "2025-06-17T14:52:33.885Z", - "version": "1.0" - } -} \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/assets/behavior_tree.json.bt.json.meta b/extensions/cocos/cocos-ecs/assets/behavior_tree.json.bt.json.meta deleted file mode 100644 index 419b443e..00000000 --- a/extensions/cocos/cocos-ecs/assets/behavior_tree.json.bt.json.meta +++ /dev/null @@ -1,11 +0,0 @@ -{ - "ver": "2.0.1", - "importer": "json", - "imported": true, - "uuid": "cb66452d-5cad-46a9-96f9-b62831e0edc3", - "files": [ - ".json" - ], - "subMetas": {}, - "userData": {} -} diff --git a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/i18n/en.js b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/i18n/en.js index 7b93061e..de152f1f 100644 --- a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/i18n/en.js +++ b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/i18n/en.js @@ -1 +1,15 @@ -"use strict";module.exports={open_panel:"Default Panel",send_to_panel:"Send message to Default Panel",description:"Professional ECS Framework Development Assistant: One-click install @esengine/ecs-framework, intelligent code generator for components and systems, project template generation, real-time status monitoring and version management. Features welcome panel, debug panel and code generator to make ECS development in Cocos Creator more efficient and convenient."}; \ No newline at end of file +"use strict"; + +module.exports = { + description: "Professional ECS Framework Development Assistant: One-click installation of @esengine/ecs-framework, intelligent code generator for quick creation of components and systems, project template generation, real-time status detection and version management. Provides welcome panel, debug panel, code generator and behavior tree AI component library to make ECS development in Cocos Creator more efficient and convenient.", + + open_panel: "Default Panel", + send_to_panel: "Send message to panel", + + menu: { + panel: "Panel", + develop: "Develop", + create: "Create", + open: "Open" + } +}; \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/i18n/zh.js b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/i18n/zh.js index 77aefbef..35ead1f5 100644 --- a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/i18n/zh.js +++ b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/i18n/zh.js @@ -10,6 +10,9 @@ module.exports = { // 菜单相关 menu: { - panel: "面板" + panel: "面板", + develop: "开发", + create: "创建", + open: "打开" } }; \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/package.json b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/package.json index 44500c0e..8e6d7679 100644 --- a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/package.json +++ b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/package.json @@ -100,20 +100,12 @@ "message": "open-panel" } ], - "asset-menu": [ - { - "path": "i18n:menu.create/ECS Framework", - "label": "创建行为树文件", - "message": "create-behavior-tree-file", - "target": "folder" - }, - { - "path": "i18n:menu.open", - "label": "用行为树编辑器打开", - "message": "open-behavior-tree-file", - "target": [".bt.json", ".json"] + "assets": { + "menu": { + "methods": "./dist/assets-menu.js", + "assetMenu": "onAssetMenu" } - ], + }, "messages": { "open-panel": { "methods": [ @@ -195,15 +187,25 @@ "create-behavior-tree-file" ] }, - "open-behavior-tree-file": { + "load-behavior-tree-file": { "methods": [ - "open-behavior-tree-file" + "load-behavior-tree-file" ] }, "create-behavior-tree-from-editor": { "methods": [ "create-behavior-tree-from-editor" ] + }, + "overwrite-behavior-tree-file": { + "methods": [ + "overwrite-behavior-tree-file" + ] + }, + "behavior-tree-panel-load-file": { + "methods": [ + "behavior-tree.loadBehaviorTreeFile" + ] } } } diff --git a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/assets-menu.ts b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/assets-menu.ts new file mode 100644 index 00000000..bbbe2517 --- /dev/null +++ b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/assets-menu.ts @@ -0,0 +1,46 @@ +export function onAssetMenu(assetInfo: any) { + console.log('[AssetMenu] onAssetMenu 被调用,资源信息:', assetInfo); + console.log('[AssetMenu] assetInfo 完整结构:', JSON.stringify(assetInfo, null, 2)); + + const menuItems = []; + + // 检查是否为行为树文件 + const isTargetFile = (assetInfo && assetInfo.name && assetInfo.name.endsWith('.bt.json')) || + (assetInfo && assetInfo.file && assetInfo.file.endsWith('.bt.json')); + + if (isTargetFile) { + console.log('[AssetMenu] 发现 .bt.json 文件,添加菜单项'); + menuItems.push({ + label: '用行为树编辑器打开', + click() { + console.log('[AssetMenu] 菜单项被点击,文件信息:', assetInfo); + + // 直接调用主进程的方法,不需要复杂的序列化 + try { + Editor.Message.send('cocos-ecs-extension', 'load-behavior-tree-file', assetInfo); + console.log('[AssetMenu] 消息发送成功'); + } catch (error) { + console.error('[AssetMenu] 消息发送失败:', error); + } + } + }); + } + + // 在目录中添加创建选项 + if (assetInfo && assetInfo.isDirectory) { + menuItems.push({ + label: '创建行为树文件', + click() { + console.log('[AssetMenu] 在目录中创建行为树文件:', assetInfo); + try { + Editor.Message.send('cocos-ecs-extension', 'create-behavior-tree-file'); + } catch (error) { + console.error('[AssetMenu] 创建消息发送失败:', error); + } + } + }); + } + + console.log('[AssetMenu] 返回菜单项数量:', menuItems.length); + return menuItems; +} \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/handlers/BehaviorTreeHandler.ts b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/handlers/BehaviorTreeHandler.ts new file mode 100644 index 00000000..aeb40d8e --- /dev/null +++ b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/handlers/BehaviorTreeHandler.ts @@ -0,0 +1,369 @@ +import { exec } from 'child_process'; +import * as path from 'path'; +import * as fs from 'fs'; +import * as fsExtra from 'fs-extra'; + +/** + * 行为树相关的处理器 + */ +export class BehaviorTreeHandler { + /** + * 安装行为树AI系统 + */ + static async install(): Promise { + const projectPath = Editor.Project.path; + const command = 'npm install @esengine/ai'; + + console.log(`Installing Behavior Tree AI to project: ${projectPath}`); + + return new Promise((resolve) => { + exec(command, { cwd: projectPath }, (error, stdout, stderr) => { + console.log('Install stdout:', stdout); + if (stderr) console.log('Install stderr:', stderr); + + if (error) { + console.error('Behavior Tree AI installation failed:', error); + resolve(false); + } else { + console.log('Behavior Tree AI installation completed successfully'); + + // 验证安装是否成功 + const nodeModulesPath = path.join(projectPath, 'node_modules', '@esengine', 'ai'); + const installSuccess = fs.existsSync(nodeModulesPath); + + if (installSuccess) { + console.log('Behavior Tree AI installed successfully'); + resolve(true); + } else { + console.warn('Behavior Tree AI directory not found after install'); + resolve(false); + } + } + }); + }); + } + + /** + * 更新行为树AI系统 + */ + static async update(): Promise { + const projectPath = Editor.Project.path; + const command = 'npm update @esengine/ai'; + + console.log(`Updating Behavior Tree AI in project: ${projectPath}`); + + return new Promise((resolve) => { + exec(command, { cwd: projectPath }, (error, stdout, stderr) => { + console.log('Update stdout:', stdout); + if (stderr) console.log('Update stderr:', stderr); + + if (error) { + console.error('Behavior Tree AI update failed:', error); + resolve(false); + } else { + console.log('Behavior Tree AI update completed successfully'); + + // 验证更新是否成功 + const nodeModulesPath = path.join(projectPath, 'node_modules', '@esengine', 'ai'); + const updateSuccess = fs.existsSync(nodeModulesPath); + + if (updateSuccess) { + console.log('Behavior Tree AI updated successfully'); + resolve(true); + } else { + console.warn('Behavior Tree AI directory not found after update'); + resolve(false); + } + } + }); + }); + } + + /** + * 检查行为树AI是否已安装 + */ + static checkInstalled(): boolean { + try { + const projectPath = Editor.Project.path; + const packageJsonPath = path.join(projectPath, 'package.json'); + + if (!fs.existsSync(packageJsonPath)) { + return false; + } + + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); + const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies }; + + return '@esengine/ai' in dependencies; + } catch (error) { + console.error('Error checking Behavior Tree AI installation:', error); + return false; + } + } + + /** + * 打开行为树文档 + */ + static openDocumentation(): void { + const url = 'https://github.com/esengine/ai/blob/master/README.md'; + + try { + const { shell } = require('electron'); + shell.openExternal(url); + console.log('Behavior Tree documentation opened successfully'); + } catch (error) { + console.error('Failed to open Behavior Tree documentation:', error); + Editor.Dialog.info('打开行为树文档', { + detail: `请手动访问以下链接查看文档:\n\n${url}`, + }); + } + } + + /** + * 创建行为树文件 + */ + static async createFile(assetInfo?: any): Promise { + try { + const projectPath = Editor.Project.path; + const assetsPath = path.join(projectPath, 'assets'); + + // 生成唯一文件名 + let fileName = 'NewBehaviorTree'; + let counter = 1; + let filePath = path.join(assetsPath, `${fileName}.bt.json`); + + while (fs.existsSync(filePath)) { + fileName = `NewBehaviorTree_${counter}`; + filePath = path.join(assetsPath, `${fileName}.bt.json`); + counter++; + } + + // 创建默认的行为树配置 + const defaultConfig = { + version: "1.0.0", + type: "behavior-tree", + metadata: { + createdAt: new Date().toISOString(), + nodeCount: 1 + }, + tree: { + id: "root", + type: "sequence", + namespace: "behaviourTree/composites", + properties: {}, + children: [] + } + }; + + // 写入文件 + await fsExtra.writeFile(filePath, JSON.stringify(defaultConfig, null, 2)); + + // 刷新资源管理器 - 使用正确的资源路径 + const relativeAssetPath = path.relative(projectPath, filePath).replace(/\\/g, '/'); + const dbAssetPath = 'db://' + relativeAssetPath; + await Editor.Message.request('asset-db', 'refresh-asset', dbAssetPath); + + console.log(`Behavior tree file created: ${filePath}`); + + Editor.Dialog.info('创建成功', { + detail: `行为树文件 "${fileName}.bt.json" 已创建完成!\n\n文件位置:assets/${fileName}.bt.json\n\n您可以右键点击文件选择"用行为树编辑器打开"来编辑它。`, + }); + + } catch (error) { + console.error('Failed to create behavior tree file:', error); + Editor.Dialog.error('创建失败', { + detail: `创建行为树文件失败:\n\n${error instanceof Error ? error.message : String(error)}`, + }); + } + } + + /** + * 打开行为树文件 + */ + static async openFile(assetInfo: any): Promise { + try { + if (!assetInfo || !assetInfo.file) { + throw new Error('无效的文件信息'); + } + + const filePath = assetInfo.file; + const fileData = await this.loadFileData(filePath); + await this.openPanel(); + await this.sendDataToPanel(fileData); + + } catch (error) { + Editor.Dialog.error('打开失败', { + detail: `打开行为树文件失败:\n\n${error instanceof Error ? error.message : String(error)}` + }); + } + } + + /** + * 读取并解析文件数据 + */ + private static async loadFileData(filePath: string): Promise { + try { + let assetPath = filePath; + + if (path.isAbsolute(filePath)) { + const projectPath = Editor.Project.path; + if (filePath.startsWith(projectPath)) { + assetPath = path.relative(projectPath, filePath); + assetPath = assetPath.replace(/\\/g, '/'); + } + } + + if (!assetPath.startsWith('db://')) { + assetPath = 'db://' + assetPath; + } + + try { + const assetInfo = await Editor.Message.request('asset-db', 'query-asset-info', assetPath); + + if (assetInfo && assetInfo.source) { + const content = await fsExtra.readFile(assetInfo.source, 'utf8'); + let fileContent: any; + + try { + fileContent = JSON.parse(content); + } catch (parseError) { + fileContent = { + version: "1.0.0", + type: "behavior-tree", + rawContent: content + }; + } + + const fileData = { + ...fileContent, + _fileInfo: { + fileName: path.basename(assetInfo.source, path.extname(assetInfo.source)), + filePath: assetInfo.source, + assetPath: assetPath + } + }; + + return fileData; + } + } catch (assetError) { + // 资源系统读取失败,尝试直接文件读取 + } + + const actualFilePath = path.isAbsolute(filePath) ? filePath : path.join(Editor.Project.path, filePath); + + if (!fs.existsSync(actualFilePath)) { + throw new Error(`文件不存在: ${actualFilePath}`); + } + + const content = await fsExtra.readFile(actualFilePath, 'utf8'); + let fileContent: any; + + try { + fileContent = JSON.parse(content); + } catch (parseError) { + fileContent = { + version: "1.0.0", + type: "behavior-tree", + rawContent: content + }; + } + + const fileData = { + ...fileContent, + _fileInfo: { + fileName: path.basename(actualFilePath, path.extname(actualFilePath)), + filePath: actualFilePath + } + }; + + return fileData; + + } catch (error) { + throw new Error(`文件读取失败: ${error instanceof Error ? error.message : String(error)}`); + } + } + + /** + * 打开行为树面板 + */ + private static async openPanel(): Promise { + await Editor.Panel.open('cocos-ecs-extension.behavior-tree'); + await new Promise(resolve => setTimeout(resolve, 300)); + } + + /** + * 发送数据到面板 + */ + private static async sendDataToPanel(fileData: any): Promise { + try { + const result = await Editor.Message.request('cocos-ecs-extension.behavior-tree', 'loadBehaviorTreeFile', fileData); + } catch (error) { + setTimeout(() => { + try { + Editor.Message.send('cocos-ecs-extension.behavior-tree', 'loadBehaviorTreeFile', fileData); + } catch (delayError) { + // 静默失败 + } + }, 100); + } + } + + /** + * 从编辑器创建行为树文件 + */ + static async createFromEditor(data: { fileName: string, content: string }): Promise { + try { + const projectPath = Editor.Project.path; + const assetsPath = path.join(projectPath, 'assets'); + + let fileName = data.fileName; + let counter = 1; + let filePath = path.join(assetsPath, `${fileName}.bt.json`); + + while (fs.existsSync(filePath)) { + fileName = `${data.fileName}_${counter}`; + filePath = path.join(assetsPath, `${fileName}.bt.json`); + counter++; + } + + await fsExtra.writeFile(filePath, data.content); + + const relativeAssetPath = path.relative(projectPath, filePath).replace(/\\/g, '/'); + const dbAssetPath = 'db://' + relativeAssetPath; + await Editor.Message.request('asset-db', 'refresh-asset', dbAssetPath); + + Editor.Dialog.info('保存成功', { + detail: `行为树文件 "${fileName}.bt.json" 已保存到 assets 目录中!`, + }); + + } catch (error) { + Editor.Dialog.error('保存失败', { + detail: `保存行为树文件失败:\n\n${error instanceof Error ? error.message : String(error)}`, + }); + } + } + + /** + * 覆盖现有行为树文件 + */ + static async overwriteFile(data: { filePath: string, content: string }): Promise { + try { + await fsExtra.writeFile(data.filePath, data.content); + + const projectPath = Editor.Project.path; + const relativeAssetPath = path.relative(projectPath, data.filePath).replace(/\\/g, '/'); + const dbAssetPath = 'db://' + relativeAssetPath; + await Editor.Message.request('asset-db', 'refresh-asset', dbAssetPath); + + const fileName = path.basename(data.filePath, path.extname(data.filePath)); + Editor.Dialog.info('覆盖成功', { + detail: `行为树文件 "${fileName}.bt.json" 已更新!`, + }); + + } catch (error) { + Editor.Dialog.error('覆盖失败', { + detail: `覆盖行为树文件失败:\n\n${error instanceof Error ? error.message : String(error)}`, + }); + } + } +} \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/handlers/EcsFrameworkHandler.ts b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/handlers/EcsFrameworkHandler.ts new file mode 100644 index 00000000..b7b313fc --- /dev/null +++ b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/handlers/EcsFrameworkHandler.ts @@ -0,0 +1,234 @@ +import { exec } from 'child_process'; +import * as path from 'path'; +import * as fs from 'fs'; +import { TemplateGenerator } from '../TemplateGenerator'; + +/** + * ECS框架相关的处理器 + */ +export class EcsFrameworkHandler { + /** + * 安装ECS Framework + */ + static async install(): Promise { + const projectPath = Editor.Project.path; + const command = 'npm install @esengine/ecs-framework'; + + console.log(`Installing ECS Framework to project: ${projectPath}`); + + return new Promise((resolve, reject) => { + exec(command, { cwd: projectPath }, (error, stdout, stderr) => { + console.log('Install stdout:', stdout); + if (stderr) console.log('Install stderr:', stderr); + + if (error) { + console.error('Installation failed:', error); + reject(error); + } else { + console.log('Installation completed successfully'); + + // 验证安装是否成功 + const nodeModulesPath = path.join(projectPath, 'node_modules', '@esengine', 'ecs-framework'); + const installSuccess = fs.existsSync(nodeModulesPath); + + if (installSuccess) { + console.log('ECS Framework installed successfully'); + resolve(); + } else { + console.warn('ECS Framework directory not found after install'); + reject(new Error('安装验证失败')); + } + } + }); + }); + } + + /** + * 更新ECS Framework + */ + static async update(targetVersion?: string): Promise { + const projectPath = Editor.Project.path; + const version = targetVersion ? `@${targetVersion}` : '@latest'; + const command = `npm install @esengine/ecs-framework${version}`; + + console.log(`Updating ECS Framework to ${version} in project: ${projectPath}`); + + return new Promise((resolve, reject) => { + exec(command, { cwd: projectPath }, (error, stdout, stderr) => { + console.log('Update stdout:', stdout); + if (stderr) console.log('Update stderr:', stderr); + + if (error) { + console.error('Update failed:', error); + reject(error); + } else { + console.log('Update completed successfully'); + + // 验证更新是否成功 + const nodeModulesPath = path.join(projectPath, 'node_modules', '@esengine', 'ecs-framework'); + const updateSuccess = fs.existsSync(nodeModulesPath); + + if (updateSuccess) { + console.log(`ECS Framework updated successfully to ${version}`); + resolve(); + } else { + console.warn('ECS Framework directory not found after update'); + reject(new Error('更新验证失败')); + } + } + }); + }); + } + + /** + * 卸载ECS Framework + */ + static async uninstall(): Promise { + const projectPath = Editor.Project.path; + const command = 'npm uninstall @esengine/ecs-framework'; + + console.log(`Uninstalling ECS Framework from project: ${projectPath}`); + + return new Promise((resolve, reject) => { + exec(command, { cwd: projectPath }, (error, stdout, stderr) => { + console.log('Uninstall stdout:', stdout); + if (stderr) console.log('Uninstall stderr:', stderr); + + if (error) { + console.error('Uninstall failed:', error); + reject(error); + } else { + console.log('Uninstall completed successfully'); + + // 检查是否真的卸载了 + const nodeModulesPath = path.join(projectPath, 'node_modules', '@esengine', 'ecs-framework'); + const stillExists = fs.existsSync(nodeModulesPath); + + if (stillExists) { + console.warn('ECS Framework directory still exists after uninstall'); + reject(new Error('卸载验证失败')); + } else { + console.log('ECS Framework uninstalled successfully'); + resolve(); + } + } + }); + }); + } + + /** + * 打开文档 + */ + static openDocumentation(): void { + const url = 'https://github.com/esengine/ecs-framework/blob/master/README.md'; + + try { + // 使用Electron的shell模块打开外部链接 + const { shell } = require('electron'); + shell.openExternal(url); + console.log('Documentation link opened successfully'); + } catch (error) { + console.error('Failed to open documentation:', error); + Editor.Dialog.info('打开文档', { + detail: `请手动访问以下链接查看文档:\n\n${url}`, + }); + } + } + + /** + * 创建ECS模板 + */ + static createTemplate(): void { + const projectPath = Editor.Project.path; + console.log(`Creating ECS template in project: ${projectPath}`); + + try { + const templateGenerator = new TemplateGenerator(projectPath); + + // 检查是否已存在模板 + if (templateGenerator.checkTemplateExists()) { + const existingFiles = templateGenerator.getExistingFiles(); + const fileList = existingFiles.length > 0 ? existingFiles.join('\n• ') : '未检测到具体文件'; + + Editor.Dialog.warn('模板已存在', { + detail: `检测到已存在ECS模板,包含以下文件:\n\n• ${fileList}\n\n是否要覆盖现有模板?`, + buttons: ['覆盖', '取消'], + }).then((result: any) => { + if (result.response === 0) { + // 用户选择覆盖 + console.log('User chose to overwrite existing template'); + templateGenerator.removeExistingTemplate(); + templateGenerator.createTemplate(); + this.showTemplateCreatedDialog(); + } else { + console.log('User cancelled template creation'); + } + }); + return; + } + + // 创建新模板 + templateGenerator.createTemplate(); + console.log('ECS template created successfully'); + this.showTemplateCreatedDialog(); + + } catch (error) { + console.error('Failed to create ECS template:', error); + const errorMessage = error instanceof Error ? error.message : String(error); + Editor.Dialog.error('模板创建失败', { + detail: `创建ECS模板时发生错误:\n\n${errorMessage}\n\n请检查项目权限和目录结构。`, + }); + } + } + + /** + * 显示模板创建成功的对话框 + */ + private static showTemplateCreatedDialog(): void { + Editor.Dialog.info('模板创建成功', { + detail: '✅ ECS项目模板已创建完成!\n\n已为您的Cocos Creator项目生成了完整的ECS架构模板,包括:\n\n' + + '• 位置、速度、Cocos节点组件\n' + + '• 移动系统和节点同步系统\n' + + '• 实体工厂和场景管理器\n' + + '• ECS管理器组件(可直接添加到节点)\n' + + '• 完整的使用文档\n\n' + + '请刷新资源管理器查看新创建的文件。', + }); + } + + /** + * 打开GitHub仓库 + */ + static openGitHub(): void { + const url = 'https://github.com/esengine/ecs-framework'; + + try { + const { shell } = require('electron'); + shell.openExternal(url); + console.log('GitHub repository opened successfully'); + } catch (error) { + console.error('Failed to open GitHub repository:', error); + Editor.Dialog.info('打开GitHub', { + detail: `请手动访问以下链接:\n\n${url}`, + }); + } + } + + /** + * 打开QQ群 + */ + static openQQGroup(): void { + const url = 'https://qm.qq.com/cgi-bin/qm/qr?k=your-qq-group-key'; + + try { + const { shell } = require('electron'); + shell.openExternal(url); + console.log('QQ group opened successfully'); + } catch (error) { + console.error('Failed to open QQ group:', error); + Editor.Dialog.info('QQ群', { + detail: '请手动搜索QQ群号或访问相关链接加入讨论群。', + }); + } + } +} \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/handlers/PanelHandler.ts b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/handlers/PanelHandler.ts new file mode 100644 index 00000000..9bb78015 --- /dev/null +++ b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/handlers/PanelHandler.ts @@ -0,0 +1,64 @@ +/** + * 面板管理相关的处理器 + */ +export class PanelHandler { + /** + * 打开默认面板 + */ + static openDefaultPanel(): void { + try { + Editor.Panel.open('cocos-ecs-extension'); + console.log('Default panel opened successfully'); + } catch (error) { + console.error('Failed to open default panel:', error); + Editor.Dialog.error('打开面板失败', { + detail: `无法打开面板:\n\n${error}\n\n请尝试重启Cocos Creator编辑器。`, + }); + } + } + + /** + * 打开调试面板 + */ + static openDebugPanel(): void { + try { + Editor.Panel.open('cocos-ecs-extension.debug'); + console.log('Debug panel opened successfully'); + } catch (error) { + console.error('Failed to open debug panel:', error); + Editor.Dialog.error('打开调试面板失败', { + detail: `无法打开调试面板:\n\n${error}\n\n请尝试重启Cocos Creator编辑器。`, + }); + } + } + + /** + * 打开代码生成器面板 + */ + static openGeneratorPanel(): void { + try { + Editor.Panel.open('cocos-ecs-extension.generator'); + console.log('Generator panel opened successfully'); + } catch (error) { + console.error('Failed to open generator panel:', error); + Editor.Dialog.error('打开代码生成器失败', { + detail: `无法打开代码生成器面板:\n\n${error}\n\n请尝试重启Cocos Creator编辑器。`, + }); + } + } + + /** + * 打开行为树面板 + */ + static openBehaviorTreePanel(): void { + try { + Editor.Panel.open('cocos-ecs-extension.behavior-tree'); + console.log('Behavior Tree panel opened successfully'); + } catch (error) { + console.error('Failed to open behavior tree panel:', error); + Editor.Dialog.error('打开行为树面板失败', { + detail: `无法打开行为树AI组件库面板:\n\n${error}\n\n请尝试重启Cocos Creator编辑器。`, + }); + } + } +} \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/handlers/index.ts b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/handlers/index.ts new file mode 100644 index 00000000..35c1a57e --- /dev/null +++ b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/handlers/index.ts @@ -0,0 +1,3 @@ +export { EcsFrameworkHandler } from './EcsFrameworkHandler'; +export { BehaviorTreeHandler } from './BehaviorTreeHandler'; +export { PanelHandler } from './PanelHandler'; \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/main.ts b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/main.ts index b23c29d7..1a0fae07 100644 --- a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/main.ts +++ b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/main.ts @@ -1,655 +1,149 @@ // @ts-ignore import packageJSON from '../package.json'; -import { exec, spawn } from 'child_process'; +import { EcsFrameworkHandler, BehaviorTreeHandler, PanelHandler } from './handlers'; +import { readJSON } from 'fs-extra'; import * as path from 'path'; -import * as fs from 'fs'; -import * as fsExtra from 'fs-extra'; -import { readFileSync, outputFile } from 'fs-extra'; -import { join } from 'path'; -import { TemplateGenerator } from './TemplateGenerator'; -import { CodeGenerator } from './CodeGenerator'; +import { AssetInfo } from '@cocos/creator-types/editor/packages/asset-db/@types/public'; /** * @en Registration method for the main process of Extension * @zh 为扩展的主进程的注册方法 */ export const methods: { [key: string]: (...any: any) => any } = { + // ================ 面板管理 ================ /** - * @en A method that can be triggered by message - * @zh 通过 message 触发的方法 + * 打开默认面板 */ openPanel() { - Editor.Panel.open(packageJSON.name); - }, - - /** - * 安装ECS Framework - */ - 'install-ecs-framework'() { - const projectPath = Editor.Project.path; - const command = 'npm install @esengine/ecs-framework'; - - console.log(`Installing ECS Framework to project: ${projectPath}`); - console.log(`Command: ${command}`); - - exec(command, { cwd: projectPath }, (error, stdout, stderr) => { - console.log('Install stdout:', stdout); - console.log('Install stderr:', stderr); - - if (error) { - console.error('Installation failed:', error); - } else { - console.log('Installation completed successfully'); - - // 验证安装是否成功 - const nodeModulesPath = path.join(projectPath, 'node_modules', '@esengine', 'ecs-framework'); - const installSuccess = require('fs').existsSync(nodeModulesPath); - - if (installSuccess) { - console.log('ECS Framework installed successfully'); - } else { - console.warn('ECS Framework directory not found after install'); - } - } - }); - }, - - /** - * 更新ECS Framework - */ - 'update-ecs-framework'(targetVersion?: string) { - const projectPath = Editor.Project.path; - const version = targetVersion ? `@${targetVersion}` : '@latest'; - const command = `npm install @esengine/ecs-framework${version}`; - - console.log(`Updating ECS Framework to ${version} in project: ${projectPath}`); - console.log(`Command: ${command}`); - - exec(command, { cwd: projectPath }, (error, stdout, stderr) => { - console.log('Update stdout:', stdout); - console.log('Update stderr:', stderr); - - if (error) { - console.error('Update failed:', error); - } else { - console.log('Update completed successfully'); - - // 验证更新是否成功 - const nodeModulesPath = path.join(projectPath, 'node_modules', '@esengine', 'ecs-framework'); - const updateSuccess = require('fs').existsSync(nodeModulesPath); - - if (updateSuccess) { - console.log(`ECS Framework updated successfully to ${version}`); - } else { - console.warn('ECS Framework directory not found after update'); - } - } - }); - }, - - /** - * 卸载ECS Framework - */ - 'uninstall-ecs-framework'() { - const projectPath = Editor.Project.path; - const command = 'npm uninstall @esengine/ecs-framework'; - - console.log(`Uninstalling ECS Framework from project: ${projectPath}`); - console.log(`Command: ${command}`); - - exec(command, { cwd: projectPath }, (error, stdout, stderr) => { - console.log('Uninstall stdout:', stdout); - console.log('Uninstall stderr:', stderr); - - if (error) { - console.error('Uninstall failed:', error); - } else { - console.log('Uninstall completed successfully'); - - // 检查是否真的卸载了 - const nodeModulesPath = path.join(projectPath, 'node_modules', '@esengine', 'ecs-framework'); - const stillExists = require('fs').existsSync(nodeModulesPath); - - if (stillExists) { - console.warn('ECS Framework directory still exists after uninstall'); - } else { - console.log('ECS Framework uninstalled successfully'); - } - } - }); - }, - - /** - * 打开文档 - */ - 'open-documentation'() { - const url = 'https://github.com/esengine/ecs-framework/blob/master/README.md'; - - try { - // 使用Electron的shell模块打开外部链接(推荐方法) - const { shell } = require('electron'); - shell.openExternal(url); - console.log('Documentation link opened successfully'); - } catch (error) { - console.error('Failed to open documentation with shell.openExternal, trying exec:', error); - - // 备用方法:使用系统命令 - exec(`start "" "${url}"`, (execError) => { - if (execError) { - console.error('Failed to open documentation with exec:', execError); - Editor.Dialog.info('打开文档', { - detail: `请手动访问以下链接查看文档:\n\n${url}`, - }); - } else { - console.log('Documentation link opened successfully with exec'); - } - }); - } - }, - - /** - * 创建ECS模板 - */ - 'create-ecs-template'() { - const projectPath = Editor.Project.path; - console.log(`Creating ECS template in project: ${projectPath}`); - - try { - const templateGenerator = new TemplateGenerator(projectPath); - - // 检查是否已存在模板 - if (templateGenerator.checkTemplateExists()) { - const existingFiles = templateGenerator.getExistingFiles(); - const fileList = existingFiles.length > 0 ? existingFiles.join('\n• ') : '未检测到具体文件'; - - Editor.Dialog.warn('模板已存在', { - detail: `检测到已存在ECS模板,包含以下文件:\n\n• ${fileList}\n\n是否要覆盖现有模板?`, - buttons: ['覆盖', '取消'], - }).then((result: any) => { - if (result.response === 0) { - // 用户选择覆盖 - console.log('User chose to overwrite existing template'); - templateGenerator.removeExistingTemplate(); - templateGenerator.createTemplate(); - - Editor.Dialog.info('模板创建成功', { - detail: '✅ ECS项目模板已覆盖并重新创建完成!\n\n已为您的Cocos Creator项目生成了完整的ECS架构模板,包括:\n\n' + - '• 位置、速度、Cocos节点组件\n' + - '• 移动系统和节点同步系统\n' + - '• 实体工厂和场景管理器\n' + - '• ECS管理器组件(可直接添加到节点)\n' + - '• 完整的使用文档\n\n' + - '请刷新资源管理器查看新创建的文件。', - }); - } else { - console.log('User cancelled template creation'); - } - }); - return; - } - - // 创建新模板 - templateGenerator.createTemplate(); - - console.log('ECS template created successfully'); - - Editor.Dialog.info('模板创建成功', { - detail: '✅ ECS项目模板已创建完成!\n\n已为您的Cocos Creator项目生成了完整的ECS架构模板,包括:\n\n' + - '• 位置、速度、Cocos节点组件\n' + - '• 移动系统和节点同步系统\n' + - '• 实体工厂和场景管理器\n' + - '• ECS管理器组件(可直接添加到节点)\n' + - '• 完整的使用文档\n\n' + - '请刷新资源管理器查看新创建的文件。', - }); - - } catch (error) { - console.error('Failed to create ECS template:', error); - const errorMessage = error instanceof Error ? error.message : String(error); - Editor.Dialog.error('模板创建失败', { - detail: `创建ECS模板时发生错误:\n\n${errorMessage}\n\n请检查项目权限和目录结构。`, - }); - } - }, - - /** - * 打开GitHub仓库 - */ - 'open-github'() { - const url = 'https://github.com/esengine/ecs-framework'; - - try { - // 使用Electron的shell模块打开外部链接(推荐方法) - const { shell } = require('electron'); - shell.openExternal(url); - console.log('GitHub link opened successfully'); - } catch (error) { - console.error('Failed to open GitHub with shell.openExternal, trying exec:', error); - - // 备用方法:使用系统命令 - exec(`start "" "${url}"`, (execError) => { - if (execError) { - console.error('Failed to open GitHub with exec:', execError); - Editor.Dialog.info('打开GitHub', { - detail: `请手动访问以下链接:\n\n${url}`, - }); - } else { - console.log('GitHub link opened successfully with exec'); - } - }); - } - }, - - /** - * 打开QQ群 - */ - 'open-qq-group'() { - const url = 'https://qm.qq.com/cgi-bin/qm/qr?k=1DMoPJEsY5xUpTAcmjIHK8whgHJHYQTL&authKey=%2FklVb3S0Momc1q1J%2FWHncuwMVHGrDbwV1Y6gAfa5e%2FgHCvyYUL2gpA6hSOU%2BVSa5&noverify=0&group_code=481923584'; - - try { - // 使用Electron的shell模块打开外部链接(推荐方法) - const { shell } = require('electron'); - shell.openExternal(url); - console.log('QQ group link opened successfully'); - } catch (error) { - console.error('Failed to open QQ group with shell.openExternal, trying exec:', error); - - // 备用方法:使用系统命令 - exec(`start "" "${url}"`, (execError) => { - if (execError) { - console.error('Failed to open QQ group with exec:', execError); - Editor.Dialog.info('加入QQ群', { - detail: `请手动访问以下链接加入QQ群:\n\n${url}\n\n或手动搜索QQ群号:481923584`, - }); - } else { - console.log('QQ group link opened successfully with exec'); - } - }); - } + PanelHandler.openDefaultPanel(); }, /** * 打开调试面板 */ 'open-debug'() { - console.log('Opening ECS Framework debug panel...'); - try { - // 正确的打开特定面板的方法 - Editor.Panel.open(packageJSON.name + '.debug'); - console.log('Debug panel opened successfully'); - } catch (error) { - console.error('Failed to open debug panel:', error); - Editor.Dialog.error('打开调试面板失败', { - detail: `无法打开调试面板:\n\n${error}\n\n请尝试重启Cocos Creator编辑器。`, - }); - } + PanelHandler.openDebugPanel(); }, /** * 打开代码生成器面板 */ 'open-generator'() { - console.log('Opening ECS Framework code generator panel...'); - try { - // 正确的打开特定面板的方法 - Editor.Panel.open(packageJSON.name + '.generator'); - console.log('Generator panel opened successfully'); - } catch (error) { - console.error('Failed to open generator panel:', error); - Editor.Dialog.error('打开代码生成器失败', { - detail: `无法打开代码生成器面板:\n\n${error}\n\n请尝试重启Cocos Creator编辑器。`, - }); - } + PanelHandler.openGeneratorPanel(); }, /** - * 打开行为树AI组件库面板 + * 打开行为树面板 */ 'open-behavior-tree'() { - console.log('Opening Behavior Tree AI panel...'); - try { - Editor.Panel.open(packageJSON.name + '.behavior-tree'); - console.log('Behavior Tree panel opened successfully'); - } catch (error) { - console.error('Failed to open behavior tree panel:', error); - Editor.Dialog.error('打开行为树面板失败', { - detail: `无法打开行为树AI组件库面板:\n\n${error}\n\n请尝试重启Cocos Creator编辑器。`, - }); - } + PanelHandler.openBehaviorTreePanel(); }, + // ================ ECS框架管理 ================ + /** + * 安装ECS Framework + */ + 'install-ecs-framework'() { + EcsFrameworkHandler.install(); + }, + + /** + * 更新ECS Framework + */ + 'update-ecs-framework'() { + EcsFrameworkHandler.update(); + }, + + /** + * 卸载ECS Framework + */ + 'uninstall-ecs-framework'() { + EcsFrameworkHandler.uninstall(); + }, + + /** + * 打开文档 + */ + 'open-documentation'() { + EcsFrameworkHandler.openDocumentation(); + }, + + /** + * 创建ECS模板 + */ + 'create-ecs-template'() { + EcsFrameworkHandler.createTemplate(); + }, + + /** + * 打开GitHub仓库 + */ + 'open-github'() { + EcsFrameworkHandler.openGitHub(); + }, + + /** + * 打开QQ群 + */ + 'open-qq-group'() { + EcsFrameworkHandler.openQQGroup(); + }, + + // ================ 行为树管理 ================ /** * 安装行为树AI系统 */ - async 'install-behavior-tree'() { - console.log('Installing Behavior Tree AI system...'); - const projectPath = Editor.Project.path; - - try { - // 检查项目路径是否有效 - if (!projectPath || !fs.existsSync(projectPath)) { - throw new Error('无效的项目路径'); - } - - const packageJsonPath = path.join(projectPath, 'package.json'); - - // 检查package.json是否存在 - if (!fs.existsSync(packageJsonPath)) { - throw new Error('项目根目录未找到package.json文件'); - } - - console.log('Installing @esengine/ai package...'); - - // 执行npm安装 - await new Promise((resolve, reject) => { - const cmd = process.platform === 'win32' ? 'npm.cmd' : 'npm'; - const npmProcess = spawn(cmd, ['install', '@esengine/ai'], { - cwd: projectPath, - stdio: 'pipe', - shell: true - }); - - let stdout = ''; - let stderr = ''; - - npmProcess.stdout?.on('data', (data) => { - stdout += data.toString(); - }); - - npmProcess.stderr?.on('data', (data) => { - stderr += data.toString(); - }); - - npmProcess.on('close', (code) => { - if (code === 0) { - console.log('NPM install completed successfully'); - console.log('STDOUT:', stdout); - resolve(); - } else { - console.error('NPM install failed with code:', code); - console.error('STDERR:', stderr); - reject(new Error(`NPM安装失败 (退出码: ${code})\n\n${stderr || stdout}`)); - } - }); - - npmProcess.on('error', (error) => { - console.error('NPM process error:', error); - reject(new Error(`NPM进程错误: ${error.message}`)); - }); - }); - - // 复制行为树相关文件到项目中 - const sourceDir = path.join(__dirname, '../../../thirdparty/BehaviourTree-ai'); - const targetDir = path.join(projectPath, 'assets/scripts/AI'); - - if (fs.existsSync(sourceDir)) { - console.log('Copying behavior tree files...'); - await fsExtra.ensureDir(targetDir); - - // 创建示例文件 - const exampleCode = `import { Scene, Entity, Component } from '@esengine/ecs-framework'; -import { BehaviorTreeSystem, BehaviorTreeFactory, TaskStatus } from '@esengine/ai/ecs-integration'; - -/** - * 示例AI组件 - */ -export class AIExampleComponent extends Component { - // 在场景中添加行为树系统 - static setupBehaviorTreeSystem(scene: Scene) { - const behaviorTreeSystem = new BehaviorTreeSystem(); - scene.addEntityProcessor(behaviorTreeSystem); - return behaviorTreeSystem; - } - - // 为实体添加简单AI行为 - static addSimpleAI(entity: Entity) { - BehaviorTreeFactory.addBehaviorTreeToEntity( - entity, - (builder) => builder - .selector() - .action((entity) => { - console.log("AI正在巡逻..."); - return TaskStatus.Success; - }) - .action((entity) => { - console.log("AI正在警戒..."); - return TaskStatus.Success; - }) - .endComposite(), - { debugMode: true } - ); - } -}`; - - const examplePath = path.join(targetDir, 'AIExample.ts'); - await fsExtra.writeFile(examplePath, exampleCode); - console.log('Example file created successfully'); - } - - console.log('Behavior Tree AI system installed successfully'); - return true; - - } catch (error) { - console.error('Failed to install Behavior Tree AI system:', error); - const errorMessage = error instanceof Error ? error.message : String(error); - throw new Error(`行为树AI系统安装失败:\n\n${errorMessage}`); - } + 'install-behavior-tree'() { + BehaviorTreeHandler.install(); }, /** * 更新行为树AI系统 */ - async 'update-behavior-tree'() { - console.log('Updating Behavior Tree AI system...'); - const projectPath = Editor.Project.path; - - try { - // 检查是否已安装 - const packageJsonPath = path.join(projectPath, 'package.json'); - if (!fs.existsSync(packageJsonPath)) { - throw new Error('项目根目录未找到package.json文件'); - } - - const packageJson = await fsExtra.readJson(packageJsonPath); - const dependencies = packageJson.dependencies || {}; - - if (!dependencies['@esengine/ai']) { - throw new Error('尚未安装行为树AI系统,请先进行安装'); - } - - console.log('Checking for updates...'); - - // 执行npm更新 - await new Promise((resolve, reject) => { - const cmd = process.platform === 'win32' ? 'npm.cmd' : 'npm'; - const npmProcess = spawn(cmd, ['update', '@esengine/ai'], { - cwd: projectPath, - stdio: 'pipe', - shell: true - }); - - npmProcess.on('close', (code) => { - if (code === 0) { - console.log('Update completed successfully'); - resolve(); - } else { - reject(new Error(`更新失败 (退出码: ${code})`)); - } - }); - - npmProcess.on('error', (error) => { - reject(new Error(`更新进程错误: ${error.message}`)); - }); - }); - - console.log('Behavior Tree AI system updated successfully'); - return true; - - } catch (error) { - console.error('Failed to update Behavior Tree AI system:', error); - const errorMessage = error instanceof Error ? error.message : String(error); - throw new Error(`行为树AI系统更新失败:\n\n${errorMessage}`); - } + 'update-behavior-tree'() { + BehaviorTreeHandler.update(); }, /** - * 检查行为树AI系统是否已安装 + * 检查行为树AI是否已安装 */ - async 'check-behavior-tree-installed'() { - const projectPath = Editor.Project.path; - - try { - const packageJsonPath = path.join(projectPath, 'package.json'); - if (!fs.existsSync(packageJsonPath)) { - return false; - } - - const packageJson = await fsExtra.readJson(packageJsonPath); - const dependencies = packageJson.dependencies || {}; - - return !!dependencies['@esengine/ai']; - } catch (error) { - console.error('Failed to check installation status:', error); - return false; - } + 'check-behavior-tree-installed'() { + return BehaviorTreeHandler.checkInstalled(); }, /** * 打开行为树文档 */ 'open-behavior-tree-docs'() { - const url = 'https://github.com/esengine/BehaviourTree-ai/blob/master/ecs-integration/README.md'; - - try { - const { shell } = require('electron'); - shell.openExternal(url); - console.log('Behavior Tree documentation opened successfully'); - } catch (error) { - console.error('Failed to open documentation:', error); - Editor.Dialog.info('打开文档', { - detail: `请手动访问以下链接查看行为树文档:\n\n${url}`, - }); - } + BehaviorTreeHandler.openDocumentation(); }, /** * 创建行为树文件 */ - async 'create-behavior-tree-file'(assetInfo: any) { - console.log('Creating behavior tree file in folder:', assetInfo?.path); - - try { - // 获取项目assets目录 - const projectPath = Editor.Project.path; - const assetsPath = path.join(projectPath, 'assets'); - - // 生成唯一文件名 - let fileName = 'NewBehaviorTree'; - let counter = 1; - let filePath = path.join(assetsPath, `${fileName}.bt.json`); - - while (fs.existsSync(filePath)) { - fileName = `NewBehaviorTree_${counter}`; - filePath = path.join(assetsPath, `${fileName}.bt.json`); - counter++; - } - - // 创建默认的行为树配置 - const defaultConfig = { - version: "1.0.0", - type: "behavior-tree", - metadata: { - createdAt: new Date().toISOString(), - nodeCount: 1 - }, - tree: { - id: "root", - type: "sequence", - namespace: "behaviourTree/composites", - properties: {}, - children: [] - } - }; - - // 写入文件 - await fsExtra.writeFile(filePath, JSON.stringify(defaultConfig, null, 2)); - - // 刷新资源管理器 - await Editor.Message.request('asset-db', 'refresh-asset', 'db://assets'); - - console.log(`Behavior tree file created: ${filePath}`); - - Editor.Dialog.info('创建成功', { - detail: `行为树文件 "${fileName}.bt.json" 已创建完成!\n\n文件位置:assets/${fileName}.bt.json\n\n您可以右键点击文件选择"用行为树编辑器打开"来编辑它。`, - }); - - } catch (error) { - console.error('Failed to create behavior tree file:', error); - Editor.Dialog.error('创建失败', { - detail: `创建行为树文件失败:\n\n${error instanceof Error ? error.message : String(error)}`, - }); - } + 'create-behavior-tree-file'() { + BehaviorTreeHandler.createFile(); }, /** - * 用行为树编辑器打开文件 + * 加载行为树文件到编辑器 */ - async 'open-behavior-tree-file'(assetInfo: any) { - console.log('Opening behavior tree file:', assetInfo); + async 'load-behavior-tree-file'(...args: any[]) { + const assetInfo = args.length >= 2 ? args[1] : args[0]; try { - // 直接从assetInfo获取文件系统路径 - const assetPath = assetInfo?.path; - if (!assetPath) { - throw new Error('无效的文件路径'); + if (!assetInfo || (!assetInfo.file && !assetInfo.path)) { + throw new Error('无效的文件信息'); } - // 转换为文件系统路径 - const projectPath = Editor.Project.path; - const relativePath = assetPath.replace('db://assets/', ''); - const fsPath = path.join(projectPath, 'assets', relativePath); + await Editor.Panel.open('cocos-ecs-extension.behavior-tree'); + await new Promise(resolve => setTimeout(resolve, 500)); - console.log('File system path:', fsPath); - - // 检查文件是否存在 - if (!fs.existsSync(fsPath)) { - throw new Error('文件不存在'); - } - - // 检查文件是否为JSON格式 - let fileContent: any; - try { - const content = await fsExtra.readFile(fsPath, 'utf8'); - fileContent = JSON.parse(content); - } catch (parseError) { - throw new Error('文件不是有效的JSON格式'); - } - - // 验证是否为行为树文件 - if (fileContent.type !== 'behavior-tree' && !fileContent.tree) { - const confirm = await new Promise((resolve) => { - Editor.Dialog.warn('文件格式提醒', { - detail: '此文件可能不是标准的行为树配置文件,仍要打开吗?', - buttons: ['打开', '取消'], - }).then((result: any) => { - resolve(result.response === 0); - }); - }); - - if (!confirm) { - return; - } - } - - // 打开行为树编辑器面板 - Editor.Panel.open('cocos-ecs-extension.behavior-tree'); - - console.log(`Behavior tree file opened in editor: ${fsPath}`); + const result = await Editor.Message.request('cocos-ecs-extension', 'behavior-tree-panel-load-file', assetInfo); } catch (error) { - console.error('Failed to open behavior tree file:', error); Editor.Dialog.error('打开失败', { - detail: `打开行为树文件失败:\n\n${error instanceof Error ? error.message : String(error)}`, + detail: `打开行为树文件失败:\n\n${error instanceof Error ? error.message : String(error)}` }); } }, @@ -657,57 +151,38 @@ export class AIExampleComponent extends Component { /** * 从编辑器创建行为树文件 */ - async 'create-behavior-tree-from-editor'(data: { fileName: string, content: string }) { - console.log('Creating behavior tree file from editor:', data.fileName); + 'create-behavior-tree-from-editor'(event: any, data: any) { + BehaviorTreeHandler.createFromEditor(data); + }, + + /** + * 覆盖现有行为树文件 + */ + 'overwrite-behavior-tree-file'(...args: any[]) { + const data = args.length >= 2 ? args[1] : args[0]; - try { - const projectPath = Editor.Project.path; - const assetsPath = path.join(projectPath, 'assets'); - - // 确保文件名唯一 - let fileName = data.fileName; - let counter = 1; - let filePath = path.join(assetsPath, `${fileName}.bt.json`); - - while (fs.existsSync(filePath)) { - fileName = `${data.fileName}_${counter}`; - filePath = path.join(assetsPath, `${fileName}.bt.json`); - counter++; - } - - // 写入文件 - await fsExtra.writeFile(filePath, data.content); - - // 刷新资源管理器 - await Editor.Message.request('asset-db', 'refresh-asset', 'db://assets'); - - console.log(`Behavior tree file created from editor: ${filePath}`); - - Editor.Dialog.info('保存成功', { - detail: `行为树文件 "${fileName}.bt.json" 已保存到 assets 目录中!`, - }); - - } catch (error) { - console.error('Failed to create behavior tree file from editor:', error); - Editor.Dialog.error('保存失败', { - detail: `保存行为树文件失败:\n\n${error instanceof Error ? error.message : String(error)}`, - }); + if (data && data.filePath) { + BehaviorTreeHandler.overwriteFile(data); + } else { + throw new Error('文件路径不存在或数据无效'); } }, }; + + /** * @en Method triggered when the extension is started * @zh 启动扩展时触发的方法 */ export function load() { - console.log('ECS Framework Extension loaded'); + console.log('[Cocos ECS Extension] 扩展已加载'); } /** - * @en Method triggered when uninstalling the extension + * @en Method triggered when the extension is uninstalled * @zh 卸载扩展时触发的方法 */ export function unload() { - console.log('ECS Framework Extension unloaded'); + console.log('[Cocos ECS Extension] 扩展已卸载'); } diff --git a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useBehaviorTreeEditor.ts b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useBehaviorTreeEditor.ts index 6aa18ddf..60a6acb3 100644 --- a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useBehaviorTreeEditor.ts +++ b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useBehaviorTreeEditor.ts @@ -74,16 +74,6 @@ export function useBehaviorTreeEditor() { appState.isInstalling ); - const fileOps = useFileOperations( - appState.treeNodes, - appState.selectedNodeId, - appState.connections, - appState.tempConnection, - appState.showExportModal, - codeGen, - () => connectionManager.updateConnections() - ); - const connectionState = reactive({ isConnecting: false, startNodeId: null as string | null, @@ -105,6 +95,16 @@ export function useBehaviorTreeEditor() { appState.zoomLevel ); + const fileOps = useFileOperations({ + treeNodes: appState.treeNodes, + selectedNodeId: appState.selectedNodeId, + connections: appState.connections, + tempConnection: appState.tempConnection, + showExportModal: appState.showExportModal, + codeGeneration: codeGen, + updateConnections: connectionManager.updateConnections + }); + const canvasManager = useCanvasManager( appState.panX, appState.panY, @@ -182,11 +182,160 @@ export function useBehaviorTreeEditor() { installation.handleInstall(); }; - // 组件挂载时初始化连接 - onMounted(() => { - // 延迟一下确保 DOM 已经渲染 - nextTick(() => { + // 自动布局功能 + const autoLayout = () => { + if (appState.treeNodes.value.length === 0) { + return; + } + + const rootNode = appState.treeNodes.value.find(node => + !appState.treeNodes.value.some(otherNode => + otherNode.children?.includes(node.id) + ) + ); + + if (!rootNode) { + return; + } + + const levelNodes: { [level: number]: any[] } = {}; + const visited = new Set(); + + const queue = [{ node: rootNode, level: 0 }]; + + while (queue.length > 0) { + const { node, level } = queue.shift()!; + + if (visited.has(node.id)) continue; + visited.add(node.id); + + if (!levelNodes[level]) { + levelNodes[level] = []; + } + levelNodes[level].push(node); + + if (node.children && Array.isArray(node.children)) { + node.children.forEach((childId: string) => { + const childNode = appState.treeNodes.value.find(n => n.id === childId); + if (childNode && !visited.has(childId)) { + queue.push({ node: childNode, level: level + 1 }); + } + }); + } + } + + const nodeWidth = 200; + const nodeHeight = 150; + const startX = 400; + const startY = 100; + + Object.keys(levelNodes).forEach(levelStr => { + const level = parseInt(levelStr); + const nodes = levelNodes[level]; + const totalWidth = (nodes.length - 1) * nodeWidth; + const offsetX = -totalWidth / 2; + + nodes.forEach((node, index) => { + node.x = startX + offsetX + index * nodeWidth; + node.y = startY + level * nodeHeight; + }); + }); + + setTimeout(() => { connectionManager.updateConnections(); + }, 100); + }; + + // 验证树结构 + const validateTree = () => { + const errors: string[] = []; + const warnings: string[] = []; + + const rootNodes = appState.treeNodes.value.filter(node => + !appState.treeNodes.value.some(otherNode => + otherNode.children?.includes(node.id) + ) + ); + + if (rootNodes.length === 0) { + errors.push('没有找到根节点'); + } else if (rootNodes.length > 1) { + warnings.push(`找到多个根节点: ${rootNodes.map(n => n.name).join(', ')}`); + } + + appState.treeNodes.value.forEach(node => { + const hasParent = appState.treeNodes.value.some(otherNode => + otherNode.children?.includes(node.id) + ); + const hasChildren = node.children && node.children.length > 0; + + if (!hasParent && !hasChildren && appState.treeNodes.value.length > 1) { + warnings.push(`节点 "${node.name}" 是孤立节点`); + } + }); + + appState.connections.value.forEach(conn => { + const sourceNode = appState.treeNodes.value.find(n => n.id === conn.sourceId); + const targetNode = appState.treeNodes.value.find(n => n.id === conn.targetId); + + if (!sourceNode) { + errors.push(`连接 ${conn.id} 的源节点不存在`); + } + if (!targetNode) { + errors.push(`连接 ${conn.id} 的目标节点不存在`); + } + }); + + let message = '树结构验证完成!\n\n'; + + if (errors.length > 0) { + message += `❌ 错误 (${errors.length}):\n${errors.map(e => `• ${e}`).join('\n')}\n\n`; + } + + if (warnings.length > 0) { + message += `⚠️ 警告 (${warnings.length}):\n${warnings.map(w => `• ${w}`).join('\n')}\n\n`; + } + + if (errors.length === 0 && warnings.length === 0) { + message += '✅ 没有发现问题!'; + } + + alert(message); + }; + + onMounted(() => { + const appContainer = document.querySelector('#behavior-tree-app'); + if (appContainer) { + (appContainer as any).loadFileContent = fileOps.loadFileContent; + (appContainer as any).showError = (errorMessage: string) => { + alert('文件加载失败: ' + errorMessage); + }; + } + + const handleLoadBehaviorTreeFile = (event: CustomEvent) => { + fileOps.loadFileContent(event.detail); + }; + + const handleFileLoadError = (event: CustomEvent) => { + console.error('[BehaviorTreeEditor] DOM事件错误:', event.detail); + alert('文件加载失败: ' + event.detail.error); + }; + + document.addEventListener('load-behavior-tree-file', handleLoadBehaviorTreeFile as EventListener); + document.addEventListener('file-load-error', handleFileLoadError as EventListener); + + console.log('[BehaviorTreeEditor] 事件系统准备完成(直接方法调用 + DOM事件备用)'); + + onUnmounted(() => { + console.log('[BehaviorTreeEditor] 清理事件监听器'); + document.removeEventListener('load-behavior-tree-file', handleLoadBehaviorTreeFile as EventListener); + document.removeEventListener('file-load-error', handleFileLoadError as EventListener); + + // 清理暴露的方法 + if (appContainer) { + delete (appContainer as any).loadFileContent; + delete (appContainer as any).showError; + } }); }); @@ -210,6 +359,8 @@ export function useBehaviorTreeEditor() { ...canvasManager, ...nodeDisplay, startNodeDrag, - dragState + dragState, + autoLayout, + validateTree }; } \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useCodeGeneration.ts b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useCodeGeneration.ts index 1b163b5b..79fab8da 100644 --- a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useCodeGeneration.ts +++ b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useCodeGeneration.ts @@ -268,13 +268,102 @@ export const config = behaviorTreeConfig;`; // 从配置创建行为树节点 const createTreeFromConfig = (config: any): TreeNode[] => { - if (!config || !config.tree) { + console.log('createTreeFromConfig被调用,接收到的配置:', config); + console.log('nodeTemplates当前数量:', nodeTemplates.value.length); + + // 处理两种不同的文件格式 + if (config.nodes && Array.isArray(config.nodes)) { + console.log('使用nodes格式处理,节点数量:', config.nodes.length); + const result = createTreeFromNodesFormat(config); + console.log('nodes格式处理结果:', result); + return result; + } else if (config.tree) { + console.log('使用tree格式处理'); + const result = createTreeFromTreeFormat(config); + console.log('tree格式处理结果:', result); + return result; + } else { + console.log('配置格式不匹配,返回空数组'); + return []; + } + }; + + // 处理新格式(nodes数组格式) + const createTreeFromNodesFormat = (config: any): TreeNode[] => { + console.log('createTreeFromNodesFormat开始处理'); + + if (!config.nodes || !Array.isArray(config.nodes)) { + console.log('nodes数据无效'); + return []; + } + + const nodes: TreeNode[] = []; + + config.nodes.forEach((nodeConfig: any, index: number) => { + console.log(`处理第${index + 1}个节点:`, nodeConfig); + + const template = findTemplateByType(nodeConfig.type); + console.log(`为节点类型 "${nodeConfig.type}" 找到的模板:`, template); + + if (!template) { + console.warn(`未找到节点类型 "${nodeConfig.type}" 的模板`); + return; + } + + const node: TreeNode = { + id: nodeConfig.id || generateNodeId(), + type: template.type, + name: nodeConfig.name || template.name, + icon: nodeConfig.icon || template.icon, + description: nodeConfig.description || template.description, + canHaveChildren: template.canHaveChildren, + canHaveParent: template.canHaveParent, + x: nodeConfig.x || 400, + y: nodeConfig.y || 100, + properties: {}, + children: nodeConfig.children || [], + parent: nodeConfig.parent, + hasError: false + }; + + // 恢复属性 + if (nodeConfig.properties && template.properties) { + Object.entries(nodeConfig.properties).forEach(([key, propConfig]: [string, any]) => { + if (template.properties![key]) { + node.properties![key] = { + ...template.properties![key], + value: propConfig.value !== undefined ? propConfig.value : template.properties![key].value + }; + } + }); + } + + // 确保所有模板属性都有默认值 + if (template.properties) { + Object.entries(template.properties).forEach(([key, propDef]) => { + if (!node.properties![key]) { + node.properties![key] = { ...propDef }; + } + }); + } + + console.log(`创建的节点:`, node); + nodes.push(node); + }); + + console.log(`createTreeFromNodesFormat完成,总共创建了${nodes.length}个节点`); + return nodes; + }; + + // 处理旧格式(tree对象格式) + const createTreeFromTreeFormat = (config: any): TreeNode[] => { + if (!config.tree) { return []; } const nodes: TreeNode[] = []; const processNode = (nodeConfig: any, parent?: TreeNode): TreeNode => { - const template = nodeTemplates.value.find(t => t.className === nodeConfig.type); + const template = findTemplateByType(nodeConfig.type); if (!template) { throw new Error(`未知节点类型: ${nodeConfig.type}`); } @@ -287,25 +376,35 @@ export const config = behaviorTreeConfig;`; description: template.description, canHaveChildren: template.canHaveChildren, canHaveParent: template.canHaveParent, - x: 400, // 默认在画布中心 - y: 100, // 从顶部开始 + x: 400, + y: 100, properties: {}, children: [], - parent: parent?.id // 设置父节点ID + parent: parent?.id, + hasError: false }; // 恢复属性 - if (nodeConfig.properties) { + if (nodeConfig.properties && template.properties) { Object.entries(nodeConfig.properties).forEach(([key, propConfig]: [string, any]) => { - if (template.properties?.[key]) { + if (template.properties![key]) { node.properties![key] = { - ...template.properties[key], - value: propConfig.value + ...template.properties![key], + value: propConfig.value !== undefined ? propConfig.value : template.properties![key].value }; } }); } + // 确保所有模板属性都有默认值 + if (template.properties) { + Object.entries(template.properties).forEach(([key, propDef]) => { + if (!node.properties![key]) { + node.properties![key] = { ...propDef }; + } + }); + } + nodes.push(node); // 处理子节点 @@ -323,6 +422,49 @@ export const config = behaviorTreeConfig;`; return nodes; }; + // 通过类型名查找模板(支持多种匹配方式) + const findTemplateByType = (typeName: string): NodeTemplate | undefined => { + // 直接匹配 type 字段 + let template = nodeTemplates.value.find(t => t.type === typeName); + if (template) return template; + + // 匹配 className 字段 + template = nodeTemplates.value.find(t => t.className === typeName); + if (template) return template; + + // 大小写不敏感匹配 type + template = nodeTemplates.value.find(t => t.type.toLowerCase() === typeName.toLowerCase()); + if (template) return template; + + // 大小写不敏感匹配 className + template = nodeTemplates.value.find(t => t.className && t.className.toLowerCase() === typeName.toLowerCase()); + if (template) return template; + + // 特殊映射处理 + const typeMapping: Record = { + 'Sequence': 'sequence', + 'Selector': 'selector', + 'Parallel': 'parallel', + 'Inverter': 'inverter', + 'Repeater': 'repeater', + 'AlwaysSucceed': 'always-succeed', + 'AlwaysFail': 'always-fail', + 'UntilSuccess': 'until-success', + 'UntilFail': 'until-fail', + 'ExecuteAction': 'execute-action', + 'LogAction': 'log-action', + 'WaitAction': 'wait-action' + }; + + const mappedType = typeMapping[typeName]; + if (mappedType) { + template = nodeTemplates.value.find(t => t.type === mappedType); + if (template) return template; + } + + return undefined; + }; + // 生成唯一节点ID const generateNodeId = (): string => { return 'node_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); diff --git a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useConnectionManager.ts b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useConnectionManager.ts index 0a7c5386..3c31347e 100644 --- a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useConnectionManager.ts +++ b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useConnectionManager.ts @@ -291,7 +291,7 @@ export function useConnectionManager( return getPortInfo(elementAtPoint as HTMLElement); } } catch (error) { - console.warn(`[ConnectionManager] elementFromPoint 查询出错:`, error); + // 查询出错时静默处理 } const allPorts = canvasAreaRef.value.querySelectorAll('.port'); diff --git a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useFileOperations.ts b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useFileOperations.ts index 0486f70e..26fb3dd9 100644 --- a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useFileOperations.ts +++ b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useFileOperations.ts @@ -1,26 +1,44 @@ import { Ref, ref, watch } from 'vue'; import { TreeNode, Connection } from '../types'; -/** - * 文件操作管理 - */ -export function useFileOperations( - treeNodes: Ref, - selectedNodeId: Ref, - connections: Ref, - tempConnection: Ref<{ path: string }>, - showExportModal: Ref, +interface FileOperationOptions { + treeNodes: Ref; + selectedNodeId: Ref; + connections: Ref; + tempConnection: Ref<{ path: string }>; + showExportModal: Ref; codeGeneration?: { createTreeFromConfig: (config: any) => TreeNode[]; - }, - updateConnections?: () => void -) { - // 跟踪未保存状态 + }; + updateConnections?: () => void; +} + +interface FileData { + nodes: TreeNode[]; + connections: Connection[]; + metadata: { + name: string; + created: string; + version: string; + }; +} + +export function useFileOperations(options: FileOperationOptions) { + const { + treeNodes, + selectedNodeId, + connections, + tempConnection, + showExportModal, + codeGeneration, + updateConnections + } = options; + const hasUnsavedChanges = ref(false); const lastSavedState = ref(''); const currentFileName = ref(''); - - // 监听树结构变化来更新未保存状态 + const currentFilePath = ref(''); + const updateUnsavedStatus = () => { const currentState = JSON.stringify({ nodes: treeNodes.value, @@ -28,11 +46,9 @@ export function useFileOperations( }); hasUnsavedChanges.value = currentState !== lastSavedState.value; }; - - // 监听变化 + watch([treeNodes, connections], updateUnsavedStatus, { deep: true }); - - // 标记为已保存 + const markAsSaved = () => { const currentState = JSON.stringify({ nodes: treeNodes.value, @@ -41,38 +57,19 @@ export function useFileOperations( lastSavedState.value = currentState; hasUnsavedChanges.value = false; }; - - // 检查是否需要保存的通用方法 - const checkUnsavedChanges = (): Promise => { - return new Promise((resolve) => { - if (!hasUnsavedChanges.value) { - resolve(true); - return; - } - - const result = confirm( - '当前行为树有未保存的更改,是否要保存?\n\n' + - '点击"确定"保存更改\n' + - '点击"取消"丢弃更改\n' + - '点击"X"取消操作' - ); - - if (result) { - // 用户选择保存 - saveBehaviorTree().then(() => { - resolve(true); - }).catch(() => { - resolve(false); - }); - } else { - // 用户选择丢弃更改 - resolve(true); - } - }); + + const setCurrentFile = (fileName: string, filePath: string = '') => { + currentFileName.value = fileName; + currentFilePath.value = filePath; + markAsSaved(); }; - - // 导出行为树数据 - const exportBehaviorTreeData = () => { + + const clearCurrentFile = () => { + currentFileName.value = ''; + currentFilePath.value = ''; + }; + + const exportBehaviorTreeData = (): FileData => { return { nodes: treeNodes.value, connections: connections.value, @@ -83,97 +80,177 @@ export function useFileOperations( } }; }; - - // 工具栏操作 + + const showMessage = (message: string, type: 'success' | 'error' = 'success') => { + const toast = document.createElement('div'); + toast.style.cssText = ` + position: fixed; + top: 20px; + right: 20px; + padding: 12px 20px; + background: ${type === 'success' ? '#4caf50' : '#f44336'}; + color: white; + border-radius: 4px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + z-index: 10001; + opacity: 0; + transform: translateX(100%); + transition: all 0.3s ease; + `; + toast.textContent = message; + + document.body.appendChild(toast); + + setTimeout(() => { + toast.style.opacity = '1'; + toast.style.transform = 'translateX(0)'; + }, 10); + + setTimeout(() => { + toast.style.opacity = '0'; + toast.style.transform = 'translateX(100%)'; + setTimeout(() => { + if (document.body.contains(toast)) { + document.body.removeChild(toast); + } + }, 300); + }, 3000); + }; + + const sendToMain = (message: string, data: any): Promise => { + return new Promise((resolve, reject) => { + try { + Editor.Message.request('cocos-ecs-extension', message, data) + .then((result) => { + resolve(); + }) + .catch((error) => { + reject(error); + }); + } catch (error) { + reject(error); + } + }); + }; + + const checkUnsavedChanges = (): Promise => { + return new Promise((resolve) => { + if (!hasUnsavedChanges.value) { + resolve(true); + return; + } + + const result = confirm( + '当前行为树有未保存的更改,是否要保存?\n\n' + + '点击"确定"保存更改\n' + + '点击"取消"丢弃更改' + ); + + if (result) { + saveBehaviorTree().then(() => { + resolve(true); + }).catch(() => { + resolve(false); + }); + } else { + resolve(true); + } + }); + }; + const newBehaviorTree = async () => { const canProceed = await checkUnsavedChanges(); if (canProceed) { - treeNodes.value = []; - selectedNodeId.value = null; - connections.value = []; - tempConnection.value.path = ''; - currentFileName.value = ''; - markAsSaved(); // 新建后标记为已保存状态 + treeNodes.value = []; + selectedNodeId.value = null; + connections.value = []; + tempConnection.value.path = ''; + clearCurrentFile(); + markAsSaved(); } }; - // 保存行为树 const saveBehaviorTree = async (): Promise => { - console.log('=== 开始保存行为树 ==='); + if (currentFilePath.value) { + return await saveToCurrentFile(); + } else { + return await saveAsBehaviorTree(); + } + }; + + const saveToCurrentFile = async (): Promise => { + if (!currentFilePath.value) { + return await saveAsBehaviorTree(); + } try { const data = exportBehaviorTreeData(); const jsonString = JSON.stringify(data, null, 2); - console.log('数据准备完成,JSON长度:', jsonString.length); - // 使用 HTML input 替代 prompt(因为 prompt 在 Cocos Creator 扩展中不支持) - const fileName = await getFileNameFromUser(); - if (!fileName) { - console.log('❌ 用户取消了保存操作'); - return false; - } + await sendToMain('overwrite-behavior-tree-file', { + filePath: currentFilePath.value, + content: jsonString + }); - console.log('✓ 用户输入文件名:', fileName); - - // 检测是否在Cocos Creator环境中 - if (typeof Editor !== 'undefined' && typeof (window as any).sendToMain === 'function') { - console.log('✓ 使用Cocos Creator保存方式'); - - try { - (window as any).sendToMain('create-behavior-tree-from-editor', { - fileName: fileName + '.json', - content: jsonString, - timestamp: new Date().toISOString() - }); - - console.log('✓ 保存消息已发送到主进程'); - - // 更新当前文件名并标记为已保存 - currentFileName.value = fileName; - markAsSaved(); - - // 用户反馈 - showMessage(`保存成功!文件名: ${fileName}.json`, 'success'); - - console.log('✅ 保存操作完成'); - return true; - } catch (sendError) { - console.error('❌ 发送消息时出错:', sendError); - showMessage('保存失败: ' + sendError, 'error'); - return false; - } - } else { - console.log('✓ 使用浏览器下载保存方式'); - - // 在浏览器环境中使用下载方式 - const blob = new Blob([jsonString], { type: 'application/json' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `${fileName}.json`; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - - // 标记为已保存 - currentFileName.value = fileName; - markAsSaved(); - - console.log('✅ 文件下载保存成功'); - return true; - } + markAsSaved(); + showMessage('保存成功!'); + return true; + } catch (error) { + showMessage('保存失败: ' + error, 'error'); + return false; + } + }; + + const saveAsBehaviorTree = async (): Promise => { + try { + const data = exportBehaviorTreeData(); + const jsonString = JSON.stringify(data, null, 2); + + const result = await Editor.Dialog.save({ + title: '保存行为树文件', + filters: [ + { name: '行为树文件', extensions: ['bt.json', 'json'] }, + { name: '所有文件', extensions: ['*'] } + ] + }); + + if (result.canceled || !result.filePath) { + return false; + } + + const fs = require('fs-extra'); + await fs.writeFile(result.filePath, jsonString); + + const path = require('path'); + const fileName = path.basename(result.filePath, path.extname(result.filePath)); + setCurrentFile(fileName, result.filePath); + showMessage(`保存成功!文件: ${result.filePath}`); + + return true; + } catch (error) { + showMessage('另存为失败: ' + error, 'error'); + return false; + } + }; + + const saveToFile = async (fileName: string, jsonString: string): Promise => { + try { + await sendToMain('create-behavior-tree-from-editor', { + fileName: fileName + '.json', + content: jsonString + }); + + setCurrentFile(fileName, `assets/${fileName}.bt.json`); + showMessage(`保存成功!文件名: ${fileName}.json`); + return true; } catch (error) { - console.error('❌ 保存过程中发生错误:', error); showMessage('保存失败: ' + error, 'error'); return false; } }; - // 使用 HTML input 获取文件名(替代 prompt) const getFileNameFromUser = (): Promise => { return new Promise((resolve) => { - // 创建模态对话框 const overlay = document.createElement('div'); overlay.style.cssText = ` position: fixed; @@ -216,11 +293,9 @@ export function useFileOperations( const saveBtn = dialog.querySelector('#save-btn') as HTMLButtonElement; const cancelBtn = dialog.querySelector('#cancel-btn') as HTMLButtonElement; - // 聚焦并选中文本 input.focus(); input.select(); - // 事件处理 const cleanup = () => { document.body.removeChild(overlay); }; @@ -236,7 +311,6 @@ export function useFileOperations( resolve(null); }; - // 回车键保存 input.onkeydown = (e) => { if (e.key === 'Enter') { const fileName = input.value.trim(); @@ -250,131 +324,108 @@ export function useFileOperations( }); }; - // 显示消息提示 - const showMessage = (message: string, type: 'success' | 'error' = 'success') => { - const toast = document.createElement('div'); - toast.style.cssText = ` - position: fixed; - top: 20px; - right: 20px; - padding: 12px 20px; - background: ${type === 'success' ? '#4caf50' : '#f44336'}; - color: white; - border-radius: 4px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); - z-index: 10001; - opacity: 0; - transform: translateX(100%); - transition: all 0.3s ease; - `; - toast.textContent = message; - - document.body.appendChild(toast); - - // 动画显示 - setTimeout(() => { - toast.style.opacity = '1'; - toast.style.transform = 'translateX(0)'; - }, 10); - - // 3秒后自动消失 - setTimeout(() => { - toast.style.opacity = '0'; - toast.style.transform = 'translateX(100%)'; - setTimeout(() => { - if (document.body.contains(toast)) { - document.body.removeChild(toast); - } - }, 300); - }, 3000); - }; - - // 生成当前行为树的配置 - const generateCurrentConfig = () => { - if (treeNodes.value.length === 0) return null; - - const rootNode = treeNodes.value.find(node => - !treeNodes.value.some(otherNode => - otherNode.children?.includes(node.id) - ) - ); - - if (!rootNode) return null; - - return { - version: "1.0.0", - type: "behavior-tree", - metadata: { - createdAt: new Date().toISOString(), - nodeCount: treeNodes.value.length - }, - tree: generateNodeConfig(rootNode) - }; - }; - - // 简化的节点配置生成(用于文件保存) - const generateNodeConfig = (node: TreeNode): any => { - const config: any = { - id: node.id, - type: node.type, - namespace: getNodeNamespace(node.type), - properties: {} - }; - - // 处理节点属性 - if (node.properties) { - Object.entries(node.properties).forEach(([key, prop]) => { - if (prop.value !== undefined && prop.value !== '') { - config.properties[key] = { - type: prop.type, - value: prop.value + const loadFileContent = (fileData: any, filePath: string = '') => { + try { + if (!fileData) { + return; + } + + let parsedData = fileData; + + if (fileData.rawContent) { + try { + parsedData = JSON.parse(fileData.rawContent); + } catch (e) { + parsedData = { + nodes: [], + connections: [] }; } - }); + } + + if (parsedData.nodes && Array.isArray(parsedData.nodes)) { + treeNodes.value = parsedData.nodes.map((node: any) => ({ + ...node, + x: node.x || 0, + y: node.y || 0, + children: node.children || [], + properties: node.properties || {}, + canHaveChildren: node.canHaveChildren !== false, + canHaveParent: node.canHaveParent !== false, + hasError: node.hasError || false + })); + } else if (parsedData.tree) { + const treeNode = parsedData.tree; + const nodes = [treeNode]; + + const extractNodes = (node: any): any[] => { + const allNodes = [node]; + if (node.children && Array.isArray(node.children)) { + node.children.forEach((child: any) => { + if (typeof child === 'object') { + allNodes.push(...extractNodes(child)); + } + }); + } + return allNodes; + }; + + const allNodes = extractNodes(treeNode); + treeNodes.value = allNodes.map((node: any, index: number) => ({ + ...node, + x: node.x || (300 + index * 150), + y: node.y || (100 + Math.floor(index / 3) * 200), + children: Array.isArray(node.children) + ? node.children.filter((child: any) => typeof child === 'string') + : [], + properties: node.properties || {}, + canHaveChildren: true, + canHaveParent: node.id !== 'root', + hasError: false + })); + } else { + treeNodes.value = []; + } + + if (parsedData.connections && Array.isArray(parsedData.connections)) { + connections.value = parsedData.connections.map((conn: any) => ({ + id: conn.id || Math.random().toString(36).substr(2, 9), + sourceId: conn.sourceId, + targetId: conn.targetId, + path: conn.path || '', + active: conn.active || false + })); + } else { + connections.value = []; + } + + if (fileData._fileInfo) { + const fileName = fileData._fileInfo.fileName || 'untitled'; + const fullPath = fileData._fileInfo.filePath || filePath; + setCurrentFile(fileName, fullPath); + } else if (parsedData.metadata?.name) { + setCurrentFile(parsedData.metadata.name, filePath); + } else { + setCurrentFile('untitled', filePath); + } + + selectedNodeId.value = null; + tempConnection.value.path = ''; + + if (updateConnections) { + setTimeout(() => { + updateConnections(); + }, 100); + } + + } catch (error) { + console.error('文件加载失败:', error); + showMessage('文件加载失败: ' + error, 'error'); + treeNodes.value = []; + connections.value = []; + selectedNodeId.value = null; + setCurrentFile('untitled', ''); } - - // 处理子节点 - if (node.children && node.children.length > 0) { - config.children = node.children - .map(childId => treeNodes.value.find(n => n.id === childId)) - .filter(Boolean) - .map(child => generateNodeConfig(child!)); - } - - return config; - }; - - // 获取节点命名空间 - const getNodeNamespace = (nodeType: string): string => { - // ECS节点 - if (['has-component', 'add-component', 'remove-component', 'modify-component', - 'has-tag', 'is-active', 'wait-time', 'destroy-entity'].includes(nodeType)) { - return 'ecs-integration/behaviors'; - } - - // 复合节点 - if (['sequence', 'selector', 'parallel', 'parallel-selector', - 'random-selector', 'random-sequence'].includes(nodeType)) { - return 'behaviourTree/composites'; - } - - // 装饰器 - if (['repeater', 'inverter', 'always-fail', 'always-succeed', - 'until-fail', 'until-success'].includes(nodeType)) { - return 'behaviourTree/decorators'; - } - - // 动作节点 - if (['execute-action', 'log-action', 'wait-action'].includes(nodeType)) { - return 'behaviourTree/actions'; - } - - // 条件节点 - if (['execute-conditional'].includes(nodeType)) { - return 'behaviourTree/conditionals'; - } - - return 'behaviourTree'; }; const loadBehaviorTree = async () => { @@ -397,20 +448,34 @@ export function useFileOperations( const newNodes = codeGeneration.createTreeFromConfig(config); treeNodes.value = newNodes; selectedNodeId.value = null; - connections.value = []; - tempConnection.value.path = ''; - markAsSaved(); // 加载后标记为已保存状态 - console.log('行为树配置加载成功'); - if (updateConnections) { - updateConnections(); + + if (config.connections && Array.isArray(config.connections)) { + connections.value = config.connections.map((conn: any) => ({ + id: conn.id, + sourceId: conn.sourceId, + targetId: conn.targetId, + path: conn.path || '', + active: conn.active || false + })); + } else { + connections.value = []; } + + tempConnection.value.path = ''; + + const fileName = file.name.replace(/\.(json|bt)$/, ''); + setCurrentFile(fileName, ''); + + setTimeout(() => { + if (updateConnections) { + updateConnections(); + } + }, 100); } else { - console.error('代码生成器未初始化'); - alert('代码生成器未初始化'); + showMessage('代码生成器未初始化', 'error'); } } catch (error) { - console.error('加载行为树配置失败:', error); - alert('配置文件格式错误'); + showMessage('配置文件格式错误', 'error'); } }; reader.readAsText(file); @@ -423,37 +488,18 @@ export function useFileOperations( showExportModal.value = true; }; - const copyToClipboard = () => { - // TODO: 实现复制到剪贴板功能 - console.log('复制到剪贴板'); - }; - - const saveToFile = () => { - // TODO: 实现保存到文件功能 - console.log('保存到文件'); - }; - - // 验证相关 - const autoLayout = () => { - // TODO: 实现自动布局功能 - console.log('自动布局'); - }; - - const validateTree = () => { - // TODO: 实现树验证功能 - console.log('验证树结构'); - }; - return { newBehaviorTree, saveBehaviorTree, + saveAsBehaviorTree, loadBehaviorTree, + loadFileContent, exportConfig, - copyToClipboard, - saveToFile, - autoLayout, - validateTree, hasUnsavedChanges, - markAsSaved + markAsSaved, + setCurrentFile, + clearCurrentFile, + currentFileName, + currentFilePath }; } \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useInstallation.ts b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useInstallation.ts index 8c37b39c..c409b759 100644 --- a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useInstallation.ts +++ b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useInstallation.ts @@ -19,7 +19,6 @@ export function useInstallation( isInstalled.value = result.installed; version.value = result.version; } catch (error) { - console.error('检查安装状态失败:', error); isInstalled.value = false; version.value = null; } finally { @@ -34,7 +33,7 @@ export function useInstallation( await installBehaviorTreeAI(Editor.Project.path); await checkInstallStatus(); } catch (error) { - console.error('安装失败:', error); + // 安装失败时静默处理 } finally { isInstalling.value = false; } diff --git a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useNodeOperations.ts b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useNodeOperations.ts index ec33df70..a83d9586 100644 --- a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useNodeOperations.ts +++ b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useNodeOperations.ts @@ -54,7 +54,7 @@ export function useNodeOperations( selectedNodeId.value = newNode.id; } catch (error) { - console.error('节点创建失败:', error); + // 节点创建失败时静默处理 } }; @@ -123,44 +123,18 @@ export function useNodeOperations( // 节点属性更新 const updateNodeProperty = (path: string, value: any) => { - console.log('updateNodeProperty called:', path, value); const node = selectedNodeId.value ? getNodeByIdLocal(selectedNodeId.value) : null; - if (!node) { - console.log('No selected node found'); - return; - } - - console.log('Current node before update:', JSON.stringify(node, null, 2)); + if (!node) return; // 使用通用方法更新属性 setNestedProperty(node, path, value); - console.log(`Updated property ${path} to:`, value); - console.log('Updated node after change:', JSON.stringify(node, null, 2)); - - // 强制触发响应式更新 - 创建新数组来强制Vue检测变化 + // 强制触发响应式更新 const nodeIndex = treeNodes.value.findIndex(n => n.id === node.id); if (nodeIndex > -1) { - // 创建新的节点数组,确保Vue能检测到变化 const newNodes = [...treeNodes.value]; - newNodes[nodeIndex] = { ...node }; // 创建节点副本确保响应式更新 + newNodes[nodeIndex] = { ...node }; treeNodes.value = newNodes; - - console.log('Triggered reactive update - replaced array'); - - // 验证更新是否成功 - nextTick(() => { - const verifyNode = treeNodes.value.find(n => n.id === node.id); - console.log('Verification - node after update:', JSON.stringify(verifyNode, null, 2)); - - // 验证属性值 - const pathParts = path.split('.'); - let checkValue: any = verifyNode; - for (const part of pathParts) { - checkValue = checkValue?.[part]; - } - console.log(`Verification - final value at ${path}:`, checkValue); - }); } }; diff --git a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/index.ts b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/index.ts index 75c68746..99e68aeb 100644 --- a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/index.ts +++ b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/index.ts @@ -2,75 +2,189 @@ import { readFileSync } from 'fs-extra'; import { join } from 'path'; import { createApp, App, defineComponent } from 'vue'; import { useBehaviorTreeEditor } from './composables/useBehaviorTreeEditor'; +import { EventManager } from './utils/EventManager'; -const panelDataMap = new WeakMap(); +// Vue应用实例 +let panelDataMap = new WeakMap(); -module.exports = Editor.Panel.define({ - listeners: { - show() { }, - hide() { }, - }, +// 待处理的文件队列 +let pendingFileData: any = null; +// Vue应用是否已挂载完成 +let vueAppMounted: boolean = false; +// 存储面板实例,用于访问面板的DOM元素 +let currentPanelInstance: any = null; +/** + * 面板定义 + */ +const panelDefinition = { + /** + * 面板模板 + */ template: readFileSync(join(__dirname, '../../../static/template/behavior-tree/index.html'), 'utf-8'), + + /** + * 面板样式 + */ style: readFileSync(join(__dirname, '../../../static/style/behavior-tree/index.css'), 'utf-8'), + /** + * 选择器 + */ $: { app: '#behavior-tree-app', }, + /** + * 面板方法 - 用于处理来自扩展主进程的消息 + */ methods: { - sendToMain(message: string, ...args: any[]) { - Editor.Message.send('cocos-ecs-extension', message, ...args); + /** + * 加载行为树文件 + */ + async loadBehaviorTreeFile(assetInfo: any) { + try { + const filePath = assetInfo?.file || assetInfo?.path; + if (!filePath) { + throw new Error('无法获取文件路径'); + } + + const fs = require('fs-extra'); + const path = require('path'); + + if (!fs.existsSync(filePath)) { + throw new Error(`文件不存在: ${filePath}`); + } + + const content = await fs.readFile(filePath, 'utf8'); + let fileContent: any; + + try { + fileContent = JSON.parse(content); + } catch (parseError) { + fileContent = { + version: "1.0.0", + type: "behavior-tree", + tree: { id: "root", type: "sequence", children: [] } + }; + } + + const fileInfo = { + ...fileContent, + _fileInfo: { + fileName: path.basename(filePath, path.extname(filePath)), + filePath: filePath + } + }; + + const notifyVueComponent = () => { + const appContainer = currentPanelInstance?.$.app; + + if (appContainer && vueAppMounted) { + if (typeof (appContainer as any).loadFileContent === 'function') { + (appContainer as any).loadFileContent(fileInfo); + } else { + const event = new CustomEvent('load-behavior-tree-file', { detail: fileInfo }); + document.dispatchEvent(event); + } + } else { + pendingFileData = fileInfo; + } + }; + + notifyVueComponent(); + + if (pendingFileData) { + setTimeout(() => { + if (pendingFileData) { + notifyVueComponent(); + } + }, 500); + } + + return { success: true, message: '文件加载成功' }; + + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + const event = new CustomEvent('file-load-error', { detail: { error: errorMessage } }); + document.dispatchEvent(event); + return { success: false, error: errorMessage }; + } }, + }, + + /** + * 面板准备完成时调用 + */ + ready() { + currentPanelInstance = this; - loadBehaviorTreeFile(fileData: any) { - console.log('Loading behavior tree file:', fileData); - - // 通知编辑器组件加载文件 - if (this.$.app) { - const event = new CustomEvent('load-behavior-tree-file', { - detail: fileData + if (this.$.app) { + try { + const BehaviorTreeEditor = defineComponent({ + setup() { + const editor = useBehaviorTreeEditor(); + return editor; + }, + template: readFileSync(join(__dirname, '../../../static/template/behavior-tree/BehaviorTreeEditor.html'), 'utf-8') }); - this.$.app.dispatchEvent(event); + + const app = createApp(BehaviorTreeEditor); + + app.config.compilerOptions.isCustomElement = (tag) => tag.startsWith('ui-'); + + app.config.errorHandler = (err, instance, info) => { + console.error('[BehaviorTreePanel] Vue错误:', err, info); + }; + + app.component('tree-node-item', defineComponent({ + props: ['node', 'level', 'getNodeByIdLocal'], + emits: ['node-select'], + template: ` +
+ {{ node.icon || '●' }} + {{ node.name || node.type }} + {{ node.type }} +
+ ` + })); + + app.mount(this.$.app); + panelDataMap.set(this, app); + vueAppMounted = true; + + if (pendingFileData) { + const event = new CustomEvent('load-behavior-tree-file', { detail: pendingFileData }); + document.dispatchEvent(event); + pendingFileData = null; + } + + } catch (error) { + console.error('[BehaviorTreePanel] 初始化失败:', error); } } }, - ready() { - if (this.$.app) { - const app = createApp({}); - app.config.compilerOptions.isCustomElement = (tag) => tag.startsWith('ui-'); - - // 暴露发送消息到主进程的方法 - (window as any).sendToMain = this.sendToMain.bind(this); - - // 树节点组件 - app.component('tree-node-item', defineComponent({ - props: ['node', 'level', 'getNodeByIdLocal'], - emits: ['node-select'], - template: readFileSync(join(__dirname, '../../../static/template/behavior-tree/TreeNodeItem.html'), 'utf-8') - })); - - // 行为树编辑器组件 - app.component('BehaviorTreeEditor', defineComponent({ - setup() { - const editor = useBehaviorTreeEditor(); - return editor; - }, - template: readFileSync(join(__dirname, '../../../static/template/behavior-tree/BehaviorTreeEditor.html'), 'utf-8') - })); - - app.mount(this.$.app); - panelDataMap.set(this, app); - } - }, - - beforeClose() { }, - + /** + * 面板关闭时调用 + */ close() { - const app = panelDataMap.get(this); - if (app) { - app.unmount(); + try { + const app = panelDataMap.get(this); + if (app) { + app.unmount(); + panelDataMap.delete(this); + } + + EventManager.getInstance().cleanup(); + + } catch (error) { + console.error('[BehaviorTreePanel] 清理资源时发生错误:', error); } - }, -}); \ No newline at end of file + } +}; + +// 导出面板定义 - 使用Editor.Panel.define()包装 +module.exports = Editor.Panel.define(panelDefinition); \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/utils/EventManager.ts b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/utils/EventManager.ts new file mode 100644 index 00000000..00c22189 --- /dev/null +++ b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/utils/EventManager.ts @@ -0,0 +1,104 @@ +/** + * 事件管理器 - 统一处理面板的事件通信 + */ +export class EventManager { + private static instance: EventManager; + private eventListeners: Map = new Map(); + + private constructor() {} + + static getInstance(): EventManager { + if (!EventManager.instance) { + EventManager.instance = new EventManager(); + } + return EventManager.instance; + } + + /** + * 添加事件监听器 + */ + addEventListener(eventType: string, listener: EventListener): void { + if (!this.eventListeners.has(eventType)) { + this.eventListeners.set(eventType, []); + } + + const listeners = this.eventListeners.get(eventType)!; + listeners.push(listener); + + // 添加到DOM + document.addEventListener(eventType, listener); + + console.log(`[EventManager] 添加事件监听器: ${eventType}`); + } + + /** + * 移除事件监听器 + */ + removeEventListener(eventType: string, listener: EventListener): void { + const listeners = this.eventListeners.get(eventType); + if (listeners) { + const index = listeners.indexOf(listener); + if (index > -1) { + listeners.splice(index, 1); + document.removeEventListener(eventType, listener); + console.log(`[EventManager] 移除事件监听器: ${eventType}`); + } + } + } + + /** + * 移除特定类型的所有监听器 + */ + removeAllListeners(eventType: string): void { + const listeners = this.eventListeners.get(eventType); + if (listeners) { + listeners.forEach(listener => { + document.removeEventListener(eventType, listener); + }); + this.eventListeners.delete(eventType); + console.log(`[EventManager] 移除所有 ${eventType} 事件监听器`); + } + } + + /** + * 清理所有事件监听器 + */ + cleanup(): void { + this.eventListeners.forEach((listeners, eventType) => { + listeners.forEach(listener => { + document.removeEventListener(eventType, listener); + }); + }); + this.eventListeners.clear(); + console.log('[EventManager] 清理所有事件监听器'); + } + + /** + * 发送消息到主进程 + */ + static sendToMain(message: string, ...args: any[]): void { + try { + if (typeof (window as any).sendToMain === 'function') { + (window as any).sendToMain(message, ...args); + console.log(`[EventManager] 发送消息到主进程: ${message}`, args); + } else { + console.error('[EventManager] sendToMain 方法不可用'); + } + } catch (error) { + console.error('[EventManager] 发送消息失败:', error); + } + } + + /** + * 触发自定义事件 + */ + static dispatch(eventType: string, detail?: any): void { + try { + const event = new CustomEvent(eventType, { detail }); + document.dispatchEvent(event); + console.log(`[EventManager] 触发事件: ${eventType}`, detail); + } catch (error) { + console.error(`[EventManager] 触发事件失败: ${eventType}`, error); + } + } +} \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/utils/installUtils.ts b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/utils/installUtils.ts index 745bda10..0cbbb553 100644 --- a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/utils/installUtils.ts +++ b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/utils/installUtils.ts @@ -33,7 +33,6 @@ export async function checkBehaviorTreeInstalled(projectPath: string): Promise throw new Error('安装请求失败,未收到主进程响应'); } - console.log('行为树AI系统安装完成'); + // 安装完成 } catch (error) { - console.error('行为树AI系统安装失败:', error); throw error; } } @@ -102,9 +100,8 @@ export async function updateBehaviorTreeAI(projectPath: string): Promise { throw new Error('更新请求失败,未收到主进程响应'); } - console.log('行为树AI系统更新完成'); + // 更新完成 } catch (error) { - console.error('行为树AI系统更新失败:', error); throw error; } } \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/static/style/behavior-tree/index.css b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/static/style/behavior-tree/index.css index c707d453..6e0a978a 100644 --- a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/static/style/behavior-tree/index.css +++ b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/static/style/behavior-tree/index.css @@ -33,6 +33,13 @@ text-shadow: 0 2px 4px rgba(0,0,0,0.3); } +.current-file { + color: #a0aec0; + font-weight: 400; + font-size: 16px; + margin-left: 8px; +} + .unsaved-indicator { color: #ff6b6b; animation: pulse-unsaved 2s infinite; diff --git a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/static/template/behavior-tree/BehaviorTreeEditor.html b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/static/template/behavior-tree/BehaviorTreeEditor.html index 6f757fa7..498c7038 100644 --- a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/static/template/behavior-tree/BehaviorTreeEditor.html +++ b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/static/template/behavior-tree/BehaviorTreeEditor.html @@ -1,14 +1,20 @@
-

🌳 行为树可视化编辑器

+

🌳 行为树可视化编辑器 + - {{ currentFileName }} + +

- + diff --git a/extensions/cocos/cocos-ecs/settings/v2/packages/project.json b/extensions/cocos/cocos-ecs/settings/v2/packages/project.json index 4129dde8..fae06d9c 100644 --- a/extensions/cocos/cocos-ecs/settings/v2/packages/project.json +++ b/extensions/cocos/cocos-ecs/settings/v2/packages/project.json @@ -1,3 +1,4 @@ { - "__version__": "1.0.6" + "__version__": "1.0.6", + "custom_joint_texture_layouts": [] }