6. Mistakes everyone makes

A short list of what goes wrong in the first weeks, with the error each one produces. Come back to this page when a script does not do what you meant.

A dot where a colon should be

-- file: example
self.Entity.Destroy()     -- wrong
self.Entity:Destroy()     -- right

Calling a function on something needs a colon. With a dot the function is not told what to act on. The Console says something like bad argument #1 or attempt to index a nil value.

The other way round is a mistake too. Input:IsDown("Jump") is wrong, because Input is a plain table of functions and not an object. The API reference shows which is which: entity:Destroy() is written with a colon there, and Input.IsDown(action) with a dot.

Forgetting local

speed = 5          -- global, shared with every script
local speed = 5    -- what you meant

No error, and that is the problem. Two scripts that use the same global name change each other's values.

A typo in a name

local player = { health = 100 }
print(player.helth)    --> nil

A name that does not exist is nil, not an error. The error comes later and elsewhere, when the nil is used: attempt to perform arithmetic on a nil value (field 'helth'). Read the name in the message. It is usually the typo.

Setting up your code editor with the engine's definitions catches most of these as you type. See Code editor setup.

Counting from 0

local items = { "a", "b", "c" }
print(items[0])    --> nil
print(items[1])    --> a

Lists start at 1 and end at #items.

Using = for a comparison

-- file: example
if lives = 0 then     -- an error: 'then' expected near '='
if lives == 0 then    -- right

One = assigns. Two compare. Not equal is ~=, and != is an error.

Joining with +

print("Score: " .. 10)    --> Score: 10

+ is only for numbers. "Score: " + 10 fails with attempt to perform arithmetic on a string value.

Joining nil fails too: attempt to concatenate a nil value. Wrap a value that might be nil in tostring(...) when printing it to find out what it is.

Expecting 0 and "" to be false

local coins = 0
if coins then
	print("This prints: 0 counts as true")
end

Only false and nil are false. Write the comparison you mean: if coins > 0 then.

Changing a copy

-- file: example
self.Entity.Position.y = 5        -- changes a copy, moves nothing

local position = self.Entity.Position
position.y = 5
self.Entity.Position = position   -- right

Reading a Vec3 from an entity or a component gives a copy. For one axis, self.Entity:Move(0, 1, 0) is shorter.

Forgetting deltaTime

-- file: example
self.Entity:Move(0, 0, -5)               -- 5 units every frame
self.Entity:Move(0, 0, -5 * deltaTime)   -- 5 units every second

OnUpdate runs once per frame, and frames are not the same length on every computer. Multiply anything that happens over time by deltaTime, the seconds since the last frame.

Waiting in a loop

-- file: example
function Door:OnUpdate()
	while not self.Open do    -- freezes the game
	end
end

Nothing else runs until OnUpdate returns. Check once per frame with if, or use Timer.After and Timer.Wait.

Using something that is gone

An entity that was destroyed cannot be used. The Console says so, with the line. When an entity might be gone, ask first:

-- file: example
if self.Target and self.Target:IsValid() then
	self.Entity:LookAt(self.Target)
end

Forgetting return at the end of the file

A script file must end with return and its table. Without it the engine has nothing to run, and the script does nothing.

Reading an error message

Assets/Scripts/Player.lua:27: attempt to index a nil value (field 'Camera')
  • Assets/Scripts/Player.lua:27 is the file and the line. Double-click the message in the Console to open it.
  • attempt to index a nil value means a dot or colon was used on something that is nil.
  • (field 'Camera') names what was nil: here self.Camera, a field nobody filled in in the inspector.

Most errors are a nil where a value was expected. The message says which name. Work back from there to why it has no value.

Coming from another language

You writeIn Lua
!=~=
&&, ||, !and, or, not
x += 1, x++x = x + 1
// comment-- comment
/* ... */--[[ ... ]]
null, None, undefinednil
{ ... } for a blockthen ... end, do ... end
else if, elifelseif
"a" + "b""a" .. "b"
array[0]list[1]
array.length, len(a)#list
a ? b : ca and b or c
thisself, and call with a colon
for (i = 0; i < n; i++)for i = 1, n do
for x in listfor _, x in ipairs(list) do
try / catchpcall(function() ... end)
Variables are local by defaultVariables are global unless you write local
Integer and float are separate typesBoth are number. 7 / 2 is 3.5 and 7 // 2 is 3

One trap in the table: a and b or c gives c when b is false or nil. Use a full if in that case.

Where to go from here

  • Scripting overview: everything the engine gives a script.
  • Recipes: short answers to common needs.
  • Your first game: the tutorial, if you have not done it.
  • The API reference, when you want to know exactly what a function takes.
  • Programming in Lua by Roberto Ierusalimschy, the language's author, for the parts of Lua this course left out: metatables, coroutines and the string library.