Easiest way to do OOP (Object Oriented Programming)

In this easy tutorial I’m going to show you the absolute easiest way to do Object Oriented Programming on Roblox. The really good thing about this method is that it’s very similar to the way you do OOP in Python, and maybe even simpler! This will be really easy, I promise!

Keep in mind that although this method is likely the easiest, it is not the fastest, as stated by @ffrostfall. So it shouldn’t be used for more complex games with numerous objects of the same class.

I am going to use a person as an example for this tutorial.

First, to create a class, all you need to do is create a function with the name of the class you want to make:

function Person()

end

Now let’s add some attributes! To do this, first create a self variable which references an empty table. Then, add some attributes by doing self.{attribute name} = {value}. You can also add parameters for the class in the brackets after Person:

function Person(name, age)
    local self = {}
    
    self.name = name
    self.age = age
    self.iq = 5
end

Next, let’s create a method for our Person class. To do this, simply do self.{method name} = {function}. You can of course add parameters for methods as well:

function Person(name, age)
    local self = {}
    
    self.name = name
    self.age = age
    self.iq = 5

    self.IncreaseIQ = function(amount)
        self.iq += amount
    end
end

Now, the last step is to simply return the self table:

function Person(name, age)
    local self = {}
    
    self.name = name
    self.age = age
    self.iq = 5

    self.IncreaseIQ = function(amount)
        self.iq += amount
    end

    return self
end

That’s it!

Now let’s create a Person object! To do this, create a variable and set it to the class function and specify the parameters:

local paul = Person("Paul", 37) -- name, age

Now we can do things like getting/setting an attribute or calling a method on the object:

local paul = Person("Paul", 37)
paul.age = 4
paul.IncreaseIQ(7) -- Paul's IQ is now 12!

Inheritance

The last part is how to implement inheritance. I’m going to use Creature as my superclass and Person and Animal as my subclasses which will inherit all attributes and methods from the Creature class.

The first step is to create a Creature class as well as a Person and Animal class in the exact same way as before. Then, set the self variable in each subclass to the Creature class, right below where you initialized the variable.

If you want the superclass to take parameters, simply specify the parameters normally and put an ellipsis (…) as the first parameter for each subclass and inside where you called the superclass:

function Creature(name, age) -- superclass
    local self = {}

    self.name = name
    self.age = age

    self.IncreaseAge = function(amount)
        self.age += amount
    end
    
    return self
end

function Person(..., cash) -- subclass
    local self = {}
    self = Creature(...) -- inheritance
    
    self.cash = cash
    self.iq = 12

    self.DecreaseIQ = function(amount)
        self.iq -= amount
    end

    return self
end

function Animal(..., favoritePrey) -- subclass
    local self = {}
    self = Creature(...) -- inheritance
    
    self.favoritePrey = favoritePrey
    self.kills = 0

    self.EatPrey = function(preyName)
        self.kills += amount
        print(self.name.." just ate "..preyName.."!")
    end

    return self
end

Finally, let’s create objects for our subclasses:

local paul = Person("Paul", 37, 0) -- name, age, cash
paul.DecreaseIQ(10) -- Paul's IQ is now 2!

local charcoal = Animal("Charcoal", 5, "Paul") -- name, age, favorite prey
charcoal.EatPrey("Paul") -- Charcoal just ate Paul!
charcoal.IncreaseAge(2) -- Charcoal is now 6 years old!

Thanks for reading! Please let me know how I can improve this method and if there is a better method.

25 Likes

this is a pretty interesting take on OOP, reminds me of python. nice!

3 Likes

What does … mean in arguments? I figured it’s just a neater way to represent multiple arguments but I’m not to sure.

This method is basically taking the metatables out of oop- which isn’t a good idea. Take the following code:

local object = {
     Key = function() end,
     Key2 = function() end,
}
local objectB = {
     Key = function() end,
     Key2 = function() end,
}

Now these two objects are the exact same, with the exact same keys, right? No. They’re very different when it boils down to how it’s handled in memory.

print(object.Key == objectB.Key)

This prints false. Meaning these, essentially duplicate functions, take up more data in memory than they should. Metatable OOP, on the other hand, points to the same function, the same function, so if you do

print(object.Key == objectB.Key)

it will actually print true!

If you’re lazy, just run this test code:

local object = {
	Key = function() end,
	Key2 = function() end,
}
local objectB = {
	Key = function() end,
	Key2 = function() end,
}

local class = {}
class.__index = class

function class.new()
	local self = setmetatable({}, class)
	
	return class
end

function class:method()
	
end

print(object.Key == objectB.Key)
print(class.new().method == class.new().method)

Why does this matter? Really simple- memory usage, performance. If you duplicate all the functions for every object, that will get expensive, as functions in Lua are stored as actual data. The more code, the higher the size.

EDIT: oh, and also! there’s a lot of optimization for idiomatic OOP in Luau too

25 Likes

Another way to achieve something similar thing to what the OP has suggested, is to do the following:

local CLASS = {}
local META = {__index = CLASS}

function CLASS:method()
    print(self.Welcome)
end

return function(Greeting)
    return setmetatable({
      Welcome = Greeting
    }, CLASS)
end

This implementation solves the memory issue although I am not sure if it is actually slower than idiomatic OOP.

3 Likes

Side-note: Majority of the functions in your tutorial have a syntax error:

The parenthesis after end are not supposed to be there because they don’t close anything

1 Like

I understand your method is more efficient, but I’d say for most basic games like roleplaying and fps games it should suffice. My method’s advantage is that it’s really easy to set up and is beginner-friendly. But your method is definitely better for large and complex projects, and mine is probably not the best in that case.

Thanks for your tutorial it is good.

1 Like

Agreeable. Just make sure to add a disclaimer about high memory on large projects.

2 Likes

It’s a way to pack an unknown amount of arguments into one parameter. You can get all of them in a table by doing local values = {...}

Example:

local function printArgs(...)
    local words = {...}

    for _, word in ipairs(words) do
        print(word)
    end
end


printArgs('one', 'two', 'three')
2 Likes

Isn’t this teaching bad habits? If you get used to doing it in an inefficient way it could be hard to re-adjust when you move on to larger projects.

It’s still better than not using any OOP method at all. Even though it’s slower than the other OOP method, it’s not noticeable for most use cases.

I still don’t understand the idea of using an inferior method simply because its slightly easier to understand. Creating a function inside an object wastes memory, you shouldn’t just throw it under the rug because “it’s not noticeable for most use cases”. Imagine hundreds of objects being created all with their own function that is identical to each others, it would definitely be noticeable.

1 Like

I updated the post with a disclaimer about my method not being the fastest.

I’m just wondering, is anyone able to make my OOP method as optimized as @ffrostfall 's method while keeping it as simple as my method? I’d really appreciate it!!

Instead of doing this:

local Person = {}

function Person(name, age)
    local self = {}
    
    self.name = name
    self.age = age
    self.iq = 5

    self.IncreaseIQ = function(amount)
        self.iq += amount
    end)

    return self
end

return Person

Just do this:

local Person = {}
Person.__index = Person

function Person.new(name, age)
    local self = setmetatable({}, Person)
    
    self.name = name
    self.age = age
    self.iq = 5

    return self
end

function Person:IncreaseIQ(amount)
    self.iq += amount
end

return Person
4 Likes

That seems like a pretty good method. Can you explain what exactly you are doing on the second line? Thanks. Person.__index = Person

Person is a metatable of self, when an index isn’t found in self, the metamethod __index is invoked, if this is a function, it will be called with the arguments self and key, if it’s a table, it will be searched through instead for the index (a key)

local meta = {}
meta.__index = meta
local object = setmetatable({}, meta)
-- or
local meta = {}
local object = setmetatable({}, {__index = meta})
local meta = {}
local object = setmetatable({}, meta)
meta.__index = function(self, key)
    print(self, "WAS INDEXED WITH THE KEY", key)
    return math.random() -- will returns some pseudo random number
end

print(object.some_key)

A metatable is simply a table that “describes the behavior” of another table.

Useful resources:
https://www.lua.org/pil/13.html
https://developer.roblox.com/en-us/articles/Metatables
https://devforum.roblox.com/t/an-in-depth-look-at-oop-roblox-lua/1049827

I wrote multiple articles on OOP, not just this one. I suggest going through them in order, or you will be confused. I also advice not to use the OOP paradigm for everything, just the core game systems that logically should use objects. In the case of everything else, a modular design patter is more efficient.

I agree; OOP tends to be overused, sometimes a simple function will suffice or a different paradigm such as a data oriented design paradigm would be better.

1 Like