2. Decisions and loops

So far every line ran once, top to bottom. This page is about running some lines only sometimes, and some lines many times.

if

local health = 20

if health < 30 then
	print("Low health!")
end

The lines between then and end run only when the condition is true. Change health to 80 and nothing prints.

Add else for what happens otherwise, and elseif for more cases:

local score = 72

if score >= 90 then
	print("Gold")
elseif score >= 60 then
	print("Silver")
else
	print("Bronze")
end
--> Silver

Lua checks the cases from the top and takes the first one that is true. Note the spelling: elseif is one word.

Indent the lines inside with a tab. Lua does not need it, but you will.

and, or, not

Join conditions with words:

local hasKey = true
local doorLocked = true

if hasKey and doorLocked then
	print("You unlock the door")
end

if not hasKey then
	print("You need a key")
end
  • a and b is true when both are.
  • a or b is true when at least one is.
  • not a turns true into false and false into true.

What counts as true

In a condition, only false and nil count as false. Everything else counts as true, including the number 0 and the empty string "". That differs from many languages.

if 0 then
	print("0 counts as true in Lua")
end

This makes "is there anything here?" a short question:

local target = nil

if target then
	print("Attacking")
else
	print("Nothing to attack")
end
--> Nothing to attack

Picking a value with or and and

or gives its first value if that counts as true, and its second otherwise. That makes it a way to give a default:

local chosenName = nil
local name = chosenName or "Player"
print(name)    --> Player

a and b or c picks b when a is true and c when it is not:

local running = true
local speed = running and 8 or 4.5
print(speed)    --> 8

You will see both in game scripts all the time.

while

while repeats for as long as its condition is true.

local countdown = 3

while countdown > 0 do
	print(countdown)
	countdown = countdown - 1
end
print("Go!")
--> 3
--> 2
--> 1
--> Go!

Something inside the loop has to change what the condition looks at. If countdown never went down, the loop would never end, and the game would freeze.

for

When you know how many times, use for. It counts for you:

for i = 1, 5 do
	print("Wave " .. i)
end
--> Wave 1 ... Wave 5

i starts at 1, goes up by 1 each time round, and the loop stops after 5. A third number sets the step:

for i = 10, 0, -5 do
	print(i)
end
--> 10
--> 5
--> 0

The loop's variable exists only inside the loop.

Leaving early

break leaves the loop it is in:

for i = 1, 100 do
	if i * i > 50 then
		print("First square over 50 is " .. i * i)
		break
	end
end
--> First square over 50 is 64

Loops inside loops

for row = 1, 3 do
	local line = ""
	for column = 1, 4 do
		line = line .. "#"
	end
	print(line)
end
--> ####
--> ####
--> ####

That is how a grid of enemies or tiles is laid out: one loop for rows, one for columns.

In a game

A game script almost never needs while. The engine already calls OnUpdate every frame, and that is the game's loop. A script decides what to do this frame and returns:

-- file: example
function Enemy:OnUpdate(deltaTime)
	local distance = self.Entity.Position:Distance(self.Target.Position)

	if distance < 2 then
		self:Attack()
	elseif distance < 15 then
		self:Chase(deltaTime)
	else
		self:Patrol(deltaTime)
	end
end

A while loop that waits for something inside OnUpdate would freeze the game, because nothing else runs until OnUpdate returns. To wait, use a timer. See Timers and coroutines.

Try it

  1. Print the numbers from 1 to 20, but print Fizz in place of every number that divides by 3. n % 3 == 0 is true when it does.
  2. A chest holds 137 gold. Each trip you can carry 25. Use while to count the trips it takes to empty it.
  3. Print a triangle of #, one in the first row and five in the last.

Next: functions.