OOP like concepts - RhythmLunatic/stepmania GitHub Wiki

It's a work in progress

Lua doesn't behave exactly like other OOP languages you might know, but you can do similar things.

"Constructors" in lua files

LoadActor() takes a second argument which will be passed in as the variable .... Usually this is the player, ex. LoadActor("lifebar",PLAYER_1) You can also pass in a table and then expand the table. For example:

LoadActor("lifebar",{PLAYER_1, true, "AAA","BBB"})

Then in the "lifebar.lua":

local tbl = ...
local player = tbl[1]
local is_EXHARD = tbl[2]
local name = (you get the idea)

(For some reason ...[1] and ...[2] does not work, but table variables are just pointer references anyways so declaring local tbl will not waste memory)

Declaring a "class"

Lua doesn't have classes. But you can copy tables and treat them as classes.

Example 1: instancing objects

This example shamelessly stolen from Simply Love and Rave It Out's code. In C# and C++ this is better known as a struct, not a class.

PlayerDefaults = {
JudgmentGraphic = "Season 2",
ScreenFilter = 0
}

We copy these tables into a another table using this function...

function table.shallowcopy(orig)
    local orig_type = type(orig)
    local copy
    if orig_type == 'table' then
        copy = {}
        for orig_key, orig_value in pairs(orig) do
            copy[orig_key] = orig_value
        end
    else -- number, string, boolean, etc
        copy = orig
    end
    return copy
end

And the "class" to hold "instances" of the "struct" we created...

PlayerOptions = {
PLAYER_1 = table.shallowcopy(PlayerDefaults),
PLAYER_2 = table.shallowcopy(PlayerDefaults)
}

Which can then be accessed anywhere by using PlayerOptions[PLAYER_1] or PlayerOptions[PLAYER_1]["ScreenFilter"] to get the "ScreenFilter" value for the player.

Make sure to reset your table or overwrite it when the player joins in with their profile, if you're using this for a player option.

If you just want one static instance, PlayerOptions = table.shallowcopy(PlayerDefaults) works.

Example 2: metatables

The actual way to create classes in lua. Usually copying a table is enough because you can assign functions to tables and use a Reset() function from the table, but if you want to do it like this anyways:

(P.S. This is taken from my MB theme, you should try it)

Let's declare our class 'MBAAClass':

local MBAAClass = {}
MBAAClass.__index = MBAAClass

Now let's add an init function (or constructor, whatever you want to call it)

MBAAClass.new = function()
	--Magic function that turns a table into a class thing
	local self = setmetatable({}, MBAAClass)
	self.info = {{Character="None",Difficulty=1},{Character="None",Difficulty=1}};
	Warn("MBSTATE initialized.")
	return self;
end;

And a couple of functions...

MBAAClass.GetDifficulty = function(self, pn)
	return self.info[PlayerNumber:Reverse()[pn]+1].Difficulty;
end;
MBAAClass.SetDifficulty = function(self, pn, diff)
	self.info[PlayerNumber:Reverse()[pn]+1].Difficulty = diff;
end;

Now let's create an instance of the class:

MBSTATE = MBAAClass.new();

Example 3: More advanced metatables

This is an example of a Vector2 class, commonly used in game engines like Godot.

local V2mt = {}
V2mt.__index=V2mt

function V2mt:__add(b)
    local res = Vector2(0,0)
    if type(b) == "number" then
        res.x = self.x + b
        res.y = self.y + b
    else
        res.x = self.x + b.x
        res.y = self.y + b.y
    end
    return res;
end
function V2mt:__tostring()
    return ("Vec2(%.1f, %.1f)"):format(self.x, self.y)
end

--And this function creates an instance of a Vector2 when you use Vector2() like you'd expect in any other OOP language.
function Vector2(x,y)
	return setmetatable( {x=x or 0, y=y or 0}, V2mt)
end

With the Vector2 class we can add two Vector2 objects.

local vec2_1 = Vector2(1,2);
local vec2_2 = Vector2(2,3);
local vec2_result = vec2_1 + vec2_2;
SCREENMAN:SystemMessage(tostring(vec2_result)); --Will print "Vec2(3,5)"

More help on metatables as classes is available here: http://lua-users.org/wiki/LuaClassesWithMetatable