The How to Make an RPG book uses Lua. Lua is a popular programming language that’s used widely in the game industry. It’s often embedded in a host program. For games the host program is the game engine, usually written in C++, then gameplay is controlled by Lua scripts.
Different languages use different paradigms to solve problems. A programming paradigm is the particular philosophy a language employs to solve a problem. Certain problems are easier when using a particular paradigm. The vast majority of games use object orientated programming aka OOP. C++ is an object orientated language. Lua isn’t. Lua is a multi paradigm language. This means if we want to make Lua work a little like an OOP language we need to write our own framework.
This article is for readers pretty new to Lua. If you’re a salty veteran then you might want something more concise.
How to Make Apple Pie
To make games we don’t need (or want!) an entire Lua OOP system. We only want to be able to define a class with certain properties and create instances of that class.
Games are full of things with shared properties; monsters, items, levels, particle systems, menus etc. These are natural candidates for classes but Lua doesn’t have a class system and therefore we must write one.
Tables
Lua has exactly one data structure; the table. It’s a dictionary which works like a luggage claim.
Let’s say you’re flying internationally and have a layover in Singapore. You want to explore the city but you have a load of bags and they’re heavy.
You don’t want to drag them around, so what do you do?
You take the bags to the luggage office, give them to a man and get a claim-ticket back. You are then bag-free and can enjoy some hairy crab and go see Merlion. Later, you give the man your claim-ticket and he returns your bags.
Tables work like this. There’s a key, the ticket, and a value, your bags. Putting things in the table requires a key as a reference for the value.
key = "ticket"
value = "bag"
store = {}
store[key] = value
print(store[key]) -- >"bag"
This can also be written inline.
store =
{
["ticket"] = "bag"
}
print(store["ticket"]) -- >"bag"
The example here uses strings for the key and value but any type can be used; numbers, booleans, functions, even other tables.
To make the OOP sytem we’ll use tables. Classes will be tables. Objects instances will be tables. In Lua all structures are tables.
Monsters
Like so many games our game has monsters. A monster can be represented by a table.
monster =
{
name = "orc",
health = 10,
attack = 3
}
That works. Now let’s say we want ten monsters; we’d have to write them out by hand! That sucks!
monster_1 = { name = "orc", health = 10, attack = 3}
monster_2 = { name = "orc", health = 10, attack = 3}
monster_3 = { name = "orc", health = 10, attack = 3}
...
monster_10 = { name = "orc", health = 10, attack = 3}
Everytime we need an orc we have to type out this long table definition string. It’s pretty easy to make a typo. How could we make it easier?
function CreateMonster()
local this =
{
name = "orc",
health = 10,
attack = 3
}
return this
end
monster_1 = CreateMonster()
That’s cool. We’ve wrapped up the table creation in a function. No more typos! Consistent orcs. The world is safe.
The CreateMonster
function represents a simple way to do classes in Lua. But we can do better!
The Self
Before we dive further into building the class system. Let’s talk about the self
keyword. We’ll start with a code snippet:
local t = {} -- an empty table
t.sayHello = function()
print("Hello")
end
t.sayHello() -- > "Hello"
Here we create an empty table. Then, using the key “sayHello”, we add a function to the table that prints “Hello”. This is standard Lua code. Let’s add a name
key to the table with the value "Bob"
.
local t = { name = "Bob" }
t.sayHello = function(parent)
print("Hello " .. parent.name)
end
t.sayHello(t) -- > "Hello Bob"
So far so good? Because this where the magic is going to happen. It’s quite common for a function to want to access it’s parent table’s data.
Lua has a shortcut for this. We use the ‘.’ syntax when calling the function but there is another syntax. You can use ‘:’. If you use a colon Lua passes in the parent table as the first argument automatically. Here’s an example.
local t = { name = "Bob" }
-- Dot syntax
t.sayHello = function(parent)
print("Hello " .. parent.name)
end
t.sayHello(t) -- > "Hello Bob"
-- Colon syntax
t:sayHello() -- > "Hello Bob" !!! :o
Hopefully this example makes the action of the ‘:’ operator clear. Use the ‘:’ syntax and the parent table is passed in as the first argument. Pretty neat. Thanks Lua.
One Last Thing
You thought that was good? Get ready for some more shorthand.
t = { name = "Bob" }
function t:sayHello() -- note the 't:' part
print("Hello" .. self.name)
end
t:sayHello() -- "Hello Bob"
In the function definition the :
character has a different meaning to than when call a function . It means:
- The function being defined should be added to the parent table
- The function should get a secret first parameter called
self
Lua adds the self
parameter automatically when it reads your code.
The code is equivalent to this code snippet:
t =
{
name = "Bob",
sayHello = function(self) print("Hello" .. self.name) end
}
t:sayHello() -- "Hello Bob"
The colon shorthand makes defining functions less verbose. We don’t need to explicitly add parent
or self
as the first argument and when defining the function we don’t need the =
sign. (Also as a bonus the end
keyword now nicely lines up with the function
keyword.)
Back to Class
Ok let’s use this new shorthand to make our Monster
class a little nicer to use.
Monster = {}
function Monster:Create()
local this =
{
name = "orc",
health = 10,
attack =3
}
return this
end
monster_1 = Monster:Create()
Spiffy.
It’s often convenient for objects to contain functions. A function embedded in an object is called a method. Orcs, as everyone knows, have a distinctive war-cry, so lt’s add a WarCry
method.
Monster = {}
function Monster:Create()
local this =
{
name = "orc",
health = 10,
attack = 3
}
function this:WarCry()
print(self.name .. ": GRAAAHH!!!")
end
return this
end
monster_1 = Monster:Create()
monster_1:WarCry() -- > "orc: GRAAAHH!!!"
This is nice but declaring the WarCry
function inside the Create
function seems messy. And there’s another issue. Each time a monster is created the monster table contains four things name
, health
, attack
and WarCry
. In an OOP system WarCry
should belong to the class. Otherwise we’re wasting memory; every WarCry
function will be the same but we’re creating and adding a unique one to each class!
There must be someway out of this pickle…
Metatables
Let’s have a break and explore one of the dustier corners of the Lua ecosystem. The metatable
.
Metatables are tables you can attach to other tables to alter how they work. For instance, imagine a table that you can index with any number and it returns double that number. With that we know so far that’s impossible! But with metatables we can make it work. Here’s some code:
meta =
{
__index = function(table, key)
return key * 2
end
}
doubleTable = {}
setmetatable(doubleTable, meta)
print(doubleTable[2]) -- 4
print(doubleTable[16]) -- 32
In this example we’ve made a metatable that changes what a table does when it’s given a key. When a key is passed into the table we try to multiple it by two and return it.
That’s neat!
The __index
entry is a function for doubleTable
but it can also be assigned a table. You can see now that works here:
parent = { name = "default name", title = "Mr" }
meta = { __index = parent }
child = { name = "Bob" }
setmetatable(child, meta)
print(child.title, child.name) -- "Mr Bob"
child.name = nil
print(child.title, child.name) -- "Mr default name"
If the __index
field of the metatable is set to a table it has the meaning; check the local table, if it’s nil then check the table the __index
field points to.
The metatable __index
can point to itself, so we can make meta
table act as the parent as well. This has the effect only needing two tables rather than three to get this working.
(This does prevent you from chaining table lookups but we won’t be doing that!)
parent = { name = "default name", title = "Mr" }
parent.__index = parent
child = { name = "Bob" }
setmetatable(child, meta)
Creating an Orc
Let’s level-up our class with metatables.
Monster = {}
Monster.__index = Monster
function Monster:Create()
local this =
{
name = "orc",
health = 10,
attack = 3
}
setmetatable(this, Monster)
return this
end
function Monster:WarCry()
print(self.name .. ": GRAAAHH!!!")
end
monster_1 = Monster:Create()
monster_1:WarCry() -- > "orc: GRAAAHH!!!"
The this
table has it’s metatable set to Monster
. The Monster
__index
field points to the Monster
table itself. This means after checking the this
table and there’s a nil, Lua will check the Monster
table.
Note that in Create
function we set the metatable to the Monster
. But we call Create
with the :
syntax which means we can rewrite the setmetatable
line to
setmetatable(this, self)
Now we can create classes that have methods. Good stuff.
Static Fields
In many OOP systems you can have fields that are stored in the class and available to all the objects. We can do something similar in Lua by adding the fields directly to the class table. These are best used for constants you want accessible to all instances of the class.
SomeClass = { id = "some_class" }
SomeClass.__index = SomeClass
function SomeClass:Create()
local this = {}
setmetatable(this, SomeClass)
return this
end
c1 = SomeClass:Create()
c2 = SomeClass:Create()
print(c1.id) -- "some_class"
print(c2.id) -- "some_class"
Creating and Using Classes
I prefer to create a class per file. For the Monster
example I’d have one file called “Monster.lua” that would only contain code relating to the monster class.