Files
esengine/packages/behavior-tree/src/execution/BehaviorTreeRuntimeComponent.ts

279 lines
6.6 KiB
TypeScript
Raw Normal View History

import { Component, ECSComponent, Property } from '@esengine/esengine';
import { Serializable, Serialize, IgnoreSerialization } from '@esengine/esengine';
import { NodeRuntimeState, createDefaultRuntimeState } from './BehaviorTreeData';
import { TaskStatus } from '../Types/TaskStatus';
/**
*
*/
export type BlackboardChangeListener = (key: string, newValue: any, oldValue: any) => void;
/**
*
*/
interface BlackboardObserver {
nodeId: string;
keys: Set<string>;
callback: BlackboardChangeListener;
}
/**
*
*
* Entity上BehaviorTreeData
* Entity独立的运行时状态
*/
@ECSComponent('BehaviorTreeRuntime')
@Serializable({ version: 1 })
export class BehaviorTreeRuntimeComponent extends Component {
/**
* ID
*/
@Serialize()
@Property({ type: 'asset', label: 'Behavior Tree', extensions: ['.btree'] })
treeAssetId: string = '';
/**
*
*/
@Serialize()
@Property({ type: 'boolean', label: 'Auto Start' })
autoStart: boolean = true;
/**
*
*/
@IgnoreSerialization()
isRunning: boolean = false;
/**
*
*
*/
@IgnoreSerialization()
private nodeStates: Map<string, NodeRuntimeState> = new Map();
/**
* Entity独立的数据
*
*/
@IgnoreSerialization()
private blackboard: Map<string, any> = new Map();
/**
*
*/
@IgnoreSerialization()
private blackboardObservers: Map<string, BlackboardObserver[]> = new Map();
/**
* ID列表
*/
@IgnoreSerialization()
activeNodeIds: Set<string> = new Set();
/**
* tick重置状态
*/
@IgnoreSerialization()
needsReset: boolean = false;
/**
* ID列表
*/
@IgnoreSerialization()
nodesToAbort: Set<string> = new Set();
/**
*
*/
@IgnoreSerialization()
executionOrderCounter: number = 0;
/**
*
*/
getNodeState(nodeId: string): NodeRuntimeState {
if (!this.nodeStates.has(nodeId)) {
this.nodeStates.set(nodeId, createDefaultRuntimeState());
}
return this.nodeStates.get(nodeId)!;
}
/**
*
*/
resetNodeState(nodeId: string): void {
const state = this.getNodeState(nodeId);
state.status = TaskStatus.Invalid;
state.currentChildIndex = 0;
delete state.startTime;
delete state.lastExecutionTime;
delete state.repeatCount;
delete state.cachedResult;
delete state.shuffledIndices;
delete state.isAborted;
delete state.lastConditionResult;
delete state.observedKeys;
}
/**
*
*/
resetAllStates(): void {
this.nodeStates.clear();
this.activeNodeIds.clear();
this.executionOrderCounter = 0;
}
/**
*
*/
getBlackboardValue<T = any>(key: string): T | undefined {
return this.blackboard.get(key) as T;
}
/**
*
*/
setBlackboardValue(key: string, value: any): void {
const oldValue = this.blackboard.get(key);
this.blackboard.set(key, value);
if (oldValue !== value) {
this.notifyBlackboardChange(key, value, oldValue);
}
}
/**
*
*/
hasBlackboardKey(key: string): boolean {
return this.blackboard.has(key);
}
/**
*
*/
initializeBlackboard(variables?: Map<string, any>): void {
if (variables) {
variables.forEach((value, key) => {
if (!this.blackboard.has(key)) {
this.blackboard.set(key, value);
}
});
}
}
/**
*
*/
clearBlackboard(): void {
this.blackboard.clear();
}
/**
*
*/
start(): void {
this.isRunning = true;
this.resetAllStates();
}
/**
*
*/
stop(): void {
this.isRunning = false;
this.activeNodeIds.clear();
}
/**
*
*/
pause(): void {
this.isRunning = false;
}
/**
*
*/
resume(): void {
this.isRunning = true;
}
/**
*
*/
observeBlackboard(nodeId: string, keys: string[], callback: BlackboardChangeListener): void {
const observer: BlackboardObserver = {
nodeId,
keys: new Set(keys),
callback
};
for (const key of keys) {
if (!this.blackboardObservers.has(key)) {
this.blackboardObservers.set(key, []);
}
this.blackboardObservers.get(key)!.push(observer);
}
}
/**
*
*/
unobserveBlackboard(nodeId: string): void {
for (const observers of this.blackboardObservers.values()) {
const index = observers.findIndex((o) => o.nodeId === nodeId);
if (index !== -1) {
observers.splice(index, 1);
}
}
}
/**
*
*/
private notifyBlackboardChange(key: string, newValue: any, oldValue: any): void {
const observers = this.blackboardObservers.get(key);
if (!observers) return;
for (const observer of observers) {
try {
observer.callback(key, newValue, oldValue);
} catch (error) {
console.error(`黑板观察者回调错误 (节点: ${observer.nodeId}):`, error);
}
}
}
/**
*
*/
requestAbort(nodeId: string): void {
this.nodesToAbort.add(nodeId);
}
/**
*
*/
shouldAbort(nodeId: string): boolean {
return this.nodesToAbort.has(nodeId);
}
/**
*
*/
clearAbortRequest(nodeId: string): void {
this.nodesToAbort.delete(nodeId);
}
/**
*
*/
clearAllAbortRequests(): void {
this.nodesToAbort.clear();
}
}