Compare commits
5 Commits
@esengine/
...
@esengine/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2c3a24404 | ||
|
|
3bfb8a1c9b | ||
|
|
2ee8d87647 | ||
|
|
2d537dc10c | ||
|
|
c2acd14fce |
@@ -1,5 +1,24 @@
|
||||
# @esengine/node-editor
|
||||
|
||||
## 1.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [`3bfb8a1`](https://github.com/esengine/esengine/commit/3bfb8a1c9baba18373717910d29f266a71c1f63e) Thanks [@esengine](https://github.com/esengine)! - feat(node-editor): add box selection and variable node error states
|
||||
- Add box selection: drag on empty canvas to select multiple nodes
|
||||
- Support Ctrl+drag for additive selection (add to existing selection)
|
||||
- Add error state styling for invalid variable references (red border, warning icon)
|
||||
- Support dynamic node title via `data.displayTitle`
|
||||
- Support hiding inputs via `data.hiddenInputs` array
|
||||
|
||||
## 1.2.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#435](https://github.com/esengine/esengine/pull/435) [`c2acd14`](https://github.com/esengine/esengine/commit/c2acd14fce83af6cd116b3f2e40607229ccc3d6e) Thanks [@esengine](https://github.com/esengine)! - fix(node-editor): 修复节点收缩后连线不显示的问题
|
||||
- 节点收缩时,连线会连接到节点头部(输入引脚在左侧,输出引脚在右侧)
|
||||
- 展开后连线会自动恢复到正确位置
|
||||
|
||||
## 1.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esengine/node-editor",
|
||||
"version": "1.2.1",
|
||||
"version": "1.3.0",
|
||||
"description": "Universal node-based visual editor for blueprint, shader graph, and state machine",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.js",
|
||||
|
||||
@@ -50,6 +50,15 @@ export interface GraphCanvasProps {
|
||||
/** Canvas context menu callback (画布右键菜单回调) */
|
||||
onContextMenu?: (position: Position, e: React.MouseEvent) => void;
|
||||
|
||||
/** Canvas mouse down callback for box selection (画布鼠标按下回调,用于框选) */
|
||||
onMouseDown?: (position: Position, e: React.MouseEvent) => void;
|
||||
|
||||
/** Canvas mouse move callback (画布鼠标移动回调) */
|
||||
onCanvasMouseMove?: (position: Position, e: React.MouseEvent) => void;
|
||||
|
||||
/** Canvas mouse up callback (画布鼠标释放回调) */
|
||||
onCanvasMouseUp?: (position: Position, e: React.MouseEvent) => void;
|
||||
|
||||
/** Children to render (要渲染的子元素) */
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
@@ -75,6 +84,9 @@ export const GraphCanvas: React.FC<GraphCanvasProps> = ({
|
||||
onZoomChange,
|
||||
onClick,
|
||||
onContextMenu,
|
||||
onMouseDown: onMouseDownProp,
|
||||
onCanvasMouseMove,
|
||||
onCanvasMouseUp,
|
||||
children
|
||||
}) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
@@ -132,22 +144,30 @@ export const GraphCanvas: React.FC<GraphCanvasProps> = ({
|
||||
}, [zoom, pan, minZoom, maxZoom, updateZoom, updatePan]);
|
||||
|
||||
/**
|
||||
* Handles mouse down for panning
|
||||
* 处理鼠标按下开始平移
|
||||
* Handles mouse down for panning or box selection
|
||||
* 处理鼠标按下开始平移或框选
|
||||
*/
|
||||
const handleMouseDown = useCallback((e: React.MouseEvent) => {
|
||||
// Middle mouse button or space + left click for panning
|
||||
// 中键或空格+左键平移
|
||||
// Middle mouse button or Alt + left click for panning
|
||||
// 中键或 Alt+左键平移
|
||||
if (e.button === 1 || (e.button === 0 && e.altKey)) {
|
||||
e.preventDefault();
|
||||
setIsPanning(true);
|
||||
lastMousePos.current = new Position(e.clientX, e.clientY);
|
||||
} else if (e.button === 0) {
|
||||
// Left click on canvas background - start box selection
|
||||
// 左键点击画布背景 - 开始框选
|
||||
const target = e.target as HTMLElement;
|
||||
if (target === containerRef.current || target.classList.contains('ne-canvas-content')) {
|
||||
const canvasPos = screenToCanvas(e.clientX, e.clientY);
|
||||
onMouseDownProp?.(canvasPos, e);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
}, [screenToCanvas, onMouseDownProp]);
|
||||
|
||||
/**
|
||||
* Handles mouse move for panning
|
||||
* 处理鼠标移动进行平移
|
||||
* Handles mouse move for panning or box selection
|
||||
* 处理鼠标移动进行平移或框选
|
||||
*/
|
||||
const handleMouseMove = useCallback((e: React.MouseEvent) => {
|
||||
if (isPanning) {
|
||||
@@ -157,13 +177,27 @@ export const GraphCanvas: React.FC<GraphCanvasProps> = ({
|
||||
const newPan = new Position(pan.x + dx, pan.y + dy);
|
||||
updatePan(newPan);
|
||||
}
|
||||
}, [isPanning, pan, updatePan]);
|
||||
// Always call canvas mouse move for box selection
|
||||
// 始终调用画布鼠标移动回调用于框选
|
||||
const canvasPos = screenToCanvas(e.clientX, e.clientY);
|
||||
onCanvasMouseMove?.(canvasPos, e);
|
||||
}, [isPanning, pan, updatePan, screenToCanvas, onCanvasMouseMove]);
|
||||
|
||||
/**
|
||||
* Handles mouse up to stop panning
|
||||
* 处理鼠标释放停止平移
|
||||
* Handles mouse up to stop panning or box selection
|
||||
* 处理鼠标释放停止平移或框选
|
||||
*/
|
||||
const handleMouseUp = useCallback(() => {
|
||||
const handleMouseUp = useCallback((e: React.MouseEvent) => {
|
||||
setIsPanning(false);
|
||||
const canvasPos = screenToCanvas(e.clientX, e.clientY);
|
||||
onCanvasMouseUp?.(canvasPos, e);
|
||||
}, [screenToCanvas, onCanvasMouseUp]);
|
||||
|
||||
/**
|
||||
* Handles mouse leave to stop panning
|
||||
* 处理鼠标离开停止平移
|
||||
*/
|
||||
const handleMouseLeave = useCallback(() => {
|
||||
setIsPanning(false);
|
||||
}, []);
|
||||
|
||||
@@ -276,7 +310,7 @@ export const GraphCanvas: React.FC<GraphCanvasProps> = ({
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseUp={handleMouseUp}
|
||||
onMouseLeave={handleMouseUp}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
onClick={handleClick}
|
||||
onContextMenu={handleContextMenu}
|
||||
>
|
||||
|
||||
@@ -80,6 +80,16 @@ interface ConnectionDragState {
|
||||
isValid?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Box selection state
|
||||
* 框选状态
|
||||
*/
|
||||
interface BoxSelectState {
|
||||
startPos: Position;
|
||||
currentPos: Position;
|
||||
additive: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* NodeEditor - Complete node graph editor component
|
||||
* NodeEditor - 完整的节点图编辑器组件
|
||||
@@ -126,6 +136,11 @@ export const NodeEditor: React.FC<NodeEditorProps> = ({
|
||||
const [dragState, setDragState] = useState<DragState | null>(null);
|
||||
const [connectionDrag, setConnectionDrag] = useState<ConnectionDragState | null>(null);
|
||||
const [hoveredPin, setHoveredPin] = useState<Pin | null>(null);
|
||||
const [boxSelectState, setBoxSelectState] = useState<BoxSelectState | null>(null);
|
||||
|
||||
// Track if box selection just ended to prevent click from clearing selection
|
||||
// 跟踪框选是否刚刚结束,以防止 click 清除选择
|
||||
const boxSelectJustEndedRef = useRef(false);
|
||||
|
||||
// Force re-render after mount to ensure connections are drawn correctly
|
||||
// 挂载后强制重渲染以确保连接线正确绘制
|
||||
@@ -477,6 +492,12 @@ export const NodeEditor: React.FC<NodeEditorProps> = ({
|
||||
* 处理画布点击取消选择
|
||||
*/
|
||||
const handleCanvasClick = useCallback((_position: Position, _e: React.MouseEvent) => {
|
||||
// Skip if box selection just ended (click fires after mouseup)
|
||||
// 如果框选刚刚结束则跳过(click 在 mouseup 之后触发)
|
||||
if (boxSelectJustEndedRef.current) {
|
||||
boxSelectJustEndedRef.current = false;
|
||||
return;
|
||||
}
|
||||
if (!readOnly) {
|
||||
onSelectionChange?.(new Set(), new Set());
|
||||
}
|
||||
@@ -490,6 +511,79 @@ export const NodeEditor: React.FC<NodeEditorProps> = ({
|
||||
onCanvasContextMenu?.(position, e);
|
||||
}, [onCanvasContextMenu]);
|
||||
|
||||
/**
|
||||
* Handles box selection start
|
||||
* 处理框选开始
|
||||
*/
|
||||
const handleBoxSelectStart = useCallback((position: Position, e: React.MouseEvent) => {
|
||||
if (readOnly) return;
|
||||
setBoxSelectState({
|
||||
startPos: position,
|
||||
currentPos: position,
|
||||
additive: e.ctrlKey || e.metaKey
|
||||
});
|
||||
}, [readOnly]);
|
||||
|
||||
/**
|
||||
* Handles box selection move
|
||||
* 处理框选移动
|
||||
*/
|
||||
const handleBoxSelectMove = useCallback((position: Position) => {
|
||||
if (boxSelectState) {
|
||||
setBoxSelectState(prev => prev ? { ...prev, currentPos: position } : null);
|
||||
}
|
||||
}, [boxSelectState]);
|
||||
|
||||
/**
|
||||
* Handles box selection end
|
||||
* 处理框选结束
|
||||
*/
|
||||
const handleBoxSelectEnd = useCallback(() => {
|
||||
if (!boxSelectState) return;
|
||||
|
||||
const { startPos, currentPos, additive } = boxSelectState;
|
||||
|
||||
// Calculate selection box bounds
|
||||
const minX = Math.min(startPos.x, currentPos.x);
|
||||
const maxX = Math.max(startPos.x, currentPos.x);
|
||||
const minY = Math.min(startPos.y, currentPos.y);
|
||||
const maxY = Math.max(startPos.y, currentPos.y);
|
||||
|
||||
// Find nodes within the selection box
|
||||
const nodesInBox: string[] = [];
|
||||
const nodeWidth = 200; // Approximate node width
|
||||
const nodeHeight = 100; // Approximate node height
|
||||
|
||||
for (const node of graph.nodes) {
|
||||
const nodeLeft = node.position.x;
|
||||
const nodeTop = node.position.y;
|
||||
const nodeRight = nodeLeft + nodeWidth;
|
||||
const nodeBottom = nodeTop + nodeHeight;
|
||||
|
||||
// Check if node intersects with selection box
|
||||
const intersects = !(nodeRight < minX || nodeLeft > maxX || nodeBottom < minY || nodeTop > maxY);
|
||||
if (intersects) {
|
||||
nodesInBox.push(node.id);
|
||||
}
|
||||
}
|
||||
|
||||
// Update selection
|
||||
if (additive) {
|
||||
// Add to existing selection
|
||||
const newSelection = new Set(selectedNodeIds);
|
||||
nodesInBox.forEach(id => newSelection.add(id));
|
||||
onSelectionChange?.(newSelection, new Set());
|
||||
} else {
|
||||
// Replace selection
|
||||
onSelectionChange?.(new Set(nodesInBox), new Set());
|
||||
}
|
||||
|
||||
// Mark that box selection just ended to prevent click from clearing selection
|
||||
// 标记框选刚刚结束,以防止 click 清除选择
|
||||
boxSelectJustEndedRef.current = true;
|
||||
setBoxSelectState(null);
|
||||
}, [boxSelectState, graph.nodes, selectedNodeIds, onSelectionChange]);
|
||||
|
||||
/**
|
||||
* Handles pin value change
|
||||
* 处理引脚值变化
|
||||
@@ -546,6 +640,9 @@ export const NodeEditor: React.FC<NodeEditorProps> = ({
|
||||
onContextMenu={handleCanvasContextMenu}
|
||||
onPanChange={handlePanChange}
|
||||
onZoomChange={handleZoomChange}
|
||||
onMouseDown={handleBoxSelectStart}
|
||||
onCanvasMouseMove={handleBoxSelectMove}
|
||||
onCanvasMouseUp={handleBoxSelectEnd}
|
||||
>
|
||||
{/* Connection layer (连接层) */}
|
||||
<ConnectionLayer
|
||||
@@ -580,6 +677,19 @@ export const NodeEditor: React.FC<NodeEditorProps> = ({
|
||||
onToggleCollapse={handleToggleCollapse}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Box selection overlay (框选覆盖层) */}
|
||||
{boxSelectState && (
|
||||
<div
|
||||
className="ne-selection-box"
|
||||
style={{
|
||||
left: Math.min(boxSelectState.startPos.x, boxSelectState.currentPos.x),
|
||||
top: Math.min(boxSelectState.startPos.y, boxSelectState.currentPos.y),
|
||||
width: Math.abs(boxSelectState.currentPos.x - boxSelectState.startPos.x),
|
||||
height: Math.abs(boxSelectState.currentPos.y - boxSelectState.startPos.y)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</GraphCanvas>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -64,6 +64,8 @@ export const GraphNodeComponent: React.FC<GraphNodeComponentProps> = ({
|
||||
return draggingFromPin.canConnectTo(pin);
|
||||
}, [draggingFromPin]);
|
||||
|
||||
const hasError = Boolean(node.data.invalidVariable);
|
||||
|
||||
const classNames = useMemo(() => {
|
||||
const classes = ['ne-node'];
|
||||
if (isSelected) classes.push('selected');
|
||||
@@ -71,8 +73,9 @@ export const GraphNodeComponent: React.FC<GraphNodeComponentProps> = ({
|
||||
if (node.isCollapsed) classes.push('collapsed');
|
||||
if (node.category === 'comment') classes.push('comment');
|
||||
if (executionState !== 'idle') classes.push(executionState);
|
||||
if (hasError) classes.push('has-error');
|
||||
return classes.join(' ');
|
||||
}, [isSelected, isDragging, node.isCollapsed, node.category, executionState]);
|
||||
}, [isSelected, isDragging, node.isCollapsed, node.category, executionState, hasError]);
|
||||
|
||||
const handleMouseDown = useCallback((e: React.MouseEvent) => {
|
||||
if (e.button !== 0) return;
|
||||
@@ -102,8 +105,10 @@ export const GraphNodeComponent: React.FC<GraphNodeComponentProps> = ({
|
||||
: undefined;
|
||||
|
||||
// Separate exec pins from data pins
|
||||
// Also filter out pins listed in data.hiddenInputs
|
||||
const hiddenInputs = (node.data.hiddenInputs as string[]) || [];
|
||||
const inputExecPins = node.inputPins.filter(p => p.isExec && !p.hidden);
|
||||
const inputDataPins = node.inputPins.filter(p => !p.isExec && !p.hidden);
|
||||
const inputDataPins = node.inputPins.filter(p => !p.isExec && !p.hidden && !hiddenInputs.includes(p.name));
|
||||
const outputExecPins = node.outputPins.filter(p => p.isExec && !p.hidden);
|
||||
const outputDataPins = node.outputPins.filter(p => !p.isExec && !p.hidden);
|
||||
|
||||
@@ -129,13 +134,17 @@ export const GraphNodeComponent: React.FC<GraphNodeComponentProps> = ({
|
||||
className={`ne-node-header ${node.category}`}
|
||||
style={headerStyle}
|
||||
>
|
||||
{/* Diamond icon for event nodes, or custom icon */}
|
||||
{/* Warning icon for invalid nodes, or diamond/custom icon */}
|
||||
<span className="ne-node-header-icon">
|
||||
{node.icon && renderIcon ? renderIcon(node.icon) : null}
|
||||
{hasError ? (
|
||||
<span className="ne-node-warning-icon" title={`Variable '${node.data.variableName}' not found`}>⚠</span>
|
||||
) : (
|
||||
node.icon && renderIcon ? renderIcon(node.icon) : null
|
||||
)}
|
||||
</span>
|
||||
|
||||
<span className="ne-node-header-title">
|
||||
{node.title}
|
||||
{(node.data.displayTitle as string) || node.title}
|
||||
{node.subtitle && (
|
||||
<span className="ne-node-header-subtitle">
|
||||
{node.subtitle}
|
||||
|
||||
@@ -38,6 +38,24 @@
|
||||
z-index: var(--ne-z-dragging);
|
||||
}
|
||||
|
||||
/* Error state for invalid variable references */
|
||||
.ne-node.has-error {
|
||||
border-color: #e74c3c;
|
||||
box-shadow: 0 0 0 1px #e74c3c,
|
||||
0 0 12px rgba(231, 76, 60, 0.4),
|
||||
0 4px 8px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.ne-node.has-error .ne-node-header {
|
||||
background: linear-gradient(180deg, #c0392b 0%, #962d22 100%) !important;
|
||||
}
|
||||
|
||||
.ne-node-warning-icon {
|
||||
color: #f1c40f;
|
||||
font-size: 14px;
|
||||
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));
|
||||
}
|
||||
|
||||
/* ==================== Node Header 节点头部 ==================== */
|
||||
.ne-node-header {
|
||||
display: flex;
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
# @esengine/blueprint
|
||||
|
||||
## 4.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#435](https://github.com/esengine/esengine/pull/435) [`c2acd14`](https://github.com/esengine/esengine/commit/c2acd14fce83af6cd116b3f2e40607229ccc3d6e) Thanks [@esengine](https://github.com/esengine)! - 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 注册的组件
|
||||
|
||||
## 4.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esengine/blueprint",
|
||||
"version": "4.2.0",
|
||||
"version": "4.3.0",
|
||||
"description": "Visual scripting system - works with any ECS framework (ESEngine, Cocos, Laya, etc.)",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.js",
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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 ?? '') } };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* @zh 变量节点导出
|
||||
* @en Variable nodes export
|
||||
*/
|
||||
|
||||
export * from './VariableNodes';
|
||||
@@ -1,5 +1,12 @@
|
||||
# @esengine/fsm
|
||||
|
||||
## 7.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`c2acd14`](https://github.com/esengine/esengine/commit/c2acd14fce83af6cd116b3f2e40607229ccc3d6e)]:
|
||||
- @esengine/blueprint@4.3.0
|
||||
|
||||
## 6.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esengine/fsm",
|
||||
"version": "6.0.0",
|
||||
"version": "7.0.0",
|
||||
"description": "Finite State Machine for ECS Framework / ECS 框架的有限状态机",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @esengine/network
|
||||
|
||||
## 8.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`c2acd14`](https://github.com/esengine/esengine/commit/c2acd14fce83af6cd116b3f2e40607229ccc3d6e)]:
|
||||
- @esengine/blueprint@4.3.0
|
||||
|
||||
## 7.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esengine/network",
|
||||
"version": "7.0.0",
|
||||
"version": "8.0.0",
|
||||
"description": "Network synchronization for multiplayer games",
|
||||
"esengine": {
|
||||
"plugin": true,
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @esengine/pathfinding
|
||||
|
||||
## 7.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`c2acd14`](https://github.com/esengine/esengine/commit/c2acd14fce83af6cd116b3f2e40607229ccc3d6e)]:
|
||||
- @esengine/blueprint@4.3.0
|
||||
|
||||
## 6.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esengine/pathfinding",
|
||||
"version": "6.0.0",
|
||||
"version": "7.0.0",
|
||||
"description": "寻路系统 | Pathfinding System - A*, Grid, NavMesh",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @esengine/procgen
|
||||
|
||||
## 7.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`c2acd14`](https://github.com/esengine/esengine/commit/c2acd14fce83af6cd116b3f2e40607229ccc3d6e)]:
|
||||
- @esengine/blueprint@4.3.0
|
||||
|
||||
## 6.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esengine/procgen",
|
||||
"version": "6.0.0",
|
||||
"version": "7.0.0",
|
||||
"description": "Procedural generation tools for ECS Framework / ECS 框架的程序化生成工具",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @esengine/spatial
|
||||
|
||||
## 7.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`c2acd14`](https://github.com/esengine/esengine/commit/c2acd14fce83af6cd116b3f2e40607229ccc3d6e)]:
|
||||
- @esengine/blueprint@4.3.0
|
||||
|
||||
## 6.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esengine/spatial",
|
||||
"version": "6.0.0",
|
||||
"version": "7.0.0",
|
||||
"description": "Spatial query and indexing system for ECS Framework / ECS 框架的空间查询和索引系统",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @esengine/timer
|
||||
|
||||
## 7.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`c2acd14`](https://github.com/esengine/esengine/commit/c2acd14fce83af6cd116b3f2e40607229ccc3d6e)]:
|
||||
- @esengine/blueprint@4.3.0
|
||||
|
||||
## 6.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esengine/timer",
|
||||
"version": "6.0.0",
|
||||
"version": "7.0.0",
|
||||
"description": "Timer and cooldown system for ECS Framework / ECS 框架的定时器和冷却系统",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# @esengine/demos
|
||||
|
||||
## 1.0.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @esengine/fsm@7.0.0
|
||||
- @esengine/pathfinding@7.0.0
|
||||
- @esengine/procgen@7.0.0
|
||||
- @esengine/spatial@7.0.0
|
||||
- @esengine/timer@7.0.0
|
||||
|
||||
## 1.0.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esengine/demos",
|
||||
"version": "1.0.12",
|
||||
"version": "1.0.13",
|
||||
"private": true,
|
||||
"description": "Demo tests for ESEngine modules documentation",
|
||||
"type": "module",
|
||||
|
||||
Reference in New Issue
Block a user