Scripting API
Scripting Overview
Razorbill scripts are C++ files compiled to dynamic libraries and hot-reloaded at runtime. Scripts interact with the engine through a stable C ABI via the ScriptContext pointer.
Script Structure
Every script is a .cpp file with three exported C functions:
#include <eng/sdk/script_context.hpp>
extern "C" {
void OnStart(ScriptContext* ctx) {
// Called once when play mode starts
ctx->log_message(ctx, "Hello from script!");
}
void OnUpdate(ScriptContext* ctx) {
// Called every frame
float dt = ctx->get_delta_time();
// ... game logic here ...
}
void OnDestroy(ScriptContext* ctx) {
// Called when play stops or entity is destroyed
}
}
Lifecycle
| Function | When Called | Typical Use |
|---|---|---|
OnStart | Once when play mode begins | Find entities, cache handles, initialize state |
OnUpdate | Every frame during play mode | Movement, input, game logic |
OnDestroy | When play ends or entity is destroyed | Cleanup, unsubscribe events |
ScriptContext
All engine interaction goes through the ctx pointer. Key built-in fields:
| Field | Type | Description |
|---|---|---|
ctx->self | EntityHandle | Handle to the entity this script is attached to |
ctx->get_delta_time() | float | Time in seconds since last frame |
Creating Scripts
Via Operations
[
{
"op": "CreateScript",
"params": { "script_name": "PlayerMovement" }
},
{
"op": "WriteScriptContent",
"params": {
"script_name": "PlayerMovement",
"new_content": "#include <eng/sdk/script_context.hpp>\n\nextern \"C\" {\nvoid OnStart(ScriptContext* ctx) {}\nvoid OnUpdate(ScriptContext* ctx) {\n float pos[3];\n ctx->get_position(ctx->self, pos);\n pos[1] += ctx->get_delta_time();\n ctx->set_position(ctx->self, pos);\n}\nvoid OnDestroy(ScriptContext* ctx) {}\n}"
}
},
{
"op": "AttachScript",
"params": {
"entity_name": "Player",
"script_name": "PlayerMovement"
}
}
]
Via CLI
# Create, compile, and attach
./build/toolchain/cli/eng_cli apply-plan ~/MyProject plan.json --json
./build/toolchain/cli/eng_cli compile-scripts ~/MyProject
./build/toolchain/cli/eng_cli attach-script ~/MyProject Main Player PlayerMovement
Script Properties
Scripts can export editable properties that appear in the inspector and are stored per-instance in the scene. Define properties using the ENG_SCRIPT_PROPERTIES_IMPL macro:
#include <eng/sdk/script_context.hpp>
// Define editable properties
ENG_SCRIPT_PROPERTIES_IMPL {
ENG_PROP_FLOAT("speed", 5.0f);
ENG_PROP_FLOAT("jump_height", 3.0f);
ENG_PROP_STRING("team", "red");
}
static float move_speed = 5.0f;
void OnStart(ScriptContext* ctx) {
const char* val = ctx->get_script_property(ctx, "speed");
if (val) {
move_speed = atof(val);
}
}
When a script exports properties:
- The inspector shows editable fields for each property
- Values are stored per-instance in the ScriptComponent JSON
- Different entities with the same script can have different property values
Hot Reload
Scripts support hot-reload during play mode:
- File watching — The engine monitors
Content/Scripts/for changes (via efsw) - Auto-recompile — Modified scripts trigger automatic CMake recompilation (150ms debounce)
- Seamless reload — The dylib is unloaded and reloaded without restarting play mode
- State preservation — Static variables in your script persist across reloads via cr.h
- Crash recovery — If a script crashes, the engine counts failures and offers
--safe-modeto skip script loading
API Categories
The scripting API has 73 functions across 10 categories:
| Category | Functions | Description |
|---|---|---|
| Transform | 12 | Position, rotation, scale, entity lifecycle |
| Input | 7 | Keyboard, mouse, cursor capture |
| Physics | 11 | Forces, velocity, mass, raycasting |
| Camera | 4 | Active camera, FOV |
| Character Controller | 11 | Movement, jumping, crouching, sprinting |
| UI | 14 | Create/modify UI elements at runtime |
| Events | 3 | Custom event pub/sub system |
| Animation | 7 | Clip playback, parameters, state queries |
| Component | 3 | Generic component get/set/has |
Common Patterns
Find and Cache Entity Handles
static EntityHandle target;
void OnStart(ScriptContext* ctx) {
target = ctx->find_entity_by_name(ctx, "Target");
}
void OnUpdate(ScriptContext* ctx) {
float target_pos[3];
ctx->get_position(target, target_pos);
// ... use target_pos ...
}
Delta-Time Movement
void OnUpdate(ScriptContext* ctx) {
float dt = ctx->get_delta_time();
float pos[3];
ctx->get_position(ctx->self, pos);
pos[0] += speed * dt; // Frame-rate independent
ctx->set_position(ctx->self, pos);
}