Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Managing Scripts

Scripts are Bevy assets (ScriptAsset), loaded from the assets directory like any other asset. A loaded script does nothing until it is attached, which evaluates it:

  • parsing the script body,
  • creating or updating the script’s context, the environment holding its globals.

Script attachments

A ScriptAttachment says where a script “lives”, and is the identity scripts are addressed by:

  • ScriptAttachment::EntityScript(entity, handle): attached to an entity through its ScriptComponent,
  • ScriptAttachment::StaticScript(handle): attached to nothing, activated through the AttachScript command.

Inserting or removing a ScriptComponent sends ScriptLifecycleMessages (Attached / Detached), which the script pipeline processes. Static scripts are attached and detached with the AttachScript and DetachScript commands.

Loading

Load scripts with the AssetServer, or create them in memory:

let handle = asset_server.load::<ScriptAsset>("my_script.lua");

let mut script = ScriptAsset::from("x = 0".to_string());
script.language = Language::Lua;
let handle = script_assets.add(script);

Neither runs any code yet.

Note

Scripts are identified by their handle, not their path. The path is only used in logs and error messages, so scripts created in memory show up by asset id instead.

Attaching scripts to entities

Add a ScriptComponent to an entity:

fn load_script(asset_server: Res<AssetServer>, mut commands: Commands) {
    let handle = asset_server.load::<ScriptAsset>("my_script.lua");
    commands.spawn(ScriptComponent::new(vec![handle]));
}

In the script, import_namespace("bms").current_entity() returns the entity the script is attached to.

Static scripts

Scripts which don’t belong to an entity are attached as static scripts:

fn add_static_script(asset_server: Res<AssetServer>, mut commands: Commands) {
    let handle = asset_server.load("scripts/my_script.lua");
    commands.queue(AttachScript::<LuaScriptingPlugin>::new(
        ScriptAttachment::StaticScript(handle),
    ));
}

fn remove_static_script(handle: Local<Handle<ScriptAsset>>, mut commands: Commands) {
    commands.queue(DetachScript::<LuaScriptingPlugin>::new(
        ScriptAttachment::StaticScript(handle.clone()),
    ));
}

The commands run the script pipeline until the attachment is processed. To avoid frame time spikes, write ScriptLifecycleMessages instead, and let the pipeline pick them up as normal.

Unloading

BMS keeps a handle to each attached script, so dropping your own handles does not unload it. To unload a script, and run its on_script_unloaded callback, remove it from its ScriptComponent or detach it with DetachScript. Once no handles are left, Bevy drops the asset.

A context is removed together with the last script in it.

Hot reloading

Enable Bevy’s file_watcher feature, and changes to script assets are picked up and the scripts reloaded. Use on_script_unloaded and on_script_reloaded to keep state across reloads.

File extensions

Each language plugin maps file extensions to its language, i.e. lua and luau for Lua. To add more:

app.add_plugins(LuaScriptingPlugin::default().add_supported_extension("pua"));

Next, run your scripts.