diff --git a/packages/blueprint/src/index.ts b/packages/blueprint/src/index.ts index 83ca22b0..dbf05e60 100644 --- a/packages/blueprint/src/index.ts +++ b/packages/blueprint/src/index.ts @@ -9,6 +9,9 @@ export * from './types'; // Runtime export * from './runtime'; +// Triggers +export * from './triggers'; + // Nodes (import to register) import './nodes'; diff --git a/packages/blueprint/src/nodes/events/EventCollision.ts b/packages/blueprint/src/nodes/events/EventCollision.ts new file mode 100644 index 00000000..5ad69ccc --- /dev/null +++ b/packages/blueprint/src/nodes/events/EventCollision.ts @@ -0,0 +1,118 @@ +/** + * @zh 碰撞事件节点 - 碰撞发生时触发 + * @en Event Collision Node - Triggered on collision events + */ + +import { BlueprintNodeTemplate, BlueprintNode } from '../../types/nodes'; +import { ExecutionResult } from '../../runtime/ExecutionContext'; +import { INodeExecutor, RegisterNode } from '../../runtime/NodeRegistry'; + +/** + * @zh EventCollisionEnter 节点模板 + * @en EventCollisionEnter node template + */ +export const EventCollisionEnterTemplate: BlueprintNodeTemplate = { + type: 'EventCollisionEnter', + title: 'Event Collision Enter', + category: 'event', + color: '#CC0000', + description: 'Triggered when collision starts / 碰撞开始时触发', + keywords: ['collision', 'enter', 'hit', 'overlap', 'event'], + menuPath: ['Event', 'Collision', 'Enter'], + inputs: [], + outputs: [ + { + name: 'exec', + type: 'exec', + displayName: '' + }, + { + name: 'otherEntityId', + type: 'string', + displayName: 'Other Entity' + }, + { + name: 'pointX', + type: 'float', + displayName: 'Point X' + }, + { + name: 'pointY', + type: 'float', + displayName: 'Point Y' + }, + { + name: 'normalX', + type: 'float', + displayName: 'Normal X' + }, + { + name: 'normalY', + type: 'float', + displayName: 'Normal Y' + } + ] +}; + +/** + * @zh EventCollisionEnter 节点执行器 + * @en EventCollisionEnter node executor + */ +@RegisterNode(EventCollisionEnterTemplate) +export class EventCollisionEnterExecutor implements INodeExecutor { + execute(_node: BlueprintNode): ExecutionResult { + return { + nextExec: 'exec', + outputs: { + otherEntityId: '', + pointX: 0, + pointY: 0, + normalX: 0, + normalY: 0 + } + }; + } +} + +/** + * @zh EventCollisionExit 节点模板 + * @en EventCollisionExit node template + */ +export const EventCollisionExitTemplate: BlueprintNodeTemplate = { + type: 'EventCollisionExit', + title: 'Event Collision Exit', + category: 'event', + color: '#CC0000', + description: 'Triggered when collision ends / 碰撞结束时触发', + keywords: ['collision', 'exit', 'end', 'separate', 'event'], + menuPath: ['Event', 'Collision', 'Exit'], + inputs: [], + outputs: [ + { + name: 'exec', + type: 'exec', + displayName: '' + }, + { + name: 'otherEntityId', + type: 'string', + displayName: 'Other Entity' + } + ] +}; + +/** + * @zh EventCollisionExit 节点执行器 + * @en EventCollisionExit node executor + */ +@RegisterNode(EventCollisionExitTemplate) +export class EventCollisionExitExecutor implements INodeExecutor { + execute(_node: BlueprintNode): ExecutionResult { + return { + nextExec: 'exec', + outputs: { + otherEntityId: '' + } + }; + } +} diff --git a/packages/blueprint/src/nodes/events/EventInput.ts b/packages/blueprint/src/nodes/events/EventInput.ts new file mode 100644 index 00000000..f71d6a30 --- /dev/null +++ b/packages/blueprint/src/nodes/events/EventInput.ts @@ -0,0 +1,79 @@ +/** + * @zh 输入事件节点 - 输入触发时触发 + * @en Event Input Node - Triggered on input events + */ + +import { BlueprintNodeTemplate, BlueprintNode } from '../../types/nodes'; +import { ExecutionContext, ExecutionResult } from '../../runtime/ExecutionContext'; +import { INodeExecutor, RegisterNode } from '../../runtime/NodeRegistry'; + +/** + * @zh EventInput 节点模板 + * @en EventInput node template + */ +export const EventInputTemplate: BlueprintNodeTemplate = { + type: 'EventInput', + title: 'Event Input', + category: 'event', + color: '#CC0000', + description: 'Triggered when input action occurs / 输入动作发生时触发', + keywords: ['input', 'key', 'button', 'action', 'event'], + menuPath: ['Event', 'Input'], + inputs: [ + { + name: 'action', + type: 'string', + displayName: 'Action', + defaultValue: '' + } + ], + outputs: [ + { + name: 'exec', + type: 'exec', + displayName: '' + }, + { + name: 'action', + type: 'string', + displayName: 'Action' + }, + { + name: 'value', + type: 'float', + displayName: 'Value' + }, + { + name: 'pressed', + type: 'bool', + displayName: 'Pressed' + }, + { + name: 'released', + type: 'bool', + displayName: 'Released' + } + ] +}; + +/** + * @zh EventInput 节点执行器 + * @en EventInput node executor + * + * @zh 注意:事件节点的输出由 VM 在触发时通过 setOutputs 设置 + * @en Note: Event node outputs are set by VM via setOutputs when triggered + */ +@RegisterNode(EventInputTemplate) +export class EventInputExecutor implements INodeExecutor { + execute(node: BlueprintNode, _context: ExecutionContext): ExecutionResult { + return { + nextExec: 'exec', + outputs: { + action: node.data?.action ?? '', + value: 0, + pressed: false, + released: false + } + }; + } +} diff --git a/packages/blueprint/src/nodes/events/EventMessage.ts b/packages/blueprint/src/nodes/events/EventMessage.ts new file mode 100644 index 00000000..ee3106d9 --- /dev/null +++ b/packages/blueprint/src/nodes/events/EventMessage.ts @@ -0,0 +1,70 @@ +/** + * @zh 消息事件节点 - 接收消息时触发 + * @en Event Message Node - Triggered when message is received + */ + +import { BlueprintNodeTemplate, BlueprintNode } from '../../types/nodes'; +import { ExecutionResult } from '../../runtime/ExecutionContext'; +import { INodeExecutor, RegisterNode } from '../../runtime/NodeRegistry'; + +/** + * @zh EventMessage 节点模板 + * @en EventMessage node template + */ +export const EventMessageTemplate: BlueprintNodeTemplate = { + type: 'EventMessage', + title: 'Event Message', + category: 'event', + color: '#CC0000', + description: 'Triggered when a message is received / 接收到消息时触发', + keywords: ['message', 'receive', 'broadcast', 'event', 'signal'], + menuPath: ['Event', 'Message'], + inputs: [ + { + name: 'messageName', + type: 'string', + displayName: 'Message Name', + defaultValue: '' + } + ], + outputs: [ + { + name: 'exec', + type: 'exec', + displayName: '' + }, + { + name: 'messageName', + type: 'string', + displayName: 'Message' + }, + { + name: 'senderId', + type: 'string', + displayName: 'Sender ID' + }, + { + name: 'payload', + type: 'any', + displayName: 'Payload' + } + ] +}; + +/** + * @zh EventMessage 节点执行器 + * @en EventMessage node executor + */ +@RegisterNode(EventMessageTemplate) +export class EventMessageExecutor implements INodeExecutor { + execute(node: BlueprintNode): ExecutionResult { + return { + nextExec: 'exec', + outputs: { + messageName: node.data?.messageName ?? '', + senderId: '', + payload: null + } + }; + } +} diff --git a/packages/blueprint/src/nodes/events/EventState.ts b/packages/blueprint/src/nodes/events/EventState.ts new file mode 100644 index 00000000..54a45c50 --- /dev/null +++ b/packages/blueprint/src/nodes/events/EventState.ts @@ -0,0 +1,132 @@ +/** + * @zh 状态事件节点 - 状态机状态变化时触发 + * @en Event State Node - Triggered on state machine state changes + */ + +import { BlueprintNodeTemplate, BlueprintNode } from '../../types/nodes'; +import { ExecutionResult } from '../../runtime/ExecutionContext'; +import { INodeExecutor, RegisterNode } from '../../runtime/NodeRegistry'; + +/** + * @zh EventStateEnter 节点模板 + * @en EventStateEnter node template + */ +export const EventStateEnterTemplate: BlueprintNodeTemplate = { + type: 'EventStateEnter', + title: 'Event State Enter', + category: 'event', + color: '#CC0000', + description: 'Triggered when entering a state / 进入状态时触发', + keywords: ['state', 'enter', 'fsm', 'machine', 'event'], + menuPath: ['Event', 'State', 'Enter'], + inputs: [ + { + name: 'stateName', + type: 'string', + displayName: 'State Name', + defaultValue: '' + } + ], + outputs: [ + { + name: 'exec', + type: 'exec', + displayName: '' + }, + { + name: 'stateMachineId', + type: 'string', + displayName: 'State Machine' + }, + { + name: 'currentState', + type: 'string', + displayName: 'Current State' + }, + { + name: 'previousState', + type: 'string', + displayName: 'Previous State' + } + ] +}; + +/** + * @zh EventStateEnter 节点执行器 + * @en EventStateEnter node executor + */ +@RegisterNode(EventStateEnterTemplate) +export class EventStateEnterExecutor implements INodeExecutor { + execute(node: BlueprintNode): ExecutionResult { + return { + nextExec: 'exec', + outputs: { + stateMachineId: '', + currentState: node.data?.stateName ?? '', + previousState: '' + } + }; + } +} + +/** + * @zh EventStateExit 节点模板 + * @en EventStateExit node template + */ +export const EventStateExitTemplate: BlueprintNodeTemplate = { + type: 'EventStateExit', + title: 'Event State Exit', + category: 'event', + color: '#CC0000', + description: 'Triggered when exiting a state / 退出状态时触发', + keywords: ['state', 'exit', 'leave', 'fsm', 'machine', 'event'], + menuPath: ['Event', 'State', 'Exit'], + inputs: [ + { + name: 'stateName', + type: 'string', + displayName: 'State Name', + defaultValue: '' + } + ], + outputs: [ + { + name: 'exec', + type: 'exec', + displayName: '' + }, + { + name: 'stateMachineId', + type: 'string', + displayName: 'State Machine' + }, + { + name: 'currentState', + type: 'string', + displayName: 'Current State' + }, + { + name: 'previousState', + type: 'string', + displayName: 'Previous State' + } + ] +}; + +/** + * @zh EventStateExit 节点执行器 + * @en EventStateExit node executor + */ +@RegisterNode(EventStateExitTemplate) +export class EventStateExitExecutor implements INodeExecutor { + execute(node: BlueprintNode): ExecutionResult { + return { + nextExec: 'exec', + outputs: { + stateMachineId: '', + currentState: '', + previousState: node.data?.stateName ?? '' + } + }; + } +} diff --git a/packages/blueprint/src/nodes/events/EventTimer.ts b/packages/blueprint/src/nodes/events/EventTimer.ts new file mode 100644 index 00000000..f8fc24fe --- /dev/null +++ b/packages/blueprint/src/nodes/events/EventTimer.ts @@ -0,0 +1,70 @@ +/** + * @zh 定时器事件节点 - 定时器触发时调用 + * @en Event Timer Node - Triggered when timer fires + */ + +import { BlueprintNodeTemplate, BlueprintNode } from '../../types/nodes'; +import { ExecutionResult } from '../../runtime/ExecutionContext'; +import { INodeExecutor, RegisterNode } from '../../runtime/NodeRegistry'; + +/** + * @zh EventTimer 节点模板 + * @en EventTimer node template + */ +export const EventTimerTemplate: BlueprintNodeTemplate = { + type: 'EventTimer', + title: 'Event Timer', + category: 'event', + color: '#CC0000', + description: 'Triggered when a timer fires / 定时器触发时执行', + keywords: ['timer', 'delay', 'schedule', 'event', 'interval'], + menuPath: ['Event', 'Timer'], + inputs: [ + { + name: 'timerId', + type: 'string', + displayName: 'Timer ID', + defaultValue: '' + } + ], + outputs: [ + { + name: 'exec', + type: 'exec', + displayName: '' + }, + { + name: 'timerId', + type: 'string', + displayName: 'Timer ID' + }, + { + name: 'isRepeating', + type: 'bool', + displayName: 'Is Repeating' + }, + { + name: 'timesFired', + type: 'int', + displayName: 'Times Fired' + } + ] +}; + +/** + * @zh EventTimer 节点执行器 + * @en EventTimer node executor + */ +@RegisterNode(EventTimerTemplate) +export class EventTimerExecutor implements INodeExecutor { + execute(node: BlueprintNode): ExecutionResult { + return { + nextExec: 'exec', + outputs: { + timerId: node.data?.timerId ?? '', + isRepeating: false, + timesFired: 0 + } + }; + } +} diff --git a/packages/blueprint/src/nodes/events/index.ts b/packages/blueprint/src/nodes/events/index.ts index b5a2d8f0..34169c7d 100644 --- a/packages/blueprint/src/nodes/events/index.ts +++ b/packages/blueprint/src/nodes/events/index.ts @@ -1,8 +1,16 @@ /** - * Event Nodes - Entry points for blueprint execution - * 事件节点 - 蓝图执行的入口点 + * @zh 事件节点 - 蓝图执行的入口点 + * @en Event Nodes - Entry points for blueprint execution */ +// 生命周期事件 | Lifecycle events export * from './EventBeginPlay'; export * from './EventTick'; export * from './EventEndPlay'; + +// 触发器事件 | Trigger events +export * from './EventInput'; +export * from './EventCollision'; +export * from './EventMessage'; +export * from './EventTimer'; +export * from './EventState'; diff --git a/packages/blueprint/src/triggers/BlueprintTrigger.ts b/packages/blueprint/src/triggers/BlueprintTrigger.ts new file mode 100644 index 00000000..d6cd360b --- /dev/null +++ b/packages/blueprint/src/triggers/BlueprintTrigger.ts @@ -0,0 +1,497 @@ +/** + * @zh 蓝图触发器 + * @en Blueprint Trigger + * + * @zh 定义触发器的核心实现 + * @en Defines core trigger implementation + */ + +import type { TriggerType, ITriggerContext } from './TriggerTypes'; +import type { ITriggerCondition } from './TriggerCondition'; +import { AlwaysTrueCondition } from './TriggerCondition'; + +// ============================================================================= +// 触发器接口 | Trigger Interface +// ============================================================================= + +/** + * @zh 触发器回调函数类型 + * @en Trigger callback function type + */ +export type TriggerCallback = (context: ITriggerContext) => void; + +/** + * @zh 蓝图触发器接口 + * @en Blueprint trigger interface + */ +export interface IBlueprintTrigger { + /** + * @zh 触发器唯一标识 + * @en Trigger unique identifier + */ + readonly id: string; + + /** + * @zh 触发器类型 + * @en Trigger type + */ + readonly type: TriggerType; + + /** + * @zh 触发器条件 + * @en Trigger conditions + */ + readonly condition: ITriggerCondition; + + /** + * @zh 是否启用 + * @en Is enabled + */ + enabled: boolean; + + /** + * @zh 优先级(越高越先执行) + * @en Priority (higher executes first) + */ + readonly priority: number; + + /** + * @zh 检查是否应该触发 + * @en Check if should fire + */ + shouldFire(context: ITriggerContext): boolean; + + /** + * @zh 执行触发器 + * @en Execute trigger + */ + fire(context: ITriggerContext): void; +} + +/** + * @zh 触发器配置 + * @en Trigger configuration + */ +export interface TriggerConfig { + /** + * @zh 触发器 ID + * @en Trigger ID + */ + id?: string; + + /** + * @zh 触发器类型 + * @en Trigger type + */ + type: TriggerType; + + /** + * @zh 触发条件 + * @en Trigger condition + */ + condition?: ITriggerCondition; + + /** + * @zh 是否启用 + * @en Is enabled + */ + enabled?: boolean; + + /** + * @zh 优先级 + * @en Priority + */ + priority?: number; + + /** + * @zh 回调函数 + * @en Callback function + */ + callback?: TriggerCallback; +} + +// ============================================================================= +// 触发器实现 | Trigger Implementation +// ============================================================================= + +let _triggerId = 0; + +/** + * @zh 生成唯一触发器 ID + * @en Generate unique trigger ID + */ +function generateTriggerId(): string { + return `trigger_${++_triggerId}`; +} + +/** + * @zh 蓝图触发器实现 + * @en Blueprint trigger implementation + */ +export class BlueprintTrigger implements IBlueprintTrigger { + readonly id: string; + readonly type: TriggerType; + readonly condition: ITriggerCondition; + readonly priority: number; + enabled: boolean; + + private readonly _callback?: TriggerCallback; + private readonly _callbacks: Set = new Set(); + + constructor(config: TriggerConfig) { + this.id = config.id ?? generateTriggerId(); + this.type = config.type; + this.condition = config.condition ?? new AlwaysTrueCondition(); + this.priority = config.priority ?? 0; + this.enabled = config.enabled ?? true; + this._callback = config.callback; + } + + /** + * @zh 检查是否应该触发 + * @en Check if should fire + */ + shouldFire(context: ITriggerContext): boolean { + if (!this.enabled) { + return false; + } + + if (context.type !== this.type && this.type !== 'custom') { + return false; + } + + return this.condition.evaluate(context); + } + + /** + * @zh 执行触发器 + * @en Execute trigger + */ + fire(context: ITriggerContext): void { + if (this._callback) { + this._callback(context); + } + + for (const callback of this._callbacks) { + callback(context); + } + } + + /** + * @zh 添加回调 + * @en Add callback + */ + addCallback(callback: TriggerCallback): void { + this._callbacks.add(callback); + } + + /** + * @zh 移除回调 + * @en Remove callback + */ + removeCallback(callback: TriggerCallback): void { + this._callbacks.delete(callback); + } + + /** + * @zh 清除所有回调 + * @en Clear all callbacks + */ + clearCallbacks(): void { + this._callbacks.clear(); + } +} + +// ============================================================================= +// 触发器注册表 | Trigger Registry +// ============================================================================= + +/** + * @zh 触发器注册表接口 + * @en Trigger registry interface + */ +export interface ITriggerRegistry { + /** + * @zh 注册触发器 + * @en Register trigger + */ + register(trigger: IBlueprintTrigger): void; + + /** + * @zh 注销触发器 + * @en Unregister trigger + */ + unregister(triggerId: string): boolean; + + /** + * @zh 获取触发器 + * @en Get trigger + */ + get(triggerId: string): IBlueprintTrigger | undefined; + + /** + * @zh 获取所有触发器 + * @en Get all triggers + */ + getAll(): IBlueprintTrigger[]; + + /** + * @zh 按类型获取触发器 + * @en Get triggers by type + */ + getByType(type: TriggerType): IBlueprintTrigger[]; + + /** + * @zh 清除所有触发器 + * @en Clear all triggers + */ + clear(): void; +} + +/** + * @zh 触发器注册表实现 + * @en Trigger registry implementation + */ +export class TriggerRegistry implements ITriggerRegistry { + private readonly _triggers: Map = new Map(); + private readonly _triggersByType: Map> = new Map(); + + /** + * @zh 注册触发器 + * @en Register trigger + */ + register(trigger: IBlueprintTrigger): void { + if (this._triggers.has(trigger.id)) { + console.warn(`Trigger ${trigger.id} already registered, overwriting`); + } + + this._triggers.set(trigger.id, trigger); + + if (!this._triggersByType.has(trigger.type)) { + this._triggersByType.set(trigger.type, new Set()); + } + this._triggersByType.get(trigger.type)!.add(trigger.id); + } + + /** + * @zh 注销触发器 + * @en Unregister trigger + */ + unregister(triggerId: string): boolean { + const trigger = this._triggers.get(triggerId); + if (!trigger) { + return false; + } + + this._triggers.delete(triggerId); + + const typeSet = this._triggersByType.get(trigger.type); + if (typeSet) { + typeSet.delete(triggerId); + } + + return true; + } + + /** + * @zh 获取触发器 + * @en Get trigger + */ + get(triggerId: string): IBlueprintTrigger | undefined { + return this._triggers.get(triggerId); + } + + /** + * @zh 获取所有触发器 + * @en Get all triggers + */ + getAll(): IBlueprintTrigger[] { + return Array.from(this._triggers.values()); + } + + /** + * @zh 按类型获取触发器 + * @en Get triggers by type + */ + getByType(type: TriggerType): IBlueprintTrigger[] { + const typeSet = this._triggersByType.get(type); + if (!typeSet) { + return []; + } + + const triggers: IBlueprintTrigger[] = []; + for (const id of typeSet) { + const trigger = this._triggers.get(id); + if (trigger) { + triggers.push(trigger); + } + } + + return triggers.sort((a, b) => b.priority - a.priority); + } + + /** + * @zh 清除所有触发器 + * @en Clear all triggers + */ + clear(): void { + this._triggers.clear(); + this._triggersByType.clear(); + } + + /** + * @zh 获取触发器数量 + * @en Get trigger count + */ + get count(): number { + return this._triggers.size; + } +} + +// ============================================================================= +// 工厂函数 | Factory Functions +// ============================================================================= + +/** + * @zh 创建触发器 + * @en Create trigger + */ +export function createTrigger(config: TriggerConfig): BlueprintTrigger { + return new BlueprintTrigger(config); +} + +/** + * @zh 创建 Tick 触发器 + * @en Create tick trigger + */ +export function createTickTrigger( + callback?: TriggerCallback, + options?: { id?: string; condition?: ITriggerCondition; priority?: number } +): BlueprintTrigger { + return new BlueprintTrigger({ + id: options?.id, + type: 'tick', + condition: options?.condition, + priority: options?.priority, + callback + }); +} + +/** + * @zh 创建输入触发器 + * @en Create input trigger + */ +export function createInputTrigger( + callback?: TriggerCallback, + options?: { id?: string; condition?: ITriggerCondition; priority?: number } +): BlueprintTrigger { + return new BlueprintTrigger({ + id: options?.id, + type: 'input', + condition: options?.condition, + priority: options?.priority, + callback + }); +} + +/** + * @zh 创建碰撞触发器 + * @en Create collision trigger + */ +export function createCollisionTrigger( + callback?: TriggerCallback, + options?: { id?: string; condition?: ITriggerCondition; priority?: number } +): BlueprintTrigger { + return new BlueprintTrigger({ + id: options?.id, + type: 'collision', + condition: options?.condition, + priority: options?.priority, + callback + }); +} + +/** + * @zh 创建消息触发器 + * @en Create message trigger + */ +export function createMessageTrigger( + callback?: TriggerCallback, + options?: { id?: string; condition?: ITriggerCondition; priority?: number } +): BlueprintTrigger { + return new BlueprintTrigger({ + id: options?.id, + type: 'message', + condition: options?.condition, + priority: options?.priority, + callback + }); +} + +/** + * @zh 创建定时器触发器 + * @en Create timer trigger + */ +export function createTimerTrigger( + callback?: TriggerCallback, + options?: { id?: string; condition?: ITriggerCondition; priority?: number } +): BlueprintTrigger { + return new BlueprintTrigger({ + id: options?.id, + type: 'timer', + condition: options?.condition, + priority: options?.priority, + callback + }); +} + +/** + * @zh 创建状态进入触发器 + * @en Create state enter trigger + */ +export function createStateEnterTrigger( + callback?: TriggerCallback, + options?: { id?: string; condition?: ITriggerCondition; priority?: number } +): BlueprintTrigger { + return new BlueprintTrigger({ + id: options?.id, + type: 'stateEnter', + condition: options?.condition, + priority: options?.priority, + callback + }); +} + +/** + * @zh 创建状态退出触发器 + * @en Create state exit trigger + */ +export function createStateExitTrigger( + callback?: TriggerCallback, + options?: { id?: string; condition?: ITriggerCondition; priority?: number } +): BlueprintTrigger { + return new BlueprintTrigger({ + id: options?.id, + type: 'stateExit', + condition: options?.condition, + priority: options?.priority, + callback + }); +} + +/** + * @zh 创建自定义触发器 + * @en Create custom trigger + */ +export function createCustomTrigger( + callback?: TriggerCallback, + options?: { id?: string; condition?: ITriggerCondition; priority?: number } +): BlueprintTrigger { + return new BlueprintTrigger({ + id: options?.id, + type: 'custom', + condition: options?.condition, + priority: options?.priority, + callback + }); +} diff --git a/packages/blueprint/src/triggers/TriggerCondition.ts b/packages/blueprint/src/triggers/TriggerCondition.ts new file mode 100644 index 00000000..68738081 --- /dev/null +++ b/packages/blueprint/src/triggers/TriggerCondition.ts @@ -0,0 +1,479 @@ +/** + * @zh 触发器条件系统 + * @en Trigger Condition System + * + * @zh 提供触发器触发前的条件检查能力 + * @en Provides condition checking before trigger fires + */ + +import type { + ITriggerContext, + TriggerType, + IInputTriggerContext, + IMessageTriggerContext, + IStateTriggerContext, + ITimerTriggerContext, + ICollisionTriggerContext, + ICustomTriggerContext +} from './TriggerTypes'; + +// ============================================================================= +// 条件接口 | Condition Interface +// ============================================================================= + +/** + * @zh 触发器条件接口 + * @en Trigger condition interface + */ +export interface ITriggerCondition { + /** + * @zh 条件类型标识 + * @en Condition type identifier + */ + readonly type: string; + + /** + * @zh 评估条件是否满足 + * @en Evaluate if condition is met + * + * @param context - @zh 触发器上下文 @en Trigger context + * @returns @zh 条件是否满足 @en Whether condition is met + */ + evaluate(context: ITriggerContext): boolean; +} + +/** + * @zh 条件组合逻辑 + * @en Condition combination logic + */ +export type ConditionLogic = 'and' | 'or'; + +// ============================================================================= +// 复合条件 | Composite Conditions +// ============================================================================= + +/** + * @zh 复合条件 - 组合多个条件 + * @en Composite condition - combines multiple conditions + */ +export class CompositeCondition implements ITriggerCondition { + readonly type = 'composite'; + + constructor( + private readonly _conditions: ITriggerCondition[], + private readonly _logic: ConditionLogic = 'and' + ) {} + + evaluate(context: ITriggerContext): boolean { + if (this._conditions.length === 0) { + return true; + } + + if (this._logic === 'and') { + return this._conditions.every(c => c.evaluate(context)); + } else { + return this._conditions.some(c => c.evaluate(context)); + } + } +} + +/** + * @zh 非条件 - 取反 + * @en Not condition - negates + */ +export class NotCondition implements ITriggerCondition { + readonly type = 'not'; + + constructor(private readonly _condition: ITriggerCondition) {} + + evaluate(context: ITriggerContext): boolean { + return !this._condition.evaluate(context); + } +} + +// ============================================================================= +// 通用条件 | Generic Conditions +// ============================================================================= + +/** + * @zh 始终为真的条件 + * @en Always true condition + */ +export class AlwaysTrueCondition implements ITriggerCondition { + readonly type = 'alwaysTrue'; + + evaluate(_context: ITriggerContext): boolean { + return true; + } +} + +/** + * @zh 始终为假的条件 + * @en Always false condition + */ +export class AlwaysFalseCondition implements ITriggerCondition { + readonly type = 'alwaysFalse'; + + evaluate(_context: ITriggerContext): boolean { + return false; + } +} + +/** + * @zh 触发器类型条件 + * @en Trigger type condition + */ +export class TriggerTypeCondition implements ITriggerCondition { + readonly type = 'triggerType'; + + constructor(private readonly _allowedTypes: TriggerType[]) {} + + evaluate(context: ITriggerContext): boolean { + return this._allowedTypes.includes(context.type); + } +} + +/** + * @zh 实体 ID 条件 + * @en Entity ID condition + */ +export class EntityIdCondition implements ITriggerCondition { + readonly type = 'entityId'; + + constructor( + private readonly _entityId: string, + private readonly _checkSource: boolean = true + ) {} + + evaluate(context: ITriggerContext): boolean { + if (this._checkSource) { + return context.sourceEntityId === this._entityId; + } + return false; + } +} + +/** + * @zh 自定义函数条件 + * @en Custom function condition + */ +export class FunctionCondition implements ITriggerCondition { + readonly type = 'function'; + + constructor( + private readonly _predicate: (context: ITriggerContext) => boolean + ) {} + + evaluate(context: ITriggerContext): boolean { + return this._predicate(context); + } +} + +// ============================================================================= +// 特定类型条件 | Type-Specific Conditions +// ============================================================================= + +/** + * @zh 输入动作条件 + * @en Input action condition + */ +export class InputActionCondition implements ITriggerCondition { + readonly type = 'inputAction'; + + constructor( + private readonly _action: string, + private readonly _checkPressed?: boolean, + private readonly _checkReleased?: boolean + ) {} + + evaluate(context: ITriggerContext): boolean { + if (context.type !== 'input') { + return false; + } + + const inputContext = context as unknown as IInputTriggerContext; + + if (inputContext.action !== this._action) { + return false; + } + + if (this._checkPressed !== undefined && inputContext.pressed !== this._checkPressed) { + return false; + } + + if (this._checkReleased !== undefined && inputContext.released !== this._checkReleased) { + return false; + } + + return true; + } +} + +/** + * @zh 消息名称条件 + * @en Message name condition + */ +export class MessageNameCondition implements ITriggerCondition { + readonly type = 'messageName'; + + constructor(private readonly _messageName: string) {} + + evaluate(context: ITriggerContext): boolean { + if (context.type !== 'message') { + return false; + } + + const messageContext = context as unknown as IMessageTriggerContext; + return messageContext.messageName === this._messageName; + } +} + +/** + * @zh 状态名称条件 + * @en State name condition + */ +export class StateNameCondition implements ITriggerCondition { + readonly type = 'stateName'; + + constructor( + private readonly _stateName: string, + private readonly _checkCurrent: boolean = true + ) {} + + evaluate(context: ITriggerContext): boolean { + if (context.type !== 'stateEnter' && context.type !== 'stateExit') { + return false; + } + + const stateContext = context as unknown as IStateTriggerContext; + + if (this._checkCurrent) { + return stateContext.currentState === this._stateName; + } else { + return stateContext.previousState === this._stateName; + } + } +} + +/** + * @zh 定时器 ID 条件 + * @en Timer ID condition + */ +export class TimerIdCondition implements ITriggerCondition { + readonly type = 'timerId'; + + constructor(private readonly _timerId: string) {} + + evaluate(context: ITriggerContext): boolean { + if (context.type !== 'timer') { + return false; + } + + const timerContext = context as unknown as ITimerTriggerContext; + return timerContext.timerId === this._timerId; + } +} + +/** + * @zh 碰撞实体条件 + * @en Collision entity condition + */ +export class CollisionEntityCondition implements ITriggerCondition { + readonly type = 'collisionEntity'; + + constructor( + private readonly _otherEntityId?: string, + private readonly _checkEnter?: boolean, + private readonly _checkExit?: boolean + ) {} + + evaluate(context: ITriggerContext): boolean { + if (context.type !== 'collision') { + return false; + } + + const collisionContext = context as unknown as ICollisionTriggerContext; + + if (this._otherEntityId !== undefined && collisionContext.otherEntityId !== this._otherEntityId) { + return false; + } + + if (this._checkEnter !== undefined && collisionContext.isEnter !== this._checkEnter) { + return false; + } + + if (this._checkExit !== undefined && collisionContext.isExit !== this._checkExit) { + return false; + } + + return true; + } +} + +/** + * @zh 自定义事件名称条件 + * @en Custom event name condition + */ +export class CustomEventCondition implements ITriggerCondition { + readonly type = 'customEvent'; + + constructor(private readonly _eventName: string) {} + + evaluate(context: ITriggerContext): boolean { + if (context.type !== 'custom') { + return false; + } + + const customContext = context as unknown as ICustomTriggerContext; + return customContext.eventName === this._eventName; + } +} + +// ============================================================================= +// 条件构建器 | Condition Builder +// ============================================================================= + +/** + * @zh 条件构建器 - 链式 API + * @en Condition builder - fluent API + */ +export class ConditionBuilder { + private _conditions: ITriggerCondition[] = []; + private _logic: ConditionLogic = 'and'; + + /** + * @zh 设置组合逻辑为 AND + * @en Set combination logic to AND + */ + and(): this { + this._logic = 'and'; + return this; + } + + /** + * @zh 设置组合逻辑为 OR + * @en Set combination logic to OR + */ + or(): this { + this._logic = 'or'; + return this; + } + + /** + * @zh 添加触发器类型条件 + * @en Add trigger type condition + */ + ofType(...types: TriggerType[]): this { + this._conditions.push(new TriggerTypeCondition(types)); + return this; + } + + /** + * @zh 添加实体 ID 条件 + * @en Add entity ID condition + */ + fromEntity(entityId: string): this { + this._conditions.push(new EntityIdCondition(entityId)); + return this; + } + + /** + * @zh 添加输入动作条件 + * @en Add input action condition + */ + onInput(action: string, options?: { pressed?: boolean; released?: boolean }): this { + this._conditions.push(new InputActionCondition(action, options?.pressed, options?.released)); + return this; + } + + /** + * @zh 添加消息条件 + * @en Add message condition + */ + onMessage(messageName: string): this { + this._conditions.push(new MessageNameCondition(messageName)); + return this; + } + + /** + * @zh 添加状态条件 + * @en Add state condition + */ + onState(stateName: string, checkCurrent: boolean = true): this { + this._conditions.push(new StateNameCondition(stateName, checkCurrent)); + return this; + } + + /** + * @zh 添加定时器条件 + * @en Add timer condition + */ + onTimer(timerId: string): this { + this._conditions.push(new TimerIdCondition(timerId)); + return this; + } + + /** + * @zh 添加碰撞条件 + * @en Add collision condition + */ + onCollision(options?: { entityId?: string; isEnter?: boolean; isExit?: boolean }): this { + this._conditions.push(new CollisionEntityCondition( + options?.entityId, + options?.isEnter, + options?.isExit + )); + return this; + } + + /** + * @zh 添加自定义事件条件 + * @en Add custom event condition + */ + onCustomEvent(eventName: string): this { + this._conditions.push(new CustomEventCondition(eventName)); + return this; + } + + /** + * @zh 添加自定义函数条件 + * @en Add custom function condition + */ + where(predicate: (context: ITriggerContext) => boolean): this { + this._conditions.push(new FunctionCondition(predicate)); + return this; + } + + /** + * @zh 添加取反条件 + * @en Add negated condition + */ + not(condition: ITriggerCondition): this { + this._conditions.push(new NotCondition(condition)); + return this; + } + + /** + * @zh 构建条件 + * @en Build condition + */ + build(): ITriggerCondition { + if (this._conditions.length === 0) { + return new AlwaysTrueCondition(); + } + + if (this._conditions.length === 1) { + return this._conditions[0]; + } + + return new CompositeCondition(this._conditions, this._logic); + } +} + +/** + * @zh 创建条件构建器 + * @en Create condition builder + */ +export function condition(): ConditionBuilder { + return new ConditionBuilder(); +} diff --git a/packages/blueprint/src/triggers/TriggerDispatcher.ts b/packages/blueprint/src/triggers/TriggerDispatcher.ts new file mode 100644 index 00000000..4e4bfe1a --- /dev/null +++ b/packages/blueprint/src/triggers/TriggerDispatcher.ts @@ -0,0 +1,461 @@ +/** + * @zh 触发器调度器 + * @en Trigger Dispatcher + * + * @zh 负责分发触发器事件到订阅者 + * @en Responsible for dispatching trigger events to subscribers + */ + +import type { TriggerType, ITriggerContext } from './TriggerTypes'; +import type { IBlueprintTrigger, ITriggerRegistry, TriggerCallback } from './BlueprintTrigger'; +import { TriggerRegistry } from './BlueprintTrigger'; + +// ============================================================================= +// 调度器接口 | Dispatcher Interface +// ============================================================================= + +/** + * @zh 触发结果 + * @en Trigger result + */ +export interface TriggerResult { + /** + * @zh 触发器 ID + * @en Trigger ID + */ + triggerId: string; + + /** + * @zh 是否成功 + * @en Is successful + */ + success: boolean; + + /** + * @zh 错误信息 + * @en Error message + */ + error?: string; +} + +/** + * @zh 调度结果 + * @en Dispatch result + */ +export interface DispatchResult { + /** + * @zh 上下文 + * @en Context + */ + context: ITriggerContext; + + /** + * @zh 触发的触发器数量 + * @en Number of triggers fired + */ + triggeredCount: number; + + /** + * @zh 各触发器结果 + * @en Results of each trigger + */ + results: TriggerResult[]; +} + +/** + * @zh 触发器调度器接口 + * @en Trigger dispatcher interface + */ +export interface ITriggerDispatcher { + /** + * @zh 调度触发器 + * @en Dispatch trigger + */ + dispatch(context: ITriggerContext): DispatchResult; + + /** + * @zh 异步调度触发器 + * @en Async dispatch trigger + */ + dispatchAsync(context: ITriggerContext): Promise; + + /** + * @zh 订阅触发器类型 + * @en Subscribe to trigger type + */ + subscribe(type: TriggerType, callback: TriggerCallback): () => void; + + /** + * @zh 取消订阅 + * @en Unsubscribe + */ + unsubscribe(type: TriggerType, callback: TriggerCallback): void; + + /** + * @zh 获取注册表 + * @en Get registry + */ + readonly registry: ITriggerRegistry; +} + +// ============================================================================= +// 调度器实现 | Dispatcher Implementation +// ============================================================================= + +/** + * @zh 触发器调度器实现 + * @en Trigger dispatcher implementation + */ +export class TriggerDispatcher implements ITriggerDispatcher { + private readonly _registry: ITriggerRegistry; + private readonly _typeSubscribers: Map> = new Map(); + private readonly _globalSubscribers: Set = new Set(); + private _isDispatching: boolean = false; + private _pendingContexts: ITriggerContext[] = []; + + constructor(registry?: ITriggerRegistry) { + this._registry = registry ?? new TriggerRegistry(); + } + + get registry(): ITriggerRegistry { + return this._registry; + } + + /** + * @zh 调度触发器 + * @en Dispatch trigger + */ + dispatch(context: ITriggerContext): DispatchResult { + if (this._isDispatching) { + this._pendingContexts.push(context); + return { + context, + triggeredCount: 0, + results: [] + }; + } + + this._isDispatching = true; + + try { + const result = this._doDispatch(context); + + while (this._pendingContexts.length > 0) { + const pendingContext = this._pendingContexts.shift()!; + this._doDispatch(pendingContext); + } + + return result; + } finally { + this._isDispatching = false; + } + } + + /** + * @zh 执行调度 + * @en Do dispatch + */ + private _doDispatch(context: ITriggerContext): DispatchResult { + const results: TriggerResult[] = []; + let triggeredCount = 0; + + const triggers = this._registry.getByType(context.type); + + for (const trigger of triggers) { + if (trigger.shouldFire(context)) { + try { + trigger.fire(context); + triggeredCount++; + results.push({ + triggerId: trigger.id, + success: true + }); + } catch (error) { + results.push({ + triggerId: trigger.id, + success: false, + error: error instanceof Error ? error.message : String(error) + }); + } + } + } + + this._notifySubscribers(context); + + return { + context, + triggeredCount, + results + }; + } + + /** + * @zh 通知订阅者 + * @en Notify subscribers + */ + private _notifySubscribers(context: ITriggerContext): void { + const typeSubscribers = this._typeSubscribers.get(context.type); + if (typeSubscribers) { + for (const callback of typeSubscribers) { + try { + callback(context); + } catch (error) { + console.error(`Trigger subscriber error: ${error}`); + } + } + } + + for (const callback of this._globalSubscribers) { + try { + callback(context); + } catch (error) { + console.error(`Global trigger subscriber error: ${error}`); + } + } + } + + /** + * @zh 异步调度触发器 + * @en Async dispatch trigger + */ + async dispatchAsync(context: ITriggerContext): Promise { + return new Promise((resolve) => { + queueMicrotask(() => { + resolve(this.dispatch(context)); + }); + }); + } + + /** + * @zh 订阅触发器类型 + * @en Subscribe to trigger type + */ + subscribe(type: TriggerType, callback: TriggerCallback): () => void { + if (!this._typeSubscribers.has(type)) { + this._typeSubscribers.set(type, new Set()); + } + + this._typeSubscribers.get(type)!.add(callback); + + return () => this.unsubscribe(type, callback); + } + + /** + * @zh 取消订阅 + * @en Unsubscribe + */ + unsubscribe(type: TriggerType, callback: TriggerCallback): void { + const subscribers = this._typeSubscribers.get(type); + if (subscribers) { + subscribers.delete(callback); + } + } + + /** + * @zh 订阅所有触发器 + * @en Subscribe to all triggers + */ + subscribeAll(callback: TriggerCallback): () => void { + this._globalSubscribers.add(callback); + return () => this.unsubscribeAll(callback); + } + + /** + * @zh 取消订阅所有 + * @en Unsubscribe from all + */ + unsubscribeAll(callback: TriggerCallback): void { + this._globalSubscribers.delete(callback); + } + + /** + * @zh 清除所有订阅 + * @en Clear all subscriptions + */ + clearSubscriptions(): void { + this._typeSubscribers.clear(); + this._globalSubscribers.clear(); + } +} + +// ============================================================================= +// 实体触发器管理器 | Entity Trigger Manager +// ============================================================================= + +/** + * @zh 实体触发器管理器接口 + * @en Entity trigger manager interface + */ +export interface IEntityTriggerManager { + /** + * @zh 为实体注册触发器 + * @en Register trigger for entity + */ + registerForEntity(entityId: string, trigger: IBlueprintTrigger): void; + + /** + * @zh 注销实体的触发器 + * @en Unregister trigger from entity + */ + unregisterFromEntity(entityId: string, triggerId: string): boolean; + + /** + * @zh 获取实体的所有触发器 + * @en Get all triggers for entity + */ + getEntityTriggers(entityId: string): IBlueprintTrigger[]; + + /** + * @zh 清除实体的所有触发器 + * @en Clear all triggers for entity + */ + clearEntityTriggers(entityId: string): void; + + /** + * @zh 调度器 + * @en Dispatcher + */ + readonly dispatcher: ITriggerDispatcher; +} + +/** + * @zh 实体触发器管理器实现 + * @en Entity trigger manager implementation + */ +export class EntityTriggerManager implements IEntityTriggerManager { + private readonly _dispatcher: ITriggerDispatcher; + private readonly _entityTriggers: Map> = new Map(); + + constructor(dispatcher?: ITriggerDispatcher) { + this._dispatcher = dispatcher ?? new TriggerDispatcher(); + } + + get dispatcher(): ITriggerDispatcher { + return this._dispatcher; + } + + /** + * @zh 为实体注册触发器 + * @en Register trigger for entity + */ + registerForEntity(entityId: string, trigger: IBlueprintTrigger): void { + this._dispatcher.registry.register(trigger); + + if (!this._entityTriggers.has(entityId)) { + this._entityTriggers.set(entityId, new Set()); + } + + this._entityTriggers.get(entityId)!.add(trigger.id); + } + + /** + * @zh 注销实体的触发器 + * @en Unregister trigger from entity + */ + unregisterFromEntity(entityId: string, triggerId: string): boolean { + const entitySet = this._entityTriggers.get(entityId); + if (!entitySet) { + return false; + } + + if (!entitySet.has(triggerId)) { + return false; + } + + entitySet.delete(triggerId); + return this._dispatcher.registry.unregister(triggerId); + } + + /** + * @zh 获取实体的所有触发器 + * @en Get all triggers for entity + */ + getEntityTriggers(entityId: string): IBlueprintTrigger[] { + const entitySet = this._entityTriggers.get(entityId); + if (!entitySet) { + return []; + } + + const triggers: IBlueprintTrigger[] = []; + for (const triggerId of entitySet) { + const trigger = this._dispatcher.registry.get(triggerId); + if (trigger) { + triggers.push(trigger); + } + } + + return triggers; + } + + /** + * @zh 清除实体的所有触发器 + * @en Clear all triggers for entity + */ + clearEntityTriggers(entityId: string): void { + const entitySet = this._entityTriggers.get(entityId); + if (!entitySet) { + return; + } + + for (const triggerId of entitySet) { + this._dispatcher.registry.unregister(triggerId); + } + + this._entityTriggers.delete(entityId); + } + + /** + * @zh 调度触发器到实体 + * @en Dispatch trigger to entity + */ + dispatchToEntity(entityId: string, context: ITriggerContext): DispatchResult { + const entityTriggers = this.getEntityTriggers(entityId); + const results: TriggerResult[] = []; + let triggeredCount = 0; + + for (const trigger of entityTriggers) { + if (trigger.shouldFire(context)) { + try { + trigger.fire(context); + triggeredCount++; + results.push({ + triggerId: trigger.id, + success: true + }); + } catch (error) { + results.push({ + triggerId: trigger.id, + success: false, + error: error instanceof Error ? error.message : String(error) + }); + } + } + } + + return { + context, + triggeredCount, + results + }; + } +} + +// ============================================================================= +// 工厂函数 | Factory Functions +// ============================================================================= + +/** + * @zh 创建触发器调度器 + * @en Create trigger dispatcher + */ +export function createTriggerDispatcher(registry?: ITriggerRegistry): TriggerDispatcher { + return new TriggerDispatcher(registry); +} + +/** + * @zh 创建实体触发器管理器 + * @en Create entity trigger manager + */ +export function createEntityTriggerManager(dispatcher?: ITriggerDispatcher): EntityTriggerManager { + return new EntityTriggerManager(dispatcher); +} diff --git a/packages/blueprint/src/triggers/TriggerTypes.ts b/packages/blueprint/src/triggers/TriggerTypes.ts new file mode 100644 index 00000000..49b680ec --- /dev/null +++ b/packages/blueprint/src/triggers/TriggerTypes.ts @@ -0,0 +1,400 @@ +/** + * @zh 蓝图触发器类型定义 + * @en Blueprint Trigger Type Definitions + * + * @zh 定义触发器的核心类型和接口 + * @en Defines core types and interfaces for triggers + */ + +// ============================================================================= +// 触发器类型 | Trigger Types +// ============================================================================= + +/** + * @zh 触发器类型枚举 + * @en Trigger type enumeration + */ +export type TriggerType = + | 'tick' // 每帧触发 | Every frame + | 'input' // 输入事件 | Input event + | 'collision' // 碰撞事件 | Collision event + | 'message' // 消息事件 | Message event + | 'timer' // 定时器事件 | Timer event + | 'stateEnter' // 状态进入 | State enter + | 'stateExit' // 状态退出 | State exit + | 'custom'; // 自定义事件 | Custom event + +/** + * @zh 触发器类型常量 + * @en Trigger type constants + */ +export const TriggerTypes = { + TICK: 'tick' as const, + INPUT: 'input' as const, + COLLISION: 'collision' as const, + MESSAGE: 'message' as const, + TIMER: 'timer' as const, + STATE_ENTER: 'stateEnter' as const, + STATE_EXIT: 'stateExit' as const, + CUSTOM: 'custom' as const +} as const; + +// ============================================================================= +// 触发器上下文 | Trigger Context +// ============================================================================= + +/** + * @zh 触发器上下文基础接口 + * @en Trigger context base interface + */ +export interface ITriggerContext { + /** + * @zh 触发器类型 + * @en Trigger type + */ + readonly type: TriggerType; + + /** + * @zh 触发时间戳 + * @en Trigger timestamp + */ + readonly timestamp: number; + + /** + * @zh 触发源实体 ID + * @en Source entity ID + */ + readonly sourceEntityId?: string; + + /** + * @zh 附加数据 + * @en Additional data + */ + readonly data?: Record; +} + +/** + * @zh Tick 触发器上下文 + * @en Tick trigger context + */ +export interface ITickTriggerContext extends ITriggerContext { + readonly type: 'tick'; + /** + * @zh 增量时间(秒) + * @en Delta time (seconds) + */ + readonly deltaTime: number; + /** + * @zh 帧计数 + * @en Frame count + */ + readonly frameCount: number; +} + +/** + * @zh 输入触发器上下文 + * @en Input trigger context + */ +export interface IInputTriggerContext extends ITriggerContext { + readonly type: 'input'; + /** + * @zh 输入动作名称 + * @en Input action name + */ + readonly action: string; + /** + * @zh 输入值 + * @en Input value + */ + readonly value: number | boolean; + /** + * @zh 是否刚按下 + * @en Is just pressed + */ + readonly pressed?: boolean; + /** + * @zh 是否刚释放 + * @en Is just released + */ + readonly released?: boolean; +} + +/** + * @zh 碰撞触发器上下文 + * @en Collision trigger context + */ +export interface ICollisionTriggerContext extends ITriggerContext { + readonly type: 'collision'; + /** + * @zh 碰撞的另一个实体 ID + * @en Other entity ID in collision + */ + readonly otherEntityId: string; + /** + * @zh 碰撞点 + * @en Collision point + */ + readonly point?: { x: number; y: number }; + /** + * @zh 碰撞法线 + * @en Collision normal + */ + readonly normal?: { x: number; y: number }; + /** + * @zh 是否开始碰撞 + * @en Is collision start + */ + readonly isEnter: boolean; + /** + * @zh 是否结束碰撞 + * @en Is collision end + */ + readonly isExit: boolean; +} + +/** + * @zh 消息触发器上下文 + * @en Message trigger context + */ +export interface IMessageTriggerContext extends ITriggerContext { + readonly type: 'message'; + /** + * @zh 消息名称 + * @en Message name + */ + readonly messageName: string; + /** + * @zh 发送者 ID + * @en Sender ID + */ + readonly senderId?: string; + /** + * @zh 消息负载 + * @en Message payload + */ + readonly payload?: unknown; +} + +/** + * @zh 定时器触发器上下文 + * @en Timer trigger context + */ +export interface ITimerTriggerContext extends ITriggerContext { + readonly type: 'timer'; + /** + * @zh 定时器 ID + * @en Timer ID + */ + readonly timerId: string; + /** + * @zh 是否循环触发 + * @en Is repeating + */ + readonly isRepeating: boolean; + /** + * @zh 已触发次数 + * @en Times fired + */ + readonly timesFired: number; +} + +/** + * @zh 状态触发器上下文 + * @en State trigger context + */ +export interface IStateTriggerContext extends ITriggerContext { + readonly type: 'stateEnter' | 'stateExit'; + /** + * @zh 状态机 ID + * @en State machine ID + */ + readonly stateMachineId: string; + /** + * @zh 当前状态 + * @en Current state + */ + readonly currentState: string; + /** + * @zh 之前状态 + * @en Previous state + */ + readonly previousState?: string; +} + +/** + * @zh 自定义触发器上下文 + * @en Custom trigger context + */ +export interface ICustomTriggerContext extends ITriggerContext { + readonly type: 'custom'; + /** + * @zh 事件名称 + * @en Event name + */ + readonly eventName: string; +} + +/** + * @zh 所有触发器上下文的联合类型 + * @en Union type of all trigger contexts + */ +export type TriggerContext = + | ITickTriggerContext + | IInputTriggerContext + | ICollisionTriggerContext + | IMessageTriggerContext + | ITimerTriggerContext + | IStateTriggerContext + | ICustomTriggerContext; + +// ============================================================================= +// 工厂函数 | Factory Functions +// ============================================================================= + +/** + * @zh 创建 Tick 触发器上下文 + * @en Create tick trigger context + */ +export function createTickContext( + deltaTime: number, + frameCount: number, + sourceEntityId?: string +): ITickTriggerContext { + return { + type: 'tick', + timestamp: Date.now(), + deltaTime, + frameCount, + sourceEntityId + }; +} + +/** + * @zh 创建输入触发器上下文 + * @en Create input trigger context + */ +export function createInputContext( + action: string, + value: number | boolean, + options?: { + pressed?: boolean; + released?: boolean; + sourceEntityId?: string; + } +): IInputTriggerContext { + return { + type: 'input', + timestamp: Date.now(), + action, + value, + pressed: options?.pressed, + released: options?.released, + sourceEntityId: options?.sourceEntityId + }; +} + +/** + * @zh 创建碰撞触发器上下文 + * @en Create collision trigger context + */ +export function createCollisionContext( + otherEntityId: string, + isEnter: boolean, + options?: { + point?: { x: number; y: number }; + normal?: { x: number; y: number }; + sourceEntityId?: string; + } +): ICollisionTriggerContext { + return { + type: 'collision', + timestamp: Date.now(), + otherEntityId, + isEnter, + isExit: !isEnter, + point: options?.point, + normal: options?.normal, + sourceEntityId: options?.sourceEntityId + }; +} + +/** + * @zh 创建消息触发器上下文 + * @en Create message trigger context + */ +export function createMessageContext( + messageName: string, + payload?: unknown, + options?: { + senderId?: string; + sourceEntityId?: string; + } +): IMessageTriggerContext { + return { + type: 'message', + timestamp: Date.now(), + messageName, + payload, + senderId: options?.senderId, + sourceEntityId: options?.sourceEntityId + }; +} + +/** + * @zh 创建定时器触发器上下文 + * @en Create timer trigger context + */ +export function createTimerContext( + timerId: string, + isRepeating: boolean, + timesFired: number, + sourceEntityId?: string +): ITimerTriggerContext { + return { + type: 'timer', + timestamp: Date.now(), + timerId, + isRepeating, + timesFired, + sourceEntityId + }; +} + +/** + * @zh 创建状态触发器上下文 + * @en Create state trigger context + */ +export function createStateContext( + type: 'stateEnter' | 'stateExit', + stateMachineId: string, + currentState: string, + previousState?: string, + sourceEntityId?: string +): IStateTriggerContext { + return { + type, + timestamp: Date.now(), + stateMachineId, + currentState, + previousState, + sourceEntityId + }; +} + +/** + * @zh 创建自定义触发器上下文 + * @en Create custom trigger context + */ +export function createCustomContext( + eventName: string, + data?: Record, + sourceEntityId?: string +): ICustomTriggerContext { + return { + type: 'custom', + timestamp: Date.now(), + eventName, + data, + sourceEntityId + }; +} diff --git a/packages/blueprint/src/triggers/index.ts b/packages/blueprint/src/triggers/index.ts new file mode 100644 index 00000000..ed543e09 --- /dev/null +++ b/packages/blueprint/src/triggers/index.ts @@ -0,0 +1,105 @@ +/** + * @zh 蓝图触发器模块 + * @en Blueprint Triggers Module + * + * @zh 提供蓝图触发器系统的所有导出 + * @en Provides all exports for the blueprint trigger system + */ + +// ============================================================================= +// 触发器类型 | Trigger Types +// ============================================================================= + +export type { + TriggerType, + ITriggerContext, + ITickTriggerContext, + IInputTriggerContext, + ICollisionTriggerContext, + IMessageTriggerContext, + ITimerTriggerContext, + IStateTriggerContext, + ICustomTriggerContext, + TriggerContext +} from './TriggerTypes'; + +export { + TriggerTypes, + createTickContext, + createInputContext, + createCollisionContext, + createMessageContext, + createTimerContext, + createStateContext, + createCustomContext +} from './TriggerTypes'; + +// ============================================================================= +// 触发器条件 | Trigger Conditions +// ============================================================================= + +export type { + ITriggerCondition, + ConditionLogic +} from './TriggerCondition'; + +export { + CompositeCondition, + NotCondition, + AlwaysTrueCondition, + AlwaysFalseCondition, + TriggerTypeCondition, + EntityIdCondition, + FunctionCondition, + InputActionCondition, + MessageNameCondition, + StateNameCondition, + TimerIdCondition, + CollisionEntityCondition, + CustomEventCondition, + ConditionBuilder, + condition +} from './TriggerCondition'; + +// ============================================================================= +// 蓝图触发器 | Blueprint Trigger +// ============================================================================= + +export type { + TriggerCallback, + IBlueprintTrigger, + TriggerConfig, + ITriggerRegistry +} from './BlueprintTrigger'; + +export { + BlueprintTrigger, + TriggerRegistry, + createTrigger, + createTickTrigger, + createInputTrigger, + createCollisionTrigger, + createMessageTrigger, + createTimerTrigger, + createStateEnterTrigger, + createStateExitTrigger, + createCustomTrigger +} from './BlueprintTrigger'; + +// ============================================================================= +// 触发器调度器 | Trigger Dispatcher +// ============================================================================= + +export type { + TriggerResult, + DispatchResult, + ITriggerDispatcher, + IEntityTriggerManager +} from './TriggerDispatcher'; + +export { + TriggerDispatcher, + EntityTriggerManager, + createTriggerDispatcher, + createEntityTriggerManager +} from './TriggerDispatcher';