Fusion Lua API (v0)
Addons are small Lua programs that add things to Fusion: tools, props, game modes, rules. Fusion uses Luau (the Lua used by Roblox), so anything you know from Lua or Garry's Mod mostly carries over.
This page covers v0. Hooks for game events, Blocks.<router>.* and HUD.* come in step 4.8; Fusion.Tool.define and Fusion.Entity.define come with the sandbox (step 3.8).
Making an addon¶
- Make a folder in
godot/addons_dev/(later: the Workshop and your addons folder), for examplegodot/addons_dev/my_addon/. - Put an
init.luain it. - Start Fusion and play any scenario: addons run in every World, Fusion's playground or a game's. Your addon loads by itself.
- Edit and save while Fusion runs: the addon reloads within half a second. Messages from addons appear at the bottom left of the screen and in the console.
-- godot/addons_dev/my_addon/init.lua
hook.add("Fusion.KeyPressed", "drop_box", function(key)
if key == "B" then
Fusion.Prop.spawn({ shape = "box", pos = Fusion.Player.eyePos() + Vector(0, 2, 0), color = "red" })
end
end)
log("Press B to drop a red box")
Two working examples ship with Fusion:
prop_spawner: press G to throw a prop where you're looking.endless_loop_demo: press L and it loops forever, on purpose, to show that Fusion stops it and keeps running.
Types for your editor: docs/types/fusion-addons.d.luau declares the whole API for luau-lsp ("luau-lsp.types.definitionFiles": ["docs/types/fusion-addons.d.luau"]): completion, and a wrong name or argument type is marked while you type. A test keeps it matching the API.
Reference¶
log(...) and print(...)¶
Writes a line to the screen and the console, like Lua's print.
hook.add(event, name, function) / hook.remove(event, name)¶
Runs function whenever event happens. name is your hook's name; adding a hook with the same event and name replaces the old one.
| Event | Arguments | When |
|---|---|---|
Fusion.KeyPressed | key: the key's name, like "G", "Space", "F1", "1" | a key is pressed (not when it repeats) |
Fusion.Think | dt: seconds since the last frame | every frame |
Fusion.EntitySpawned | entity (a table, below) | an entity appears in any game (or a Godot prop) |
Fusion.EntityRemoved | id | an entity is gone (its game stopped listing it) |
Fusion.EntityDamaged | entity, amount | its health went down by amount |
Fusion.EntityDied | entity | its health reached 0 |
Fusion.Signal | name | an addon or Device sent Fusion.signal(name) (it arrives the next frame) |
These entity hooks work the same for every game: Fusion compares its entity registry from one frame to the next.
Fusion.Prop.spawn(options)¶
Spawns a physics prop and returns its id (a number).
| Option | Required | Meaning |
|---|---|---|
pos | yes | where, a vector in meters (Y is up) |
shape | no | "box" (default), "sphere" or "cylinder" |
color | no | "#3a86ff", "#38f", or a name: red, orange, yellow, green, blue, purple, pink, brown, white, gray, black. Default: the playground's colors in turn |
velocity | no | a vector in meters per second, to throw it |
Fusion.Entities: every game's NPCs and props¶
An entity is a table: id (Fusion's id, unique across games), source (the block it comes from, e.g. "fake.cube", or "fusion" for Godot's props), kind, name, pos (a vector), health, maxHealth, team.
| Call | Does |
|---|---|
Fusion.Entities.all([source]) | every entity, or only those of one block |
Fusion.Entities.get(id) | one entity, or nil |
Fusion.Entities.near(pos, radius) | entities within radius meters, nearest first |
Fusion.Entities.act(id, action, ...) | asks the entity's game to do action (up to 8 numbers after it), e.g. act(e.id, "stop"). Which actions a game knows is listed in Blocks.list() |
Fusion.Entities.steer(id, pos) | walks the entity to pos around walls, on any game's ground (Fusion's steering; its block must need steering) |
hook.add("Fusion.KeyPressed", "come_here", function(key)
if key ~= "J" then return end
for _, e in Fusion.Entities.near(Fusion.Player.eyePos(), 20) do
Fusion.Entities.steer(e.id, Fusion.Player.eyePos())
end
end)
Fusion.Tool.define(id, tool): a tool for the sandbox (M3)¶
Fusion.Tool.define("carrot_launcher", {
name = "Carrot Launcher", -- shown in the spawn menu's Tools tab
description = "Throws carrots.", -- its tooltip
onFire = function(pos, normal, entity) end, -- left click: where the player aimed, and the
-- entity there (any game's, or a prop), or nil
onAltFire = function(pos, normal, entity) end, -- right click (optional)
})
id is lowercase letters, digits and _. Defining the same id again replaces the tool; an addon that's reloaded without it loses it. The functions run with the same time limits as hooks. Fusion's own props take set_health, remove and freeze through Fusion.Entities.act, like games' entities take their blocks' actions. Example: godot/addons_dev/carrot_launcher. Not yet: Fusion.Entity.define (Lua-made entities).
Fusion.signal(name)¶
Tells every addon and Device something happened: their Fusion.Signal hooks get name on the next frame. Devices use signals to work together (docs/devices.md), and an Experience's goals and ending listen to them. In an Experience, Fusion sends experience_start when the player presses Start.
Experience: ending the game¶
| Call | Does |
|---|---|
Experience.win([text]) | ends the Experience being played: the ending screen says "You win" and text (or the Experience's win_text) |
Experience.lose([text]) | the same, "Game over" |
Outside an Experience they do nothing. An Experience (and a stack) runs only the addons its scenario lists (addons = [...], docs/block-manifest.md); a plain scenario runs all of yours.
Blocks: the scenario's blocks¶
| Call | Does |
|---|---|
Blocks.list() | every block: id, name, kind, router, actions |
Blocks.sleep(router) / Blocks.wake(router) | puts a game to sleep (no CPU) or wakes it, also early before it's needed. The World's and the played Character's games can't sleep |
HUD: the player's screen¶
| Call | Does |
|---|---|
HUD.toast(text, [seconds]) | a message at the top of the screen for a few seconds (3 by default) |
HUD.text(key, text) | a line at the top right that stays until changed; HUD.text(key) removes it |
Fusion.Player.eyePos() / Fusion.Player.aimDir()¶
Where the player's eyes are, and which way they're looking (a vector of length 1). Fusion.Player:eyePos() works too.
Vectors¶
Vector(x, y, z) makes a vector (Luau's built-in vector.create also works). Vectors add, subtract and multiply: eye + aim * 3. Read parts with v.x, v.y, v.z.
Fusion.version¶
The Lua API version, e.g. "0.1.0".
Rules (the sandbox)¶
- No files, network or programs:
io,os.execute,requireandloadstringdon't exist.os.time,os.clockandos.datedo. - Each addon runs on its own. Its global variables are its own, and if it breaks, the others keep running.
- Time limit: loading
init.luamay take 250 ms, and handling one event 50 ms. An addon that takes longer (usually an endless loop) is stopped with a message. Fix it and save to load it again. - Memory limit: 64 MB per addon. Over that, it's stopped the same way.
- Spawn limit: 32 props per addon per frame.
- Request limit: 64 actions, steering, sleep/wake and HUD calls per addon per frame.
- Mistakes are explained: a call that can't work (an unknown game, an entity that can't be steered) writes why in the addon log instead of failing silently.
- Log limit: 100 lines per addon per frame, 2000 characters per line.
Why these rules: docs/decisions/0002-one-luau-vm-per-addon.md.