This post is a repost from the AGF Games blog.
Lua is not object oriented… But some people like it, and it has some advantages…
I decided to use a simple form of it on my project, it does not support multiple inheritance and many other features, it was made to be very, very simple, only to organize the project and advance some form of code reuse.
So, what features I tried to support?
First, very ease of use to create a class
Second, ease to create a instance.
Third, automatic constructor if you do not want to make one.
Fourth, easy “operator overloading” support.
Fifth, static variables (I like them).
Some people will tell me now that there are lots of already done implementations of this, that you only need to search the web… So why make a new one? Well, almost all of the ones I found were too complex for my taste, and mostly for Lua 5.0. Lua 5.1 has a new particular feature, that I really wanted to use to make my class system, a powerful feature.
What we need to use from Lua? Mostly metatables, and the metamethods __index and __call, the second one being available only in Lua 5.1 and onward.
So, how we do it? First, we need to create a new chunk (I suggest you do it creating a file… I will make later a tutorial on how to make a non-file chunk).
My file is named kidclass.lua (the “kid” comes from Kidoteca, the company where I am working now to make children games).
This file will follow my usual style to make “modules”, without using the module() function (that got removed in Lua 5.2 and rightfully so… module() was just plain stupid).
1 2 3 4 5 |
local kidclass = {}; --content will go here return kidclass; |
Important, remember to make kidclass (or whatever name you want) local, so you do not end polluting the global namespace (ie: _G) and also avoid some accidental name conflicts…
How you use files made in that style?
1 2 3 |
mymodulename = require "somemodule"; --this is the line that matters, the next two is example of how to use mymodulename mymodulename.someMethod(); local myvar = mymodulename.somevar; |
Note that doing just require without assigning its return value to something, would be useless, because back when we created the module, we made it local.
Now, how we achieve the first objective, the ease of use to create a class?
The most easy way would just do: myclass = {};
But that would prevent us from achieving the other features, so we need a function, to the sort that creating a class would be “myclass = kidclass.new();”
So we know a kidclass.new, that would at most basic return a {} (this means a empty table), the following could would be the result:
1 2 3 4 |
function kidclass.new() local class = {}; return class; end |
And how we make the second object work now?
I thought that the nicest way to create a instance, would follow the style of C++ (more or less) so “instance = Object()” except Lua does not have a way to create a constructor that is named like a Object… Oh, actually it does have something like that, since 5.1! This is the reason why our code will be different from what you usually see on internet. We will use the might __call.
So, what __call do? __call is a metamethod (a method from a metatable) that is called when you attempt to call a table (ie: you do “table = {}; table()”).
__call is a metamethod, this means that we need to use metatable… I thus define the metatable that contains call on our “new” function we already created.
1 2 3 4 5 |
function kidclass.new() local class = {}; setmetatable(class, kidclass); --we define kidclass as metatable for class. return class; end |
Since kidclass is what we defined as metatable for class, we need to create __call on kidclass:
1 2 3 4 |
kidclass.__call = function () local instance = {}; return instance; end |
Ok, so what we did for now? We made a way to create a class using a simple method.
Thus doing “myClass = kidclass.new()” will create a empty table that has kidclass as metatable.
And we created a way to create a instance.
Thus doing “myInstance = myClass()” will make Lua check if myClass has a metatable (it has… it is kidclass), and then it will check in the metatable (kidclass) if it has a __call. And then it will run the function contained in __call (this is why __call is defined as “= function()”)
But what if we want for example to create a Point class, that supports Point(x, y) as constructor? This mean now we need to create a support for constructor…
We change __call to this:
1 2 3 4 5 6 7 8 |
kidclass.__call = function (class, ...) local instance = {}; --Remember to make this local, or every time you call a constructor inside another constructor you will destroy the first object if class.Constructor then class.Constructor(instance, ...); end return instance; end |
Alright, now a constructor can be created for our class, following the following format:
1 2 3 4 |
function ourClass.Constructor(self, randomVars) self.defaultVar = 123; self.anotherVar = randomVars; end |
This actually already works as very simple OOP, so if you want, you can stop here… But I still want to support operator overloading!
Operator overloading is done in Lua using metamethods too, they are __add for +, __mul for * and so on (look on the Lua manual for all of them, there are also overloading for = and >= and more)
This means we need to add a metatable for the instances, that will look for __add function for example. Since we are making something OOP, the most logical place for __add is the class…
Thus when we do instanceOfBallC = instanceOfBallA + instanceOfBallB; Lua will look for __add in Ball.
We modify again our __call to that:
1 2 3 4 5 6 7 8 9 |
kidclass.__call = function (class, ...) local instance = {}; if class.Constructor then class.Constructor(instance, ...); end setmetatable(instance, class); return instance; end |
Static variables we already support, just add them to class (ie: myClass.staticVar = 23) but I think we can add one last cool feature… We can make our instances read from their class any variable that does not exist, thus creating default variables that are also static variables… We do that in lua by setting the __index of the table that is missing a variable… since __index is a metamethod too (we can set it to a table, as a shorthand version of sorts) we need to put it in the metatable.
Thus just before setmetatable(instance, class); line we add class.__index = class, meaning now that any time someone try to read something that does not exist from instance (that has class as metatable) it will read from what the metatable __index pointed (class itself too), thus in practice instances actually inherit from our class.
This is the final version of the code (PLEASE do not just copy paste it… it is here for studying, I am placing a commercial private code here in a goodwill gesture to educate you, not to you just leech it without understanding it).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
-- Mauricio Gomes -- Kidoteca object oriented system. --[[ How to use: To create for example a Dog class, we do: local class = require "kidclass" local Dog = class.new(); --If you forget the local here, it will pollute your _G local function realBark(...) --Private function print(...) end function Dog.Constructor(self, initialbarks) --This is a optional constructor... self.barks = initialbarks or 0; --This is a variable initialization plus default variable end local spots = 76; --This is a private static variable. Dog.color = "white"; --This is a public static variable. function Dog:bark() self.barks = self.barks + 1; realBark("We have " .. self.color .. " color and barked " .. self.barks .. " times"); end function Dog.blacken(amount) --static function (or rather, a function that alter static variables ) spots = spots + amount; print(spots); if spots > 100 then Dog.color = "black"; end end return Dog; To create a instance of Dog local Dog = require "dog.lua"; Max = Dog(4); --]] local kidclass = {}; kidclass.__call = function (class, ...) local instance = {}; if class.Constructor then class.Constructor(instance, ...); end class.__index = class; setmetatable(instance, class); return instance; end function kidclass.new() local class = {}; setmetatable(class, kidclass); return class; end return kidclass; |