Voxel Game Maker

Docs Quick code

Light, sky and weather

Give the player a lantern, change the sky, fog, weather and light from code, and put the level's own look back. With lots of ready-to-use recipes.

Four functions change how the world looks while the game runs. Everything fades smoothly, so a change feels like the world reacting, not a jump cut.

Function What it does
player_light(kind, reach, color) The player carries a light
sky(horizon, overhead, seconds) Fades the sky to new colors
look(settings, seconds) Fades several things at once: sky, fog, mist, weather, light
look_reset(seconds) Back to the level's own look (and its zones' looks)

#The player's light

player_light("torch")                   -- lantern, torch, bright, cool, magic
player_light("lantern", 10)             -- reaches 10 tiles
player_light("bright", 8, "#88ffcc")    -- your own color
player_light("none")                    -- put it away
Kind Looks like
"lantern" A warm glow with a gentle candle flicker (the default)
"torch" Orange firelight that flickers
"bright" A strong, steady white light
"cool" A steady pale blue light
"magic" A purple glow that slowly pulses
"none" No light

reach is how far it lights, in tiles: 6 if you leave it out, anything from 1 to 40. color is a #rrggbb code; "" keeps the kind's own color. Calling it again swaps the light.

#The sky

sky("#7a3a9a", "#12061e")               -- horizon, overhead: fades over 1.5 s
sky("#ff8040", "", 5)                   -- slow sunset; "" keeps the overhead color

The first color is the sky near the horizon, the second straight up. Leave one as "" to keep it as it is. The last number is how many seconds the fade takes (1.5 if you leave it out).

#The whole look at once

look({sky = "#203040", overhead = "#000010", fog = 0.04, fog_color = "#334455",
      mist = 0.5, weather = "snow", light = "#8899cc", light_amount = 0.6}, 2)
look_reset()                            -- back to the level's own look

look() takes a table. Put in only what you want to change; everything else stays.

Name What it changes Values
sky (or horizon) The sky near the horizon a color
overhead The sky straight up a color
fog How thick the fog is 0 (none) to about 0.1 (very thick)
fog_color The fog's color a color
mist Low mist over the ground 0 to 1
weather What falls or floats around the player "none", "rain", "snow", "fireflies", "dust", "leaves"
weather_amount How much of it 0 to 1
light (or light_color) The color of the world's light a color
light_amount (or brightness) How bright the world's light is about 0 (dark) to 1.5

Numbers and colors glide over the fade; the weather switches halfway through.

look_reset(seconds) drops every change your code made and fades back to the level's own look. If the player is standing in a zone with its own look, that comes back too.

#Recipes

Copy these into a Code node and change the numbers. Each one says where the code goes.

#Pick up a torch

On a torch on the wall (Event: Player presses E):

give("Torch")
player_light("torch", 8)
popup("The torch lights up the walls around you.")
remove_self()                            -- it's in your hand now

#A lantern that runs out

On a lantern you press E to take. It lights for 60 seconds, then fades out.

-- Event: Player presses E
player_light("lantern", 9)
popup("You light the lantern. It won't last long...")
start_timer("lantern", 60)
wait(60)
player_light("lantern", 4)               -- the flame gets small
popup("The lantern is flickering...")
wait(5)
player_light("none")
popup("The lantern goes out.")

#Walking into a cave

In a script with the Event Player enters a zone, set to the cave's zone:

look({light = "#202030", light_amount = 0.15, fog = 0.03, fog_color = "#05050a", weather = "none"}, 2)
if has("Torch") > 0 then
  player_light("torch", 8)
else
  popup("It's very dark in here. A torch would help.")
end

And a second script with Player leaves a zone, same zone:

look_reset(2)
player_light("none")

(A zone can also have its own look set in the editor without any code. Code is for when the look depends on something, like whether the player has a torch.)

#Sunset when the boss is beaten

On the boss (Event: It is destroyed):

banner("The Bone Queen falls")
sky("#ff8040", "#3a1850", 6)
look({light = "#ffb070", light_amount = 0.9, weather = "none"}, 6)

#A storm rolls in

popup("The wind picks up...")
look({sky = "#4a5260", overhead = "#20242c", light_amount = 0.5}, 8)
wait(8)
look({weather = "rain", weather_amount = 1, fog = 0.02, fog_color = "#3a4048"}, 4)

#Lightning

A bright flash, a dark sky snapping back, and a rumble. Run it during the storm:

every(rand(6, 12), function()
  screen_effect("flash", 0.2, 1, "white")
  sky("#d8e0ff", "", 0.05)               -- the sky lights up...
  wait(0.1)
  sky("#4a5260", "", 0.3)                -- ...and goes dark again
  wait(rand(1, 3))
  play("thunder")                        -- your own sound named "thunder"
  shake(0.25, 0.8)
end)

#The light follows the clock

Run this when the level starts (Event: Player enters the level). It checks once a minute and changes the player's light and the weather with the time of day:

every(60, function()
  if is_night() then
    player_light("lantern", 7)
    look({weather = "fireflies", weather_amount = 0.6}, 5)
  else
    player_light("none")
    look_reset(5)
  end
end)

#Jump to midnight

On a bed or a clock (Event: Player presses E). Needs the day & night cycle on:

local pick = choice("Sleep until...", ["Morning", "Midnight", "Stay awake"])
if pick == "Morning" then
  set_hour(7)
  look_reset(3)
elseif pick == "Midnight" then
  set_hour(0)
  sky("#0a0a30", "#000008", 3)
end

#A spooky room

On Player enters a zone, for a zone inside a haunted house (and look_reset(3) on Player leaves a zone):

look({light = "#6050a0", light_amount = 0.35, mist = 0.8, fog = 0.03, fog_color = "#1a1028"}, 3)
player_light("cool", 5)
play("creak")

#Under the water

On Player enters a zone, for a zone filling a lake:

look({sky = "#0a4060", overhead = "#022030", fog = 0.07, fog_color = "#0a5070",
      light = "#60c0e0", light_amount = 0.6, weather = "none"}, 0.5)
player_light("cool", 6)

#A magic potion that lets you see in the dark

On a glowing mushroom you press E to eat (Event: Player presses E):

remove_self()
player_light("magic", 14, "#c080ff")
look({light = "#a080ff", light_amount = 1.2}, 1)
popup("Everything glows!")
wait(20)
look_reset(3)
player_light("none")

#Cycle the light color with a key

A script with the Event Player presses a key / mouse button, set to T. The light's number is kept on the player (player.light_index), so it carries on from where it was:

local colors = ["#ffb060", "#80c0ff", "#80ffa0", "#ff80c0"]
player.light_index = ((player.light_index or -1) + 1) % #colors
player_light("bright", 8, colors[player.light_index])

#A sky picked from a Data table

Keep looks in a Data table called "Skies" with columns horizon, overhead and weather, one row per mood (calm, storm, night…). Any script can then switch mood by name:

function set_mood(name)
  local m = data("Skies")[name]
  if m == nil then
    warn("No sky called " .. name)
    return
  end
  sky(m.horizon, m.overhead, 3)
  look({weather = m.weather}, 3)
end

set_mood("storm")

Put set_mood in a library script and every level can use it with require.

#Wins and losses

-- Just before win(): a golden glow
look({sky = "#ffe080", overhead = "#ffb040", light = "#fff0c0", light_amount = 1.3}, 2)

-- When lives_left() reaches 0: everything goes gray and cold
look({sky = "#606060", overhead = "#303030", light = "#a0a0a0", light_amount = 0.4, weather = "none"}, 3)

#Darkness that follows a boss fight

On the boss arena's door (Event: Player presses E):

banner("Something stirs in the dark...")
look({light = "#401010", light_amount = 0.25, fog = 0.04, fog_color = "#200000"}, 3)
player_light("torch", 10)
on_message("boss beaten", function()
  look_reset(4)
  player_light("none")
end)

And in the boss's script, when it's destroyed: send("boss beaten").

#Tips

  • Changes from code stay until you change them again or call look_reset(). Going to another level starts that level with its own look.
  • 0 seconds changes things at once (good for the very start of a level).
  • If something should only change in one area, try a zone with its own look in the editor first. Use code when the change depends on what the player has done.
  • Every fade can run while other code carries on; there's no need to wait() for it unless you want the next step to start after it.