Appearance
Effects and atmosphere
Particles, light and weather — the things that make a place feel like somewhere.
A campfire
luau
--!strict
local self = require("self")
self:particles({ preset = "fire" })
self:set({
light = true,
light_color = "#ff9a3c",
light_radius = 9,
light_intensity = 2.2,
})Two lines and it is a fire that lights the ground around it.
The emitter belongs to the object: it follows it and dies with it. Nothing is sent per particle, so it costs the same for one watcher or forty.
Making fire look like fire
luau
self:particles({
preset = "fire",
rate = 60,
life = { 0.7, 1.4 },
speed = { 0.7, 1.6 },
size = { 0.16, 0.02 },
glow = 3.5,
softness = 0.3,
})Fire that does not look like fire is almost always fire with glow = 1.glow is a multiple of how bright white is where the particle sits: at 1 it is merely lit by the world; above 1 it gives off light and blooms.
size is birth-then-death, not a random range: { 0.16, 0.02 } shrinks as it rises. It is the one pair that does not mean min and max.
softness is what stops smoke from cutting the ground in a straight line.
A burst
There is no burst verb. Turn the emitter on and off again:
luau
local function puff()
self:particles({ preset = "dust", rate = 200 })
self:after(0.15, function()
self:particles()
end)
endA fire that burns out
luau
--!strict
local self = require("self")
local world = require("world")
local BURN = 300
local lit_at = tonumber(self.store:get("lit") or "0") or 0
local function light_it()
lit_at = world:clock()
self.store:set("lit", tostring(lit_at))
self:particles({ preset = "fire" })
self:set({ light = true, light_radius = 9, light_intensity = 2.2 })
end
local function put_out()
lit_at = 0
self.store:set("lit", "0")
self:particles()
self:set({ light = false })
end
self:on_touch(function()
if lit_at > 0 then put_out() else light_it() end
end)
self:on_timer(10, function()
if lit_at > 0 and world:clock() - lit_at >= BURN then
put_out()
end
end)The timestamp is what makes this survive a restart: a fire lit ten minutes before the region went down is out when it comes back, because the clock kept running.
A lamp that comes on at dusk
luau
--!strict
local self = require("self")
local world = require("world")
local DAY = 600
local lit = false
self:on_timer(10, function()
local night = (world:clock() % DAY) > DAY * 0.5
if night == lit then return end -- only act when it CHANGES
lit = night
self:set({
light = lit,
emissive = lit and 2.5 or 0,
color = lit and "#ffe9b0" or "#3a3a3a",
})
end)Writing the same properties every ten seconds would spend the region's budget for nothing. Act on the change.
emissive and light are different. emissive makes the object look bright; light makes it illuminate what is around it. A glowing sign wants the first; a lamp wants both.
Weather
luau
local world = require("world")
world:set_weather({ rain = 0.8, fade = 6 })
world:set_weather({ rain = 0 }) -- back to sunshineEvery field is optional and every omitted one keeps its value.
| Key | Range | |
|---|---|---|
rain | 0 – 1 | |
snow | 0 – 1 | independent of rain — both is sleet |
lightning | 0 – 1 | a frequency, not a trigger |
wind | 0 – 40 | m/s; also pushes dynamic objects |
wind_dir | degrees | |
fade | seconds | belongs to the change, never carried over |
sound | table | your own rain and thunder — see below |
Snow is a visibility dial: 0.4 is about 190 metres, 0.8 about 23, and 1.0 is a whiteout with no horizon. What closes a street down is the air, not the number of flakes.
Lightning is a frequency. To fire one at a dramatic moment, raise it and lower it again — you get a strike within a second or two, not on an exact frame.
Weather belongs to the place, not to your script. It survives the script being re-saved, the object being deleted and the region restarting. A world that should always open in the same weather says so from world:on_start:
luau
world:on_start(function()
world:set_weather({ rain = 0, snow = 0, wind = 2 })
end)A storm that builds
luau
--!strict
local world = require("world")
local STEPS = {
{ rain = 0.2, wind = 4, lightning = 0, fade = 20 },
{ rain = 0.6, wind = 12, lightning = 0.3, fade = 30 },
{ rain = 1.0, wind = 22, lightning = 0.8, fade = 25 },
{ rain = 0.3, wind = 8, lightning = 0.1, fade = 40 },
{ rain = 0, wind = 2, lightning = 0, fade = 60 },
}
local i = 0
local self = require("self")
self:on_timer(120, function()
i = (i % #STEPS) + 1
world:set_weather(STEPS[i])
end)fade does the work — each change eases in over tens of seconds, so nobody sees the sky snap.
A storm with your own sound
The engine synthesises the rain and the thunder, and it is good at it — noise has no loop point, costs no download, and bends with how hard it is falling and whether you are under a roof. What it cannot be is your storm.
luau
world:set_weather({
rain = 0.9,
lightning = 0.7,
sound = {
rain = "world/sons/chuva",
wind = "world/sons/vento",
thunder = {
{ sound = "world/sons/trovao-longe", strength = 0.15 },
{ sound = "world/sons/trovao-medio", strength = 0.5 },
{ sound = "world/sons/trovao-perto", strength = 0.95 },
},
},
})Thunder is chosen by how hard the bolt hit — 0 on the horizon, 1 overhead — and it is the same falloff the flash was drawn with, so the clap matches the bolt the player just saw. Up to six recordings. There is no gap to fall into: the engine plays whichever is nearest, so one entry answers for the whole storm and no strike is ever silent.
If you have three files and no opinion about which is the far one, a bare list spreads them across the storm in the order given:
luau
sound = { thunder = { "longe", "medio", "perto" } }Everything is per layer. Naming a rain leaves the synthesised thunder alone. Omitting a key keeps its recording; false takes that one away, and sound = false goes back to the synthesised storm entirely.
You supply the sound; the engine keeps the mix. How loud it is follows the weather, and a roof still muffles it — only the player's own machine knows they just stepped under an awning.
Publish weather sounds on the world library (world/…). A name out of an object's Contents works, but the world comes back up before any object does — so after a restart the first storm would be the synthesised one until that script runs again.
Wind that moves things
luau
self:set({ dynamic = true, mass = 0.4, drag = 3 })Wind pushes anything dynamic, in proportion to its drag and inversely to its mass. drag = 0 opts out entirely.
Weight alone does not decide who blows away — the force carries the object's area, which is why a heavy ball ignores a gale that carries a light sheet across a field.
It is self-limiting: an object accelerates until it moves with the air and no further, and a settled object the air cannot slide is left asleep, so a courtyard of crates costs nothing.
See also
- Particles — every field
- world:set_weather
- Object properties
