Repository navigation
Scripting with AngelScript
Stable-v1 boundary:
stable-v1is blocked and uncertified, with its declared product shape limited to Windows 11 x64, MSVC v143, D3D11 or the no-render Windows NullRHI path, and C++ game modules. AngelScript and visual scripting tooling are experimental and outside that profile, not certified gameplay-runtime support.
SparkEngine contains an optional AngelScript integration for development,
including VM, binding, hot-reload, and visual-scripting generation surfaces.
Those surfaces remain experimental and incomplete. There is no LuaScriptEngine
or Lua runtime implementation in the engine source; generic .lua asset labels
do not provide Lua execution support. A mod system enables
user-created content.
Source: SparkEngine/Source/Engine/Scripting/
AngelScript is a statically-typed scripting language with C/C++-like syntax. Scripts are attached to ECS entities and driven through lifecycle callbacks, similar to Unity's MonoBehaviour pattern. The scripting subsystem comprises three major components:
| Component | Header | Purpose |
|---|---|---|
AngelScriptEngine |
AngelScriptEngine.h |
Core VM wrapper, compilation, entity binding, lifecycle dispatch |
VisualScriptSystem |
VisualScriptSystem.h |
Node-graph visual scripting with AngelScript compilation backend |
ScriptHotReloadManager |
ScriptHotReload.h |
File watcher and automatic recompilation on save |
+---------------------------+
| AngelScript VM (asIScriptEngine) |
+---------------------------+
^ ^
| |
+-------+------+ +-+-------------------+
| CompileFile | | CompileFromString |
| CompileGraph | | (Visual Script |
| (.as) | | compilation) |
+--------------+ +---------------------+
| |
v v
+------------------------------------------+
| Module Registry |
| m_modules: map<string, asIScriptModule> |
+------------------------------------------+
|
v
+------------------------------------------+
| Entity Script Binding |
| m_entityScripts: map<EntityID, |
| ScriptInstance> |
+------------------------------------------+
|
v
+------------------------------------------+
| Lifecycle Dispatch |
| CallStart / CallUpdate / CallOnCollision|
+------------------------------------------+
Create an .as file in Assets/Scripts/:
// Assets/Scripts/EnemyAI.as
class EnemyBehavior
{
float health = 100.0f;
float moveSpeed = 5.0f;
void Start()
{
print("Enemy spawned!");
}
void Update(float dt)
{
// Game logic runs every frame
if (getKeyDown("F")) {
health -= 10.0f;
print("Enemy hit! Health: " + health);
}
}
void OnCollision(uint entityId)
{
print("Collided with entity: " + entityId);
}
}- Every script class must be declared at the top level of the
.asfile. - Method names are case-sensitive and must match the lifecycle signatures exactly.
- Member variables are instance-scoped and persist between ordinary
Update()calls. Hot reload does not preserve per-instance script state; constructors run again for recreated instances. - Scripts can define additional methods beyond the lifecycle callbacks; they are callable from other scripts or from C++ via the AngelScript context API.
| Callback | Signature | When Called | Notes |
|---|---|---|---|
Start |
void Start() |
Once, when the script is first attached | Initialization logic goes here |
Update |
void Update(float dt) |
Every frame, with delta time in seconds | Main game loop tick |
OnCollision |
void OnCollision(uint entityId) |
When the entity collides with another | Requires a collider component |
Lifecycle callbacks are dispatched during the Scripting phase of the ECS update loop. The overall engine execution order is:
Physics -> Animation -> AI -> Scripting -> Audio -> Lifecycle -> Render
Within the Scripting phase, Start() is called before Update() for any newly attached scripts. OnCollision() is dispatched after the Physics phase delivers collision events.
The ScriptAPIRegistry in ScriptHotReload.h documents every function registered with the AngelScript VM. They are organized by category:
| Signature | Description |
|---|---|
void Log(const string &in) |
Print to console |
void LogWarning(const string &in) |
Print warning |
void LogError(const string &in) |
Print error |
float GetDeltaTime() |
Frame delta time in seconds |
float GetGameTime() |
Total elapsed game time |
| Signature | Description |
|---|---|
uint CreateEntity() |
Create a new entity |
void DestroyEntity(uint) |
Destroy entity by ID |
Vector3 GetPosition(uint) |
Get entity world position |
void SetPosition(uint, Vector3) |
Set entity world position |
Quaternion GetRotation(uint) |
Get entity rotation |
void SetRotation(uint, Quaternion) |
Set entity rotation |
float GetHealth(uint) |
Get entity health |
void SetHealth(uint, float) |
Set entity health |
bool IsAlive(uint) |
Check if entity is alive |
| Signature | Description |
|---|---|
bool Raycast(Vector3, Vector3, float, RayHit &out) |
Cast ray and get hit info |
void ApplyForce(uint, Vector3) |
Apply force to rigidbody |
void ApplyImpulse(uint, Vector3) |
Apply impulse to rigidbody |
void SetVelocity(uint, Vector3) |
Set linear velocity |
Vector3 GetVelocity(uint) |
Get linear velocity |
| Signature | Description |
|---|---|
void PlaySound(const string &in) |
Play a sound effect by name |
void PlaySoundAt(const string &in, Vector3) |
Play 3D sound at position |
void StopSound(const string &in) |
Stop a playing sound |
void SetVolume(float) |
Set master volume [0, 1] |
| Signature | Description |
|---|---|
bool IsKeyDown(int) |
Check if key is currently held |
bool IsKeyPressed(int) |
Check if key was just pressed |
Vector2 GetMousePosition() |
Get mouse screen position |
Vector2 GetMouseDelta() |
Get mouse movement delta |
bool IsMouseButtonDown(int) |
Check mouse button state |
float GetGamepadAxis(int, int) |
Get gamepad axis value |
| Signature | Description |
|---|---|
void DrawText(const string &in, float, float, float) |
Draw text at screen position |
void DrawProgressBar(float, float, float, float, float) |
Draw progress bar (x, y, w, h, value) |
| Signature | Description |
|---|---|
void LoadScene(const string &in) |
Load scene by name |
string GetCurrentScene() |
Get current scene name |
| Signature | Description |
|---|---|
bool FindPath(Vector3, Vector3, array<Vector3> &out) |
Find NavMesh path between two points |
Vector3 GetRandomNavPoint() |
Random walkable NavMesh point |
| Signature | Description |
|---|---|
void PlayAnimation(uint, const string &in) |
Play animation clip on entity |
void SetAnimationSpeed(uint, float) |
Set animation playback speed |
| Signature | Description |
|---|---|
void DebugDrawLine(Vector3, Vector3, Color) |
Draw debug line for one frame |
void DebugDrawSphere(Vector3, float, Color) |
Draw debug sphere for one frame |
The following math types are registered as value types in AngelScript:
-
Vector2-- 2D vector (x, y) -
Vector3-- 3D vector (x, y, z) -
Vector4-- 4D vector (x, y, z, w) -
Quaternion-- Rotation quaternion -
Color-- RGBA color -
RayHit-- Raycast result (position, normal, distance, entityId)
class AngelScriptEngine
{
public:
// Lifecycle
bool Initialize();
void Shutdown();
// Compilation
bool CompileScriptFile(const std::string& scriptPath);
bool CompileScriptFromString(const std::string& script, const std::string& moduleName);
// Entity binding
bool AttachScript(EntityID entity, const std::string& className, const std::string& moduleName);
void DetachScript(EntityID entity);
// Lifecycle dispatch
void CallStart(EntityID entity);
void CallUpdate(EntityID entity, float deltaTime);
void CallOnCollision(EntityID entity, EntityID other);
// Error handling
std::string GetLastError() const;
// Singleton
static AngelScriptEngine* GetInstance();
};AngelScriptEngine& scriptEngine = AngelScriptEngine::GetInstance();
scriptEngine.Initialize();
// Compile a script file
scriptEngine.CompileScriptFile("Assets/Scripts/EnemyAI.as");
// Attach a script class to an entity
scriptEngine.AttachScript(enemyEntity, "EnemyBehavior", "EnemyAI");
// Call lifecycle methods
scriptEngine.CallStart(enemyEntity); // Called once
scriptEngine.CallUpdate(enemyEntity, dt); // Called every framescriptEngine.CompileScriptFromString(
"class Test { void Start() { print(\"Hello!\"); } }",
"InlineModule");scriptEngine.DetachScript(enemyEntity);Each entity-script binding is tracked by a ScriptInstance struct:
struct ScriptInstance
{
asIScriptObject* object; // The instantiated script object
asITypeInfo* typeInfo; // Type metadata for the script class
asIScriptContext* context; // Execution context for calling methods
asIScriptFunction* startMethod; // Cached pointer to Start()
asIScriptFunction* updateMethod; // Cached pointer to Update(float)
asIScriptFunction* onCollisionMethod; // Cached pointer to OnCollision(EntityID)
std::string className;
std::string moduleName;
};Method pointers are cached at attach time via CacheScriptMethods() to avoid repeated lookups during per-frame dispatch.
Use the Script component to bind scripts to entities:
auto& script = world.AddComponent<Script>(entity);
script.scriptFile = "EnemyAI";
script.className = "EnemyBehavior";
script.moduleName = "EnemyAI";The VisualScriptSystem (in VisualScriptSystem.h) provides a node-graph visual scripting frontend that compiles to AngelScript. This enables designers to author gameplay logic without writing code.
enum class PinType : uint8_t
{
Execution, // White wire -- controls which node fires next
Bool,
Int,
Float,
String,
Vector2,
Vector3,
Vector4,
Entity, // Entity ID reference
Any // Wildcard -- resolved at connection time
};| Category | Examples |
|---|---|
Event |
BeginPlay, Tick, OnCollision |
FlowControl |
Branch, ForLoop, Sequence |
Math |
Add, Multiply, Clamp, Lerp |
Logic |
AND, OR, NOT, Compare |
String |
Concat, Format, Length |
Variable |
Get/Set local or graph variables |
Entity |
GetComponent, SpawnEntity, Destroy |
Physics |
AddForce, Raycast, SetVelocity |
Input |
IsKeyDown, GetAxis, GetMousePos |
Audio |
PlaySound, StopSound, SetVolume |
Debug |
Print, DrawLine, Log |
Custom |
User-registered nodes |
1. Create VisualScriptGraph
2. AddNode() -- places nodes from the NodeLibrary
3. AddLink() -- connect output pins to input pins
4. Validate() -- check for type mismatches and cycles
5. CompileToAngelScript() -- generate .as source code
6. Register with AngelScriptEngine for execution
Graph-level variables can be declared as public (exposed in the editor Inspector) or private:
struct GraphVariable
{
std::string name;
PinType type = PinType::Float;
PinValue value;
bool isPublic = false; // Exposed to the editor / inspector
};Graphs serialize to JSON for editor persistence:
std::string json = graph->SerializeToJSON();
graph->DeserializeFromJSON(json);| Command | Description |
|---|---|
vs_status |
Show visual script system status |
vs_list_graphs |
List all loaded visual script graphs |
vs_list_nodes |
List all available node templates |
The ScriptHotReloadManager watches script directories for file changes and automatically recompiles modified scripts without restarting the engine.
ScriptHotReloadManager hotReload;
hotReload.AddWatchDirectory("Assets/Scripts/", true); // recursive
hotReload.SetWatchExtensions({".as", ".angelscript"});
hotReload.SetDebounceMs(300); // 300ms debounce to avoid rapid re-triggers
hotReload.SetRecompileCallback([&](const std::string& file) -> RecompileResult {
RecompileResult result;
result.success = scriptEngine.CompileScriptFile(file);
result.filePath = file;
if (!result.success)
result.errorMessage = scriptEngine.GetLastError();
return result;
});
hotReload.SetErrorCallback([](const RecompileResult& err) {
LOG_ERROR("Script error in {}: {}", err.filePath, err.errorMessage);
});
hotReload.Start();// In the main game loop:
int recompiled = hotReload.PollChanges();
if (recompiled > 0)
LOG_INFO("Hot-reloaded {} script(s)", recompiled);enum class FileChangeType
{
Modified,
Created,
Deleted,
Renamed
};struct RecompileResult
{
bool success = false;
std::string filePath;
std::string errorMessage;
int errorLine = 0;
float compileTimeMs = 0.0f;
};| Method | Returns |
|---|---|
IsRunning() |
Whether the watcher is active |
GetWatchedFileCount() |
Number of tracked script files |
GetRecompileCount() |
Total recompilations since start |
GetErrorCount() |
Total compilation errors since start |
GetRecentErrors() |
Last 10 RecompileResult errors |
Compilation and runtime errors are captured via the AngelScript message callback and stored for retrieval:
if (!scriptEngine.CompileScriptFile("Assets/Scripts/Broken.as")) {
std::string error = scriptEngine.GetLastError();
LOG_ERROR("Script error: {}", error);
}Errors include the file path, line number, and column where the error occurred. The ScriptHotReloadManager additionally tracks per-file error history with GetRecentErrors().
| Error | Cause | Resolution |
|---|---|---|
Identifier not found |
Using an unregistered function | Check the API registry or spelling |
No matching signatures |
Wrong argument types | Verify parameter types match the API table |
Module already exists |
Compiling the same module twice | Use unique module names or detach first |
Script class not found |
Typo in className passed to AttachScript
|
Ensure class name matches the .as file exactly |
For multiplayer games, scripts can be separated into client-only, server-only, and shared contexts. This prevents server logic from running on clients and vice versa.
// On the server:
scriptEngine.SetScriptContext(AngelScriptEngine::ScriptContext::Server);
// On the client:
scriptEngine.SetScriptContext(AngelScriptEngine::ScriptContext::Client);
// Default (runs everywhere):
scriptEngine.SetScriptContext(AngelScriptEngine::ScriptContext::Shared);When set to Server, scripts tagged [client] are skipped during execution. When set to Client, scripts tagged [server] are skipped. This enables clean separation of authoritative server logic (e.g., damage calculation, loot drops) from client-only logic (e.g., UI effects, camera shake).
Each script file is compiled into a separate AngelScript module, providing namespace isolation between scripts. The module name is derived from the filename by default, or can be specified explicitly when compiling from a string.
Modules are stored in m_modules: std::unordered_map<std::string, asIScriptModule*>. Multiple entities can share the same module (and thus the same compiled bytecode) while maintaining independent script object instances.
-
Method caching:
Start(),Update(), andOnCollision()function pointers are cached at attach time. This avoids the cost of name-based lookup on every frame. -
Context reuse: Each
ScriptInstanceholds a dedicatedasIScriptContext. Contexts are created once and reused across calls. - Hot-reload debounce: The default 300ms debounce prevents rapid recompilation while the user is still typing/saving.
- Visual script compilation: Visual graphs target the same AngelScript runtime as hand-written scripts. No generated-code performance-parity benchmark or stable-v1 certification exists.
Script contexts are not thread-safe. All script calls must happen on the main game loop thread. This includes:
-
CompileScriptFile()/CompileScriptFromString() -
AttachScript()/DetachScript() -
CallStart()/CallUpdate()/CallOnCollision() ScriptHotReloadManager::PollChanges()
The ScriptHotReloadManager file scanning runs on the main thread during PollChanges(). It does not use background threads.
| Command | Description |
|---|---|
script_reload_all |
Force recompile all watched scripts |
script_reload_status |
Show hot-reload watcher status |
vs_status |
Visual script system status |
vs_list_graphs |
List loaded visual script graphs |
vs_list_nodes |
List available node templates |
- Verify the script file compiles without errors (check
GetLastError()). - Ensure
AttachScript()was called with the correct class name and module name. - Confirm
CallStart()andCallUpdate()are being called each frame. - Check the ECS
Scriptcomponent fields match the compiled module.
- Confirm
ScriptHotReloadManager::Start()was called. - Verify the watch directory path is correct and the file extension is
.asor.angelscript. - Increase the debounce interval if saves happen in rapid succession.
- Call
PollChanges()every frame in the main loop.
- Run
graph->Validate()and inspect theerrorsandwarningsvectors. - Check for disconnected execution flow wires (every execution output must connect to an input).
- Ensure no cycles exist in the data flow graph.
- Verify all required input pins have connections or default values.
- Entity Component System -- Script component
- Creating a Game Module -- Module lifecycle
- Event System -- Publishing and subscribing to events from scripts
- Physics -- Physics API available in scripts
- Input System -- Input API available in scripts
- Audio -- Audio API available in scripts
- Animation -- Controlling animations from scripts
- Scene Management -- Scene operations from scripts
- SparkEditor -- Visual script graph editor panel
Published from 2b03dc797148. 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
- Platform Certification
- 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
- Fuzz Policy and Parser Security
- Memory Safety
- Memory Management Patterns
- Build System and CMake Modules
- Profiler and Debugging
- Performance Profiling Guide
- Telemetry System
- Crash Reporting
- Golden Image Testing
- Utilities
- Testing
- Fuzz Policy and Parser Security
- 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