I want to understand OOP syntax’s and just OOP in general that’s basically it any help will be super appreciated Thank you!!
just giving a brief overview:
- It’s important to note that OOP isn’t possible in Lua as it is in other languages; that it is not natively supported. The OOP you see is a common workaround to allow complex data structures like this to exist.
self
self
is a variable used in Lua to refer to a table from the table itself. That’s… literally all it does. It’s used in OOP to refer to the current object.
calling a function with a colon
a colon in Lua indicates that the item itself should be passed as a parameter. If you define a function with colon notation, self
will automatically be defined as the first parameter.
function Class:Method(otherParam)
--self is defined here
end
--other cose:
Object:Method(3) --> this tells Lua to pass Object as the first parameter to Method
Object.Method(Object, 3) --> does the same thing as the above statement
__index
__index
is known as a metamethod and is used as a part of a metatable. A metatable is a table about a table; it defines certain behaviour of the table. The __index
metamethod is used in Lua to basically incorporate OOP and make it work.
__index
is invoked whenever a key in a table isn’t found. You can use it to return a custom value, or just direct Lua to another table to search for the key. Since you don’t define any methods in the object table, they will always be nil
. So, whenever you try and call one, it’ll always be redirected to the Class table, where all of the methods are.
function Class.new()
local self = setmetatable({}, {__index = Class}) --> setmetatable returns the first parameter
-->now, whenever we try and locate a key in self that doesn't exist, Lua will also try to find it in the Class table
return self
end
In summary, Lua uses workarounds to create the same functionality as OOP.
Any questions, please ask.
Hmm I understand it more now, but I don’t really know how I would use this in scripting. Could you maybe give me an example of code in ways I could use this? Is OOP something that you should use when u get the chance, or is there really just no point to it? How does it benefit you in scripting? Sorry I have a lot of questions.
it can be extremely useful when you need to create data structures with similar functionality but with different data.
Take for example a notification system. You want all your notifications to look the same and behave the same, but each one should have different data.
so each notification object could look like this:
{
NotificationItem: Frame, --the notification itself
Content: string, --the message to be displayed
Duration: 5 --the duration in seconds
}
and then you could create tweens and apply changes to each object through the same piece of code, the method created for the class.
let’s take a real-life example - a car.
Each car is it’s own car, but it’s still known as a car. Think of a car as an object, and the idea of a car itself as the class. Each car would have its own data, such as its max gear, number of seats, et cetera. Each car would respond to methods, like accelerating and braking. But, each car is just an object.
Cars are (probably) manufactured from blueprints, right? It gives instructions on how a car should be constructed, and then since each car is slightly different, they might have different niche things like the colour or the number of doors. So, each car is it’s own object; that it is self-contained and responds to functionality from an outside source (like you pressing the accelerator, just how an object responds to methods when you call them through that object)
Finally, don’t force yourself to use OOP. Don’t try to find places to fit it in. Using it incorrectly can make your systems a lot more complicated than they need to be (I learnt that the hard way).
If you see situations like those I’ve described, 100% use it if it’s feasable.
Ok I’m getting there I think lol, but going back to the car thing you said. How would that look in code such as each car having different acceleration and braking? Would you make a function for the braking and acceleration? And just using the values based on the value in the table of car braking and acceleration? Or am I misunderstanding how to use OOP?
yes, there would be functions for them; when you accelerate a car, it doesn’t just show a higher value. it has logic to increase the car’s velocity as well, so it needs logic in code too!
function Car.new(colour: Color3, doors: number --[[etc.]])
local self = setmetatable({}, {__index = Car})
self.Colour = colour
self.CarPart = --[[PrimaryPart of a Car model for example]]
self.Doors = doors
self.Speed = 0
return car
end
function Car:Accelerate()
--accelerating the car by 5 speed
self.Speed = math.clamp(self.Speed, 0, 100)
--the logic would probably be a little more complex but here's the jist of it
self.CarPart.AssemblyLinearVelocity = self.CarPart.LookVector * self.Speed
end
function Car:Brake()
--remove 5 speed from the car
self.Speed = math.clamp(self.Speed - 5, 0, 100)
--apply the same speed logic here because it's the same in this scenario
--it might not be in other scenarios
self.CarPart.AssemblyLinearVelocity = self.CarPart.LookVector * self.Speed
end
--[[
now, for other things like the number of doors,
you don't need any specific logic. All the logic for it
can depend on other pieces of code that need to
utilise it when-and-where; think of it like a variable in
a standard script. the logic in the script just does things
based off of that variable. Well, it's the same here,
but it's called an attribute in OOP.
]]
Yes thank you I know how to do the logic in the functions but I wasn’t sure how you would go about creating functions with the like
Car:FunctionNameHere()
What does the colon mean?
It is syntatic sugar for table.function(table)
, that looks ugly, so instead they made it so functions in a table, defined with the colon pass the table implicitly as a first argument. So these are equivalent:
table:f()
table.f(table)
--example with other args
table:f(1, 2, 3)
table.f(table, 1, 2, 3)
Colon just means the table is passed implicitly as the first argument which you can access inside of the function using self.
--equivalent function definitions
function table:f()
end
function table.f(self)
end
I don’t think I will ever learn OOP never I’m to dumb for this lol thank yall for helping though
Even if you don’t learn it now, it’s a great paradigm to know and use so definitely learn it at some point.
Gonna show you a super simple example.
Let’s start with making our metatable.
local animal = {}
animal.__index = animal
animal.sound = "No sound"
function animal:make_sound()
print( self.sound ) -- Prints "No sound"
end
This is our metatable.
The line animal.__index = animal
, here you should see __index
as a instruction.
animal
is now our metatable.
Now we make a new and empty table like so.
local dog = setmetatable( {}, animal ) -- Creates a table and immediately sets it's metatable to "animal"
(Longer, simpler version of the above code would be this.)
local dog = {}
setmetatable( dog, animal ) -- Does the same thing.
Now we set dog
’s metatable to animal
.
Because of the __index
instruction, Lua will now look inside of the animal
table for any functions or properties if it does not exist inside dog
.
Now if we call a function like so…
dog:make_sound()
The dog
table, does NOT have a function named make_sound()
, so it will look if that function exists inside animal
instead and use THAT function.
Now you may also have noticed that we used the keyword self
.
function animal:make_sound()
print( self.sound ) -- Prints "No sound"
end
The self
keyword is basically just syntax sugar.
self
will always refer to the table that the function was used on.
Without self
, our code would look like this nonsense below.
function animal.make_sound( my_animal )
print( my_animal.sound ) -- Prints "No sound"
end
...
dog.make_sound(dog)
But there’s a problem here.
Our dog doesn’t dog, it still makes the “No sound” sound.
Let’s change that!
dog.sound = "Meow, whatever sound a dog makes"
Now if we call this function here…
dog:make_sound()
Now our dog will make the appropriate sound just as mother nature intended.
Our dog
table now looks like this on the inside.
dog = {
sound = "Meow, whatever sound a dog makes"
}
So self.sound
will now return that instead.
Our full code would look like this below.
local animal = {}
animal.__index = animal
animal.sound = "No sound"
function animal:make_sound()
print( self.sound ) -- Prints "No sound"
end
local dog = setmetatable( {}, animal )
dog.sound = "Meow, whatever sound a dog makes"
dog:make_sound() -- Prints "Meow, whatever sound a dog makes"
That’s the basics of OOP, you can now make classes and re-usable code!
I kept it as simple as possible to make it easy to understand.
I encourage asking questions because that’s how you learn things, it’s okay to not understand something.
That’s why you have all these nice and kind people on forums giving answers!
You have explained this is the most noob way possible aka making me able to understand it thank you so much for this I understand it now
But would I use this for maybe like a gun system with different weapons is that a good way to use it to practice on?
Not just thank you for the solution but everyone who has helped me understand OOP greatly appreciated
It can be used for anything you want really!
Gun systems, enemies, items, inventory, cars, literally anything.
Typically you do this sort of thing inside module scripts so that any script can basically “inherit” it.
Lua actually doesn’t really have OOP, it’s more of a functional language.
But Metatables (as we call them) can replicate OOP-like behavior and allow setting default values for empty tables.
For instance…
local gun = {}
gun.__index = gun
gun.damage = 20
gun.accuracy = 85
gun.ammo = 12
gun.capacity = 12
gun.max_ammo = 120
gun.sound = "Pew!"
function gun:shoot( target )
if not self.ammo > 0 then
print("Ah shoot, not enough ammo!")
return -- Immediately stops the entire function.
end
print( self.sound )
print( "Dealt " .. self.damage .. " damage to " .. tostring(target) )
self.ammo -= 1
end
function gun:reload()
self.ammo = self.capacity
print("Reloaded!")
end
local revolver = setmetatable( {}, gun )
revolver.damage = 50
revolver.capacity = 6 -- Revolvers typically only fire 6 bullets.
revolver.ammo = 6
revolver.accuracy = 98
revolver:shoot("Zombie")
Oh, we can also simplify the entire process of making revolver
and do this instead.
If we add this piece of code to our gun
metatable, we can make revolvers quicker and easier to make.
function gun:new(ammo, damage, accuracy)
local new_gun = setmetatable( {}, self ) -- self refers to "gun" metatable here.
new_gun.ammo = ammo
new_gun.capacity = ammo
new_gun.damage = damage
new_gun.accuracy = accuracy
return new_gun
end
Also notice the use of self
here again, this keyword will ALWAYS refer to the table that it was called on, in this case our gun
table because we are going to do gun:new()
.
We use self
so we can write this:
local revolver = gun:new()
Instead of this:
local revolver = gun.new(gun)
Now that we have this function, we can write less code and just create a new revolver like so.
local revolver = gun:new(6, 50, 98)
revolver:shoot("Zombie")
Our complete code would now look like this.
local gun = {}
gun.__index = gun
gun.damage = 20
gun.accuracy = 85
gun.ammo = 12
gun.capacity = 12
gun.max_ammo = 120
gun.sound = "Pew!"
function gun:shoot( target )
if not self.ammo > 0 then
print("Ah shoot, not enough ammo!")
return -- Immediately stops the entire function.
end
print( self.sound )
print( "Dealt " .. self.damage .. " damage to " .. tostring(target) )
self.ammo -= 1
end
function gun:reload()
self.ammo = self.capacity
print("Reloaded!")
end
function gun:new(ammo, damage, accuracy)
local new_gun = setmetatable( {}, self ) -- self refers to "gun" metatable here.
new_gun.ammo = ammo
new_gun.capacity = ammo
new_gun.damage = damage
new_gun.accuracy = accuracy
return new_gun
end
local revolver = gun:new(6, 50, 98)
revolver:shoot("Zombie")
Metatables can even inherit from each other, although this practice is generally not recommended (by me personally) because Lua is not very optimized for that sort of thing and it can make code more complicated than necessary hence why I won’t explain it in this post.
I hope this clarifies the essentials for you, this is honestly usually the only thing you need.
Once you understand OOP, I also recommend looking into OTHER metatable functions.
It’s in fact even possible to write functions that allow you to add/subtract/multiply tables together and customize what happens to them!
But that’s also something for another day, as to not overwhelm you with too much new information.
Ok so based on the code you have showed I managed to get this
Local Script Controlling the Module:
local testmodule = require(script.Test)
local Ar = testmodule.gun:CreateNewGun("Ar", 30, 20, 0.1, 1, "Auto", 300)
print(Ar)
Module Script I Have:
local GunModule = {}
GunModule.gun = {}
GunModule.gun.__index = GunModule.gun
function GunModule.gun:CreateNewGun(gunname, ammo, damage, firerate, shotsperclick, firemode, startingammo)
local created_Gun = setmetatable( {}, self)
created_Gun.Ammo = ammo;
created_Gun.Damage = damage;
created_Gun.Firerate = firerate;
created_Gun.ShotsPerClick = shotsperclick;
created_Gun.FireMode = firemode
created_Gun.StartingAmmo = startingammo;
return created_Gun
end
return GunModule
Please let me know if I am doing something wrong in this code
I do have a few questions about this though
- What is the point in doing this? What does this do?
GunModule.gun.__index = GunModule.gun
- How would I use something like this to position the ViewModel?