docs(blueprint): improve graph visualization with auto-layout
- Add blueprint-graph.js for automatic node layout - Use weighted graph algorithm for better node positioning - Add drag-to-scroll functionality for large graphs - Update CSS to support scrollable graph containers - Sync Chinese and English docs for Example 2 (Health System) - Add explanation for custom Event OnDamage node
This commit is contained in:
462
docs/public/js/blueprint-graph.js
Normal file
462
docs/public/js/blueprint-graph.js
Normal file
@@ -0,0 +1,462 @@
|
|||||||
|
/**
|
||||||
|
* Blueprint Graph Renderer
|
||||||
|
* Custom layout algorithm designed specifically for blueprint-style graphs
|
||||||
|
*/
|
||||||
|
(function() {
|
||||||
|
const PIN_COLORS = {
|
||||||
|
exec: '#ffffff',
|
||||||
|
entity: '#00a0e0',
|
||||||
|
component: '#7030c0',
|
||||||
|
float: '#7ecd32',
|
||||||
|
int: '#1cc4c4',
|
||||||
|
bool: '#8c0000',
|
||||||
|
string: '#e060e0',
|
||||||
|
any: '#707070'
|
||||||
|
};
|
||||||
|
|
||||||
|
const HEADER_CLASSES = {
|
||||||
|
event: 'event',
|
||||||
|
function: 'function',
|
||||||
|
pure: 'pure',
|
||||||
|
flow: 'flow',
|
||||||
|
math: 'math',
|
||||||
|
time: 'time',
|
||||||
|
debug: 'debug',
|
||||||
|
variable: 'variable'
|
||||||
|
};
|
||||||
|
|
||||||
|
const H_GAP = 50; // Horizontal gap between columns
|
||||||
|
const V_GAP = 25; // Vertical gap between nodes
|
||||||
|
const START_X = 20;
|
||||||
|
const START_Y = 20;
|
||||||
|
|
||||||
|
function estimateNodeSize(node) {
|
||||||
|
const headerHeight = 28; // Match CSS: min-height 28px
|
||||||
|
const pinRowHeight = 22; // Match CSS: pin row ~22px
|
||||||
|
const bodyPadding = 12; // Top + bottom padding
|
||||||
|
|
||||||
|
// Count all pins in body (each pin is its own row now)
|
||||||
|
const inputExecCount = node.inputs ? node.inputs.filter(p => p.type === 'exec').length : 0;
|
||||||
|
const inputDataCount = node.inputs ? node.inputs.filter(p => p.type !== 'exec').length : 0;
|
||||||
|
const outputExecCount = node.outputs ? node.outputs.filter(p => p.type === 'exec' && !p.inHeader).length : 0;
|
||||||
|
const outputDataCount = node.outputs ? node.outputs.filter(p => p.type !== 'exec' && !p.inHeader).length : 0;
|
||||||
|
const totalPins = inputExecCount + inputDataCount + outputExecCount + outputDataCount;
|
||||||
|
|
||||||
|
// Calculate height: header + body padding + all pin rows
|
||||||
|
const bodyHeight = totalPins > 0 ? bodyPadding + (totalPins * pinRowHeight) : 0;
|
||||||
|
const height = headerHeight + bodyHeight;
|
||||||
|
|
||||||
|
// Calculate width based on content
|
||||||
|
let maxLabelLen = node.title.length;
|
||||||
|
if (node.inputs) {
|
||||||
|
node.inputs.forEach(p => {
|
||||||
|
const len = (p.label || '').length + (p.value ? String(p.value).length + 3 : 0);
|
||||||
|
maxLabelLen = Math.max(maxLabelLen, len);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (node.outputs) {
|
||||||
|
node.outputs.forEach(p => {
|
||||||
|
maxLabelLen = Math.max(maxLabelLen, (p.label || '').length);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const width = Math.max(110, Math.min(170, maxLabelLen * 8 + 40));
|
||||||
|
return { width, height };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Smart blueprint layout algorithm
|
||||||
|
*
|
||||||
|
* Uses weighted graph analysis:
|
||||||
|
* - All connections matter (exec has higher weight)
|
||||||
|
* - Topological sort for X ordering
|
||||||
|
* - Force-directed optimization for Y positions
|
||||||
|
*/
|
||||||
|
function autoLayout(graphData, maxWidth) {
|
||||||
|
const nodes = graphData.nodes;
|
||||||
|
const connections = graphData.connections;
|
||||||
|
|
||||||
|
if (nodes.length === 0) return { positions: {}, sizes: {} };
|
||||||
|
|
||||||
|
// Calculate node sizes
|
||||||
|
const nodeSizes = {};
|
||||||
|
nodes.forEach(n => { nodeSizes[n.id] = estimateNodeSize(n); });
|
||||||
|
|
||||||
|
// Build maps
|
||||||
|
const pinToNode = {};
|
||||||
|
const nodeById = {};
|
||||||
|
nodes.forEach(n => {
|
||||||
|
nodeById[n.id] = n;
|
||||||
|
(n.inputs || []).forEach(p => { pinToNode[p.id] = n.id; });
|
||||||
|
(n.outputs || []).forEach(p => { pinToNode[p.id] = n.id; });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build weighted adjacency: outgoing[nodeId] = [{to, weight}]
|
||||||
|
const outgoing = {};
|
||||||
|
const incoming = {};
|
||||||
|
nodes.forEach(n => { outgoing[n.id] = []; incoming[n.id] = []; });
|
||||||
|
|
||||||
|
connections.forEach(c => {
|
||||||
|
const from = pinToNode[c.from];
|
||||||
|
const to = pinToNode[c.to];
|
||||||
|
if (!from || !to || from === to) return;
|
||||||
|
|
||||||
|
const weight = c.type === 'exec' ? 3 : 1;
|
||||||
|
outgoing[from].push({ to, weight });
|
||||||
|
incoming[to].push({ from, weight });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Calculate node "depth" using weighted longest path
|
||||||
|
const nodeDepth = {};
|
||||||
|
const visited = new Set();
|
||||||
|
const inProcess = new Set();
|
||||||
|
|
||||||
|
function calcDepth(nodeId) {
|
||||||
|
if (visited.has(nodeId)) return nodeDepth[nodeId];
|
||||||
|
if (inProcess.has(nodeId)) return 0; // Cycle detected
|
||||||
|
|
||||||
|
inProcess.add(nodeId);
|
||||||
|
|
||||||
|
let maxPrevDepth = -1;
|
||||||
|
incoming[nodeId].forEach(({ from, weight }) => {
|
||||||
|
const prevDepth = calcDepth(from);
|
||||||
|
maxPrevDepth = Math.max(maxPrevDepth, prevDepth);
|
||||||
|
});
|
||||||
|
|
||||||
|
inProcess.delete(nodeId);
|
||||||
|
visited.add(nodeId);
|
||||||
|
nodeDepth[nodeId] = maxPrevDepth + 1;
|
||||||
|
return nodeDepth[nodeId];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate depth for all nodes
|
||||||
|
nodes.forEach(n => calcDepth(n.id));
|
||||||
|
|
||||||
|
// Group nodes by depth (column)
|
||||||
|
const columnNodes = {};
|
||||||
|
nodes.forEach(n => {
|
||||||
|
const depth = nodeDepth[n.id];
|
||||||
|
if (!columnNodes[depth]) columnNodes[depth] = [];
|
||||||
|
columnNodes[depth].push(n.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sort columns
|
||||||
|
const sortedColumns = Object.keys(columnNodes).map(Number).sort((a, b) => a - b);
|
||||||
|
|
||||||
|
// Calculate X positions
|
||||||
|
const columnX = {};
|
||||||
|
let currentX = START_X;
|
||||||
|
sortedColumns.forEach(col => {
|
||||||
|
columnX[col] = currentX;
|
||||||
|
let maxW = 0;
|
||||||
|
columnNodes[col].forEach(id => {
|
||||||
|
maxW = Math.max(maxW, nodeSizes[id].width);
|
||||||
|
});
|
||||||
|
currentX += maxW + H_GAP;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize Y positions - simple stacking first
|
||||||
|
const positions = {};
|
||||||
|
sortedColumns.forEach(col => {
|
||||||
|
let y = START_Y;
|
||||||
|
columnNodes[col].forEach(id => {
|
||||||
|
positions[id] = { x: columnX[col], y };
|
||||||
|
y += nodeSizes[id].height + V_GAP;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Force-directed optimization for Y positions (few iterations)
|
||||||
|
for (let iter = 0; iter < 5; iter++) {
|
||||||
|
const forces = {};
|
||||||
|
nodes.forEach(n => { forces[n.id] = 0; });
|
||||||
|
|
||||||
|
// Calculate forces from connections
|
||||||
|
connections.forEach(c => {
|
||||||
|
const from = pinToNode[c.from];
|
||||||
|
const to = pinToNode[c.to];
|
||||||
|
if (!from || !to || from === to) return;
|
||||||
|
|
||||||
|
const weight = c.type === 'exec' ? 2 : 1;
|
||||||
|
const fromY = positions[from].y + nodeSizes[from].height / 2;
|
||||||
|
const toY = positions[to].y + nodeSizes[to].height / 2;
|
||||||
|
const diff = toY - fromY;
|
||||||
|
|
||||||
|
// Pull nodes toward each other
|
||||||
|
forces[from] += diff * 0.1 * weight;
|
||||||
|
forces[to] -= diff * 0.1 * weight;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Apply forces
|
||||||
|
nodes.forEach(n => {
|
||||||
|
positions[n.id].y += forces[n.id];
|
||||||
|
positions[n.id].y = Math.max(START_Y, positions[n.id].y);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Resolve overlaps within columns
|
||||||
|
sortedColumns.forEach(col => {
|
||||||
|
const nodesInCol = columnNodes[col];
|
||||||
|
nodesInCol.sort((a, b) => positions[a].y - positions[b].y);
|
||||||
|
|
||||||
|
for (let i = 1; i < nodesInCol.length; i++) {
|
||||||
|
const prevId = nodesInCol[i - 1];
|
||||||
|
const currId = nodesInCol[i];
|
||||||
|
const minY = positions[prevId].y + nodeSizes[prevId].height + V_GAP;
|
||||||
|
if (positions[currId].y < minY) {
|
||||||
|
positions[currId].y = minY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { positions, sizes: nodeSizes };
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPinSvg(type, filled = true) {
|
||||||
|
const color = PIN_COLORS[type] || PIN_COLORS.any;
|
||||||
|
if (type === 'exec') {
|
||||||
|
return `<svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="${filled ? '#fff' : 'none'}" stroke="${filled ? 'none' : '#fff'}" stroke-width="2"/></svg>`;
|
||||||
|
}
|
||||||
|
return `<svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="${filled ? color : 'none'}" stroke="${filled ? 'none' : color}" stroke-width="2"/></svg>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderNode(node, position, size) {
|
||||||
|
const isEvent = node.category === 'event';
|
||||||
|
const headerClass = HEADER_CLASSES[node.category] || 'function';
|
||||||
|
|
||||||
|
let html = `<div class="bp-node" style="left: ${position.x}px; top: ${position.y}px; width: ${size.width}px;">`;
|
||||||
|
html += `<div class="bp-node-header ${headerClass}">`;
|
||||||
|
if (isEvent) html += `<span class="bp-node-header-icon"></span>`;
|
||||||
|
html += `<span class="bp-node-header-title">${node.title}</span>`;
|
||||||
|
|
||||||
|
const headerExec = node.outputs && node.outputs.find(p => p.type === 'exec' && p.inHeader);
|
||||||
|
if (headerExec) {
|
||||||
|
html += `<span class="bp-header-exec" data-pin="${headerExec.id}">${renderPinSvg('exec')}</span>`;
|
||||||
|
}
|
||||||
|
html += `</div>`;
|
||||||
|
|
||||||
|
// Separate exec and data pins (matching node-editor order)
|
||||||
|
const inputExecPins = (node.inputs || []).filter(p => p.type === 'exec');
|
||||||
|
const inputDataPins = (node.inputs || []).filter(p => p.type !== 'exec');
|
||||||
|
const outputExecPins = (node.outputs || []).filter(p => p.type === 'exec' && !p.inHeader);
|
||||||
|
const outputDataPins = (node.outputs || []).filter(p => p.type !== 'exec' && !p.inHeader);
|
||||||
|
|
||||||
|
const hasBody = inputExecPins.length > 0 || inputDataPins.length > 0 ||
|
||||||
|
outputDataPins.length > 0 || outputExecPins.length > 0;
|
||||||
|
|
||||||
|
if (hasBody) {
|
||||||
|
html += `<div class="bp-node-body">`;
|
||||||
|
|
||||||
|
// Input exec pins first
|
||||||
|
inputExecPins.forEach(pin => {
|
||||||
|
const filled = pin.connected !== false;
|
||||||
|
html += `<div class="bp-pin-row input">`;
|
||||||
|
html += `<span class="bp-pin" data-pin="${pin.id}">${renderPinSvg(pin.type, filled)}</span>`;
|
||||||
|
html += `<span class="bp-pin-label">${pin.label || ''}</span>`;
|
||||||
|
html += `</div>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Input data pins
|
||||||
|
inputDataPins.forEach(pin => {
|
||||||
|
const filled = pin.connected !== false;
|
||||||
|
html += `<div class="bp-pin-row input">`;
|
||||||
|
html += `<span class="bp-pin" data-pin="${pin.id}">${renderPinSvg(pin.type, filled)}</span>`;
|
||||||
|
html += `<span class="bp-pin-label">${pin.label || ''}</span>`;
|
||||||
|
if (pin.value !== undefined) html += `<span class="bp-pin-value">${pin.value}</span>`;
|
||||||
|
html += `</div>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Output data pins (pin first, then label - CSS row-reverse will flip them)
|
||||||
|
outputDataPins.forEach(pin => {
|
||||||
|
html += `<div class="bp-pin-row output">`;
|
||||||
|
html += `<span class="bp-pin" data-pin="${pin.id}">${renderPinSvg(pin.type)}</span>`;
|
||||||
|
html += `<span class="bp-pin-label">${pin.label || ''}</span>`;
|
||||||
|
html += `</div>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Output exec pins
|
||||||
|
outputExecPins.forEach(pin => {
|
||||||
|
html += `<div class="bp-pin-row output">`;
|
||||||
|
html += `<span class="bp-pin" data-pin="${pin.id}">${renderPinSvg(pin.type)}</span>`;
|
||||||
|
html += `<span class="bp-pin-label">${pin.label || ''}</span>`;
|
||||||
|
html += `</div>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
html += `</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `</div>`;
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup drag-to-scroll for graph container
|
||||||
|
* Works with native overflow:auto scrolling
|
||||||
|
*/
|
||||||
|
function setupDragScroll(container) {
|
||||||
|
let isDragging = false;
|
||||||
|
let startX = 0, startY = 0;
|
||||||
|
let scrollLeft = 0, scrollTop = 0;
|
||||||
|
|
||||||
|
container.addEventListener('mousedown', (e) => {
|
||||||
|
if (e.button !== 0) return;
|
||||||
|
isDragging = true;
|
||||||
|
startX = e.pageX - container.offsetLeft;
|
||||||
|
startY = e.pageY - container.offsetTop;
|
||||||
|
scrollLeft = container.scrollLeft;
|
||||||
|
scrollTop = container.scrollTop;
|
||||||
|
container.style.cursor = 'grabbing';
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
container.addEventListener('mousemove', (e) => {
|
||||||
|
if (!isDragging) return;
|
||||||
|
const x = e.pageX - container.offsetLeft;
|
||||||
|
const y = e.pageY - container.offsetTop;
|
||||||
|
container.scrollLeft = scrollLeft - (x - startX);
|
||||||
|
container.scrollTop = scrollTop - (y - startY);
|
||||||
|
});
|
||||||
|
|
||||||
|
container.addEventListener('mouseup', () => {
|
||||||
|
isDragging = false;
|
||||||
|
container.style.cursor = 'grab';
|
||||||
|
});
|
||||||
|
|
||||||
|
container.addEventListener('mouseleave', () => {
|
||||||
|
isDragging = false;
|
||||||
|
container.style.cursor = 'grab';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderConnections(container, graphData) {
|
||||||
|
const svg = container.querySelector('.bp-connections');
|
||||||
|
if (!svg) return;
|
||||||
|
|
||||||
|
const content = container.querySelector('.bp-graph-content') || container;
|
||||||
|
const graphRect = content.getBoundingClientRect();
|
||||||
|
|
||||||
|
graphData.connections.forEach(c => {
|
||||||
|
const fromPin = container.querySelector(`[data-pin="${c.from}"]`);
|
||||||
|
const toPin = container.querySelector(`[data-pin="${c.to}"]`);
|
||||||
|
if (!fromPin || !toPin) return;
|
||||||
|
|
||||||
|
const fromRect = fromPin.getBoundingClientRect();
|
||||||
|
const toRect = toPin.getBoundingClientRect();
|
||||||
|
|
||||||
|
const x1 = fromRect.left - graphRect.left + fromRect.width / 2;
|
||||||
|
const y1 = fromRect.top - graphRect.top + fromRect.height / 2;
|
||||||
|
const x2 = toRect.left - graphRect.left + toRect.width / 2;
|
||||||
|
const y2 = toRect.top - graphRect.top + toRect.height / 2;
|
||||||
|
|
||||||
|
// Simple bezier curve
|
||||||
|
const dx = Math.abs(x2 - x1) * 0.5;
|
||||||
|
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||||
|
path.setAttribute('d', `M${x1},${y1} C${x1+dx},${y1} ${x2-dx},${y2} ${x2},${y2}`);
|
||||||
|
path.setAttribute('class', `bp-conn ${c.type || 'exec'}`);
|
||||||
|
svg.appendChild(path);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function initBlueprintGraphs() {
|
||||||
|
document.querySelectorAll('.bp-graph[data-graph]').forEach(container => {
|
||||||
|
try {
|
||||||
|
const graphData = JSON.parse(container.dataset.graph);
|
||||||
|
if (!graphData.nodes || graphData.nodes.length === 0) {
|
||||||
|
console.warn('Blueprint graph has no nodes');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get container width for layout calculation
|
||||||
|
let containerWidth = container.parentElement?.offsetWidth || 0;
|
||||||
|
if (containerWidth < 200) {
|
||||||
|
containerWidth = 650;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { positions, sizes } = autoLayout(graphData, containerWidth - 30);
|
||||||
|
|
||||||
|
let maxX = 0, maxY = 0;
|
||||||
|
graphData.nodes.forEach(n => {
|
||||||
|
const pos = positions[n.id];
|
||||||
|
const size = sizes[n.id];
|
||||||
|
if (pos && size) {
|
||||||
|
maxX = Math.max(maxX, pos.x + size.width);
|
||||||
|
maxY = Math.max(maxY, pos.y + size.height);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add generous padding to ensure all nodes visible
|
||||||
|
maxX += 80;
|
||||||
|
maxY += 80;
|
||||||
|
|
||||||
|
// Set minimum height but allow natural expansion
|
||||||
|
const containerHeight = Math.max(maxY, 200);
|
||||||
|
container.style.minHeight = containerHeight + 'px';
|
||||||
|
|
||||||
|
let html = `<div class="bp-graph-content" style="width:${maxX}px;height:${maxY}px;position:relative;">`;
|
||||||
|
html += `<svg class="bp-connections" width="${maxX}" height="${maxY}"></svg>`;
|
||||||
|
graphData.nodes.forEach(n => {
|
||||||
|
if (positions[n.id] && sizes[n.id]) {
|
||||||
|
html += renderNode(n, positions[n.id], sizes[n.id]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
html += `</div>`;
|
||||||
|
container.innerHTML = html;
|
||||||
|
|
||||||
|
// Setup drag-to-scroll
|
||||||
|
setupDragScroll(container);
|
||||||
|
|
||||||
|
requestAnimationFrame(() => renderConnections(container, graphData));
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Blueprint graph error:', e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Legacy format
|
||||||
|
document.querySelectorAll('.bp-graph:not([data-graph])').forEach(graph => {
|
||||||
|
const nodes = graph.querySelectorAll('.bp-node');
|
||||||
|
let maxX = 0, maxY = 0;
|
||||||
|
nodes.forEach(node => {
|
||||||
|
const left = parseInt(node.style.left) || 0;
|
||||||
|
const top = parseInt(node.style.top) || 0;
|
||||||
|
const width = parseInt(node.style.width) || 150;
|
||||||
|
maxX = Math.max(maxX, left + width + 40);
|
||||||
|
maxY = Math.max(maxY, top + node.offsetHeight + 40);
|
||||||
|
});
|
||||||
|
// Don't set fixed width - let CSS handle it
|
||||||
|
graph.style.minHeight = Math.max(maxY, 120) + 'px';
|
||||||
|
|
||||||
|
const svg = graph.querySelector('.bp-connections');
|
||||||
|
if (!svg) return;
|
||||||
|
svg.setAttribute('width', maxX);
|
||||||
|
svg.setAttribute('height', Math.max(maxY, 120));
|
||||||
|
|
||||||
|
const conns = JSON.parse(graph.dataset.connections || '[]');
|
||||||
|
const graphRect = graph.getBoundingClientRect();
|
||||||
|
|
||||||
|
conns.forEach(c => {
|
||||||
|
const fromPin = graph.querySelector(`[data-pin="${c.from}"]`);
|
||||||
|
const toPin = graph.querySelector(`[data-pin="${c.to}"]`);
|
||||||
|
if (!fromPin || !toPin) return;
|
||||||
|
|
||||||
|
const fromRect = fromPin.getBoundingClientRect();
|
||||||
|
const toRect = toPin.getBoundingClientRect();
|
||||||
|
|
||||||
|
const x1 = fromRect.left - graphRect.left + fromRect.width / 2;
|
||||||
|
const y1 = fromRect.top - graphRect.top + fromRect.height / 2;
|
||||||
|
const x2 = toRect.left - graphRect.left + toRect.width / 2;
|
||||||
|
const y2 = toRect.top - graphRect.top + toRect.height / 2;
|
||||||
|
|
||||||
|
const dx = Math.abs(x2 - x1) * 0.5;
|
||||||
|
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||||
|
path.setAttribute('d', `M${x1},${y1} C${x1+dx},${y1} ${x2-dx},${y2} ${x2},${y2}`);
|
||||||
|
path.setAttribute('class', `bp-conn ${c.type || 'exec'}`);
|
||||||
|
svg.appendChild(path);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', initBlueprintGraphs);
|
||||||
|
} else {
|
||||||
|
initBlueprintGraphs();
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -3,6 +3,8 @@ title: "Blueprint Editor User Guide"
|
|||||||
description: "Complete guide for using the Cocos Creator Blueprint Visual Scripting Editor"
|
description: "Complete guide for using the Cocos Creator Blueprint Visual Scripting Editor"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<script src="/js/blueprint-graph.js"></script>
|
||||||
|
|
||||||
This guide covers how to use the Blueprint Visual Scripting Editor in Cocos Creator.
|
This guide covers how to use the Blueprint Visual Scripting Editor in Cocos Creator.
|
||||||
|
|
||||||
## Download & Installation
|
## Download & Installation
|
||||||
@@ -345,50 +347,244 @@ Blueprints are saved as JSON, compatible with `@esengine/blueprint` runtime:
|
|||||||
|
|
||||||
Move entity every frame:
|
Move entity every frame:
|
||||||
|
|
||||||
```
|
<div class="bp-graph" style="" data-connections='[{"from":"ex1-exec","to":"ex1-setprop","type":"exec"},{"from":"ex1-delta","to":"ex1-mul-a","type":"float"},{"from":"ex1-mul-result","to":"ex1-x","type":"float"}]'>
|
||||||
[Event Tick] ─Exec─→ [Get Self] ─Entity─→ [Get Component: Transform]
|
<svg class="bp-connections"></svg>
|
||||||
│
|
<div class="bp-node" style="left: 20px; top: 20px; width: 140px;">
|
||||||
[Get Delta Time] ▼
|
<div class="bp-node-header event">
|
||||||
│ [Set Property: x]
|
<span class="bp-node-header-icon"></span>
|
||||||
│ │
|
<span class="bp-node-header-title">Event Tick</span>
|
||||||
[Multiply] ◄──────────────┘
|
<span class="bp-header-exec" data-pin="ex1-exec"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
│
|
</div>
|
||||||
└─ Speed: 100
|
<div class="bp-node-body">
|
||||||
```
|
<div class="bp-pin-row output">
|
||||||
|
<span class="bp-pin" data-pin="ex1-delta"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#7ecd32"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Delta Time</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bp-node" style="left: 200px; top: 110px; width: 120px;">
|
||||||
|
<div class="bp-node-header math">Multiply</div>
|
||||||
|
<div class="bp-node-body">
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin" data-pin="ex1-mul-a"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#7ecd32"/></svg></span>
|
||||||
|
<span class="bp-pin-label">A</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||||
|
<span class="bp-pin-label">B (Speed)</span>
|
||||||
|
<span class="bp-pin-value">100</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row output">
|
||||||
|
<span class="bp-pin" data-pin="ex1-mul-result"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#7ecd32"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Result</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bp-node" style="left: 380px; top: 20px; width: 150px;">
|
||||||
|
<div class="bp-node-header function">Set Property</div>
|
||||||
|
<div class="bp-node-body">
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin" data-pin="ex1-setprop"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Exec</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7030c0" stroke-width="2"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Target</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin" data-pin="ex1-x"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#7ecd32"/></svg></span>
|
||||||
|
<span class="bp-pin-label">x</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
### Example 2: Health System
|
### Example 2: Health System
|
||||||
|
|
||||||
Check death after taking damage:
|
Check death after taking damage. `Event OnDamage` is a custom event node that can be triggered from code via `vm.triggerCustomEvent('OnDamage', { damage: 50 })`:
|
||||||
|
|
||||||
```
|
<div class="bp-graph" data-graph='{
|
||||||
[On Damage Event] ─→ [Get Component: Health] ─→ [Get Property: current]
|
"nodes": [
|
||||||
│
|
{
|
||||||
▼
|
"id": "event", "title": "Event OnDamage", "category": "event",
|
||||||
[Subtract]
|
"outputs": [
|
||||||
│
|
{"id": "event-exec", "type": "exec", "inHeader": true},
|
||||||
▼
|
{"id": "event-self", "type": "entity", "label": "Self"},
|
||||||
[Set Property: current]
|
{"id": "event-damage", "type": "float", "label": "Damage"}
|
||||||
│
|
]
|
||||||
▼
|
},
|
||||||
┌─ True ─→ [Destroy Self]
|
{
|
||||||
[Branch]─┤
|
"id": "getcomp", "title": "Get Component", "category": "function",
|
||||||
└─ False ─→ (continue)
|
"inputs": [
|
||||||
▲
|
{"id": "getcomp-exec", "type": "exec", "label": "Exec"},
|
||||||
│
|
{"id": "getcomp-entity", "type": "entity", "label": "Entity"},
|
||||||
[Less Or Equal]
|
{"id": "getcomp-type", "type": "string", "label": "Type", "value": "Health", "connected": false}
|
||||||
│
|
],
|
||||||
current <= 0
|
"outputs": [
|
||||||
```
|
{"id": "getcomp-out", "type": "exec"},
|
||||||
|
{"id": "getcomp-comp", "type": "component", "label": "Component"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "getprop", "title": "Get Property", "category": "pure",
|
||||||
|
"inputs": [
|
||||||
|
{"id": "getprop-target", "type": "component", "label": "Target"},
|
||||||
|
{"id": "getprop-prop", "type": "string", "label": "Property", "value": "current", "connected": false}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{"id": "getprop-val", "type": "float", "label": "Value"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sub", "title": "Subtract", "category": "math",
|
||||||
|
"inputs": [
|
||||||
|
{"id": "sub-exec", "type": "exec", "label": "Exec"},
|
||||||
|
{"id": "sub-a", "type": "float", "label": "A"},
|
||||||
|
{"id": "sub-b", "type": "float", "label": "B"}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{"id": "sub-out", "type": "exec"},
|
||||||
|
{"id": "sub-result", "type": "float", "label": "Result"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "setprop", "title": "Set Property", "category": "function",
|
||||||
|
"inputs": [
|
||||||
|
{"id": "setprop-exec", "type": "exec", "label": "Exec"},
|
||||||
|
{"id": "setprop-target", "type": "component", "label": "Target"},
|
||||||
|
{"id": "setprop-prop", "type": "string", "label": "Property", "value": "current", "connected": false},
|
||||||
|
{"id": "setprop-val", "type": "float", "label": "Value"}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{"id": "setprop-out", "type": "exec"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "lte", "title": "Less Or Equal", "category": "pure",
|
||||||
|
"inputs": [
|
||||||
|
{"id": "lte-a", "type": "float", "label": "A"},
|
||||||
|
{"id": "lte-b", "type": "float", "label": "B", "value": "0", "connected": false}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{"id": "lte-result", "type": "bool", "label": "Result"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "branch", "title": "Branch", "category": "flow",
|
||||||
|
"inputs": [
|
||||||
|
{"id": "branch-exec", "type": "exec", "label": "Exec"},
|
||||||
|
{"id": "branch-cond", "type": "bool", "label": "Condition"}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{"id": "branch-true", "type": "exec", "label": "True"},
|
||||||
|
{"id": "branch-false", "type": "exec", "label": "False"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "destroy", "title": "Destroy Entity", "category": "function",
|
||||||
|
"inputs": [
|
||||||
|
{"id": "destroy-exec", "type": "exec", "label": "Exec"},
|
||||||
|
{"id": "destroy-entity", "type": "entity", "label": "Entity"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"connections": [
|
||||||
|
{"from": "event-exec", "to": "getcomp-exec", "type": "exec"},
|
||||||
|
{"from": "getcomp-out", "to": "sub-exec", "type": "exec"},
|
||||||
|
{"from": "sub-out", "to": "setprop-exec", "type": "exec"},
|
||||||
|
{"from": "setprop-out", "to": "branch-exec", "type": "exec"},
|
||||||
|
{"from": "branch-true", "to": "destroy-exec", "type": "exec"},
|
||||||
|
{"from": "event-self", "to": "getcomp-entity", "type": "entity"},
|
||||||
|
{"from": "event-self", "to": "destroy-entity", "type": "entity"},
|
||||||
|
{"from": "getcomp-comp", "to": "getprop-target", "type": "component"},
|
||||||
|
{"from": "getcomp-comp", "to": "setprop-target", "type": "component"},
|
||||||
|
{"from": "getprop-val", "to": "sub-a", "type": "float"},
|
||||||
|
{"from": "event-damage", "to": "sub-b", "type": "float"},
|
||||||
|
{"from": "sub-result", "to": "setprop-val", "type": "float"},
|
||||||
|
{"from": "sub-result", "to": "lte-a", "type": "float"},
|
||||||
|
{"from": "lte-result", "to": "branch-cond", "type": "bool"}
|
||||||
|
]
|
||||||
|
}'></div>
|
||||||
|
|
||||||
### Example 3: Delayed Spawning
|
### Example 3: Delayed Spawning
|
||||||
|
|
||||||
Spawn an enemy every 2 seconds:
|
Spawn an enemy every 2 seconds:
|
||||||
|
|
||||||
```
|
<div class="bp-graph" style="" data-connections='[{"from":"ex3-begin-exec","to":"ex3-loop","type":"exec"},{"from":"ex3-loop-body","to":"ex3-delay","type":"exec"},{"from":"ex3-delay-done","to":"ex3-create","type":"exec"}]'>
|
||||||
[Event BeginPlay] ─→ [Do N Times] ─Loop─→ [Delay: 2.0] ─→ [Create Entity: Enemy]
|
<svg class="bp-connections"></svg>
|
||||||
│
|
<div class="bp-node" style="left: 20px; top: 20px; width: 170px;">
|
||||||
└─ N: 10
|
<div class="bp-node-header event">
|
||||||
```
|
<span class="bp-node-header-icon"></span>
|
||||||
|
<span class="bp-node-header-title">Event BeginPlay</span>
|
||||||
|
<span class="bp-header-exec" data-pin="ex3-begin-exec"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bp-node" style="left: 240px; top: 20px; width: 130px;">
|
||||||
|
<div class="bp-node-header flow">Do N Times</div>
|
||||||
|
<div class="bp-node-body">
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin" data-pin="ex3-loop"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Exec</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#1cc4c4" stroke-width="2"/></svg></span>
|
||||||
|
<span class="bp-pin-label">N</span>
|
||||||
|
<span class="bp-pin-value">10</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row output">
|
||||||
|
<span class="bp-pin" data-pin="ex3-loop-body"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Loop Body</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row output">
|
||||||
|
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#1cc4c4"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Index</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row output">
|
||||||
|
<span class="bp-pin"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Completed</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bp-node" style="left: 430px; top: 20px; width: 120px;">
|
||||||
|
<div class="bp-node-header time">Delay</div>
|
||||||
|
<div class="bp-node-body">
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin" data-pin="ex3-delay"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Exec</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Duration</span>
|
||||||
|
<span class="bp-pin-value">2.0</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row output">
|
||||||
|
<span class="bp-pin" data-pin="ex3-delay-done"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Done</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bp-node" style="left: 610px; top: 20px; width: 140px;">
|
||||||
|
<div class="bp-node-header function">Create Entity</div>
|
||||||
|
<div class="bp-node-body">
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin" data-pin="ex3-create"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Exec</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#e060e0" stroke-width="2"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Name</span>
|
||||||
|
<span class="bp-pin-value">"Enemy"</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row output">
|
||||||
|
<span class="bp-pin"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row output">
|
||||||
|
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#00a0e0"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Entity</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ title: "蓝图编辑器使用指南"
|
|||||||
description: "Cocos Creator 蓝图可视化脚本编辑器完整使用教程"
|
description: "Cocos Creator 蓝图可视化脚本编辑器完整使用教程"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<script src="/js/blueprint-graph.js"></script>
|
||||||
|
|
||||||
本指南介绍如何在 Cocos Creator 中使用蓝图可视化脚本编辑器。
|
本指南介绍如何在 Cocos Creator 中使用蓝图可视化脚本编辑器。
|
||||||
|
|
||||||
## 下载与安装
|
## 下载与安装
|
||||||
@@ -93,10 +95,37 @@ your-project/
|
|||||||
| **Event EndPlay** | 蓝图停止时 | Exec |
|
| **Event EndPlay** | 蓝图停止时 | Exec |
|
||||||
|
|
||||||
**示例:游戏开始时打印消息**
|
**示例:游戏开始时打印消息**
|
||||||
```
|
|
||||||
[Event BeginPlay] ──Exec──→ [Print]
|
<div class="bp-graph" style="" data-connections='[{"from":"eg1-exec","to":"eg1-print","type":"exec"}]'>
|
||||||
└─ Message: "游戏开始!"
|
<svg class="bp-connections"></svg>
|
||||||
```
|
<div class="bp-node" style="left: 20px; top: 20px; width: 170px;">
|
||||||
|
<div class="bp-node-header event">
|
||||||
|
<span class="bp-node-header-icon"></span>
|
||||||
|
<span class="bp-node-header-title">Event BeginPlay</span>
|
||||||
|
<span class="bp-header-exec" data-pin="eg1-exec"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-node-body">
|
||||||
|
<div class="bp-pin-row output">
|
||||||
|
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#00a0e0"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Self</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bp-node" style="left: 280px; top: 20px; width: 150px;">
|
||||||
|
<div class="bp-node-header debug">Print</div>
|
||||||
|
<div class="bp-node-body">
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin" data-pin="eg1-print"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Exec</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#e060e0" stroke-width="2"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Message</span>
|
||||||
|
<span class="bp-pin-value">"游戏开始!"</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
### 实体节点 (Entity)
|
### 实体节点 (Entity)
|
||||||
|
|
||||||
@@ -114,10 +143,52 @@ your-project/
|
|||||||
| **Set Active** | 设置激活状态 | Exec, Entity, Active | Exec |
|
| **Set Active** | 设置激活状态 | Exec, Entity, Active | Exec |
|
||||||
|
|
||||||
**示例:创建新实体**
|
**示例:创建新实体**
|
||||||
```
|
|
||||||
[Event BeginPlay] ──→ [Create Entity] ──→ [Add Component]
|
<div class="bp-graph" style="" data-connections='[{"from":"eg2-exec","to":"eg2-create","type":"exec"},{"from":"eg2-create-out","to":"eg2-add","type":"exec"},{"from":"eg2-entity","to":"eg2-add-entity","type":"entity"}]'>
|
||||||
└─ Name: "Bullet" └─ Type: Transform
|
<svg class="bp-connections"></svg>
|
||||||
```
|
<div class="bp-node" style="left: 20px; top: 20px; width: 170px;">
|
||||||
|
<div class="bp-node-header event">
|
||||||
|
<span class="bp-node-header-icon"></span>
|
||||||
|
<span class="bp-node-header-title">Event BeginPlay</span>
|
||||||
|
<span class="bp-header-exec" data-pin="eg2-exec"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bp-node" style="left: 280px; top: 20px; width: 150px;">
|
||||||
|
<div class="bp-node-header function">Create Entity</div>
|
||||||
|
<div class="bp-node-body">
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin" data-pin="eg2-create"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Exec</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#e060e0" stroke-width="2"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Name</span>
|
||||||
|
<span class="bp-pin-value">"Bullet"</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row output">
|
||||||
|
<span class="bp-pin" data-pin="eg2-create-out"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Exec</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row output">
|
||||||
|
<span class="bp-pin" data-pin="eg2-entity"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#00a0e0"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Entity</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bp-node" style="left: 520px; top: 20px; width: 150px;">
|
||||||
|
<div class="bp-node-header function">Add Transform</div>
|
||||||
|
<div class="bp-node-body">
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin" data-pin="eg2-add"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Exec</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin" data-pin="eg2-add-entity"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#00a0e0"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Entity</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
### 组件节点 (Component)
|
### 组件节点 (Component)
|
||||||
|
|
||||||
@@ -132,11 +203,50 @@ your-project/
|
|||||||
| **Get/Set Property** | 获取/设置组件属性 |
|
| **Get/Set Property** | 获取/设置组件属性 |
|
||||||
|
|
||||||
**示例:修改 Transform 组件**
|
**示例:修改 Transform 组件**
|
||||||
```
|
|
||||||
[Get Self] ─Entity─→ [Get Component: Transform] ─Component─→ [Set Property]
|
<div class="bp-graph" style="" data-connections='[{"from":"eg3-self","to":"eg3-getcomp","type":"entity"},{"from":"eg3-comp","to":"eg3-setprop","type":"component"}]'>
|
||||||
├─ Property: x
|
<svg class="bp-connections"></svg>
|
||||||
└─ Value: 100
|
<div class="bp-node" style="left: 20px; top: 20px; width: 100px;">
|
||||||
```
|
<div class="bp-node-header pure">Get Self</div>
|
||||||
|
<div class="bp-node-body">
|
||||||
|
<div class="bp-pin-row output">
|
||||||
|
<span class="bp-pin" data-pin="eg3-self"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#00a0e0"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Entity</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bp-node" style="left: 200px; top: 20px; width: 150px;">
|
||||||
|
<div class="bp-node-header pure">Get Component</div>
|
||||||
|
<div class="bp-node-body">
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin" data-pin="eg3-getcomp"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#00a0e0"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Entity</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row output">
|
||||||
|
<span class="bp-pin" data-pin="eg3-comp"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#7030c0"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Transform</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bp-node" style="left: 430px; top: 20px; width: 130px;">
|
||||||
|
<div class="bp-node-header function">Set Property</div>
|
||||||
|
<div class="bp-node-body">
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Exec</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin" data-pin="eg3-setprop"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#7030c0"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Target</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||||
|
<span class="bp-pin-label">x</span>
|
||||||
|
<span class="bp-pin-value">100</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
### 流程控制节点 (Flow)
|
### 流程控制节点 (Flow)
|
||||||
|
|
||||||
@@ -146,42 +256,161 @@ your-project/
|
|||||||
|
|
||||||
条件判断,类似 if/else。
|
条件判断,类似 if/else。
|
||||||
|
|
||||||
```
|
<div class="bp-graph" style="" data-connections='[{"from":"eg4-true","to":"eg4-do1","type":"exec"},{"from":"eg4-false","to":"eg4-do2","type":"exec"}]'>
|
||||||
┌─ True ──→ [DoSomething]
|
<svg class="bp-connections"></svg>
|
||||||
[Branch]─┤
|
<div class="bp-node" style="left: 20px; top: 50px; width: 110px;">
|
||||||
└─ False ─→ [DoOtherThing]
|
<div class="bp-node-header flow">Branch</div>
|
||||||
```
|
<div class="bp-node-body">
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Exec</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#8c0000" stroke-width="2"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Condition</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row output">
|
||||||
|
<span class="bp-pin" data-pin="eg4-true"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">True</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row output">
|
||||||
|
<span class="bp-pin" data-pin="eg4-false"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">False</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bp-node" style="left: 220px; top: 20px; width: 130px;">
|
||||||
|
<div class="bp-node-header function">DoSomething</div>
|
||||||
|
<div class="bp-node-body">
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin" data-pin="eg4-do1"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Exec</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bp-node" style="left: 220px; top: 110px; width: 130px;">
|
||||||
|
<div class="bp-node-header function">DoOtherThing</div>
|
||||||
|
<div class="bp-node-body">
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin" data-pin="eg4-do2"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Exec</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
#### Sequence (序列)
|
#### Sequence (序列)
|
||||||
|
|
||||||
按顺序执行多个分支。
|
按顺序执行多个分支。
|
||||||
|
|
||||||
```
|
<div class="bp-graph" style="" data-connections='[{"from":"eg5-then0","to":"eg5-step1","type":"exec"},{"from":"eg5-then1","to":"eg5-step2","type":"exec"},{"from":"eg5-then2","to":"eg5-step3","type":"exec"}]'>
|
||||||
┌─ Then 0 ──→ [Step1]
|
<svg class="bp-connections"></svg>
|
||||||
[Sequence]─┼─ Then 1 ──→ [Step2]
|
<div class="bp-node" style="left: 20px; top: 20px; width: 110px;">
|
||||||
└─ Then 2 ──→ [Step3]
|
<div class="bp-node-header flow">Sequence</div>
|
||||||
```
|
<div class="bp-node-body">
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Exec</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row output">
|
||||||
|
<span class="bp-pin" data-pin="eg5-then0"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Then 0</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row output">
|
||||||
|
<span class="bp-pin" data-pin="eg5-then1"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Then 1</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row output">
|
||||||
|
<span class="bp-pin" data-pin="eg5-then2"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Then 2</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bp-node" style="left: 220px; top: 20px; width: 100px;">
|
||||||
|
<div class="bp-node-header function">Step 1</div>
|
||||||
|
<div class="bp-node-body">
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin" data-pin="eg5-step1"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Exec</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bp-node" style="left: 220px; top: 100px; width: 100px;">
|
||||||
|
<div class="bp-node-header function">Step 2</div>
|
||||||
|
<div class="bp-node-body">
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin" data-pin="eg5-step2"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Exec</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bp-node" style="left: 220px; top: 180px; width: 100px;">
|
||||||
|
<div class="bp-node-header function">Step 3</div>
|
||||||
|
<div class="bp-node-body">
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin" data-pin="eg5-step3"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Exec</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
#### For Loop (循环)
|
#### For Loop (循环)
|
||||||
|
|
||||||
循环执行指定次数。
|
循环执行指定次数。
|
||||||
|
|
||||||
```
|
<div class="bp-graph" style="" data-connections='[{"from":"eg6-body","to":"eg6-iter","type":"exec"},{"from":"eg6-done","to":"eg6-finish","type":"exec"}]'>
|
||||||
[For Loop] ─Loop Body─→ [每次迭代执行]
|
<svg class="bp-connections"></svg>
|
||||||
│
|
<div class="bp-node" style="left: 20px; top: 20px; width: 140px;">
|
||||||
└─ Completed ────→ [循环结束后执行]
|
<div class="bp-node-header flow">For Loop</div>
|
||||||
```
|
<div class="bp-node-body">
|
||||||
|
<div class="bp-pin-row input">
|
||||||
| 输入 | 说明 |
|
<span class="bp-pin"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|------|------|
|
<span class="bp-pin-label">Exec</span>
|
||||||
| First Index | 起始索引 |
|
</div>
|
||||||
| Last Index | 结束索引 |
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#1cc4c4" stroke-width="2"/></svg></span>
|
||||||
| 输出 | 说明 |
|
<span class="bp-pin-label">First Index</span>
|
||||||
|------|------|
|
<span class="bp-pin-value">0</span>
|
||||||
| Loop Body | 每次迭代执行 |
|
</div>
|
||||||
| Index | 当前索引 |
|
<div class="bp-pin-row input">
|
||||||
| Completed | 循环结束后执行 |
|
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#1cc4c4" stroke-width="2"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Last Index</span>
|
||||||
|
<span class="bp-pin-value">10</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row output">
|
||||||
|
<span class="bp-pin" data-pin="eg6-body"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Loop Body</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row output">
|
||||||
|
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#1cc4c4"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Index</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row output">
|
||||||
|
<span class="bp-pin" data-pin="eg6-done"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Completed</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bp-node" style="left: 250px; top: 80px; width: 130px;">
|
||||||
|
<div class="bp-node-header function">每次迭代执行</div>
|
||||||
|
<div class="bp-node-body">
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin" data-pin="eg6-iter"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Exec</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bp-node" style="left: 250px; top: 160px; width: 140px;">
|
||||||
|
<div class="bp-node-header function">循环结束后执行</div>
|
||||||
|
<div class="bp-node-body">
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin" data-pin="eg6-finish"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Exec</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
#### For Each (遍历)
|
#### For Each (遍历)
|
||||||
|
|
||||||
@@ -212,10 +441,49 @@ your-project/
|
|||||||
| **Get Time** | 获取运行总时间 | Number |
|
| **Get Time** | 获取运行总时间 | Number |
|
||||||
|
|
||||||
**示例:延迟 2 秒后执行**
|
**示例:延迟 2 秒后执行**
|
||||||
```
|
|
||||||
[Event BeginPlay] ──→ [Delay] ──→ [Print]
|
<div class="bp-graph" style="" data-connections='[{"from":"eg7-exec","to":"eg7-delay","type":"exec"},{"from":"eg7-done","to":"eg7-print","type":"exec"}]'>
|
||||||
└─ Duration: 2.0 └─ "2秒后执行"
|
<svg class="bp-connections"></svg>
|
||||||
```
|
<div class="bp-node" style="left: 20px; top: 20px; width: 170px;">
|
||||||
|
<div class="bp-node-header event">
|
||||||
|
<span class="bp-node-header-icon"></span>
|
||||||
|
<span class="bp-node-header-title">Event BeginPlay</span>
|
||||||
|
<span class="bp-header-exec" data-pin="eg7-exec"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bp-node" style="left: 280px; top: 20px; width: 120px;">
|
||||||
|
<div class="bp-node-header time">Delay</div>
|
||||||
|
<div class="bp-node-body">
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin" data-pin="eg7-delay"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Exec</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Duration</span>
|
||||||
|
<span class="bp-pin-value">2.0</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row output">
|
||||||
|
<span class="bp-pin" data-pin="eg7-done"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Done</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bp-node" style="left: 490px; top: 20px; width: 130px;">
|
||||||
|
<div class="bp-node-header debug">Print</div>
|
||||||
|
<div class="bp-node-body">
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin" data-pin="eg7-print"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Exec</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#e060e0" stroke-width="2"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Msg</span>
|
||||||
|
<span class="bp-pin-value">"2秒后执行"</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
### 数学节点 (Math)
|
### 数学节点 (Math)
|
||||||
|
|
||||||
@@ -345,50 +613,244 @@ your-project/
|
|||||||
|
|
||||||
实现每帧移动实体:
|
实现每帧移动实体:
|
||||||
|
|
||||||
```
|
<div class="bp-graph" style="" data-connections='[{"from":"ex1-exec","to":"ex1-setprop","type":"exec"},{"from":"ex1-delta","to":"ex1-mul-a","type":"float"},{"from":"ex1-mul-result","to":"ex1-x","type":"float"}]'>
|
||||||
[Event Tick] ─Exec─→ [Get Self] ─Entity─→ [Get Component: Transform]
|
<svg class="bp-connections"></svg>
|
||||||
│
|
<div class="bp-node" style="left: 20px; top: 20px; width: 140px;">
|
||||||
[Get Delta Time] ▼
|
<div class="bp-node-header event">
|
||||||
│ [Set Property: x]
|
<span class="bp-node-header-icon"></span>
|
||||||
│ │
|
<span class="bp-node-header-title">Event Tick</span>
|
||||||
[Multiply] ◄──────────────┘
|
<span class="bp-header-exec" data-pin="ex1-exec"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
│
|
</div>
|
||||||
└─ Speed: 100
|
<div class="bp-node-body">
|
||||||
```
|
<div class="bp-pin-row output">
|
||||||
|
<span class="bp-pin" data-pin="ex1-delta"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#7ecd32"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Delta Time</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bp-node" style="left: 200px; top: 110px; width: 120px;">
|
||||||
|
<div class="bp-node-header math">Multiply</div>
|
||||||
|
<div class="bp-node-body">
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin" data-pin="ex1-mul-a"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#7ecd32"/></svg></span>
|
||||||
|
<span class="bp-pin-label">A</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||||
|
<span class="bp-pin-label">B (Speed)</span>
|
||||||
|
<span class="bp-pin-value">100</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row output">
|
||||||
|
<span class="bp-pin" data-pin="ex1-mul-result"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#7ecd32"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Result</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bp-node" style="left: 380px; top: 20px; width: 150px;">
|
||||||
|
<div class="bp-node-header function">Set Property</div>
|
||||||
|
<div class="bp-node-body">
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin" data-pin="ex1-setprop"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Exec</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7030c0" stroke-width="2"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Target</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin" data-pin="ex1-x"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#7ecd32"/></svg></span>
|
||||||
|
<span class="bp-pin-label">x</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
### 示例 2:生命值系统
|
### 示例 2:生命值系统
|
||||||
|
|
||||||
受伤后检查死亡:
|
受伤后检查死亡逻辑。`Event OnDamage` 是一个自定义事件节点,可以通过代码 `vm.triggerCustomEvent('OnDamage', { damage: 50 })` 触发:
|
||||||
|
|
||||||
```
|
<div class="bp-graph" data-graph='{
|
||||||
[On Damage Event] ─→ [Get Component: Health] ─→ [Get Property: current]
|
"nodes": [
|
||||||
│
|
{
|
||||||
▼
|
"id": "event", "title": "Event OnDamage", "category": "event",
|
||||||
[Subtract]
|
"outputs": [
|
||||||
│
|
{"id": "event-exec", "type": "exec", "inHeader": true},
|
||||||
▼
|
{"id": "event-self", "type": "entity", "label": "Self"},
|
||||||
[Set Property: current]
|
{"id": "event-damage", "type": "float", "label": "Damage"}
|
||||||
│
|
]
|
||||||
▼
|
},
|
||||||
┌─ True ─→ [Destroy Self]
|
{
|
||||||
[Branch]─┤
|
"id": "getcomp", "title": "Get Component", "category": "function",
|
||||||
└─ False ─→ (继续)
|
"inputs": [
|
||||||
▲
|
{"id": "getcomp-exec", "type": "exec", "label": "Exec"},
|
||||||
│
|
{"id": "getcomp-entity", "type": "entity", "label": "Entity"},
|
||||||
[Less Or Equal]
|
{"id": "getcomp-type", "type": "string", "label": "Type", "value": "Health", "connected": false}
|
||||||
│
|
],
|
||||||
current <= 0
|
"outputs": [
|
||||||
```
|
{"id": "getcomp-out", "type": "exec"},
|
||||||
|
{"id": "getcomp-comp", "type": "component", "label": "Component"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "getprop", "title": "Get Property", "category": "pure",
|
||||||
|
"inputs": [
|
||||||
|
{"id": "getprop-target", "type": "component", "label": "Target"},
|
||||||
|
{"id": "getprop-prop", "type": "string", "label": "Property", "value": "current", "connected": false}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{"id": "getprop-val", "type": "float", "label": "Value"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sub", "title": "Subtract", "category": "math",
|
||||||
|
"inputs": [
|
||||||
|
{"id": "sub-exec", "type": "exec", "label": "Exec"},
|
||||||
|
{"id": "sub-a", "type": "float", "label": "A"},
|
||||||
|
{"id": "sub-b", "type": "float", "label": "B"}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{"id": "sub-out", "type": "exec"},
|
||||||
|
{"id": "sub-result", "type": "float", "label": "Result"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "setprop", "title": "Set Property", "category": "function",
|
||||||
|
"inputs": [
|
||||||
|
{"id": "setprop-exec", "type": "exec", "label": "Exec"},
|
||||||
|
{"id": "setprop-target", "type": "component", "label": "Target"},
|
||||||
|
{"id": "setprop-prop", "type": "string", "label": "Property", "value": "current", "connected": false},
|
||||||
|
{"id": "setprop-val", "type": "float", "label": "Value"}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{"id": "setprop-out", "type": "exec"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "lte", "title": "Less Or Equal", "category": "pure",
|
||||||
|
"inputs": [
|
||||||
|
{"id": "lte-a", "type": "float", "label": "A"},
|
||||||
|
{"id": "lte-b", "type": "float", "label": "B", "value": "0", "connected": false}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{"id": "lte-result", "type": "bool", "label": "Result"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "branch", "title": "Branch", "category": "flow",
|
||||||
|
"inputs": [
|
||||||
|
{"id": "branch-exec", "type": "exec", "label": "Exec"},
|
||||||
|
{"id": "branch-cond", "type": "bool", "label": "Condition"}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{"id": "branch-true", "type": "exec", "label": "True"},
|
||||||
|
{"id": "branch-false", "type": "exec", "label": "False"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "destroy", "title": "Destroy Entity", "category": "function",
|
||||||
|
"inputs": [
|
||||||
|
{"id": "destroy-exec", "type": "exec", "label": "Exec"},
|
||||||
|
{"id": "destroy-entity", "type": "entity", "label": "Entity"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"connections": [
|
||||||
|
{"from": "event-exec", "to": "getcomp-exec", "type": "exec"},
|
||||||
|
{"from": "getcomp-out", "to": "sub-exec", "type": "exec"},
|
||||||
|
{"from": "sub-out", "to": "setprop-exec", "type": "exec"},
|
||||||
|
{"from": "setprop-out", "to": "branch-exec", "type": "exec"},
|
||||||
|
{"from": "branch-true", "to": "destroy-exec", "type": "exec"},
|
||||||
|
{"from": "event-self", "to": "getcomp-entity", "type": "entity"},
|
||||||
|
{"from": "event-self", "to": "destroy-entity", "type": "entity"},
|
||||||
|
{"from": "getcomp-comp", "to": "getprop-target", "type": "component"},
|
||||||
|
{"from": "getcomp-comp", "to": "setprop-target", "type": "component"},
|
||||||
|
{"from": "getprop-val", "to": "sub-a", "type": "float"},
|
||||||
|
{"from": "event-damage", "to": "sub-b", "type": "float"},
|
||||||
|
{"from": "sub-result", "to": "setprop-val", "type": "float"},
|
||||||
|
{"from": "sub-result", "to": "lte-a", "type": "float"},
|
||||||
|
{"from": "lte-result", "to": "branch-cond", "type": "bool"}
|
||||||
|
]
|
||||||
|
}'></div>
|
||||||
|
|
||||||
### 示例 3:延迟生成
|
### 示例 3:延迟生成
|
||||||
|
|
||||||
每 2 秒生成一个敌人:
|
每 2 秒生成一个敌人:
|
||||||
|
|
||||||
```
|
<div class="bp-graph" style="" data-connections='[{"from":"ex3-begin-exec","to":"ex3-loop","type":"exec"},{"from":"ex3-loop-body","to":"ex3-delay","type":"exec"},{"from":"ex3-delay-done","to":"ex3-create","type":"exec"}]'>
|
||||||
[Event BeginPlay] ─→ [Do N Times] ─Loop─→ [Delay: 2.0] ─→ [Create Entity: Enemy]
|
<svg class="bp-connections"></svg>
|
||||||
│
|
<div class="bp-node" style="left: 20px; top: 20px; width: 170px;">
|
||||||
└─ N: 10
|
<div class="bp-node-header event">
|
||||||
```
|
<span class="bp-node-header-icon"></span>
|
||||||
|
<span class="bp-node-header-title">Event BeginPlay</span>
|
||||||
|
<span class="bp-header-exec" data-pin="ex3-begin-exec"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bp-node" style="left: 240px; top: 20px; width: 130px;">
|
||||||
|
<div class="bp-node-header flow">Do N Times</div>
|
||||||
|
<div class="bp-node-body">
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin" data-pin="ex3-loop"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Exec</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#1cc4c4" stroke-width="2"/></svg></span>
|
||||||
|
<span class="bp-pin-label">N</span>
|
||||||
|
<span class="bp-pin-value">10</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row output">
|
||||||
|
<span class="bp-pin" data-pin="ex3-loop-body"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Loop Body</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row output">
|
||||||
|
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#1cc4c4"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Index</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row output">
|
||||||
|
<span class="bp-pin"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Completed</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bp-node" style="left: 430px; top: 20px; width: 120px;">
|
||||||
|
<div class="bp-node-header time">Delay</div>
|
||||||
|
<div class="bp-node-body">
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin" data-pin="ex3-delay"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Exec</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Duration</span>
|
||||||
|
<span class="bp-pin-value">2.0</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row output">
|
||||||
|
<span class="bp-pin" data-pin="ex3-delay-done"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Done</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bp-node" style="left: 610px; top: 20px; width: 140px;">
|
||||||
|
<div class="bp-node-header function">Create Entity</div>
|
||||||
|
<div class="bp-node-body">
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin" data-pin="ex3-create"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Exec</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row input">
|
||||||
|
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#e060e0" stroke-width="2"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Name</span>
|
||||||
|
<span class="bp-pin-value">"Enemy"</span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row output">
|
||||||
|
<span class="bp-pin"><svg width="12" height="12"><polygon points="1,1 11,6 1,11" fill="#fff"/></svg></span>
|
||||||
|
</div>
|
||||||
|
<div class="bp-pin-row output">
|
||||||
|
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#00a0e0"/></svg></span>
|
||||||
|
<span class="bp-pin-label">Entity</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
## 常见问题
|
## 常见问题
|
||||||
|
|
||||||
|
|||||||
@@ -236,3 +236,257 @@ nav.sidebar-content ul li a[aria-current="page"] {
|
|||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ==================== Blueprint Node Visualization ==================== */
|
||||||
|
/* Matches the actual node-editor component styles */
|
||||||
|
|
||||||
|
/* Graph Container - 使用固定像素坐标 */
|
||||||
|
.bp-graph {
|
||||||
|
position: relative;
|
||||||
|
background: #141419;
|
||||||
|
background-image:
|
||||||
|
linear-gradient(rgba(255,255,255,0.03) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(255,255,255,0.03) 1px, transparent 1px);
|
||||||
|
background-size: 20px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
border: 1px solid #2a2a35;
|
||||||
|
overflow: auto;
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp-graph:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inner container for panning */
|
||||||
|
.bp-graph-content {
|
||||||
|
position: relative;
|
||||||
|
transform-origin: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SVG Connections Layer */
|
||||||
|
.bp-connections {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Node absolute positioning in graph */
|
||||||
|
.bp-graph > .bp-node {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Connection paths */
|
||||||
|
.bp-conn {
|
||||||
|
fill: none;
|
||||||
|
stroke-width: 2px;
|
||||||
|
stroke-linecap: round;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp-conn.exec { stroke: #ffffff; stroke-width: 3px; }
|
||||||
|
.bp-conn.float { stroke: #7ecd32; }
|
||||||
|
.bp-conn.int { stroke: #1cc4c4; }
|
||||||
|
.bp-conn.bool { stroke: #8c0000; }
|
||||||
|
.bp-conn.string { stroke: #e060e0; }
|
||||||
|
.bp-conn.object { stroke: #00a0e0; }
|
||||||
|
.bp-conn.entity { stroke: #00a0e0; }
|
||||||
|
.bp-conn.component { stroke: #7030c0; }
|
||||||
|
.bp-conn.array { stroke: #7030c0; }
|
||||||
|
.bp-conn.any { stroke: #707070; }
|
||||||
|
|
||||||
|
/* ==================== Node Container ==================== */
|
||||||
|
.bp-node {
|
||||||
|
position: relative;
|
||||||
|
min-width: 140px;
|
||||||
|
background: rgba(12, 12, 16, 0.95);
|
||||||
|
border: 1px solid rgba(40, 40, 50, 0.8);
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
|
||||||
|
font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||||
|
font-size: 11px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== Node Header ==================== */
|
||||||
|
/* 与 node-editor 完全一致 */
|
||||||
|
.bp-node-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
min-height: 28px;
|
||||||
|
border-radius: 5px 5px 0 0;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header icon - diamond for events */
|
||||||
|
.bp-node-header-icon {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp-node-header.event .bp-node-header-icon {
|
||||||
|
background: #fff;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
border-radius: 1px;
|
||||||
|
box-shadow: 0 0 4px rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header title */
|
||||||
|
.bp-node-header-title {
|
||||||
|
flex: 1;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header exec pin (for event nodes) - at right edge */
|
||||||
|
/* 与 node-editor 完全一致 */
|
||||||
|
.bp-header-exec {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
margin-left: auto;
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp-header-exec svg {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Category Colors */
|
||||||
|
.bp-node-header.event {
|
||||||
|
background: linear-gradient(180deg, #b81c1c 0%, #6b1010 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp-node-header.function {
|
||||||
|
background: linear-gradient(180deg, #1b6eb5 0%, #0d3d66 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp-node-header.pure {
|
||||||
|
background: linear-gradient(180deg, #3d8b3d 0%, #1f5a1f 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp-node-header.flow {
|
||||||
|
background: linear-gradient(180deg, #4a4a4a 0%, #2a2a2a 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp-node-header.variable {
|
||||||
|
background: linear-gradient(180deg, #7b3d9b 0%, #4a1f66 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp-node-header.debug {
|
||||||
|
background: linear-gradient(180deg, #6a6a6a 0%, #3a3a3a 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp-node-header.time {
|
||||||
|
background: linear-gradient(180deg, #1cc4c4 0%, #0d7070 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp-node-header.math {
|
||||||
|
background: linear-gradient(180deg, #7ecd32 0%, #4a7a1e 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== Node Body ==================== */
|
||||||
|
/* 与 node-editor 完全一致 */
|
||||||
|
.bp-node-body {
|
||||||
|
padding: 6px 0;
|
||||||
|
background: rgba(8, 8, 12, 0.95);
|
||||||
|
border-radius: 0 0 5px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== Pin Row ==================== */
|
||||||
|
/* 与 node-editor 完全一致 */
|
||||||
|
.bp-pin-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
min-height: 20px;
|
||||||
|
padding: 1px 0;
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp-pin-row.input {
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp-pin-row.output {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
justify-content: flex-start;
|
||||||
|
padding-right: 10px;
|
||||||
|
padding-left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== Pin (SVG-based) ==================== */
|
||||||
|
.bp-pin {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp-pin svg {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pin Label */
|
||||||
|
.bp-pin-label {
|
||||||
|
color: #b0b0b0;
|
||||||
|
font-size: 11px;
|
||||||
|
white-space: nowrap;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp-pin-row.output .bp-pin-label {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pin Value */
|
||||||
|
.bp-pin-value {
|
||||||
|
color: #7ecd32;
|
||||||
|
font-size: 10px;
|
||||||
|
margin-left: auto;
|
||||||
|
font-family: 'Consolas', 'Monaco', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp-pin-row.output .bp-pin-value {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== Legend ==================== */
|
||||||
|
.bp-legend {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 10px;
|
||||||
|
background: rgba(20, 20, 25, 0.8);
|
||||||
|
border-radius: 6px;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp-legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: #a0a0a0;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user