Voxel Game Maker

Docs Quick code

Examples

Ready-to-use recipes — doors and keys, enemies, bosses, waves, shops, timers, elevators, lasers, loot tables and more.

Each recipe says where the code goes. "On the object's script" means: make a script (Scripts tab), drop it on the object, start it with the Event named, and put the code in a Code node.

#A door that needs a key

On the door's script, Event: Player presses E.

if has("* key") > 0 then
  take("Gold key")
  play("door")
  self.move(0, 3, 0, 1)       -- slide up out of the way
  self.opened = true
else
  self.say("It's locked.")
end

#A pressure plate that opens a gate

On the plate's script, Event: Player enters the level.

on("touched", function()
  if not self.pressed then
    self.pressed = true
    self.tween("y", self.y - 0.1, 0.2)
    object("gate").move(0, 3, 0, 1.5)
  end
end)

#A coin counter on screen

A level script, Event: Player enters the level.

hud_text("coins", "Coins: {Coin}", "top_right")   -- {Coin} shows how many the player has

#A guard that chases you when it sees you

On the guard's script, Event: Player enters the level.

local alert = false
while self.exists do
  local sees = self.distance(player) < 8 and line_of_sight(self, player)
  if sees and not alert then
    alert = true
    self.say("Halt!")
    self.follow(player, 3.5, 1.2)
  elseif not sees and alert then
    alert = false
    self.stop()
    self.go_home(2, false)
  end
  if alert and self.distance(player) < 1.5 then
    hurt(2)
    wait(0.8)                 -- a hit at most every 0.8 s
  end
  wait(0.1)
end

#A boss with phases

On the boss's script (it has Health), Event: Player enters the level.

local phase = 1
show_hud("Boss bar")

on("hurt", function()
  hud_bar("Boss", self.hp, self.max_hp, "top_center")
  if phase == 1 and self.hp < self.max_hp / 2 then
    phase = 2
    send("boss_phase", {phase = 2})
    self.tint("red")
    self.say("Now I'm angry!")
  end
end)

on("destroyed", function()
  banner("Victory!")
  hud_clear("Boss")
  set_switch("boss_beaten")
end)

-- Attack pattern: phase 2 attacks twice as often.
while self.exists do
  wait(phase == 1 and 3 or 1.5)
  if self.exists then
    self.play("attack")
    if self.distance(player) < 3 then hurt(10) end
  end
end

The arena's level script:

on_message("boss_phase", function(d)
  shake(0.25, 0.6)
  object("arena_bars").hide()
  music_fade("boss_fast", 1)
end)

#Waves of enemies

A level script, Event: Player enters the level.

local waves = [
  {enemy = "Slime", count = 4, gap = 1.0},
  {enemy = "Goblin", count = 3, gap = 1.5},
  {enemy = "Troll", count = 1, gap = 0},
]
for i, w in waves do
  banner("Wave " .. (i + 1))
  for n = 1, w.count do
    spawn(w.enemy, 8, 0, 8)
    wait(w.gap)
  end
  wait_until(function() return #objects("enemy") == 0 end)
end
banner("All waves cleared!")
win()

#A shop with a question

On the shopkeeper's script, Event: Player presses E.

local pick = choice("What will it be?", ["Potion (5)", "Sword (20)", "Nothing"])
local prices = {["Potion (5)"] = 5, ["Sword (20)"] = 20}
local items = {["Potion (5)"] = "Potion", ["Sword (20)"] = "Sword"}
if prices[pick] ~= nil then
  if money_count() >= prices[pick] then
    take("Coin", prices[pick])
    give(items[pick])
    self.say("Pleasure doing business!")
  else
    self.say("Come back with more coins.")
  end
end

#A countdown

A level script, Event: Player enters the level.

start_timer("escape", 60, "lose")             -- 60 seconds, then the player loses
wait_until(function() return in_zone("Exit") end)
stop_timer("escape")
banner("Escaped with " .. round(time_left("escape")) .. " s to spare!")
win()

#A bouncy elevator

On the platform's script, Event: Every few seconds (6 s).

self.tween("y", 3, 2, "smooth")
wait(1.5)
self.tween("y", 0, 1.2, "bounce")

#A laser that hurts what it hits

On the turret's script, Event: Every few seconds (1 s).

local dir = vec(0, 0, -1)                      -- the way it faces
local hit = raycast(self, dir, 15)
if hit ~= nil then
  particles("sparks")
  if hit.object ~= nil and hit.object.has_tag("enemy") then
    hit.object.hurt(5)
  elseif hit.object ~= nil and hit.object.name == "player" then
    hurt(5)
  end
end

#Random loot from a data table

A Data table "Loot" with columns name, weight: Coin 70, Gem 25, Crown 5. On a chest's script, Event: Player presses E.

if self.opened then
  self.say("Empty.")
  return
end
local total = 0
for name, row in data("Loot") do total += row.weight end
local roll = random(0, total)
for name, row in data("Loot") do
  roll -= row.weight
  if roll <= 0 then
    give(name)
    popup("You found: " .. name)
    break
  end
end
self.opened = true
self.play("open")

#Lamps that light up at night

A level script, Event: Player enters the level.

every(5, function()
  for lamp in objects("lamp") do
    lamp.hidden = not is_night()
  end
end)

#A helper library

A script named "Utils" (not on any object):

function count_tagged(tag)
  return #objects(tag)
end

function say_all(tag, text)
  for o in objects(tag) do o.say(text) end
end

Anywhere else:

local u = require("Utils")
if u.count_tagged("villager") > 3 then
  u.say_all("villager", "What a crowd!")
end

#Debugging an enemy

while self.exists do
  watch(self.name .. " hp", self.hp)
  watch(self.name .. " state", self.mood or "calm")
  if self.hp ~= nil and self.hp < 0 then breakpoint() end
  wait(0.2)
end