Voxel Game Maker

Docs Quick code

The language

Values, variables and their scopes, maths, text, conditions and loops.

#Lines and comments

  • Write one statement per line (several on a line work too). A ; at the end of a line is allowed and ignored.
  • Comments: -- to the end of the line, --[[ … ]] across lines, and a line starting with # is a comment too.
  • Capitals matter in names: coins and Coins are different variables.

#Values

Kind Looks like type(x)
Number 3, 2.5, -1, 1e3, 0x1F "number"
Text "hello" or 'hello' (escapes: \n, \t, \") "text"
True / false true, false "bool"
Nothing nil (null works too) "nil"
List [1, 2, 3] or {1, 2, 3} "list"
Table {hp = 10, name = "Rat"} "table"
Vector vec(1, 0, 2) "vector"
Function function(x) return x * 2 end "function"
Object self, player, object("door") "object"

Truth: false, nil and 0 count as false; everything else — text, lists, tables, objects that exist — counts as true. Whole numbers stay whole (3, not 3.0).

#Variables

coins = coins + 1      -- a game variable: every script sees it, saved with the game
score += 10            -- also -=  *=  /=  %=  ..=
local tries = 3        -- local: only this code, only this run
self.opened = true     -- this object's own value (see Objects)
Scope Where it lives Lasts
Game Explorer ▸ Player ▸ Variables The whole playthrough, one value everywhere (saved).
World Explorer ▸ a world ▸ World variables While the player is in that world's levels; another world can have its own with the same name.
Local local x = … The block it's in (a function, a loop, an if), for this run.
Object obj.name = …, or Properties ▸ Values for code That object, while the game runs (saved). See Objects.

A variable code sets without it being listed in the Explorer is a game variable that starts at 0 (it shows there as (made by code)). Game variables are saved when they hold numbers, text, true / false, or lists and tables of those; functions, objects and vectors in game variables aren't saved.

Locals are block-scoped: a local made inside a loop or an if disappears at its end, and doesn't touch a variable of the same name outside. Use locals for anything temporary, so two scripts never fight over the same name.

local x = 1
for i = 1, 3 do
  local x = i * 10   -- a different x, only inside the loop
end
popup(x)              -- 1

Several at once: a, b = 1, 2 — and a, b = b, a swaps. Several names from one list take its items: local ok, msg = pcall(f), x, y = [3, 4].

Names: letters, numbers and _, starting with a letter (door_open, wave2). They can't be language words (if, end, function…) or the older game functions (popup, give…). A variable may share its name with a newer built-in (time, keys, data…): calling time() still works.

#Maths

Operator Means Example
+ - * add, subtract, multiply 2 + 3 * 4 → 14
/ divide (can make a fraction) 7 / 2 → 3.5
// divide, rounded down 7 // 2 → 3
% remainder (always 0 or positive) 7 % 3 → 1, -1 % 3 → 2
^ power 2 ^ 10 → 1024
-x negative -2 ^ 2 → -4 (power first)

Brackets ( ) group. Maths functions (abs, min, round, clamp, sin in degrees…) are in the reference. Vectors add, subtract and multiply too: vec(1, 0, 0) * 3 + vec(0, 1, 0).

#Comparing and logic

== equal, ~= not equal (!= works too), < > <= >=. Numbers compare as numbers, texts alphabetically. Values of different kinds are simply not equal ("3" == 3 is false).

and, or, not (also &&, ||, !). Like Lua, and / or give one of their values, which makes handy defaults:

local name = self.nickname or "stranger"   -- "stranger" when nickname is nil
local hp = (self.hp or 0) + 1

#Text

name = "Mira"
popup("Hello, " .. name .. "!")     -- .. joins text — and numbers, true / false, nil
popup("Coins: " .. coins)
local line = format("{0} has {1} hp", name, 12)   -- "Mira has 12 hp"
local shout = name:upper()                          -- "MIRA"

+ joins two texts, but not text and a number — .. handles everything. Text functions (upper, lower, sub, find, split, replace, trim, format…) work both ways: upper(s) or s:upper(). #s is its length, s[0] its first letter.

#Conditions

if has("Key") > 0 then popup("Unlocked") end     -- on one line (end is optional here)

if coins >= 10 then
  popup("Rich!")
elseif coins > 0 then
  popup("Some coins")
else
  popup("Broke")
end

Every block if needs a closing end; Check says which one is missing it. To compare use == — a single = puts a value in (Check points that out).

#Loops

while n < 5 do n += 1 end                 -- while a condition holds

for i = 1, 10 do … end                    -- 1, 2, … 10 (both ends included)
for i = 10, 1, -1 do … end                -- count down with a step of -1
for i = 0, 100, 25 do … end               -- 0, 25, 50, 75, 100

for o in objects("enemy") do o.hide() end -- each item of a list
for i, v in ["a", "b"] do … end           -- index (0, 1, …) and item
for key, value in {hp = 3, mp = 1} do … end
for k in {hp = 3} do … end                -- a table with one name: its keys
for i in 3 do … end                       -- a number counts 1, 2, 3

repeat                                    -- runs at least once
  tries -= 1
until tries <= 0 or has("Key") > 0
  • break leaves the loop; continue skips to its next round.
  • A loop with one statement can sit on one line: for i = 1, 3 do give("Coin") end.

#Loops can't freeze the game

A loop in a running game that goes round 500 times without waiting pauses for one frame by itself, as if it had wait(0), and carries on next frame. So while true do … end is fine:

while true do
  if key_pressed("E") then popup("E!") end
end

(Adding your own wait(0) or wait(0.1) is still a little cheaper.) Code in a dialogue runs all at once and can't pause, so an endless loop there is stopped after 10,000 rounds, with a message in the Output panel. Check also warns about loops that can never stop or never run.