Two facts you already have. A function is a value (Activity 1.7). And a local is only visible inside the block that declared it (Activity 1.2). Put them together and something genuinely useful happens.
A function defined inside another one can still see the outer locals — even after the outer function has finished and returned.
local function makeGreeter(greeting) return function(name) return greeting .. ", " .. name endend
local hello = makeGreeter("Hello")local hey = makeGreeter("Hey")
print(hello("Ada"))print(hey("Bo"))Hello, AdaHey, BoBy the time hello("Ada") runs, makeGreeter finished long ago. Its greeting should have been gone. It is not, because the returned function still holds onto it. A function bundled together with the variables it captured is called a closure, and it is the most powerful thing in this unit.
Note also that each call made its own separate copy. hello and hey do not share a greeting.
This is where it gets interesting. The closure does not get a snapshot — it gets the variable itself, and it can write to it.
local function makeCounter() local count = 0 return function() count = count + 1 return count endend
local tick = makeCounter()print(tick())print(tick())print(tick())123count survives between calls and remembers where it got to. It is also genuinely private — there is no name you could write outside makeCounter that reaches it. No keyword made it private. It is private because of where it was declared.
Closures made in the same place share what they captured. This is how you build something with several operations and hidden state.
local function makeBay() local docked = 0
local function arrive() docked = docked + 1 return docked end
local function depart() docked = docked - 1 return docked end
return arrive, departend
local arrive, depart = makeBay()print(arrive())print(arrive())print(depart())121arrive and depart are looking at the same docked. If you have used objects in another language, notice what you just built without any object syntax: a thing with two methods and one private field. Activity 1.11 does this a different way, and it is worth remembering that this way existed first.
Activity 1.6 covered three loops. There is a fourth form, for x in f do, and it works by calling a function over and over until it returns nil. A closure is exactly what you need to write one.
local function countdown(n) return function() if n > 0 then n = n - 1 return n + 1 end endend
for t in countdown(3) do print("T-minus", t)endT-minus 3T-minus 2T-minus 1The loop calls the returned function, gets 3, then 2, then 1, then nil — and stopping on nil is the entire contract. This matters more than it looks: ipairs and pairs, which you will use constantly from Activity 1.9 on, are not special syntax. They are ordinary functions doing what you just did.
A game is full of things that need to remember something between frames without announcing it to the rest of the program: a spawn timer, a reload delay, a cooldown, a wave counter. Every one of those is a closure, and the alternative is a global, which Activity 1.2 already talked you out of.
Start a fresh main.lua. You are building a docking-bay ticket machine, then your own loop.
makeCounter(label) which returns a function.-- main.lua - Skynest bay counter
local function makeCounter(label) local count = 0 return function() count = count + 1 return label .. " #" .. count endend
local bayA = makeCounter("Bay A")
print(bayA())print(bayA())Bay A #1Bay A #2-- main.lua - Skynest bay counter
local function makeCounter(label) local count = 0 return function() count = count + 1 return label .. " #" .. count endend
local bayA = makeCounter("Bay A")local bayB = makeCounter("Bay B")
print(bayA())print(bayA())print(bayB())print(bayA())Bay A #1Bay A #2Bay B #1Bay A #3countdown(n) that returns a function counting down from n.nil, which is the signal the loop is waiting for.for t in countdown(3) do.-- main.lua - Skynest bay counter
local function makeCounter(label) local count = 0 return function() count = count + 1 return label .. " #" .. count endend
local bayA = makeCounter("Bay A")local bayB = makeCounter("Bay B")
print(bayA())print(bayA())print(bayB())print(bayA())
local function countdown(n) return function() if n > 0 then n = n - 1 return n + 1 end endend
for t in countdown(3) do print("T-minus", t)endmakeBay() returning two functions that share one private count, as in the third figure. Then add a third that only reports the count without changing it.makeCooldown(frames) that returns a function which reports false the first few times it is called and true after that. You will genuinely use this in Unit 2 — it is how a weapon stops firing every frame.countdown count up to a limit instead, then check whether you have accidentally written the numeric for loop from scratch.Your finished file should produce this.
Bay A #1Bay A #2Bay B #1Bay A #3T-minus 3T-minus 2T-minus 1Bay B is on #1 while Bay A is on #3. That is two independent private variables, created by the same three lines of code, with no class and no object anywhere. You have now met the hard half of Lua. The rest of the unit is one data structure.