Script cookbook
Working scripts for common gameplay. Paste one into an object’s script (or the Level Script), adjust the names, and press Play.
play_sound("sounds/...") expect a sounds folder next to your .blend. Use any WAV/MP3/OGG you like, or delete those lines.Coins & score
On each coin: Body Trigger, Visible In Game ticked. Name them Coin, Coin.001… (Blender does this when you duplicate).
function on_update(self, dt)
self:rotate(0, 0, 90 * dt)
end
function on_trigger_enter(self, other)
if other.is_player then
game.score = (game.score or 0) + 1
ui.set("score", "Coins: " .. game.score)
play_sound("sounds/coin.wav")
self:destroy()
end
end
Collect them all to open the gate
Level Script — count the coins when the level starts:
function on_start()
game.score = 0
game.total = #find_all("Coin")
ui.set("score", "Coins: 0 / " .. game.total)
end
Then in the coin script, after adding to the score:
ui.set("score", "Coins: " .. game.score .. " / " .. game.total)
if game.score == game.total then
ui.message("The gate is open!")
find("Gate"):call("open") -- see the Gate script below
end
Door on a hinge
Add an Empty at the hinge edge, parent the door mesh to it (select door, then Empty, Ctrl+P → Object), set the door to Kinematic, and put this script on the Empty. Pressing E on the door works because interaction reaches parents.
prompt = "Open door"
local open, angle = false, 0
function on_interact(self)
open = not open
prompt = open and "Close door" or "Open door"
end
function on_update(self, dt)
angle = angle + ((open and 100 or 0) - angle) * math.min(1, dt * 5) -- ease toward 100 or 0 degrees
self.rotation = vec3(0, 0, angle)
end
Pressure plate & gate
Gate (Kinematic, named Gate) — slides into the floor when something calls open:
local target
function on_start(self) target = self.position end
function open(self) -- other scripts call this with find("Gate"):call("open")
target = target - vec3(0, 0, 3)
play_sound("sounds/door.wav", 1, self.position)
end
function on_update(self, dt)
self.position = self.position:lerp(target, math.min(1, dt * 2))
end
Plate — a flat Trigger on the floor. Anything can press it: the player, or a crate you throw onto it.
local used = false
function on_trigger_enter(self, other)
if not used then
used = true
find("Gate"):call("open")
end
end
Hazard (lava, spikes, falling off the world)
A Trigger covering the dangerous area. For a “fell off the map” catch, make a huge flat trigger well below the level.
function on_trigger_enter(self, other)
if other.is_player then
ui.message("Ouch!", 1.5)
respawn()
end
end
Countdown timer
Level Script:
local left = 60
local over = false
function on_update(dt)
if over then return end
left = left - dt
ui.set("time", "Time: " .. math.ceil(left))
if left <= 0 then
over = true
ui.message("Time's up!", 3)
after(3, restart)
end
end
Spawner
Make a ball, set it to Dynamic, tick Template, and name it Ball. This Level Script drops one somewhere random every two seconds:
local function drop()
spawn("Ball", vec3(random(-5, 5), random(-5, 5), 10))
after(2, drop) -- schedule the next one
end
function on_start() drop() end
Shooting balls with F
Level Script, using the same Ball template:
function on_update(dt)
if input.pressed("F") then
local b = spawn("Ball", camera.position() + camera.forward() * 1.5)
b.velocity = camera.forward() * 20
after(10, function() if b.valid then b:destroy() end end) -- tidy up
end
end
Enemies with health
On each enemy, add Blender custom properties (Object Properties → Custom Properties): enemy = 1 and health = 3. Enemy script:
local hp
function on_start(self) hp = self.props.health or 3 end
function hurt(self, amount)
hp = hp - amount
if hp <= 0 then
play_sound("sounds/pop.wav", 1, self.position)
self:destroy()
end
end
Level Script — press F to zap whatever you’re aiming at:
function on_update(dt)
if input.pressed("F") then
local hit = raycast(camera.position(), camera.forward(), 50)
if hit and hit.entity and hit.entity.props.enemy then
hit.entity:call("hurt", 1)
end
end
end
Chasing guard
Tick AI Agent on the guard. It wanders until you get close, then chases you.
function on_update(self, dt)
if self:distance_to(player) < 7 then
self:move_to(player.position)
if self:distance_to(player) < 1.3 then
ui.message("Caught!", 1.5)
respawn()
end
elseif self.arrived or not self.moving then
self:move_to(random_point(self.position, 12))
end
end
Talking sign / NPC
prompt = "Read"
local lines = {
"Welcome, traveller.",
"The gate opens when every coin is found.",
"Watch out for the guard!",
}
local i = 0
function on_interact(self)
i = i % #lines + 1 -- 1, 2, 3, 1, 2, ...
ui.message(lines[i], 3)
prompt = (i == #lines) and "Read again" or "Next"
end
Light a fire
On a campfire: tick Particles, click the Fire preset and untick Emit On Start. Optionally give it a looping crackle Sound without Play On Start.
prompt = "Light fire"
local lit = false
function on_interact(self)
lit = not lit
self.emitting = lit
if lit then
self:emit(40) -- a burst to get it going
self:play_sound()
prompt = "Put out"
else
self:stop_sound()
prompt = "Light fire"
end
end
Cutscene: show the gate opening
Add an Empty named GateCam where the camera should be. Call this from anywhere (e.g. right after find("Gate"):call("open")):
local function show_gate()
camera.set(find("GateCam").position, find("Gate").position)
after(3, function() camera.release() end)
end
Exit to the next level
Each Blender scene becomes a level when you package the game. On a Trigger at the exit of scene Level1:
function on_trigger_enter(self, other)
if other.is_player then
save("score", game.score or 0) -- game.* is wiped on level change
load_level("Level2.glb")
end
end
In Level2’s Level Script:
function on_start()
game.score = load("score", 0)
ui.set("score", "Coins: " .. game.score)
end
.bf_cache folder next to your saved .blend. load_level in a test run finds the other level there only if you’ve pressed Play in that scene since last editing it. The packaged game always has every level.High score that survives quitting
-- call when the player finishes
local function finish()
local best = load("best", 0)
if game.score > best then
save("best", game.score)
ui.message("New high score: " .. game.score .. "!", 4)
else
ui.message("Score " .. game.score .. " (best " .. best .. ")", 4)
end
end
2D games
A side-scrolling hero (run, jump, animation, a following camera), coins, a camera that stops at the level’s ends and parallax backgrounds are on the 2D games page.
More examples
The Samples & docs download includes samples/test_level.blend with 15 working scripts — coins, a door, a lift, a gate, a ball spawner and an animated tentacle. Open it and click objects to read their scripts.