diff --git a/.changeset/blueprint-ecs-component.md b/.changeset/blueprint-ecs-component.md new file mode 100644 index 00000000..4bf6594a --- /dev/null +++ b/.changeset/blueprint-ecs-component.md @@ -0,0 +1,18 @@ +--- +"@esengine/blueprint": minor +--- + +feat(blueprint): 添加 Add Component 节点支持 + 变量节点 + ECS 模式重构 + +新功能: +- 为每个 @BlueprintExpose 组件自动生成 Add_ComponentName 节点 +- Add 节点支持设置初始属性值 +- 添加通用 ECS_AddComponent 节点用于动态添加组件 +- @BlueprintExpose 装饰的组件自动注册,无需手动调用 registerComponentClass() +- 添加变量节点:GetVariable, SetVariable, GetBoolVariable, GetFloatVariable, GetIntVariable, GetStringVariable + +重构: +- BlueprintComponent 使用 @ECSComponent 装饰器注册 +- BlueprintSystem 继承标准 System 基类 +- 简化组件 API,优化 VM 生命周期管理 +- ExecutionContext.getComponentClass() 自动查找 @BlueprintExpose 注册的组件 diff --git a/.changeset/node-editor-collapse-fix.md b/.changeset/node-editor-collapse-fix.md new file mode 100644 index 00000000..dd092db9 --- /dev/null +++ b/.changeset/node-editor-collapse-fix.md @@ -0,0 +1,8 @@ +--- +"@esengine/node-editor": patch +--- + +fix(node-editor): 修复节点收缩后连线不显示的问题 + +- 节点收缩时,连线会连接到节点头部(输入引脚在左侧,输出引脚在右侧) +- 展开后连线会自动恢复到正确位置 diff --git a/packages/framework/blueprint/src/nodes/index.ts b/packages/framework/blueprint/src/nodes/index.ts index ee75f3f9..81a888b1 100644 --- a/packages/framework/blueprint/src/nodes/index.ts +++ b/packages/framework/blueprint/src/nodes/index.ts @@ -5,6 +5,7 @@ * @zh 节点分类: * - events: 生命周期事件(BeginPlay, Tick, EndPlay) * - ecs: ECS 操作(Entity, Component, Flow) + * - variables: 变量读写 * - math: 数学运算 * - time: 时间工具 * - debug: 调试工具 @@ -12,6 +13,7 @@ * @en Node categories: * - events: Lifecycle events (BeginPlay, Tick, EndPlay) * - ecs: ECS operations (Entity, Component, Flow) + * - variables: Variable get/set * - math: Math operations * - time: Time utilities * - debug: Debug utilities @@ -23,6 +25,9 @@ export * from './events'; // ECS operations | ECS 操作 export * from './ecs'; +// Variables | 变量 +export * from './variables'; + // Math operations | 数学运算 export * from './math'; diff --git a/packages/framework/blueprint/src/nodes/variables/VariableNodes.ts b/packages/framework/blueprint/src/nodes/variables/VariableNodes.ts new file mode 100644 index 00000000..21eef4ec --- /dev/null +++ b/packages/framework/blueprint/src/nodes/variables/VariableNodes.ts @@ -0,0 +1,189 @@ +/** + * @zh 变量节点 - 读取和设置蓝图变量 + * @en Variable Nodes - Get and set blueprint variables + */ + +import { BlueprintNodeTemplate, BlueprintNode } from '../../types/nodes'; +import { ExecutionContext, ExecutionResult } from '../../runtime/ExecutionContext'; +import { INodeExecutor, RegisterNode } from '../../runtime/NodeRegistry'; + +// ============================================================================ +// Get Variable | 获取变量 +// ============================================================================ + +export const GetVariableTemplate: BlueprintNodeTemplate = { + type: 'GetVariable', + title: 'Get Variable', + category: 'variable', + color: '#4a9c6d', + isPure: true, + description: 'Gets the value of a variable (获取变量的值)', + keywords: ['variable', 'get', 'read', 'value'], + menuPath: ['Variable', 'Get Variable'], + inputs: [ + { name: 'variableName', type: 'string', displayName: 'Variable Name', defaultValue: '' } + ], + outputs: [ + { name: 'value', type: 'any', displayName: 'Value' } + ] +}; + +@RegisterNode(GetVariableTemplate) +export class GetVariableExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult { + const variableName = context.evaluateInput(node.id, 'variableName', '') as string; + + if (!variableName) { + return { outputs: { value: null } }; + } + + const value = context.getVariable(variableName); + return { outputs: { value } }; + } +} + +// ============================================================================ +// Set Variable | 设置变量 +// ============================================================================ + +export const SetVariableTemplate: BlueprintNodeTemplate = { + type: 'SetVariable', + title: 'Set Variable', + category: 'variable', + color: '#4a9c6d', + description: 'Sets the value of a variable (设置变量的值)', + keywords: ['variable', 'set', 'write', 'assign', 'value'], + menuPath: ['Variable', 'Set Variable'], + inputs: [ + { name: 'exec', type: 'exec', displayName: '' }, + { name: 'variableName', type: 'string', displayName: 'Variable Name', defaultValue: '' }, + { name: 'value', type: 'any', displayName: 'Value' } + ], + outputs: [ + { name: 'exec', type: 'exec', displayName: '' }, + { name: 'value', type: 'any', displayName: 'Value' } + ] +}; + +@RegisterNode(SetVariableTemplate) +export class SetVariableExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult { + const variableName = context.evaluateInput(node.id, 'variableName', '') as string; + const value = context.evaluateInput(node.id, 'value', null); + + if (!variableName) { + return { outputs: { value: null }, nextExec: 'exec' }; + } + + context.setVariable(variableName, value); + return { outputs: { value }, nextExec: 'exec' }; + } +} + +// ============================================================================ +// Get Variable By Name (typed variants) | 按名称获取变量(类型变体) +// ============================================================================ + +export const GetBoolVariableTemplate: BlueprintNodeTemplate = { + type: 'GetBoolVariable', + title: 'Get Bool', + category: 'variable', + color: '#8b1e3f', + isPure: true, + description: 'Gets a boolean variable (获取布尔变量)', + keywords: ['variable', 'get', 'bool', 'boolean'], + menuPath: ['Variable', 'Get Bool'], + inputs: [ + { name: 'variableName', type: 'string', displayName: 'Variable Name', defaultValue: '' } + ], + outputs: [ + { name: 'value', type: 'bool', displayName: 'Value' } + ] +}; + +@RegisterNode(GetBoolVariableTemplate) +export class GetBoolVariableExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult { + const variableName = context.evaluateInput(node.id, 'variableName', '') as string; + const value = context.getVariable(variableName); + return { outputs: { value: Boolean(value) } }; + } +} + +export const GetFloatVariableTemplate: BlueprintNodeTemplate = { + type: 'GetFloatVariable', + title: 'Get Float', + category: 'variable', + color: '#39c5bb', + isPure: true, + description: 'Gets a float variable (获取浮点变量)', + keywords: ['variable', 'get', 'float', 'number'], + menuPath: ['Variable', 'Get Float'], + inputs: [ + { name: 'variableName', type: 'string', displayName: 'Variable Name', defaultValue: '' } + ], + outputs: [ + { name: 'value', type: 'float', displayName: 'Value' } + ] +}; + +@RegisterNode(GetFloatVariableTemplate) +export class GetFloatVariableExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult { + const variableName = context.evaluateInput(node.id, 'variableName', '') as string; + const value = context.getVariable(variableName); + return { outputs: { value: Number(value) || 0 } }; + } +} + +export const GetIntVariableTemplate: BlueprintNodeTemplate = { + type: 'GetIntVariable', + title: 'Get Int', + category: 'variable', + color: '#1c8b8b', + isPure: true, + description: 'Gets an integer variable (获取整数变量)', + keywords: ['variable', 'get', 'int', 'integer', 'number'], + menuPath: ['Variable', 'Get Int'], + inputs: [ + { name: 'variableName', type: 'string', displayName: 'Variable Name', defaultValue: '' } + ], + outputs: [ + { name: 'value', type: 'int', displayName: 'Value' } + ] +}; + +@RegisterNode(GetIntVariableTemplate) +export class GetIntVariableExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult { + const variableName = context.evaluateInput(node.id, 'variableName', '') as string; + const value = context.getVariable(variableName); + return { outputs: { value: Math.floor(Number(value) || 0) } }; + } +} + +export const GetStringVariableTemplate: BlueprintNodeTemplate = { + type: 'GetStringVariable', + title: 'Get String', + category: 'variable', + color: '#e91e8c', + isPure: true, + description: 'Gets a string variable (获取字符串变量)', + keywords: ['variable', 'get', 'string', 'text'], + menuPath: ['Variable', 'Get String'], + inputs: [ + { name: 'variableName', type: 'string', displayName: 'Variable Name', defaultValue: '' } + ], + outputs: [ + { name: 'value', type: 'string', displayName: 'Value' } + ] +}; + +@RegisterNode(GetStringVariableTemplate) +export class GetStringVariableExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult { + const variableName = context.evaluateInput(node.id, 'variableName', '') as string; + const value = context.getVariable(variableName); + return { outputs: { value: String(value ?? '') } }; + } +} diff --git a/packages/framework/blueprint/src/nodes/variables/index.ts b/packages/framework/blueprint/src/nodes/variables/index.ts new file mode 100644 index 00000000..04debbca --- /dev/null +++ b/packages/framework/blueprint/src/nodes/variables/index.ts @@ -0,0 +1,6 @@ +/** + * @zh 变量节点导出 + * @en Variable nodes export + */ + +export * from './VariableNodes';