-
Notifications
You must be signed in to change notification settings - Fork 1
Visual Scripting
SparkEngine now includes a unified visual scripting system — a single, properly wired implementation that compiles node graphs to AngelScript source code.
| Component | File | Purpose |
|---|---|---|
VisualScriptCompiler |
SparkEngine/Source/Engine/Scripting/VisualScriptCompiler.h |
Node graph → AngelScript compiler |
VisualScriptPanel |
SparkEditor/Source/Panels/VisualScriptPanel.h |
ImGui node graph editor |
- Non-coders build gameplay logic by connecting nodes in the Visual Script panel
- Clicking Compile runs
VisualScriptCompiler::Compile()which generates a.asfile - The generated file is written to
Assets/Scripts/Generated/ - The existing
ScriptHotReloadsystem detects the change and recompiles automatically - No new runtime — visual scripts use the same AngelScript pipeline as hand-written scripts
| Category | Examples |
|---|---|
| Events | OnStart, OnUpdate, OnTriggerEnter, OnDamaged, OnKeyPress, OnCollision |
| Flow Control | Branch (If), ForLoop, Sequence |
| Actions | SetPosition, PlaySound, PlayAnimation, SpawnEntity, PrintMessage |
| Math | Add, Subtract, Multiply, Divide, Lerp, Clamp, Random |
| Logic | AND, OR, NOT, Equal, Greater, Less |
| Getters | GetKeyDown, GetDeltaTime, GetSelf |
| Constants | Float, Int, Bool, String, Vector3 |
| Variables | GetVariable, SetVariable |
- Left sidebar: Node palette organized by category
- Center: Canvas with pan (middle-mouse), zoom (scroll), node dragging, bezier connections
- Right sidebar: Variable definitions + selected node properties
- Bottom bar: Compile button, error display
A visual script with:
-
OnStart→PrintMessage("Hello World") -
OnUpdate→GetKeyDown("Space")→Branch→ApplyForce(0, 10, 0)
Generates:
class MyScript
{
void Start()
{
print("Hello World");
}
void Update(float dt)
{
if (getKeyDown("Space"))
{
// ApplyForce...
}
}
}The original visual scripting system (~7,300 lines) was removed in March 2026 as dead code — two parallel implementations that were never wired in. The current system is a clean reimplementation following the ShaderGraphCompiler pattern (node graph → code generation) with a single unified engine + editor implementation.
- Event Response System -- Data-driven "When/If/Then" rules (simpler alternative for common gameplay)
- Scripting with AngelScript -- Text-based scripting (the compilation target)
- Entity-Component-System -- How scripts attach to entities
The Event Response System provides a no-code "When/If/Then" rule engine for common gameplay patterns. Rules are defined as data (no scripting required) and evaluated at runtime.
| Component | File | Purpose |
|---|---|---|
EventResponseSystem |
SparkEngine/Source/Engine/Gameplay/EventResponseSystem.h |
Rule engine (singleton) |
EventResponsePanel |
SparkEditor/Source/Panels/EventResponsePanel.h |
Visual rule builder |
WHEN [Event Trigger]
IF [Conditions] (optional, AND/OR/NOT groups)
THEN [Action 1] → [Action 2] → ...
OnTriggerEnter, OnTriggerExit, OnDamaged, OnKilled, OnItemPickup, OnKeyPress, OnKeyRelease, OnCollision, OnQuestComplete, OnTimer, OnStart, OnWeatherChange, OnTimeOfDay, OnEntityCreated, OnEntityDestroyed, OnCustom
SpawnEntity, DestroyEntity, EnableEntity, DisableEntity, SetPosition, MoveToward, TeleportEntity, RotateEntity, SetHealth, DealDamage, HealEntity, PlaySound, StopSound, PlayAnimation, ApplyForce, ApplyImpulse, ShowDialogue, ShowMessage, SetWeather, SetTimeOfDay, SetWorldVariable, SetWorldFlag, Delay, FireCustomEvent
- Subscribes to existing
EventBusevents via RAII handles - Conditions reuse
ConditionSystem(60+ condition types) - Rules can be serialized to/from JSON
- Timer-based rules and delayed actions processed each frame via
Update(dt) - One-shot rules auto-disable after firing once
Pre-configured entity templates for rapid no-code game creation.
| Category | Presets |
|---|---|
| Character | Player, Enemy, NPC |
| Prop | Door, Chest, Destructible |
| Pickup | Health Pickup, Ammo Pickup, Key Item |
| Effect | Sound Source, Particle Effect, Spawn Point |
Right-click in the Hierarchy panel → Create From Preset → select category → select preset.
| Component | File | Purpose |
|---|---|---|
EntityPresetManager |
SparkEngine/Source/Engine/ECS/EntityPresetManager.h |
Preset registry (singleton) |
| HierarchyPanel integration | SparkEditor/Source/Panels/HierarchyPanel.cpp |
Context menu preset picker |
Open the Visual Script panel from the editor menu. The panel is divided into four areas:
- Left sidebar -- Node Palette organized by category (Events, Flow Control, Actions, Math, Logic, Getters, Constants, Variables)
- Center -- Canvas with pan (middle-mouse), zoom (scroll wheel), node dragging, and bezier connection drawing
- Right sidebar -- Variable definitions and selected node properties
- Bottom bar -- Compile button, error display, generated script path
Nodes can be added in two ways:
- Click a node type in the left sidebar palette to place it on the canvas
- Right-click the canvas to open the context menu and select a node type
// Programmatic node creation (used by undo/redo system)
panel.AddNodeAtPositionDirect(Spark::Scripting::ScriptNodeType::OnUpdate, 100.0f, 200.0f);
panel.AddNodeAtPositionDirect(Spark::Scripting::ScriptNodeType::GetKeyDown, 300.0f, 200.0f);
panel.AddNodeAtPositionDirect(Spark::Scripting::ScriptNodeType::Branch, 500.0f, 200.0f);
panel.AddNodeAtPositionDirect(Spark::Scripting::ScriptNodeType::ApplyForce, 700.0f, 150.0f);Drag from an output pin to an input pin to create a connection. The system enforces type safety:
// Type compatibility check (from VisualScriptPanel)
// Execution pins only connect to Execution pins
// Data pins connect if types match, or if either pin is PinKind::Any
bool AreTypesCompatible(PinKind a, PinKind b)
{
if (a == PinKind::Any || b == PinKind::Any) return true;
return a == b;
}Pin types and their wire colors:
| PinKind | Purpose | Typical Color |
|---|---|---|
Execution |
Flow control (white wire) | White |
Bool |
Boolean values | Red |
Int |
Integer values | Cyan |
Float |
Floating-point values | Green |
String |
Text values | Magenta |
Vector3 |
3D position/direction | Yellow |
Entity |
Entity ID reference | Orange |
Any |
Untyped (resolved at compile) | Gray |
In the right sidebar, add variables that become class members in the generated script:
// Variable definitions compile to class member declarations
struct VariableDecl
{
std::string name; // Variable name (e.g., "speed")
PinKind type = PinKind::Float; // Data type
std::string defaultValue; // Initial value (e.g., "10.0")
};
// In the generated AngelScript:
// class MyScript {
// float speed = 10.0;
// ...
// }Click the Compile button. The compiler performs:
-
Find event entry points -- Scans for
OnStart,OnUpdate,OnTriggerEnter, etc. - Topological sort -- Walks execution and data connections to determine evaluation order
- Code emission -- Generates AngelScript class with member variables and lifecycle methods
-
Write to disk -- Saves
.asfile toAssets/Scripts/Generated/ -
Hot reload --
ScriptHotReloaddetects the file change and recompiles automatically
// Programmatic compilation
Spark::Scripting::VisualScriptGraph graph;
graph.className = "PlayerController";
// ... add nodes, connections, variables ...
auto result = Spark::Scripting::VisualScriptCompiler::Compile(graph);
if (result.success)
{
// result.angelScriptSource contains the generated code
// Write it to Assets/Scripts/Generated/PlayerController.as
}
else
{
for (const auto& error : result.errors)
{
LOG_ERROR("Compile error: {}", error);
}
}Event nodes are the starting points of execution chains. Each generates a method in the AngelScript class.
| Node Type | ScriptNodeType Value | Generated Method | Parameters |
|---|---|---|---|
| OnStart | 0 | void Start() |
None |
| OnUpdate | 1 | void Update(float dt) |
deltaTime |
| OnTriggerEnter | 2 | void OnTriggerEnter(uint entity) |
triggering entity |
| OnTriggerExit | 3 | void OnTriggerExit(uint entity) |
exiting entity |
| OnDamaged | 4 | void OnDamaged(float amount) |
damage amount |
| OnKeyPress | 5 | void OnKeyPress(string key) |
key name |
| OnCollision | 6 | void OnCollision(uint entity) |
colliding entity |
| OnCustomEvent | 7 | void OnCustomEvent(...) |
user-defined |
| Node | Behavior |
|---|---|
| Branch | If/else -- routes execution based on a bool input |
| ForLoop | Repeats execution N times with an index output |
| Sequence | Executes multiple output chains in order |
| DoNothing | Pass-through (useful for graph organization) |
| Node | Effect |
|---|---|
| SetPosition | Move entity to a Vector3 position |
| SetRotation | Set entity rotation |
| SetHealth | Set entity health value |
| ApplyForce | Apply physics force to entity |
| PlaySound | Play an audio clip |
| PlayAnimation | Trigger animation state |
| SpawnEntity | Create a new entity at position |
| DestroyEntity | Remove entity from world |
| PrintMessage | Output debug text |
| FireEvent | Fire a custom event |
| Node | Operation | Inputs | Output |
|---|---|---|---|
| Add | a + b | Float/Vector3 | Float/Vector3 |
| Subtract | a - b | Float/Vector3 | Float/Vector3 |
| Multiply | a * b | Float | Float |
| Divide | a / b | Float | Float |
| Lerp | Linear interpolate | a, b, t | Float/Vector3 |
| Clamp | Constrain to range | value, min, max | Float |
| Random | Random [0, 1] | None | Float |
| RandomRange | Random [min, max] | min, max | Float |
| Distance | Vector distance | a, b | Float |
| Normalize | Unit vector | Vector3 | Vector3 |
| DotProduct | Dot product | a, b | Float |
| Abs | Absolute value | Float | Float |
| Negate | -value | Float | Float |
Visual scripts support reusable function sub-graphs that compile to separate methods:
// Define a function sub-graph
Spark::Scripting::FunctionGraph healFunction;
healFunction.name = "HealEntity";
healFunction.returnType = Spark::Scripting::PinKind::Execution; // void return
// Add parameters
Spark::Scripting::VariableDecl targetParam;
targetParam.name = "target";
targetParam.type = Spark::Scripting::PinKind::Entity;
healFunction.parameters.push_back(targetParam);
Spark::Scripting::VariableDecl amountParam;
amountParam.name = "amount";
amountParam.type = Spark::Scripting::PinKind::Float;
amountParam.defaultValue = "25.0";
healFunction.parameters.push_back(amountParam);
// Add nodes inside the function (SetHealth, etc.)
// ... add nodes and connections ...
// Register with the graph
graph.functions.push_back(healFunction);This compiles to:
class MyScript
{
void HealEntity(uint target, float amount)
{
setHealth(target, getHealth(target) + amount);
}
}Spark::Scripting::CustomEventDef onLevelUp;
onLevelUp.name = "OnLevelUp";
Spark::Scripting::VariableDecl levelParam;
levelParam.name = "newLevel";
levelParam.type = Spark::Scripting::PinKind::Int;
onLevelUp.parameters.push_back(levelParam);
graph.customEvents.push_back(onLevelUp);Enable debug mode to insert debugTrace() calls at every node, allowing step-by-step execution tracing:
auto result = Spark::Scripting::VisualScriptCompiler::Compile(graph, true); // debugMode = trueThis inserts trace calls into the generated AngelScript:
void Update(float dt)
{
debugTrace("Node_1: GetKeyDown"); // Trace each node execution
bool node_1_0 = getKeyDown("Space");
debugTrace("Node_2: Branch");
if (node_1_0)
{
debugTrace("Node_3: ApplyForce");
applyForce(0, 10, 0);
}
}The VisualScriptPanel has a m_debugCompile flag that can be toggled in the UI. When enabled, compiled scripts include trace output.
All graph operations support undo/redo through the editor command system:
// AddNodeCommand -- undoable node creation
class AddNodeCommand : public EditorCommand
{
void Execute() override; // Creates the node
void Undo() override; // Removes the node
};
// RemoveNodeCommand -- undoable node deletion (saves full node state)
class RemoveNodeCommand : public EditorCommand
{
void Execute() override; // Removes node + saves connections
void Undo() override; // Restores node + reconnects
};
// AddConnectionCommand -- undoable connection creation
class AddConnectionCommand : public EditorCommand
{
void Execute() override; // Creates the connection
void Undo() override; // Removes the connection
};Graphs can be saved and loaded independently of compilation:
// Save graph to JSON
panel.SaveGraph("Assets/Scripts/Graphs/PlayerController.vsgraph");
// Load graph from JSON
panel.LoadGraph("Assets/Scripts/Graphs/PlayerController.vsgraph");- No runtime overhead -- Visual scripts compile to the same AngelScript as hand-written code. There is no visual script interpreter or VM.
- Compile-time cost -- The topological sort and code generation are O(N) where N is the node count. Compilation is near-instant for typical graphs (< 200 nodes).
-
Hot reload latency -- After compilation,
ScriptHotReloaddetects the file change within one frame and triggers AngelScript recompilation. Total turnaround is typically under 100ms. - Graph size -- Large graphs (500+ nodes) may slow canvas rendering in the editor. Consider splitting logic across multiple scripts attached to different entities.
The generated code uses the standard AngelScript API bindings:
// All visual script outputs use the same engine bindings as hand-written scripts
class PlayerController
{
float speed = 5.0;
float jumpForce = 10.0;
void Start()
{
print("PlayerController initialized");
}
void Update(float dt)
{
if (getKeyDown("W"))
{
vec3 pos = getPosition(getSelf());
pos.y += speed * dt;
setPosition(getSelf(), pos);
}
if (getKeyDown("Space"))
{
applyForce(getSelf(), 0, jumpForce, 0);
}
}
}The compiler's PinTypeString() maps each PinKind to its AngelScript equivalent:
| PinKind | AngelScript Type |
|---|---|
Bool |
bool |
Int |
int |
Float |
float |
String |
string |
Vector3 |
vec3 |
Entity |
uint |
Execution |
(flow control, no type) |
Published from 708ae987d905. Edit the canonical source in wiki/.
- Documentation
- Docs route
- Wiki index
- Guides
- Tutorials
- Samples
- Examples
- API Reference
- API route
- Reference
- Build Guide
- Dependencies
- FAQ
- Changelog
- Roadmap
- Contributing
- Code of Conduct
- Home
- FAQ
- Getting Started
- Quick-Start Tutorial
- Making Your First Game
- Making Your First Multiplayer Game
- Artist Workflow Guide
- Editor Walkthrough
- Migration Guide
- How SparkEngine Works
- Architecture Overview
- Engine Architecture Flowchart
- Creating a Game Module
- Game Modules (catalog)
- Entity Component System
- Rendering and Graphics
- Physics
- Cloth Simulation
- Audio
- Input System
- Camera System
- Scripting with AngelScript
- Visual Scripting
- AI and Navigation
- Animation
- 2D Systems
- Networking
- Dedicated Server
- Multiplayer Quick Start
- Area Server Architecture
- Scene Management
- Large World Support
- Collaborative Editing
- Coroutine System
- Event System
- Event Response System
- Job System
- UI System
- UI Layout Extensions
- Localization
- Dialogue System
- Destruction System
- Replay System
- Achievement System
- Loading System
- Mod System
- Content Delivery
- Tween System
- Memory Integrity
- Gameplay Systems
- Terrain and Procedural Generation
- Save System
- Persistence System
- Day Night Cycle and Weather
- Cinematic Sequencer
- Runtime Prefabs
- SparkEditor
- Editor Tutorials
- SparkConsole
- SparkDaemon
- Shader Pipeline
- Asset Pipeline
- Asset Validation
- Asset Migration
- Game Packaging
- Online Services
- DataTable System
- Loot and Crafting System
- CSG System
- Font System
- Timer Manager
- Movie Render Pipeline
- HLOD and World Partition
- Remote Debug System
- Selection Manager
- Asset Dependency Graph
- Editor Automation
- File Watcher
- Project Templates
- System Requirements
- VR Support
- Mobile Platform
- Accessibility
- Platform Input
- Cross-Compilation: Wine Testing
- RHI Abstraction Layer
- D3D11 Backend
- D3D12 Backend
- Vulkan Backend
- OpenGL Backend
- Metal Backend
- DXR Raytracing
- Hybrid Ray Tracing
- Upscaling (DLSS/FSR)
- Render Graph
- Shader Graph
- GPU Particles
- GPU-Driven Rendering
- Volumetric Fog
- Volumetric Clouds
- Global Illumination
- Virtual Texturing
- Water Rendering
- Clustered Lighting
- Material System
- Post-Processing
- Shadow System
- Particle System
- Decal System
- Sky and Atmosphere
- Foliage System
- Mesh Shaders
- Neural Rendering
- Configuration Reference
- Performance Tips
- Benchmark Framework
- Threading Model
- Memory Safety
- Memory Management Patterns
- Build System and CMake Modules
- Profiler and Debugging
- Performance Profiling Guide
- Telemetry System
- Golden Image Testing
- Utilities
- Testing
- Codebase Statistics
- Codebase Health
- Error Handling Patterns
- Hot Reload Overview
- Troubleshooting
- Contributing
- Workflow Patterns
- Build Optimizations
- CI Reproducible Builds
- GitHub API and PR Checks
- Git Rebase Conflicts
- Clang-Format
- Code Quality Violations
- AI Bloat Pattern
- MinGW + Wine Cross-Compilation
- Live Editor Testing
- Engine & Renderer Landscape
- DuetOS Portability Catalog
- Five-Engine Analysis
- Eleven-Engine Analysis
- ThorVG / Unity Graphics Analysis
- Advanced Techniques Catalog
- Third-Party Library Evaluation
- Engine Viability Evaluation
- Engine Feature Recommendations
- Project Recommendations
- Mac Compatibility Analysis
- Codebase Observations
- Codebase Bloat Audit
- Test Suite Audit
- Documentation Coverage Audit
- ThirdParty Dependencies Audit
- Load Test Baseline
- Gameplay Systems Status
- SparkGame Module Status
- Stub and Abandoned Features
- Memory Integrity System
- Memory Safety Evaluation
- Hardware Acceleration Systems
- Jolt Physics Integration
- GPU/CPU Separation Plan
- Daemon Services Architecture
- Reflection & Polymorphism Refactoring Plan
- SparkBuild In-Tree
- Wine No-JobSystem Breakthrough
- Wine Role and Fallback Tiers