Should I use OOP here? If yes, then how?

Hi! I’m currently trying to create a game. In my game I need to make quite a lot of systems, such as inventory, guns, GUI, input, interaction and so on. I already know what OOP is and I’m considering using it in my scripts. Is it a good idea?

Also, if I decide to actually use it then which option is better: put each system in a separate script OR maybe use one script and some modules?

Last of all, I already know what OOP is and how it works. However, I’ve never used it in Lua, so I’m still a bit confused here. I tried learning it, but I’m having trouble understanding all this stuff with metatables and such. What is the best way to learn OOP in roblox lua?

1 Like

OOP is not necessary if you’re looking for a game without organisation. OOP is really only good to use for organisation, so it’s up to you

2 Likes

What do you mean by ordering?

Sorry, I meant to say organisation there

Well right now I’ve only done a small part of the work so I’m still not sure about how will the organisation turn out. I’d like to be able to modify one of the systems without having to adapt all other ones to it. Would OOP help me with this?

You can do OOP if you like, however I think that if you’re still struggling to figure out how to structure a Roblox game, it’s probably better if you just keep things as simple as possible (e.g. using plain scripts and modules) and go into proper Lua OOP when you have a bit more experience.

OOP is useful for games because they use objects very frequently, however every now and again it can be a drawback as it requires boilerplate and a certain structure that could hinder in you in just quickly making things work.

Whichever you like. I prefer having the client and server each use a single Script/LocalScript with all other code in ModuleScripts. I once preferred using separate Scripts/LocalScripts, but I found the separation between scripts unhelpful as it made it difficult for two scripts to share data and code without them both sharing a ModuleScript, forced me to make arbitrary decisions on how to split up the codebase, and made script execution harder to understand (e.g. when does the code in this script execute vs the code in this other script?).

First thing: OOP is not an official feature of Lua, though something emulating it is possible thanks to having metatables and some syntax sugar.

Metatables are tables that allow you to control how another table responds to things being done to it. (That’s why they’re called metatables – they’re tables used for the purpose of changing the behavior of other tables.)

For example, you can control how a table responds to being part of an addition like 1 + table by putting a specific function inside the metatable to “catch” this (these functions are called metamethods):

local meta = {
   __add = function(ThisTable, ValueToAddWith)
      return ThisTable.SomeNumber + ValueToAddWith
   end
}

What makes metatables useful for OOP is the __index metamethod.

__index gets called when the table is indexed with some key and it doesn’t have a value for that key. Let’s say we have an list table with 3 values: {1, 2, 3}. If we did table[5], well this element does not exist in the table, so the __index metamethod will get called if it is available. If we did table[2], this does exist so __index will not be called.

-- This will throw an error when you attempt to get a value that isn't in the table.
__index = function(ThisTable, KeyThatWasNil)
    error(KeyThatWasNil.. " is not a valid member of this table.")
end

The __index value can also be set to another table. In this situation, the table you set as __index will act as a sort of backup table when you try to index the main one. For example:

local Table1 = {Damage = 5}
local Table2 = {Speed = 3, Name = "Fred"}

print(Table1.Name) -- "nil"

-- set Table2 as the metatable for Table1
-- so that failed indexes on table1 will use table2 as a backup
local Metatable = {__index = Table2}
setmetatable(Table1, Metatable)

print(Table1.Name) -- "Fred"

Note how I slipped in setmetatable() there. It just “applies” a metatable to a table. This is how you say which table the metatable is intended to be for.

So how does this relate to OOP? It allows you to make a table that defines everything for a class, i.e. all of its properties and methods,

local Class = {}
Class.SomeProperty = 5
Class.DoSomething = function() end

Then when you make a new instance of that class (AKA a new object), you create a new and empty table and set its metatable’s __index to refer to the table that defines the class.

local NewObject = {}
local NewMetatable = {__index = Class}
setmetatable(NewObject , NewMetatable )

print(NewObject.SomeProperty) -- "5"
NewObject.DoSomething()

So now NewObject is a unique table which can be used as if it were the Class table, but without being the same table.

The above two blocks of code in practice tend to be used like:

local Class = {}
-- You can use the Class table as both the metatable and the class definition
Class.__index = Class
Class.SomeValue = 5
Class.DoSomething = function() end

-- Function that creates a new instance of that class
function Class.new()
   local NewObject = {}
   setmetatable(NewObject, Class)
   return NewObject
end

-- Typical usage code
local MyObject = Class.new()
print(MyObject .SomeProperty) -- "5"
MyObject .DoSomething()

We’re now close to proper OOP, but we’re missing the concept of “methods” and (the “self” variable inside them). Methods refer to functions that can be called on a specific instance of a class. Here’s what they look like in Lua:

-- In the class definition
function Class:SetSomeValue(Number)
   -- This only affects the object this method was called on, not the class or
   -- any of the other objects that exist
   self.SomeValue = Number
end

-- Usage code
MyObject:SetSomeValue(10)

The above usage of : and self is just syntax sugar - it is actually equivalent to the following code.

-- In the class definition
function Class.SetSomeValue(self, Number)
   self.SomeValue = Number
end

-- In the usage code
MyObject.SetSomeValue(MyObject, 10)

-- or without metatables..
Class.SetSomeValue(MyObject, 10)

You can see that the self table is just the first argument to the SetSomeValue function, and calling a function inside a table with : instead of . passes in the table as the first argument to the function. Using : when calling or defining the function essentially just saves you some typing and reading.

So that’s how you do OOP in Lua. To summarize:

  • Make a table that defines a class and set table’s __index member to refer to itself
  • When creating a new instance of this class you create a new, empty table and set the class table as its metatable: setmetatable(NewTable, ClassTable).
  • When defining methods you can either use the colon syntax sugar and have self implicitly defined: function Class:Method() or manually declare the self variable: function Class.Method(self).
  • When calling methods you can either use the colon syntax sugar and have the object table implicitly passed to the method as self: MyObject:Method() or manually pass it in: MyObject.Method(MyObject)

This is a ton of information, but hopefully that gets you started on understanding how OOP in Lua works. I think there are a few tutorials about OOP elsewhere on this forum, so check those out.

Maybe a little bit, but not in any miraculous way. Games by their nature are interconnected. What little you can do to keep things separated can be done without OOP; it is simply one method by which you can attempt to accomplish that goal. It’s probably a good start, though.

8 Likes

I mainly use OOP when the game has a lot of content or objects that behave similarly but with small differences. (Like weapons, NPCs, etc.)

OOP is useful when you want to organize huge code bases or want to more easily expand it in the future. I can easily create new modules (classes) and drop them in a folder to create new content for games I work on. You can achieve this with procedural or functional programming too so it’s really just up to personal preference. I come from a Java (Minecraft modding) background so my preference is OOP, but the programming styles have no significant benefit over eachother. The power is in the user, not the method.

It also helps not to overdo it, you don’t need a class for every little function or task.

3 Likes