fix(behavior-tree): 修复插件节点执行问题并完善文档

This commit is contained in:
YHH
2025-10-28 11:45:35 +08:00
parent fe791e83a8
commit f0b4453a5f
28 changed files with 5475 additions and 127 deletions

View File

@@ -1,4 +1,4 @@
import { Entity, IScene, createLogger } from '@esengine/ecs-framework';
import { Entity, IScene, createLogger, ComponentRegistry, Component } from '@esengine/ecs-framework';
import type { BehaviorTreeAsset, BehaviorTreeNodeData, BlackboardVariableDefinition, PropertyBinding } from './BehaviorTreeAsset';
import { BehaviorTreeNode } from '../Components/BehaviorTreeNode';
import { BlackboardComponent } from '../Components/BlackboardComponent';
@@ -306,6 +306,19 @@ export class BehaviorTreeAssetLoader {
} else if (nameLower.includes('execute') || nameLower.includes('自定义')) {
const action = entity.addComponent(new ExecuteAction());
action.actionCode = data.actionCode ?? 'return TaskStatus.Success;';
} else if (data.className) {
const ComponentClass = ComponentRegistry.getComponentType(data.className);
if (ComponentClass) {
try {
const component = new (ComponentClass as any)();
Object.assign(component, data);
entity.addComponent(component as Component);
} catch (error) {
logger.error(`创建动作组件失败: ${data.className}, error: ${error}`);
}
} else {
logger.warn(`未找到动作组件类: ${data.className}`);
}
} else {
logger.warn(`未知的动作类型: ${name}`);
}
@@ -335,6 +348,19 @@ export class BehaviorTreeAssetLoader {
const condition = entity.addComponent(new ExecuteCondition());
condition.conditionCode = data.conditionCode ?? '';
condition.invertResult = data.invertResult ?? false;
} else if (data.className) {
const ComponentClass = ComponentRegistry.getComponentType(data.className);
if (ComponentClass) {
try {
const component = new (ComponentClass as any)();
Object.assign(component, data);
entity.addComponent(component as Component);
} catch (error) {
logger.error(`创建条件组件失败: ${data.className}, error: ${error}`);
}
} else {
logger.warn(`未找到条件组件类: ${data.className}`);
}
} else {
logger.warn(`未知的条件类型: ${name}`);
}

View File

@@ -67,13 +67,11 @@ export class EditorFormatConverter {
static toAsset(editorData: EditorFormat, metadata?: Partial<AssetMetadata>): BehaviorTreeAsset {
logger.info('开始转换编辑器格式到资产格式');
// 查找根节点
const rootNode = this.findRootNode(editorData.nodes);
if (!rootNode) {
throw new Error('未找到根节点');
}
// 转换元数据
const assetMetadata: AssetMetadata = {
name: metadata?.name || editorData.metadata?.name || 'Untitled Behavior Tree',
description: metadata?.description || editorData.metadata?.description,
@@ -82,13 +80,10 @@ export class EditorFormatConverter {
modifiedAt: metadata?.modifiedAt || new Date().toISOString()
};
// 转换节点
const nodes = this.convertNodes(editorData.nodes);
// 转换黑板
const blackboard = this.convertBlackboard(editorData.blackboard);
// 转换属性绑定
const propertyBindings = this.convertPropertyBindings(
editorData.connections,
editorData.nodes,
@@ -130,11 +125,13 @@ export class EditorFormatConverter {
* 转换单个节点
*/
private static convertNode(editorNode: EditorNode): BehaviorTreeNodeData {
// 复制data去除编辑器特有的字段
const data = { ...editorNode.data };
// 移除可能存在的UI相关字段
delete data.nodeType; // 这个信息已经在nodeType字段中
delete data.nodeType;
if (editorNode.template.className) {
data.className = editorNode.template.className;
}
return {
id: editorNode.id,
@@ -152,7 +149,6 @@ export class EditorFormatConverter {
const variables: BlackboardVariableDefinition[] = [];
for (const [name, value] of Object.entries(blackboard)) {
// 推断类型
const type = this.inferBlackboardType(value);
variables.push({
@@ -191,7 +187,6 @@ export class EditorFormatConverter {
const bindings: PropertyBinding[] = [];
const blackboardVarNames = new Set(blackboard.map(v => v.name));
// 只处理属性类型的连接
const propertyConnections = connections.filter(conn => conn.connectionType === 'property');
for (const conn of propertyConnections) {
@@ -205,7 +200,6 @@ export class EditorFormatConverter {
let variableName: string | undefined;
// 检查 from 节点是否是黑板变量节点
if (fromNode.data.nodeType === 'blackboard-variable') {
variableName = fromNode.data.variableName;
} else if (conn.fromProperty) {
@@ -241,22 +235,18 @@ export class EditorFormatConverter {
static fromAsset(asset: BehaviorTreeAsset): EditorFormat {
logger.info('开始转换资产格式到编辑器格式');
// 转换节点
const nodes = this.convertNodesFromAsset(asset.nodes);
// 转换黑板
const blackboard: Record<string, any> = {};
for (const variable of asset.blackboard) {
blackboard[variable.name] = variable.defaultValue;
}
// 转换属性绑定为连接
const connections = this.convertPropertyBindingsToConnections(
asset.propertyBindings || [],
asset.nodes
);
// 添加节点连接基于children关系
const nodeConnections = this.buildNodeConnections(asset.nodes);
connections.push(...nodeConnections);
@@ -287,19 +277,24 @@ export class EditorFormatConverter {
*/
private static convertNodesFromAsset(assetNodes: BehaviorTreeNodeData[]): EditorNode[] {
return assetNodes.map((node, index) => {
// 简单的自动布局:按索引计算位置
const position = {
x: 100 + (index % 5) * 250,
y: 100 + Math.floor(index / 5) * 150
};
const template: any = {
displayName: node.name,
category: this.inferCategory(node.nodeType),
type: node.nodeType
};
if (node.data.className) {
template.className = node.data.className;
}
return {
id: node.id,
template: {
displayName: node.name,
category: this.inferCategory(node.nodeType),
type: node.nodeType
},
template,
data: { ...node.data },
position,
children: node.children
@@ -335,10 +330,8 @@ export class EditorFormatConverter {
const connections: EditorConnection[] = [];
for (const binding of bindings) {
// 需要找到代表这个黑板变量的节点(如果有的话)
// 这里简化处理,在实际使用中可能需要更复杂的逻辑
connections.push({
from: 'blackboard', // 占位符,实际使用时需要更复杂的处理
from: 'blackboard',
to: binding.nodeId,
toProperty: binding.propertyName,
connectionType: 'property'

View File

@@ -2,7 +2,7 @@ import { NodeType } from '../Types/TaskStatus';
import { getRegisteredNodeTemplates } from '../Decorators/BehaviorNodeDecorator';
/**
* 节点数据JSON格式(用于编辑器)
* 节点数据JSON格式
*/
export interface NodeDataJSON {
nodeType: string;
@@ -11,12 +11,49 @@ export interface NodeDataJSON {
[key: string]: any;
}
/**
* 内置属性类型常量
*/
export const PropertyType = {
/** 字符串 */
String: 'string',
/** 数值 */
Number: 'number',
/** 布尔值 */
Boolean: 'boolean',
/** 选择框 */
Select: 'select',
/** 黑板变量引用 */
Blackboard: 'blackboard',
/** 代码编辑器 */
Code: 'code',
/** 变量引用 */
Variable: 'variable',
/** 资产引用 */
Asset: 'asset'
} as const;
/**
* 属性类型(支持自定义扩展)
*
* @example
* ```typescript
* // 使用内置类型
* type: PropertyType.String
*
* // 使用自定义类型
* type: 'color-picker'
* type: 'curve-editor'
* ```
*/
export type PropertyType = typeof PropertyType[keyof typeof PropertyType] | string;
/**
* 属性定义(用于编辑器)
*/
export interface PropertyDefinition {
name: string;
type: 'string' | 'number' | 'boolean' | 'select' | 'blackboard' | 'code' | 'variable' | 'asset';
type: PropertyType;
label: string;
description?: string;
defaultValue?: any;
@@ -25,6 +62,62 @@ export interface PropertyDefinition {
max?: number;
step?: number;
required?: boolean;
/**
* 自定义渲染配置
*
* 用于指定编辑器如何渲染此属性
*
* @example
* ```typescript
* renderConfig: {
* component: 'ColorPicker', // 渲染器组件名称
* props: { // 传递给组件的属性
* showAlpha: true,
* presets: ['#FF0000', '#00FF00']
* }
* }
* ```
*/
renderConfig?: {
/** 渲染器组件名称或路径 */
component?: string;
/** 传递给渲染器的属性配置 */
props?: Record<string, any>;
/** 渲染器的样式类名 */
className?: string;
/** 渲染器的内联样式 */
style?: Record<string, any>;
/** 其他自定义配置 */
[key: string]: any;
};
/**
* 验证规则
*
* 用于在编辑器中验证输入
*
* @example
* ```typescript
* validation: {
* pattern: /^\d+$/,
* message: '只能输入数字',
* validator: (value) => value > 0
* }
* ```
*/
validation?: {
/** 正则表达式验证 */
pattern?: RegExp | string;
/** 验证失败的提示信息 */
message?: string;
/** 自定义验证函数 */
validator?: string; // 函数字符串,编辑器会解析
/** 最小长度(字符串) */
minLength?: number;
/** 最大长度(字符串) */
maxLength?: number;
};
}
/**
@@ -38,6 +131,7 @@ export interface NodeTemplate {
description: string;
color?: string;
className?: string;
componentClass?: Function;
requiresChildren?: boolean;
defaultConfig: Partial<NodeDataJSON>;
properties: PropertyDefinition[];