Feature/ecs behavior tree (#188)
* feat(behavior-tree): 完全 ECS 化的行为树系统 * feat(editor-app): 添加行为树可视化编辑器 * chore: 移除 Cocos Creator 扩展目录 * feat(editor-app): 行为树编辑器功能增强 * fix(editor-app): 修复 TypeScript 类型错误 * feat(editor-app): 使用 FlexLayout 重构面板系统并优化资产浏览器 * feat(editor-app): 改进编辑器UI样式并修复行为树执行顺序 * feat(behavior-tree,editor-app): 添加装饰器系统并优化编辑器性能 * feat(behavior-tree,editor-app): 添加属性绑定系统 * feat(editor-app,behavior-tree): 优化编辑器UI并改进行为树功能 * feat(editor-app,behavior-tree): 添加全局黑板系统并增强资产浏览器功能 * feat(behavior-tree,editor-app): 添加运行时资产导出系统 * feat(behavior-tree,editor-app): 添加SubTree系统和资产选择器 * feat(behavior-tree,editor-app): 优化系统架构并改进编辑器文件管理 * fix(behavior-tree,editor-app): 修复SubTree节点错误显示空节点警告 * fix(editor-app): 修复局部黑板类型定义文件扩展名错误
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
import { Component, ECSComponent, Entity } from '@esengine/ecs-framework';
|
||||
import { Serializable, Serialize, IgnoreSerialization } from '@esengine/ecs-framework';
|
||||
import { TaskStatus, NodeType } from '../../Types/TaskStatus';
|
||||
import { BlackboardComponent } from '../BlackboardComponent';
|
||||
import { BehaviorNode, BehaviorProperty } from '../../Decorators/BehaviorNodeDecorator';
|
||||
|
||||
/**
|
||||
* 自定义动作函数类型
|
||||
*/
|
||||
export type CustomActionFunction = (
|
||||
entity: Entity,
|
||||
blackboard?: BlackboardComponent,
|
||||
deltaTime?: number
|
||||
) => TaskStatus;
|
||||
|
||||
/**
|
||||
* 执行自定义函数动作组件
|
||||
*
|
||||
* 允许用户提供自定义的动作执行函数
|
||||
*/
|
||||
@BehaviorNode({
|
||||
displayName: '自定义动作',
|
||||
category: '动作',
|
||||
type: NodeType.Action,
|
||||
icon: 'Code',
|
||||
description: '执行自定义代码',
|
||||
color: '#FFC107'
|
||||
})
|
||||
@ECSComponent('ExecuteAction')
|
||||
@Serializable({ version: 1 })
|
||||
export class ExecuteAction extends Component {
|
||||
@BehaviorProperty({
|
||||
label: '动作代码',
|
||||
type: 'code',
|
||||
description: 'JavaScript 代码,返回 TaskStatus',
|
||||
required: true
|
||||
})
|
||||
@Serialize()
|
||||
actionCode?: string = 'return TaskStatus.Success;';
|
||||
|
||||
@Serialize()
|
||||
parameters: Record<string, any> = {};
|
||||
|
||||
/** 编译后的函数(不序列化) */
|
||||
@IgnoreSerialization()
|
||||
private compiledFunction?: CustomActionFunction;
|
||||
|
||||
/**
|
||||
* 获取或编译执行函数
|
||||
*/
|
||||
getFunction(): CustomActionFunction | undefined {
|
||||
if (!this.compiledFunction && this.actionCode) {
|
||||
try {
|
||||
const func = new Function(
|
||||
'entity',
|
||||
'blackboard',
|
||||
'deltaTime',
|
||||
'parameters',
|
||||
'TaskStatus',
|
||||
`
|
||||
const { Success, Failure, Running, Invalid } = TaskStatus;
|
||||
try {
|
||||
${this.actionCode}
|
||||
} catch (error) {
|
||||
return TaskStatus.Failure;
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
this.compiledFunction = (entity, blackboard, deltaTime) => {
|
||||
return func(entity, blackboard, deltaTime, this.parameters, TaskStatus) || TaskStatus.Success;
|
||||
};
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return this.compiledFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置自定义函数(运行时使用)
|
||||
*/
|
||||
setFunction(func: CustomActionFunction): void {
|
||||
this.compiledFunction = func;
|
||||
}
|
||||
}
|
||||
49
packages/behavior-tree/src/Components/Actions/LogAction.ts
Normal file
49
packages/behavior-tree/src/Components/Actions/LogAction.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { Component, ECSComponent } from '@esengine/ecs-framework';
|
||||
import { Serializable, Serialize } from '@esengine/ecs-framework';
|
||||
import { NodeType } from '../../Types/TaskStatus';
|
||||
import { BehaviorNode, BehaviorProperty } from '../../Decorators/BehaviorNodeDecorator';
|
||||
|
||||
/**
|
||||
* 日志动作组件
|
||||
*
|
||||
* 输出日志信息
|
||||
*/
|
||||
@BehaviorNode({
|
||||
displayName: '日志',
|
||||
category: '动作',
|
||||
type: NodeType.Action,
|
||||
icon: 'FileText',
|
||||
description: '输出日志消息',
|
||||
color: '#673AB7'
|
||||
})
|
||||
@ECSComponent('LogAction')
|
||||
@Serializable({ version: 1 })
|
||||
export class LogAction extends Component {
|
||||
@BehaviorProperty({
|
||||
label: '消息',
|
||||
type: 'string',
|
||||
required: true
|
||||
})
|
||||
@Serialize()
|
||||
message: string = 'Hello';
|
||||
|
||||
@BehaviorProperty({
|
||||
label: '级别',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: 'Log', value: 'log' },
|
||||
{ label: 'Info', value: 'info' },
|
||||
{ label: 'Warn', value: 'warn' },
|
||||
{ label: 'Error', value: 'error' }
|
||||
]
|
||||
})
|
||||
@Serialize()
|
||||
level: 'log' | 'info' | 'warn' | 'error' = 'log';
|
||||
|
||||
@BehaviorProperty({
|
||||
label: '包含实体信息',
|
||||
type: 'boolean'
|
||||
})
|
||||
@Serialize()
|
||||
includeEntityInfo: boolean = false;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import { Component, ECSComponent } from '@esengine/ecs-framework';
|
||||
import { Serializable, Serialize } from '@esengine/ecs-framework';
|
||||
import { NodeType } from '../../Types/TaskStatus';
|
||||
import { BehaviorNode, BehaviorProperty } from '../../Decorators/BehaviorNodeDecorator';
|
||||
|
||||
/**
|
||||
* 修改操作类型
|
||||
*/
|
||||
export enum ModifyOperation {
|
||||
/** 加法 */
|
||||
Add = 'add',
|
||||
/** 减法 */
|
||||
Subtract = 'subtract',
|
||||
/** 乘法 */
|
||||
Multiply = 'multiply',
|
||||
/** 除法 */
|
||||
Divide = 'divide',
|
||||
/** 取模 */
|
||||
Modulo = 'modulo',
|
||||
/** 追加(数组/字符串) */
|
||||
Append = 'append',
|
||||
/** 移除(数组) */
|
||||
Remove = 'remove'
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改黑板变量值动作组件
|
||||
*
|
||||
* 对黑板变量执行数学或逻辑操作
|
||||
*/
|
||||
@BehaviorNode({
|
||||
displayName: '修改变量',
|
||||
category: '动作',
|
||||
type: NodeType.Action,
|
||||
icon: 'Calculator',
|
||||
description: '对黑板变量执行数学或逻辑操作',
|
||||
color: '#FF9800'
|
||||
})
|
||||
@ECSComponent('ModifyBlackboardValueAction')
|
||||
@Serializable({ version: 1 })
|
||||
export class ModifyBlackboardValueAction extends Component {
|
||||
@BehaviorProperty({
|
||||
label: '变量名',
|
||||
type: 'variable',
|
||||
required: true
|
||||
})
|
||||
@Serialize()
|
||||
variableName: string = '';
|
||||
|
||||
@BehaviorProperty({
|
||||
label: '操作类型',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '加法', value: 'add' },
|
||||
{ label: '减法', value: 'subtract' },
|
||||
{ label: '乘法', value: 'multiply' },
|
||||
{ label: '除法', value: 'divide' },
|
||||
{ label: '取模', value: 'modulo' },
|
||||
{ label: '追加', value: 'append' },
|
||||
{ label: '移除', value: 'remove' }
|
||||
]
|
||||
})
|
||||
@Serialize()
|
||||
operation: ModifyOperation = ModifyOperation.Add;
|
||||
|
||||
@BehaviorProperty({
|
||||
label: '操作数',
|
||||
type: 'string',
|
||||
description: '可以是固定值或变量引用 {{varName}}'
|
||||
})
|
||||
@Serialize()
|
||||
operand: any = 0;
|
||||
|
||||
@Serialize()
|
||||
force: boolean = false;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Component, ECSComponent } from '@esengine/ecs-framework';
|
||||
import { Serializable, Serialize } from '@esengine/ecs-framework';
|
||||
import { NodeType } from '../../Types/TaskStatus';
|
||||
import { BehaviorNode, BehaviorProperty } from '../../Decorators/BehaviorNodeDecorator';
|
||||
|
||||
/**
|
||||
* 设置黑板变量值动作组件
|
||||
*
|
||||
* 将指定值或另一个黑板变量的值设置到目标变量
|
||||
*/
|
||||
@BehaviorNode({
|
||||
displayName: '设置变量',
|
||||
category: '动作',
|
||||
type: NodeType.Action,
|
||||
icon: 'Edit',
|
||||
description: '设置黑板变量的值',
|
||||
color: '#3F51B5'
|
||||
})
|
||||
@ECSComponent('SetBlackboardValueAction')
|
||||
@Serializable({ version: 1 })
|
||||
export class SetBlackboardValueAction extends Component {
|
||||
@BehaviorProperty({
|
||||
label: '变量名',
|
||||
type: 'variable',
|
||||
required: true
|
||||
})
|
||||
@Serialize()
|
||||
variableName: string = '';
|
||||
|
||||
@BehaviorProperty({
|
||||
label: '值',
|
||||
type: 'string',
|
||||
description: '可以使用 {{varName}} 引用其他变量'
|
||||
})
|
||||
@Serialize()
|
||||
value: any = '';
|
||||
|
||||
@Serialize()
|
||||
sourceVariable?: string;
|
||||
|
||||
@Serialize()
|
||||
force: boolean = false;
|
||||
}
|
||||
43
packages/behavior-tree/src/Components/Actions/WaitAction.ts
Normal file
43
packages/behavior-tree/src/Components/Actions/WaitAction.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Component, ECSComponent } from '@esengine/ecs-framework';
|
||||
import { Serializable, Serialize, IgnoreSerialization } from '@esengine/ecs-framework';
|
||||
import { NodeType } from '../../Types/TaskStatus';
|
||||
import { BehaviorNode, BehaviorProperty } from '../../Decorators/BehaviorNodeDecorator';
|
||||
|
||||
/**
|
||||
* 等待动作组件
|
||||
*
|
||||
* 等待指定时间后返回成功
|
||||
*/
|
||||
@BehaviorNode({
|
||||
displayName: '等待',
|
||||
category: '动作',
|
||||
type: NodeType.Action,
|
||||
icon: 'Clock',
|
||||
description: '等待指定时间',
|
||||
color: '#9E9E9E'
|
||||
})
|
||||
@ECSComponent('WaitAction')
|
||||
@Serializable({ version: 1 })
|
||||
export class WaitAction extends Component {
|
||||
@BehaviorProperty({
|
||||
label: '等待时间',
|
||||
type: 'number',
|
||||
min: 0,
|
||||
step: 0.1,
|
||||
description: '等待时间(秒)',
|
||||
required: true
|
||||
})
|
||||
@Serialize()
|
||||
waitTime: number = 1.0;
|
||||
|
||||
/** 已等待时间(秒) */
|
||||
@IgnoreSerialization()
|
||||
elapsedTime: number = 0;
|
||||
|
||||
/**
|
||||
* 重置等待状态
|
||||
*/
|
||||
reset(): void {
|
||||
this.elapsedTime = 0;
|
||||
}
|
||||
}
|
||||
20
packages/behavior-tree/src/Components/ActiveNode.ts
Normal file
20
packages/behavior-tree/src/Components/ActiveNode.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Component, ECSComponent } from '@esengine/ecs-framework';
|
||||
|
||||
/**
|
||||
* 活跃节点标记组件
|
||||
*
|
||||
* 标记当前应该被执行的节点。
|
||||
* 只有带有此组件的节点才会被各个执行系统处理。
|
||||
*
|
||||
* 这是一个标记组件(Tag Component),不包含数据,只用于标识。
|
||||
*
|
||||
* 执行流程:
|
||||
* 1. 初始时只有根节点带有 ActiveNode
|
||||
* 2. 父节点决定激活哪个子节点时,为子节点添加 ActiveNode
|
||||
* 3. 节点执行完成后移除 ActiveNode
|
||||
* 4. 通过这种方式实现按需执行,避免每帧遍历整棵树
|
||||
*/
|
||||
@ECSComponent('ActiveNode')
|
||||
export class ActiveNode extends Component {
|
||||
// 标记组件,无需数据字段
|
||||
}
|
||||
61
packages/behavior-tree/src/Components/AssetMetadata.ts
Normal file
61
packages/behavior-tree/src/Components/AssetMetadata.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { Component, ECSComponent, Serializable, Serialize } from '@esengine/ecs-framework';
|
||||
|
||||
/**
|
||||
* 资产元数据组件
|
||||
*
|
||||
* 附加到从资产实例化的行为树根节点上,
|
||||
* 用于标记资产ID和版本信息,便于循环引用检测和调试。
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const rootEntity = BehaviorTreeAssetLoader.instantiate(asset, scene);
|
||||
*
|
||||
* // 添加元数据
|
||||
* const metadata = rootEntity.addComponent(new BehaviorTreeAssetMetadata());
|
||||
* metadata.assetId = 'patrol';
|
||||
* metadata.assetVersion = '1.0.0';
|
||||
* ```
|
||||
*/
|
||||
@ECSComponent('BehaviorTreeAssetMetadata')
|
||||
@Serializable({ version: 1 })
|
||||
export class BehaviorTreeAssetMetadata extends Component {
|
||||
/**
|
||||
* 资产ID
|
||||
*/
|
||||
@Serialize()
|
||||
assetId: string = '';
|
||||
|
||||
/**
|
||||
* 资产版本
|
||||
*/
|
||||
@Serialize()
|
||||
assetVersion: string = '';
|
||||
|
||||
/**
|
||||
* 资产名称
|
||||
*/
|
||||
@Serialize()
|
||||
assetName: string = '';
|
||||
|
||||
/**
|
||||
* 加载时间
|
||||
*/
|
||||
@Serialize()
|
||||
loadedAt: number = 0;
|
||||
|
||||
/**
|
||||
* 资产描述
|
||||
*/
|
||||
@Serialize()
|
||||
description: string = '';
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
initialize(assetId: string, assetVersion: string, assetName?: string): void {
|
||||
this.assetId = assetId;
|
||||
this.assetVersion = assetVersion;
|
||||
this.assetName = assetName || assetId;
|
||||
this.loadedAt = Date.now();
|
||||
}
|
||||
}
|
||||
44
packages/behavior-tree/src/Components/BehaviorTreeNode.ts
Normal file
44
packages/behavior-tree/src/Components/BehaviorTreeNode.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Component, ECSComponent } from '@esengine/ecs-framework';
|
||||
import { Serializable, Serialize, IgnoreSerialization } from '@esengine/ecs-framework';
|
||||
import { TaskStatus, NodeType } from '../Types/TaskStatus';
|
||||
|
||||
/**
|
||||
* 行为树节点基础组件
|
||||
*
|
||||
* 所有行为树节点都必须包含此组件
|
||||
*/
|
||||
@ECSComponent('BehaviorTreeNode')
|
||||
@Serializable({ version: 1 })
|
||||
export class BehaviorTreeNode extends Component {
|
||||
/** 节点类型 */
|
||||
@Serialize()
|
||||
nodeType: NodeType = NodeType.Action;
|
||||
|
||||
/** 节点名称(用于调试) */
|
||||
@Serialize()
|
||||
nodeName: string = 'Node';
|
||||
|
||||
/** 当前执行状态 */
|
||||
@IgnoreSerialization()
|
||||
status: TaskStatus = TaskStatus.Invalid;
|
||||
|
||||
/** 当前执行的子节点索引(用于复合节点) */
|
||||
@IgnoreSerialization()
|
||||
currentChildIndex: number = 0;
|
||||
|
||||
/**
|
||||
* 重置节点状态
|
||||
*/
|
||||
reset(): void {
|
||||
this.status = TaskStatus.Invalid;
|
||||
this.currentChildIndex = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记节点为失效(递归重置子节点)
|
||||
* 注意:此方法只重置当前节点,子节点需要在 System 中处理
|
||||
*/
|
||||
invalidate(): void {
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
201
packages/behavior-tree/src/Components/BlackboardComponent.ts
Normal file
201
packages/behavior-tree/src/Components/BlackboardComponent.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
import { Component, ECSComponent, Core } from '@esengine/ecs-framework';
|
||||
import { Serializable, Serialize } from '@esengine/ecs-framework';
|
||||
import { BlackboardValueType } from '../Types/TaskStatus';
|
||||
import { GlobalBlackboardService } from '../Services/GlobalBlackboardService';
|
||||
|
||||
/**
|
||||
* 黑板变量定义
|
||||
*/
|
||||
export interface BlackboardVariable {
|
||||
name: string;
|
||||
type: BlackboardValueType;
|
||||
value: any;
|
||||
readonly?: boolean;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 黑板组件 - 用于节点间共享数据
|
||||
*
|
||||
* 支持分层查找:
|
||||
* 1. 先查找本地变量
|
||||
* 2. 如果找不到,自动查找全局 Blackboard
|
||||
*
|
||||
* 通常附加到行为树的根节点上
|
||||
*/
|
||||
@ECSComponent('Blackboard')
|
||||
@Serializable({ version: 1 })
|
||||
export class BlackboardComponent extends Component {
|
||||
/** 存储的本地变量 */
|
||||
@Serialize()
|
||||
private variables: Map<string, BlackboardVariable> = new Map();
|
||||
|
||||
/** 是否启用全局 Blackboard 查找 */
|
||||
private useGlobalBlackboard: boolean = true;
|
||||
|
||||
/**
|
||||
* 定义一个新变量
|
||||
*/
|
||||
defineVariable(
|
||||
name: string,
|
||||
type: BlackboardValueType,
|
||||
initialValue: any,
|
||||
options?: {
|
||||
readonly?: boolean;
|
||||
description?: string;
|
||||
}
|
||||
): void {
|
||||
this.variables.set(name, {
|
||||
name,
|
||||
type,
|
||||
value: initialValue,
|
||||
readonly: options?.readonly ?? false,
|
||||
description: options?.description
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取变量值
|
||||
* 先查找本地变量,找不到则查找全局变量
|
||||
*/
|
||||
getValue<T = any>(name: string): T | undefined {
|
||||
const variable = this.variables.get(name);
|
||||
if (variable !== undefined) {
|
||||
return variable.value as T;
|
||||
}
|
||||
|
||||
if (this.useGlobalBlackboard) {
|
||||
return Core.services.resolve(GlobalBlackboardService).getValue<T>(name);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本地变量值(不查找全局)
|
||||
*/
|
||||
getLocalValue<T = any>(name: string): T | undefined {
|
||||
const variable = this.variables.get(name);
|
||||
return variable?.value as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置变量值
|
||||
* 优先设置本地变量,如果本地不存在且全局存在,则设置全局变量
|
||||
*/
|
||||
setValue(name: string, value: any, force: boolean = false): boolean {
|
||||
const variable = this.variables.get(name);
|
||||
|
||||
if (variable) {
|
||||
if (variable.readonly && !force) {
|
||||
return false;
|
||||
}
|
||||
variable.value = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.useGlobalBlackboard) {
|
||||
return Core.services.resolve(GlobalBlackboardService).setValue(name, value, force);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置本地变量值(不影响全局)
|
||||
*/
|
||||
setLocalValue(name: string, value: any, force: boolean = false): boolean {
|
||||
const variable = this.variables.get(name);
|
||||
|
||||
if (!variable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (variable.readonly && !force) {
|
||||
return false;
|
||||
}
|
||||
|
||||
variable.value = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查变量是否存在(包括本地和全局)
|
||||
*/
|
||||
hasVariable(name: string): boolean {
|
||||
if (this.variables.has(name)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.useGlobalBlackboard) {
|
||||
return Core.services.resolve(GlobalBlackboardService).hasVariable(name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查本地变量是否存在
|
||||
*/
|
||||
hasLocalVariable(name: string): boolean {
|
||||
return this.variables.has(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除变量
|
||||
*/
|
||||
removeVariable(name: string): boolean {
|
||||
return this.variables.delete(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有变量名
|
||||
*/
|
||||
getVariableNames(): string[] {
|
||||
return Array.from(this.variables.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有本地变量
|
||||
*/
|
||||
clear(): void {
|
||||
this.variables.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用/禁用全局 Blackboard 查找
|
||||
*/
|
||||
setUseGlobalBlackboard(enabled: boolean): void {
|
||||
this.useGlobalBlackboard = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否启用全局 Blackboard 查找
|
||||
*/
|
||||
isUsingGlobalBlackboard(): boolean {
|
||||
return this.useGlobalBlackboard;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有变量(包括本地和全局)
|
||||
*/
|
||||
getAllVariables(): BlackboardVariable[] {
|
||||
const locals = Array.from(this.variables.values());
|
||||
|
||||
if (this.useGlobalBlackboard) {
|
||||
const globals = Core.services.resolve(GlobalBlackboardService).getAllVariables();
|
||||
const localNames = new Set(this.variables.keys());
|
||||
const filteredGlobals = globals.filter(v => !localNames.has(v.name));
|
||||
return [...locals, ...filteredGlobals];
|
||||
}
|
||||
|
||||
return locals;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全局 Blackboard 服务的引用
|
||||
*/
|
||||
static getGlobalBlackboard(): GlobalBlackboardService {
|
||||
return Core.services.resolve(GlobalBlackboardService);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Component, ECSComponent } from '@esengine/ecs-framework';
|
||||
import { Serializable, Serialize } from '@esengine/ecs-framework';
|
||||
import { CompositeType } from '../Types/TaskStatus';
|
||||
|
||||
/**
|
||||
* 复合节点组件
|
||||
*
|
||||
* 用于标识复合节点类型(Sequence, Selector, Parallel等)
|
||||
*/
|
||||
@ECSComponent('CompositeNode')
|
||||
@Serializable({ version: 1 })
|
||||
export class CompositeNodeComponent extends Component {
|
||||
/** 复合节点类型 */
|
||||
@Serialize()
|
||||
compositeType: CompositeType = CompositeType.Sequence;
|
||||
|
||||
/** 随机化的子节点索引顺序 */
|
||||
protected shuffledIndices: number[] = [];
|
||||
|
||||
/** 是否在重启时重新洗牌(子类可选) */
|
||||
protected reshuffleOnRestart: boolean = true;
|
||||
|
||||
/**
|
||||
* 获取下一个子节点索引
|
||||
*/
|
||||
getNextChildIndex(currentIndex: number, totalChildren: number): number {
|
||||
// 对于随机类型,使用洗牌后的索引
|
||||
if (this.compositeType === CompositeType.RandomSequence ||
|
||||
this.compositeType === CompositeType.RandomSelector) {
|
||||
|
||||
// 首次执行或需要重新洗牌
|
||||
if (this.shuffledIndices.length === 0 || currentIndex === 0 && this.reshuffleOnRestart) {
|
||||
this.shuffleIndices(totalChildren);
|
||||
}
|
||||
|
||||
if (currentIndex < this.shuffledIndices.length) {
|
||||
return this.shuffledIndices[currentIndex];
|
||||
}
|
||||
return totalChildren; // 结束
|
||||
}
|
||||
|
||||
// 普通顺序执行
|
||||
return currentIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* 洗牌子节点索引
|
||||
*/
|
||||
private shuffleIndices(count: number): void {
|
||||
this.shuffledIndices = Array.from({ length: count }, (_, i) => i);
|
||||
|
||||
// Fisher-Yates 洗牌算法
|
||||
for (let i = this.shuffledIndices.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[this.shuffledIndices[i], this.shuffledIndices[j]] =
|
||||
[this.shuffledIndices[j], this.shuffledIndices[i]];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置洗牌状态
|
||||
*/
|
||||
resetShuffle(): void {
|
||||
this.shuffledIndices = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { ECSComponent } from '@esengine/ecs-framework';
|
||||
import { Serializable, Serialize } from '@esengine/ecs-framework';
|
||||
import { NodeType, CompositeType } from '../../Types/TaskStatus';
|
||||
import { BehaviorNode, BehaviorProperty } from '../../Decorators/BehaviorNodeDecorator';
|
||||
import { CompositeNodeComponent } from '../CompositeNodeComponent';
|
||||
|
||||
/**
|
||||
* 并行节点
|
||||
*
|
||||
* 同时执行所有子节点
|
||||
*/
|
||||
@BehaviorNode({
|
||||
displayName: '并行',
|
||||
category: '组合',
|
||||
type: NodeType.Composite,
|
||||
icon: 'Layers',
|
||||
description: '同时执行所有子节点',
|
||||
color: '#CDDC39'
|
||||
})
|
||||
@ECSComponent('ParallelNode')
|
||||
@Serializable({ version: 1 })
|
||||
export class ParallelNode extends CompositeNodeComponent {
|
||||
@BehaviorProperty({
|
||||
label: '成功策略',
|
||||
type: 'select',
|
||||
description: '多少个子节点成功时整体成功',
|
||||
options: [
|
||||
{ label: '全部成功', value: 'all' },
|
||||
{ label: '任意一个成功', value: 'one' }
|
||||
]
|
||||
})
|
||||
@Serialize()
|
||||
successPolicy: 'all' | 'one' = 'all';
|
||||
|
||||
@BehaviorProperty({
|
||||
label: '失败策略',
|
||||
type: 'select',
|
||||
description: '多少个子节点失败时整体失败',
|
||||
options: [
|
||||
{ label: '任意一个失败', value: 'one' },
|
||||
{ label: '全部失败', value: 'all' }
|
||||
]
|
||||
})
|
||||
@Serialize()
|
||||
failurePolicy: 'one' | 'all' = 'one';
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.compositeType = CompositeType.Parallel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { ECSComponent } from '@esengine/ecs-framework';
|
||||
import { Serializable, Serialize } from '@esengine/ecs-framework';
|
||||
import { NodeType, CompositeType } from '../../Types/TaskStatus';
|
||||
import { BehaviorNode, BehaviorProperty } from '../../Decorators/BehaviorNodeDecorator';
|
||||
import { CompositeNodeComponent } from '../CompositeNodeComponent';
|
||||
|
||||
/**
|
||||
* 并行选择节点
|
||||
*
|
||||
* 并行执行子节点,任一成功则成功
|
||||
*/
|
||||
@BehaviorNode({
|
||||
displayName: '并行选择',
|
||||
category: '组合',
|
||||
type: NodeType.Composite,
|
||||
icon: 'Sparkles',
|
||||
description: '并行执行子节点,任一成功则成功',
|
||||
color: '#FFC107'
|
||||
})
|
||||
@ECSComponent('ParallelSelectorNode')
|
||||
@Serializable({ version: 1 })
|
||||
export class ParallelSelectorNode extends CompositeNodeComponent {
|
||||
@BehaviorProperty({
|
||||
label: '失败策略',
|
||||
type: 'select',
|
||||
description: '多少个子节点失败时整体失败',
|
||||
options: [
|
||||
{ label: '任意一个失败', value: 'one' },
|
||||
{ label: '全部失败', value: 'all' }
|
||||
]
|
||||
})
|
||||
@Serialize()
|
||||
failurePolicy: 'one' | 'all' = 'all';
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.compositeType = CompositeType.ParallelSelector;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { ECSComponent } from '@esengine/ecs-framework';
|
||||
import { Serializable, Serialize } from '@esengine/ecs-framework';
|
||||
import { NodeType, CompositeType } from '../../Types/TaskStatus';
|
||||
import { BehaviorNode, BehaviorProperty } from '../../Decorators/BehaviorNodeDecorator';
|
||||
import { CompositeNodeComponent } from '../CompositeNodeComponent';
|
||||
|
||||
/**
|
||||
* 随机选择节点
|
||||
*
|
||||
* 随机顺序执行子节点选择
|
||||
*/
|
||||
@BehaviorNode({
|
||||
displayName: '随机选择',
|
||||
category: '组合',
|
||||
type: NodeType.Composite,
|
||||
icon: 'Dices',
|
||||
description: '随机顺序执行子节点选择',
|
||||
color: '#F44336'
|
||||
})
|
||||
@ECSComponent('RandomSelectorNode')
|
||||
@Serializable({ version: 1 })
|
||||
export class RandomSelectorNode extends CompositeNodeComponent {
|
||||
@BehaviorProperty({
|
||||
label: '重启时重新洗牌',
|
||||
type: 'boolean',
|
||||
description: '每次重启时是否重新随机子节点顺序'
|
||||
})
|
||||
@Serialize()
|
||||
override reshuffleOnRestart: boolean = true;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.compositeType = CompositeType.RandomSelector;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { ECSComponent } from '@esengine/ecs-framework';
|
||||
import { Serializable, Serialize } from '@esengine/ecs-framework';
|
||||
import { NodeType, CompositeType } from '../../Types/TaskStatus';
|
||||
import { BehaviorNode, BehaviorProperty } from '../../Decorators/BehaviorNodeDecorator';
|
||||
import { CompositeNodeComponent } from '../CompositeNodeComponent';
|
||||
|
||||
/**
|
||||
* 随机序列节点
|
||||
*
|
||||
* 随机顺序执行子节点序列
|
||||
*/
|
||||
@BehaviorNode({
|
||||
displayName: '随机序列',
|
||||
category: '组合',
|
||||
type: NodeType.Composite,
|
||||
icon: 'Shuffle',
|
||||
description: '随机顺序执行子节点序列',
|
||||
color: '#FF5722'
|
||||
})
|
||||
@ECSComponent('RandomSequenceNode')
|
||||
@Serializable({ version: 1 })
|
||||
export class RandomSequenceNode extends CompositeNodeComponent {
|
||||
@BehaviorProperty({
|
||||
label: '重启时重新洗牌',
|
||||
type: 'boolean',
|
||||
description: '每次重启时是否重新随机子节点顺序'
|
||||
})
|
||||
@Serialize()
|
||||
override reshuffleOnRestart: boolean = true;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.compositeType = CompositeType.RandomSequence;
|
||||
}
|
||||
}
|
||||
27
packages/behavior-tree/src/Components/Composites/RootNode.ts
Normal file
27
packages/behavior-tree/src/Components/Composites/RootNode.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { ECSComponent } from '@esengine/ecs-framework';
|
||||
import { Serializable } from '@esengine/ecs-framework';
|
||||
import { NodeType, CompositeType } from '../../Types/TaskStatus';
|
||||
import { BehaviorNode } from '../../Decorators/BehaviorNodeDecorator';
|
||||
import { CompositeNodeComponent } from '../CompositeNodeComponent';
|
||||
|
||||
/**
|
||||
* 根节点
|
||||
*
|
||||
* 行为树的根节点,简单地激活第一个子节点
|
||||
*/
|
||||
@BehaviorNode({
|
||||
displayName: '根节点',
|
||||
category: '根节点',
|
||||
type: NodeType.Composite,
|
||||
icon: 'TreePine',
|
||||
description: '行为树的根节点',
|
||||
color: '#FFD700'
|
||||
})
|
||||
@ECSComponent('RootNode')
|
||||
@Serializable({ version: 1 })
|
||||
export class RootNode extends CompositeNodeComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.compositeType = CompositeType.Sequence;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { ECSComponent } from '@esengine/ecs-framework';
|
||||
import { Serializable, Serialize } from '@esengine/ecs-framework';
|
||||
import { NodeType, CompositeType, AbortType } from '../../Types/TaskStatus';
|
||||
import { BehaviorNode, BehaviorProperty } from '../../Decorators/BehaviorNodeDecorator';
|
||||
import { CompositeNodeComponent } from '../CompositeNodeComponent';
|
||||
|
||||
/**
|
||||
* 选择节点
|
||||
*
|
||||
* 按顺序执行子节点,任一成功则成功
|
||||
*/
|
||||
@BehaviorNode({
|
||||
displayName: '选择',
|
||||
category: '组合',
|
||||
type: NodeType.Composite,
|
||||
icon: 'GitBranch',
|
||||
description: '按顺序执行子节点,任一成功则成功',
|
||||
color: '#8BC34A'
|
||||
})
|
||||
@ECSComponent('SelectorNode')
|
||||
@Serializable({ version: 1 })
|
||||
export class SelectorNode extends CompositeNodeComponent {
|
||||
@BehaviorProperty({
|
||||
label: '中止类型',
|
||||
type: 'select',
|
||||
description: '条件变化时的中止行为',
|
||||
options: [
|
||||
{ label: '无', value: 'none' },
|
||||
{ label: '自身', value: 'self' },
|
||||
{ label: '低优先级', value: 'lower-priority' },
|
||||
{ label: '两者', value: 'both' }
|
||||
]
|
||||
})
|
||||
@Serialize()
|
||||
abortType: AbortType = AbortType.None;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.compositeType = CompositeType.Selector;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { ECSComponent } from '@esengine/ecs-framework';
|
||||
import { Serializable, Serialize } from '@esengine/ecs-framework';
|
||||
import { NodeType, CompositeType, AbortType } from '../../Types/TaskStatus';
|
||||
import { BehaviorNode, BehaviorProperty } from '../../Decorators/BehaviorNodeDecorator';
|
||||
import { CompositeNodeComponent } from '../CompositeNodeComponent';
|
||||
|
||||
/**
|
||||
* 序列节点
|
||||
*
|
||||
* 按顺序执行所有子节点,全部成功才成功
|
||||
*/
|
||||
@BehaviorNode({
|
||||
displayName: '序列',
|
||||
category: '组合',
|
||||
type: NodeType.Composite,
|
||||
icon: 'List',
|
||||
description: '按顺序执行子节点,全部成功才成功',
|
||||
color: '#4CAF50'
|
||||
})
|
||||
@ECSComponent('SequenceNode')
|
||||
@Serializable({ version: 1 })
|
||||
export class SequenceNode extends CompositeNodeComponent {
|
||||
@BehaviorProperty({
|
||||
label: '中止类型',
|
||||
type: 'select',
|
||||
description: '条件变化时的中止行为',
|
||||
options: [
|
||||
{ label: '无', value: 'none' },
|
||||
{ label: '自身', value: 'self' },
|
||||
{ label: '低优先级', value: 'lower-priority' },
|
||||
{ label: '两者', value: 'both' }
|
||||
]
|
||||
})
|
||||
@Serialize()
|
||||
abortType: AbortType = AbortType.None;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.compositeType = CompositeType.Sequence;
|
||||
}
|
||||
}
|
||||
172
packages/behavior-tree/src/Components/Composites/SubTreeNode.ts
Normal file
172
packages/behavior-tree/src/Components/Composites/SubTreeNode.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
import { ECSComponent, Serializable, Serialize, Entity } from '@esengine/ecs-framework';
|
||||
import { CompositeNodeComponent } from '../CompositeNodeComponent';
|
||||
import { TaskStatus, NodeType } from '../../Types/TaskStatus';
|
||||
import { BehaviorNode, BehaviorProperty } from '../../Decorators/BehaviorNodeDecorator';
|
||||
|
||||
/**
|
||||
* SubTree 节点 - 引用其他行为树作为子树
|
||||
*
|
||||
* 允许将其他行为树嵌入到当前树中,实现行为树的复用和模块化。
|
||||
*
|
||||
* 注意:SubTreeNode 是一个特殊的叶子节点,它不会执行编辑器中静态连接的子节点,
|
||||
* 只会执行从 assetId 动态加载的外部行为树文件。
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const subTree = entity.addComponent(SubTreeNode);
|
||||
* subTree.assetId = 'patrol';
|
||||
* subTree.inheritParentBlackboard = true;
|
||||
* ```
|
||||
*/
|
||||
@BehaviorNode({
|
||||
displayName: '子树',
|
||||
category: '组合',
|
||||
type: NodeType.Composite,
|
||||
icon: 'GitBranch',
|
||||
description: '引用并执行外部行为树文件(不支持静态子节点)',
|
||||
color: '#FF9800',
|
||||
requiresChildren: false
|
||||
})
|
||||
@ECSComponent('SubTreeNode')
|
||||
@Serializable({ version: 1 })
|
||||
export class SubTreeNode extends CompositeNodeComponent {
|
||||
/**
|
||||
* 引用的子树资产ID
|
||||
* 逻辑标识符,例如 'patrol' 或 'ai/patrol'
|
||||
* 实际的文件路径由 AssetLoader 决定
|
||||
*/
|
||||
@BehaviorProperty({
|
||||
label: '资产ID',
|
||||
type: 'asset',
|
||||
description: '要引用的行为树资产ID'
|
||||
})
|
||||
@Serialize()
|
||||
assetId: string = '';
|
||||
|
||||
/**
|
||||
* 是否将父黑板传递给子树
|
||||
*
|
||||
* - true: 子树可以访问和修改父树的黑板变量
|
||||
* - false: 子树使用独立的黑板实例
|
||||
*/
|
||||
@BehaviorProperty({
|
||||
label: '继承父黑板',
|
||||
type: 'boolean',
|
||||
description: '子树是否可以访问父树的黑板变量'
|
||||
})
|
||||
@Serialize()
|
||||
inheritParentBlackboard: boolean = true;
|
||||
|
||||
/**
|
||||
* 子树执行失败时是否传播失败状态
|
||||
*
|
||||
* - true: 子树失败时,SubTree 节点返回 Failure
|
||||
* - false: 子树失败时,SubTree 节点返回 Success(忽略失败)
|
||||
*/
|
||||
@BehaviorProperty({
|
||||
label: '传播失败',
|
||||
type: 'boolean',
|
||||
description: '子树失败时是否传播失败状态'
|
||||
})
|
||||
@Serialize()
|
||||
propagateFailure: boolean = true;
|
||||
|
||||
/**
|
||||
* 是否在行为树启动时预加载子树
|
||||
*
|
||||
* - true: 在根节点开始执行前预加载此子树,确保执行时子树已就绪
|
||||
* - false: 运行时异步加载,执行到此节点时才开始加载(可能会有延迟)
|
||||
*/
|
||||
@BehaviorProperty({
|
||||
label: '预加载',
|
||||
type: 'boolean',
|
||||
description: '在行为树启动时预加载子树,避免运行时加载延迟'
|
||||
})
|
||||
@Serialize()
|
||||
preload: boolean = true;
|
||||
|
||||
/**
|
||||
* 子树的根实体(运行时)
|
||||
* 在执行时动态创建,执行结束后销毁
|
||||
*/
|
||||
private subTreeRoot?: Entity;
|
||||
|
||||
/**
|
||||
* 子树是否已完成
|
||||
*/
|
||||
private subTreeCompleted: boolean = false;
|
||||
|
||||
/**
|
||||
* 子树的最终状态
|
||||
*/
|
||||
private subTreeResult: TaskStatus = TaskStatus.Invalid;
|
||||
|
||||
/**
|
||||
* 获取子树根实体
|
||||
*/
|
||||
getSubTreeRoot(): Entity | undefined {
|
||||
return this.subTreeRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置子树根实体(由执行系统调用)
|
||||
*/
|
||||
setSubTreeRoot(root: Entity | undefined): void {
|
||||
this.subTreeRoot = root;
|
||||
this.subTreeCompleted = false;
|
||||
this.subTreeResult = TaskStatus.Invalid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记子树完成(由执行系统调用)
|
||||
*/
|
||||
markSubTreeCompleted(result: TaskStatus): void {
|
||||
this.subTreeCompleted = true;
|
||||
this.subTreeResult = result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查子树是否已完成
|
||||
*/
|
||||
isSubTreeCompleted(): boolean {
|
||||
return this.subTreeCompleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取子树执行结果
|
||||
*/
|
||||
getSubTreeResult(): TaskStatus {
|
||||
return this.subTreeResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置子树状态
|
||||
*/
|
||||
reset(): void {
|
||||
this.subTreeRoot = undefined;
|
||||
this.subTreeCompleted = false;
|
||||
this.subTreeResult = TaskStatus.Invalid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置完成状态(用于复用预加载的子树)
|
||||
* 保留子树根引用,只重置完成标记
|
||||
*/
|
||||
resetCompletionState(): void {
|
||||
this.subTreeCompleted = false;
|
||||
this.subTreeResult = TaskStatus.Invalid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证配置
|
||||
*/
|
||||
validate(): string[] {
|
||||
const errors: string[] = [];
|
||||
|
||||
if (!this.assetId || this.assetId.trim() === '') {
|
||||
errors.push('SubTree 节点必须指定资产ID');
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import { Component, ECSComponent } from '@esengine/ecs-framework';
|
||||
import { Serializable, Serialize } from '@esengine/ecs-framework';
|
||||
import { NodeType } from '../../Types/TaskStatus';
|
||||
import { BehaviorNode, BehaviorProperty } from '../../Decorators/BehaviorNodeDecorator';
|
||||
|
||||
/**
|
||||
* 比较运算符
|
||||
*/
|
||||
export enum CompareOperator {
|
||||
/** 等于 */
|
||||
Equal = 'equal',
|
||||
/** 不等于 */
|
||||
NotEqual = 'notEqual',
|
||||
/** 大于 */
|
||||
Greater = 'greater',
|
||||
/** 大于等于 */
|
||||
GreaterOrEqual = 'greaterOrEqual',
|
||||
/** 小于 */
|
||||
Less = 'less',
|
||||
/** 小于等于 */
|
||||
LessOrEqual = 'lessOrEqual',
|
||||
/** 包含(字符串/数组) */
|
||||
Contains = 'contains',
|
||||
/** 正则匹配 */
|
||||
Matches = 'matches'
|
||||
}
|
||||
|
||||
/**
|
||||
* 黑板变量比较条件组件
|
||||
*
|
||||
* 比较黑板变量与指定值或另一个变量
|
||||
*/
|
||||
@BehaviorNode({
|
||||
displayName: '比较变量',
|
||||
category: '条件',
|
||||
type: NodeType.Condition,
|
||||
icon: 'Scale',
|
||||
description: '比较黑板变量与指定值',
|
||||
color: '#2196F3'
|
||||
})
|
||||
@ECSComponent('BlackboardCompareCondition')
|
||||
@Serializable({ version: 1 })
|
||||
export class BlackboardCompareCondition extends Component {
|
||||
@BehaviorProperty({
|
||||
label: '变量名',
|
||||
type: 'variable',
|
||||
required: true
|
||||
})
|
||||
@Serialize()
|
||||
variableName: string = '';
|
||||
|
||||
@BehaviorProperty({
|
||||
label: '运算符',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '等于', value: 'equal' },
|
||||
{ label: '不等于', value: 'notEqual' },
|
||||
{ label: '大于', value: 'greater' },
|
||||
{ label: '大于等于', value: 'greaterOrEqual' },
|
||||
{ label: '小于', value: 'less' },
|
||||
{ label: '小于等于', value: 'lessOrEqual' },
|
||||
{ label: '包含', value: 'contains' },
|
||||
{ label: '正则匹配', value: 'matches' }
|
||||
]
|
||||
})
|
||||
@Serialize()
|
||||
operator: CompareOperator = CompareOperator.Equal;
|
||||
|
||||
@BehaviorProperty({
|
||||
label: '比较值',
|
||||
type: 'string',
|
||||
description: '可以是固定值或变量引用 {{varName}}'
|
||||
})
|
||||
@Serialize()
|
||||
compareValue: any = null;
|
||||
|
||||
@BehaviorProperty({
|
||||
label: '反转结果',
|
||||
type: 'boolean'
|
||||
})
|
||||
@Serialize()
|
||||
invertResult: boolean = false;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { Component, ECSComponent } from '@esengine/ecs-framework';
|
||||
import { Serializable, Serialize } from '@esengine/ecs-framework';
|
||||
import { NodeType } from '../../Types/TaskStatus';
|
||||
import { BehaviorNode, BehaviorProperty } from '../../Decorators/BehaviorNodeDecorator';
|
||||
|
||||
/**
|
||||
* 黑板变量存在性检查条件组件
|
||||
*
|
||||
* 检查黑板变量是否存在
|
||||
*/
|
||||
@BehaviorNode({
|
||||
displayName: '检查变量存在',
|
||||
category: '条件',
|
||||
type: NodeType.Condition,
|
||||
icon: 'Search',
|
||||
description: '检查黑板变量是否存在',
|
||||
color: '#00BCD4'
|
||||
})
|
||||
@ECSComponent('BlackboardExistsCondition')
|
||||
@Serializable({ version: 1 })
|
||||
export class BlackboardExistsCondition extends Component {
|
||||
@BehaviorProperty({
|
||||
label: '变量名',
|
||||
type: 'variable',
|
||||
required: true
|
||||
})
|
||||
@Serialize()
|
||||
variableName: string = '';
|
||||
|
||||
@BehaviorProperty({
|
||||
label: '检查非空',
|
||||
type: 'boolean',
|
||||
description: '检查值不为 null/undefined'
|
||||
})
|
||||
@Serialize()
|
||||
checkNotNull: boolean = false;
|
||||
|
||||
@BehaviorProperty({
|
||||
label: '反转结果',
|
||||
type: 'boolean',
|
||||
description: '检查不存在'
|
||||
})
|
||||
@Serialize()
|
||||
invertResult: boolean = false;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import { Component, ECSComponent, Entity } from '@esengine/ecs-framework';
|
||||
import { Serializable, Serialize, IgnoreSerialization } from '@esengine/ecs-framework';
|
||||
import { NodeType } from '../../Types/TaskStatus';
|
||||
import { BehaviorNode, BehaviorProperty } from '../../Decorators/BehaviorNodeDecorator';
|
||||
import { BlackboardComponent } from '../BlackboardComponent';
|
||||
|
||||
/**
|
||||
* 自定义条件函数类型
|
||||
*/
|
||||
export type CustomConditionFunction = (
|
||||
entity: Entity,
|
||||
blackboard?: BlackboardComponent,
|
||||
deltaTime?: number
|
||||
) => boolean;
|
||||
|
||||
/**
|
||||
* 执行自定义条件组件
|
||||
*
|
||||
* 允许用户提供自定义的条件检查函数
|
||||
*/
|
||||
@BehaviorNode({
|
||||
displayName: '自定义条件',
|
||||
category: '条件',
|
||||
type: NodeType.Condition,
|
||||
icon: 'Code',
|
||||
description: '执行自定义条件代码',
|
||||
color: '#9C27B0'
|
||||
})
|
||||
@ECSComponent('ExecuteCondition')
|
||||
@Serializable({ version: 1 })
|
||||
export class ExecuteCondition extends Component {
|
||||
@BehaviorProperty({
|
||||
label: '条件代码',
|
||||
type: 'code',
|
||||
description: 'JavaScript 代码,返回 boolean',
|
||||
required: true
|
||||
})
|
||||
@Serialize()
|
||||
conditionCode?: string;
|
||||
|
||||
@Serialize()
|
||||
parameters: Record<string, any> = {};
|
||||
|
||||
@BehaviorProperty({
|
||||
label: '反转结果',
|
||||
type: 'boolean'
|
||||
})
|
||||
@Serialize()
|
||||
invertResult: boolean = false;
|
||||
|
||||
/** 编译后的函数(不序列化) */
|
||||
@IgnoreSerialization()
|
||||
private compiledFunction?: CustomConditionFunction;
|
||||
|
||||
/**
|
||||
* 获取或编译条件函数
|
||||
*/
|
||||
getFunction(): CustomConditionFunction | undefined {
|
||||
if (!this.compiledFunction && this.conditionCode) {
|
||||
try {
|
||||
const func = new Function(
|
||||
'entity',
|
||||
'blackboard',
|
||||
'deltaTime',
|
||||
'parameters',
|
||||
`
|
||||
try {
|
||||
${this.conditionCode}
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
this.compiledFunction = (entity, blackboard, deltaTime) => {
|
||||
return Boolean(func(entity, blackboard, deltaTime, this.parameters));
|
||||
};
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return this.compiledFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置自定义函数(运行时使用)
|
||||
*/
|
||||
setFunction(func: CustomConditionFunction): void {
|
||||
this.compiledFunction = func;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { Component, ECSComponent } from '@esengine/ecs-framework';
|
||||
import { Serializable, Serialize } from '@esengine/ecs-framework';
|
||||
import { NodeType } from '../../Types/TaskStatus';
|
||||
import { BehaviorNode, BehaviorProperty } from '../../Decorators/BehaviorNodeDecorator';
|
||||
|
||||
/**
|
||||
* 随机概率条件组件
|
||||
*
|
||||
* 根据概率返回成功或失败
|
||||
*/
|
||||
@BehaviorNode({
|
||||
displayName: '随机概率',
|
||||
category: '条件',
|
||||
type: NodeType.Condition,
|
||||
icon: 'Dice',
|
||||
description: '根据概率返回成功或失败',
|
||||
color: '#E91E63'
|
||||
})
|
||||
@ECSComponent('RandomProbabilityCondition')
|
||||
@Serializable({ version: 1 })
|
||||
export class RandomProbabilityCondition extends Component {
|
||||
@BehaviorProperty({
|
||||
label: '成功概率',
|
||||
type: 'number',
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.1,
|
||||
description: '0.0 - 1.0',
|
||||
required: true
|
||||
})
|
||||
@Serialize()
|
||||
probability: number = 0.5;
|
||||
|
||||
@BehaviorProperty({
|
||||
label: '总是重新随机',
|
||||
type: 'boolean',
|
||||
description: 'false则第一次随机后固定结果'
|
||||
})
|
||||
@Serialize()
|
||||
alwaysRandomize: boolean = true;
|
||||
|
||||
/** 缓存的随机结果(不序列化) */
|
||||
private cachedResult?: boolean;
|
||||
|
||||
/**
|
||||
* 评估随机概率
|
||||
*/
|
||||
evaluate(): boolean {
|
||||
if (this.alwaysRandomize || this.cachedResult === undefined) {
|
||||
this.cachedResult = Math.random() < this.probability;
|
||||
}
|
||||
return this.cachedResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置缓存
|
||||
*/
|
||||
reset(): void {
|
||||
this.cachedResult = undefined;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Component, ECSComponent } from '@esengine/ecs-framework';
|
||||
import { Serializable, Serialize } from '@esengine/ecs-framework';
|
||||
import { DecoratorType } from '../Types/TaskStatus';
|
||||
|
||||
/**
|
||||
* 装饰器节点组件基类
|
||||
*
|
||||
* 只包含通用的装饰器类型标识
|
||||
* 具体的属性由各个子类自己定义
|
||||
*/
|
||||
@ECSComponent('DecoratorNode')
|
||||
@Serializable({ version: 1 })
|
||||
export class DecoratorNodeComponent extends Component {
|
||||
/** 装饰器类型 */
|
||||
@Serialize()
|
||||
decoratorType: DecoratorType = DecoratorType.Inverter;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { ECSComponent } from '@esengine/ecs-framework';
|
||||
import { Serializable } from '@esengine/ecs-framework';
|
||||
import { NodeType, DecoratorType } from '../../Types/TaskStatus';
|
||||
import { BehaviorNode } from '../../Decorators/BehaviorNodeDecorator';
|
||||
import { DecoratorNodeComponent } from '../DecoratorNodeComponent';
|
||||
|
||||
/**
|
||||
* 总是失败节点
|
||||
*
|
||||
* 无论子节点结果如何都返回失败
|
||||
*/
|
||||
@BehaviorNode({
|
||||
displayName: '总是失败',
|
||||
category: '装饰器',
|
||||
type: NodeType.Decorator,
|
||||
icon: 'ThumbsDown',
|
||||
description: '无论子节点结果如何都返回失败',
|
||||
color: '#FF5722'
|
||||
})
|
||||
@ECSComponent('AlwaysFailNode')
|
||||
@Serializable({ version: 1 })
|
||||
export class AlwaysFailNode extends DecoratorNodeComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.decoratorType = DecoratorType.AlwaysFail;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { ECSComponent } from '@esengine/ecs-framework';
|
||||
import { Serializable } from '@esengine/ecs-framework';
|
||||
import { NodeType, DecoratorType } from '../../Types/TaskStatus';
|
||||
import { BehaviorNode } from '../../Decorators/BehaviorNodeDecorator';
|
||||
import { DecoratorNodeComponent } from '../DecoratorNodeComponent';
|
||||
|
||||
/**
|
||||
* 总是成功节点
|
||||
*
|
||||
* 无论子节点结果如何都返回成功
|
||||
*/
|
||||
@BehaviorNode({
|
||||
displayName: '总是成功',
|
||||
category: '装饰器',
|
||||
type: NodeType.Decorator,
|
||||
icon: 'ThumbsUp',
|
||||
description: '无论子节点结果如何都返回成功',
|
||||
color: '#8BC34A'
|
||||
})
|
||||
@ECSComponent('AlwaysSucceedNode')
|
||||
@Serializable({ version: 1 })
|
||||
export class AlwaysSucceedNode extends DecoratorNodeComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.decoratorType = DecoratorType.AlwaysSucceed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import { ECSComponent, Entity } from '@esengine/ecs-framework';
|
||||
import { Serializable, Serialize, IgnoreSerialization } from '@esengine/ecs-framework';
|
||||
import { NodeType, DecoratorType } from '../../Types/TaskStatus';
|
||||
import { BehaviorNode, BehaviorProperty } from '../../Decorators/BehaviorNodeDecorator';
|
||||
import { DecoratorNodeComponent } from '../DecoratorNodeComponent';
|
||||
import { BlackboardComponent } from '../BlackboardComponent';
|
||||
|
||||
/**
|
||||
* 条件装饰器节点
|
||||
*
|
||||
* 基于条件判断是否执行子节点
|
||||
*/
|
||||
@BehaviorNode({
|
||||
displayName: '条件装饰器',
|
||||
category: '装饰器',
|
||||
type: NodeType.Decorator,
|
||||
icon: 'Filter',
|
||||
description: '基于条件判断是否执行子节点',
|
||||
color: '#3F51B5'
|
||||
})
|
||||
@ECSComponent('ConditionalNode')
|
||||
@Serializable({ version: 1 })
|
||||
export class ConditionalNode extends DecoratorNodeComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.decoratorType = DecoratorType.Conditional;
|
||||
}
|
||||
|
||||
@BehaviorProperty({
|
||||
label: '条件代码',
|
||||
type: 'code',
|
||||
description: 'JavaScript 代码,返回 boolean',
|
||||
required: true
|
||||
})
|
||||
@Serialize()
|
||||
conditionCode?: string;
|
||||
|
||||
@BehaviorProperty({
|
||||
label: '重新评估条件',
|
||||
type: 'boolean',
|
||||
description: '每次执行时是否重新评估条件'
|
||||
})
|
||||
@Serialize()
|
||||
shouldReevaluate: boolean = true;
|
||||
|
||||
/** 编译后的条件函数(不序列化) */
|
||||
@IgnoreSerialization()
|
||||
private compiledCondition?: (entity: Entity, blackboard?: BlackboardComponent) => boolean;
|
||||
|
||||
/**
|
||||
* 评估条件
|
||||
*/
|
||||
evaluateCondition(entity: Entity, blackboard?: BlackboardComponent): boolean {
|
||||
if (!this.conditionCode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.compiledCondition) {
|
||||
try {
|
||||
const func = new Function(
|
||||
'entity',
|
||||
'blackboard',
|
||||
`
|
||||
try {
|
||||
return Boolean(${this.conditionCode});
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
this.compiledCondition = (entity, blackboard) => {
|
||||
return Boolean(func(entity, blackboard));
|
||||
};
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return this.compiledCondition(entity, blackboard);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置条件函数(运行时使用)
|
||||
*/
|
||||
setConditionFunction(func: (entity: Entity, blackboard?: BlackboardComponent) => boolean): void {
|
||||
this.compiledCondition = func;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { ECSComponent } from '@esengine/ecs-framework';
|
||||
import { Serializable, Serialize, IgnoreSerialization } from '@esengine/ecs-framework';
|
||||
import { NodeType, DecoratorType } from '../../Types/TaskStatus';
|
||||
import { BehaviorNode, BehaviorProperty } from '../../Decorators/BehaviorNodeDecorator';
|
||||
import { DecoratorNodeComponent } from '../DecoratorNodeComponent';
|
||||
|
||||
/**
|
||||
* 冷却节点
|
||||
*
|
||||
* 在冷却时间内阻止子节点执行
|
||||
*/
|
||||
@BehaviorNode({
|
||||
displayName: '冷却',
|
||||
category: '装饰器',
|
||||
type: NodeType.Decorator,
|
||||
icon: 'Timer',
|
||||
description: '在冷却时间内阻止子节点执行',
|
||||
color: '#00BCD4'
|
||||
})
|
||||
@ECSComponent('CooldownNode')
|
||||
@Serializable({ version: 1 })
|
||||
export class CooldownNode extends DecoratorNodeComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.decoratorType = DecoratorType.Cooldown;
|
||||
}
|
||||
|
||||
@BehaviorProperty({
|
||||
label: '冷却时间',
|
||||
type: 'number',
|
||||
min: 0,
|
||||
step: 0.1,
|
||||
description: '冷却时间(秒)',
|
||||
required: true
|
||||
})
|
||||
@Serialize()
|
||||
cooldownTime: number = 1.0;
|
||||
|
||||
/** 上次执行时间 */
|
||||
@IgnoreSerialization()
|
||||
lastExecutionTime: number = 0;
|
||||
|
||||
/**
|
||||
* 检查是否可以执行
|
||||
*/
|
||||
canExecute(currentTime: number): boolean {
|
||||
// 如果从未执行过,允许执行
|
||||
if (this.lastExecutionTime === 0) {
|
||||
return true;
|
||||
}
|
||||
return currentTime - this.lastExecutionTime >= this.cooldownTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录执行时间
|
||||
*/
|
||||
recordExecution(currentTime: number): void {
|
||||
this.lastExecutionTime = currentTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置状态
|
||||
*/
|
||||
reset(): void {
|
||||
this.lastExecutionTime = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { ECSComponent } from '@esengine/ecs-framework';
|
||||
import { Serializable } from '@esengine/ecs-framework';
|
||||
import { NodeType, DecoratorType } from '../../Types/TaskStatus';
|
||||
import { BehaviorNode } from '../../Decorators/BehaviorNodeDecorator';
|
||||
import { DecoratorNodeComponent } from '../DecoratorNodeComponent';
|
||||
|
||||
/**
|
||||
* 反转节点
|
||||
*
|
||||
* 反转子节点的执行结果
|
||||
*/
|
||||
@BehaviorNode({
|
||||
displayName: '反转',
|
||||
category: '装饰器',
|
||||
type: NodeType.Decorator,
|
||||
icon: 'RotateCcw',
|
||||
description: '反转子节点的执行结果',
|
||||
color: '#607D8B'
|
||||
})
|
||||
@ECSComponent('InverterNode')
|
||||
@Serializable({ version: 1 })
|
||||
export class InverterNode extends DecoratorNodeComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.decoratorType = DecoratorType.Inverter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { ECSComponent } from '@esengine/ecs-framework';
|
||||
import { Serializable, Serialize, IgnoreSerialization } from '@esengine/ecs-framework';
|
||||
import { NodeType, DecoratorType } from '../../Types/TaskStatus';
|
||||
import { BehaviorNode, BehaviorProperty } from '../../Decorators/BehaviorNodeDecorator';
|
||||
import { DecoratorNodeComponent } from '../DecoratorNodeComponent';
|
||||
|
||||
/**
|
||||
* 重复节点
|
||||
*
|
||||
* 重复执行子节点指定次数
|
||||
*/
|
||||
@BehaviorNode({
|
||||
displayName: '重复',
|
||||
category: '装饰器',
|
||||
type: NodeType.Decorator,
|
||||
icon: 'Repeat',
|
||||
description: '重复执行子节点指定次数',
|
||||
color: '#9E9E9E'
|
||||
})
|
||||
@ECSComponent('RepeaterNode')
|
||||
@Serializable({ version: 1 })
|
||||
export class RepeaterNode extends DecoratorNodeComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.decoratorType = DecoratorType.Repeater;
|
||||
}
|
||||
|
||||
@BehaviorProperty({
|
||||
label: '重复次数',
|
||||
type: 'number',
|
||||
min: -1,
|
||||
step: 1,
|
||||
description: '-1表示无限重复',
|
||||
required: true
|
||||
})
|
||||
@Serialize()
|
||||
repeatCount: number = 1;
|
||||
|
||||
@BehaviorProperty({
|
||||
label: '失败时停止',
|
||||
type: 'boolean',
|
||||
description: '子节点失败时是否停止重复'
|
||||
})
|
||||
@Serialize()
|
||||
endOnFailure: boolean = false;
|
||||
|
||||
/** 当前已重复次数 */
|
||||
@IgnoreSerialization()
|
||||
currentRepeatCount: number = 0;
|
||||
|
||||
/**
|
||||
* 增加重复计数
|
||||
*/
|
||||
incrementRepeat(): void {
|
||||
this.currentRepeatCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否应该继续重复
|
||||
*/
|
||||
shouldContinueRepeat(): boolean {
|
||||
if (this.repeatCount === -1) {
|
||||
return true;
|
||||
}
|
||||
return this.currentRepeatCount < this.repeatCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置状态
|
||||
*/
|
||||
reset(): void {
|
||||
this.currentRepeatCount = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import { ECSComponent } from '@esengine/ecs-framework';
|
||||
import { Serializable, Serialize, IgnoreSerialization } from '@esengine/ecs-framework';
|
||||
import { NodeType, DecoratorType } from '../../Types/TaskStatus';
|
||||
import { BehaviorNode, BehaviorProperty } from '../../Decorators/BehaviorNodeDecorator';
|
||||
import { DecoratorNodeComponent } from '../DecoratorNodeComponent';
|
||||
|
||||
/**
|
||||
* 超时节点
|
||||
*
|
||||
* 子节点执行超时则返回失败
|
||||
*/
|
||||
@BehaviorNode({
|
||||
displayName: '超时',
|
||||
category: '装饰器',
|
||||
type: NodeType.Decorator,
|
||||
icon: 'Clock',
|
||||
description: '子节点执行超时则返回失败',
|
||||
color: '#FF9800'
|
||||
})
|
||||
@ECSComponent('TimeoutNode')
|
||||
@Serializable({ version: 1 })
|
||||
export class TimeoutNode extends DecoratorNodeComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.decoratorType = DecoratorType.Timeout;
|
||||
}
|
||||
|
||||
@BehaviorProperty({
|
||||
label: '超时时间',
|
||||
type: 'number',
|
||||
min: 0,
|
||||
step: 0.1,
|
||||
description: '超时时间(秒)',
|
||||
required: true
|
||||
})
|
||||
@Serialize()
|
||||
timeoutDuration: number = 5.0;
|
||||
|
||||
/** 开始执行时间 */
|
||||
@IgnoreSerialization()
|
||||
startTime: number = 0;
|
||||
|
||||
/**
|
||||
* 记录开始时间
|
||||
*/
|
||||
recordStartTime(currentTime: number): void {
|
||||
if (this.startTime === 0) {
|
||||
this.startTime = currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否超时
|
||||
*/
|
||||
isTimeout(currentTime: number): boolean {
|
||||
if (this.startTime === 0) {
|
||||
return false;
|
||||
}
|
||||
return currentTime - this.startTime >= this.timeoutDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置状态
|
||||
*/
|
||||
reset(): void {
|
||||
this.startTime = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { ECSComponent } from '@esengine/ecs-framework';
|
||||
import { Serializable } from '@esengine/ecs-framework';
|
||||
import { NodeType, DecoratorType } from '../../Types/TaskStatus';
|
||||
import { BehaviorNode } from '../../Decorators/BehaviorNodeDecorator';
|
||||
import { DecoratorNodeComponent } from '../DecoratorNodeComponent';
|
||||
|
||||
/**
|
||||
* 直到失败节点
|
||||
*
|
||||
* 重复执行子节点直到失败
|
||||
*/
|
||||
@BehaviorNode({
|
||||
displayName: '直到失败',
|
||||
category: '装饰器',
|
||||
type: NodeType.Decorator,
|
||||
icon: 'XCircle',
|
||||
description: '重复执行子节点直到失败',
|
||||
color: '#F44336'
|
||||
})
|
||||
@ECSComponent('UntilFailNode')
|
||||
@Serializable({ version: 1 })
|
||||
export class UntilFailNode extends DecoratorNodeComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.decoratorType = DecoratorType.UntilFail;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { ECSComponent } from '@esengine/ecs-framework';
|
||||
import { Serializable } from '@esengine/ecs-framework';
|
||||
import { NodeType, DecoratorType } from '../../Types/TaskStatus';
|
||||
import { BehaviorNode } from '../../Decorators/BehaviorNodeDecorator';
|
||||
import { DecoratorNodeComponent } from '../DecoratorNodeComponent';
|
||||
|
||||
/**
|
||||
* 直到成功节点
|
||||
*
|
||||
* 重复执行子节点直到成功
|
||||
*/
|
||||
@BehaviorNode({
|
||||
displayName: '直到成功',
|
||||
category: '装饰器',
|
||||
type: NodeType.Decorator,
|
||||
icon: 'CheckCircle',
|
||||
description: '重复执行子节点直到成功',
|
||||
color: '#4CAF50'
|
||||
})
|
||||
@ECSComponent('UntilSuccessNode')
|
||||
@Serializable({ version: 1 })
|
||||
export class UntilSuccessNode extends DecoratorNodeComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.decoratorType = DecoratorType.UntilSuccess;
|
||||
}
|
||||
}
|
||||
36
packages/behavior-tree/src/Components/LogOutput.ts
Normal file
36
packages/behavior-tree/src/Components/LogOutput.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Component, ECSComponent } from '@esengine/ecs-framework';
|
||||
|
||||
/**
|
||||
* 日志输出组件
|
||||
*
|
||||
* 存储运行时输出的日志信息,用于在UI中显示
|
||||
*/
|
||||
@ECSComponent('LogOutput')
|
||||
export class LogOutput extends Component {
|
||||
/**
|
||||
* 日志消息列表
|
||||
*/
|
||||
messages: Array<{
|
||||
timestamp: number;
|
||||
message: string;
|
||||
level: 'log' | 'info' | 'warn' | 'error';
|
||||
}> = [];
|
||||
|
||||
/**
|
||||
* 添加日志消息
|
||||
*/
|
||||
addMessage(message: string, level: 'log' | 'info' | 'warn' | 'error' = 'log'): void {
|
||||
this.messages.push({
|
||||
timestamp: Date.now(),
|
||||
message,
|
||||
level
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空日志
|
||||
*/
|
||||
clear(): void {
|
||||
this.messages = [];
|
||||
}
|
||||
}
|
||||
42
packages/behavior-tree/src/Components/PropertyBindings.ts
Normal file
42
packages/behavior-tree/src/Components/PropertyBindings.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { Component } from '@esengine/ecs-framework';
|
||||
|
||||
/**
|
||||
* 属性绑定组件
|
||||
* 记录节点属性到黑板变量的绑定关系
|
||||
*/
|
||||
export class PropertyBindings extends Component {
|
||||
/**
|
||||
* 属性绑定映射
|
||||
* key: 属性名称 (如 'message')
|
||||
* value: 黑板变量名 (如 'test1')
|
||||
*/
|
||||
bindings: Map<string, string> = new Map();
|
||||
|
||||
/**
|
||||
* 添加属性绑定
|
||||
*/
|
||||
addBinding(propertyName: string, blackboardKey: string): void {
|
||||
this.bindings.set(propertyName, blackboardKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取属性绑定的黑板变量名
|
||||
*/
|
||||
getBinding(propertyName: string): string | undefined {
|
||||
return this.bindings.get(propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查属性是否绑定到黑板变量
|
||||
*/
|
||||
hasBinding(propertyName: string): boolean {
|
||||
return this.bindings.has(propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有绑定
|
||||
*/
|
||||
clearBindings(): void {
|
||||
this.bindings.clear();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user