Razorbill
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

FunctionWhen CalledTypical Use
OnStartOnce when play mode beginsFind entities, cache handles, initialize state
OnUpdateEvery frame during play modeMovement, input, game logic
OnDestroyWhen play ends or entity is destroyedCleanup, unsubscribe events

ScriptContext

All engine interaction goes through the ctx pointer. Key built-in fields:

FieldTypeDescription
ctx->selfEntityHandleHandle to the entity this script is attached to
ctx->get_delta_time()floatTime 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:

  1. File watching — The engine monitors Content/Scripts/ for changes (via efsw)
  2. Auto-recompile — Modified scripts trigger automatic CMake recompilation (150ms debounce)
  3. Seamless reload — The dylib is unloaded and reloaded without restarting play mode
  4. State preservation — Static variables in your script persist across reloads via cr.h
  5. Crash recovery — If a script crashes, the engine counts failures and offers --safe-mode to skip script loading

API Categories

The scripting API has 73 functions across 10 categories:

CategoryFunctionsDescription
Transform12Position, rotation, scale, entity lifecycle
Input7Keyboard, mouse, cursor capture
Physics11Forces, velocity, mass, raycasting
Camera4Active camera, FOV
Character Controller11Movement, jumping, crouching, sprinting
UI14Create/modify UI elements at runtime
Events3Custom event pub/sub system
Animation7Clip playback, parameters, state queries
Component3Generic 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);
}

On this page