All about Object Oriented Programming


ALL ABOUT OOP!


Prerequisites

  • An understanding of meta-tables (although the required code will be explained)
  • How tables work and a competent grasp of the Lua syntax

Parts

What is OOP?
How does it help me?
How do I make this work in Lua?
Integrating with module scripts
What about inheritance?


What is OOP?

OOP stands for Object Orientated Programming and is a way of laying out code in a more friendly way whilst also keeping large projects organised. You have used objects in programming, even if you didnā€™t know. Things such as Parts or models are objects. This may seem obvious however most of the Roblox api is made up of objects. Even Vectors are objects in the Roblox API. Letā€™s define what an object is.

ā€œAn object can be a variable, function, or data structure. In the object-oriented programming paradigm, ā€œobjectā€ refers to a particular instance of a class where the object can be a combination of variables, functions, and data structures.ā€ -Wikipedia

In simple terms it is simply something which contains data about itself and functions to manipulate that data more easily.

For example letā€™s say a variable ā€œbrickā€ is a Part. It has some data, its size, colour, shape, position and it also have some functions associated with it, Destroy, Clone, GetMass etc. Manipulating parts is easy for us and it intuitive, but have you ever wanted more functions or your own type of part?


How does it help me?

Dealing with objects is a great way to enforce an idea called abstraction. Abstraction is simply being able to do things without worrying about underlying processes. For example when you do something in Lua it is in fact being run in C which is turned into assembly and then machine code. (Yes I know that isnā€™t totally accurate but that isnā€™t what this tutorial is about). Although all that is going on you donā€™t need to worry about it. Likewise when you make a part in Roblox you donā€™t need to worry about itā€™s physics or rendering it or any of that, due to abstraction. Not only does this clear up code but it allows collaboration to work much more seamlessly.

Letā€™s say youā€™re making a racing game. It would be useful to be able to do car = Instance.new(ā€œCarā€, Workspace) and then be able to find useful information such as car.Position or use methods such as car:Respawn(). Instance.new is part of the Roblox API so we wont touch it however we can make something similar, Car.new().


How do I make this work in Lua?

In Lua (or at least the system I will be teaching you) an object is a table with some variables in. A car object might look something like this.
car = {"RacePosition" = "3", "Speed" = 50, "Driver" = "Guest12372", "WorldPosition" = vector(25.31, 3.23, 53.86)}

And each car would be a similar table but with different values at each index, depending on where it is. First letā€™s define the function which will let us make new cars. Our own personal Instance.new() sorta function.

function newCar(position, driver, model)

end

However this will get quite annoying very fast. Lonely functions are not a happy sight in OOP. So in Lua we should have a table to contain all of our functions. I will name this table Car (it will become obvious why later). Every function related to the Car object will be in this table. So now the code will look like.

Car = {}
Car["new"] = (function(position, driver, model)

end)

The problem with this is it doesnā€™t look very readable. Using some special Lua syntax candy we can do this instead.

Car = {}

function Car.new(position, driver, model)

end

This looks much nicer. Just by looking at it we can see what the function is designed to do. Since this function is creating new objects it is a special type of function. It is known as the constructor. It constructs new Car objects (yeah programmers are creative). Also note, here is a class called Car which makes Car objects. The constructor and functions are the class whereas the things made by the constructor are the objects.

Since each Car object is just a table, a new table will need to be constructed which all the relevant data in it.

Car = {}

function Car.new(position, driver, model)
    local newcar = {}

    newcar.Position = position
    newcar.Driver = driver
    newcar.Model = model

    return newcar
end

Now when we call Car.new() with the relevant arguments it will give us back a nice table. Itā€™s organised but not very useful. There are no functions. It might be tempting just to put the functions in the table when it is constructed however this is both inefficient and messy. Since the functions are the same for every Car object they only need to be made once so it is better to provide Lua with the functions when it is trying to find a variable but cannot find it. Letā€™s say we are trying to get the driver of a car. We would do driver = Car.Driver. Lua looks in the car and finds the variable straight away without any extra variables in that table getting in the way. It would be useful if we could detect if the variable we are trying to find is a function, and in fact using meta-tables we can do exactly that.

Meta-tables are tables with a set of methods which perform various special tasks on other tables. I wonā€™t go too much into detail on this. The metamethod we want to use is the .__index metamethod. It is fired whenever Lua tries to find an index in a table but it has a nil value. We can just redirect Lua to a new table which have all the functions in. Conveniently our Car table has exactly that!

Car = {}
Car.__index = Car

function Car.new(position, driver, model)
    local newcar = {}
    setmetatable(newcar, Car)

    newcar.Position = position
    newcar.Driver = driver
    newcar.Model = model

    return newcar
end

Now if we did newcar = Car.New() and then tried to call a function on car is would look through the Car table too, not just newcar. Letā€™s add a function to our car object to make it more useful.

Car = {}
Car.__index = Car

function Car.new(position, driver, model)
    local newcar = {}
    setmetatable(newcar, Car)

    newcar.Position = position
    newcar.Driver = driver
    newcar.Model = model

    return newcar
end

function Car:Boost()
    self.Speed = self.Speed + 5
end

Now we can do.

newcar = Car.new(Vector3.new(1,0,1), "Guest1892", game.ReplicatedStorage.F1Car)
newcar:Boost()

This creates a new car object and then calls :Boost() on it. What happens behind the scenes is Lua tries to find newcar[ā€œBoostā€] however this does not exist, sees that Car is at newcars meta-tableā€™s .__index and then tries to find Car[ā€œBoostā€] which does exist! There is also a neat little feature in Lua where if you do function table:Method() self is auto declared. It is the same as doing function table.Method(self). Remember how boop:Beep() is the same as calling boop.Beep(boop). It passes the object to the function allowing the function to perform actions on that individual object. In this case :Boost() can perform the speed increase on individual cars.


Integrating with module scripts

This method of OOP works extremely well with module scripts. Simply put return Car at the bottom of the Car script and the module script will return the Car table ready for use, allowing you to do things like this.

--module script called Car in game.ReplicatedStorage
Car = {}
Car.__index = Car

function Car.new(position, driver, model)
    local newcar = {}
    setmetatable(newcar, Car)

    newcar.Position = position
    newcar.Driver = driver
    newcar.Model = model

    return newcar
end

function Car:Boost()
    self.Speed = self.Speed + 5
end

return Car

--main script

Car = require(game.ReplicatedStorage.Car)

newcar = Car.new()
newcar:Boost()

This gives you a way of neatly splitting potentially big scripts into little chunks which are easy to understand and change if needed.


What about inheritance?

A quick explanation of inheritance to those new to OOP. Inheritance is where a class can ā€˜inheritā€™ functions and behaviours from another class. So if we made a new class (type of object) for special types of cars, letā€™s say trucks. A truck is pretty similar to a car so there is no need to rewrite all of the code. Instead you would make truck ā€˜inheritā€™ all the methods of car. For the sake of scripting trucks can have power ups will allow special behaviour whilst cars cannot. Letā€™s make the truck constructor.

Truck = {}
Truck.__index = Truck

function Truck.new(position, driver, model, powerup)
    local newtruck = {}
    setmetatable(newtruck, Truck)

    return newtruck
end

return Truck

If we did this we would still need to put all of the declaring code into the constructor. This is pretty redundant so instead we would just create a car object inside of the constructor.

Car = require(game.ReplicatedStorage.Car)

Truck = {}
Truck.__index = Truck

function Truck.new(position, driver, model, powerup)
    local newtruck = Car.new(position, driver, model)
    setmetatable(newtruck, Truck)

    newtruck.Powerup = powerup

    return newtruck
end

return Truck

Great. Now the truck is properly constructed however if we tried to do

newtruck = Truck.new()
newtruck:Boost()

It would error saying something along the lines of ā€œattempt to call method ā€˜Boostā€™, a nil valueā€. This is because is looks at newTruck[ā€œBoostā€] and sees nil and then looks at Truck[ā€œBoostā€] and sees nil. What we really want is for it then to look at Car[ā€œBoostā€] as that is where the method is. To do this we simple add a metatable to the Truck table to point Lua to Car. Like so.

Car = require(game.ReplicatedStorage.Car)

Truck = {}
Truck.__index = Truck
setmetatable(Truck, Car)

function Truck.new(position, driver, model, powerup)
    local newtruck = Car.new(position, driver, model)
    setmetatable(newtruck, Truck)

    newtruck.Powerup = powerup

    return newtruck
end

return Truck

Now we could use :Boost() on a truck.

Since Lua will look at the Truck table first it means if you re declared :Boost() in the Truck table it will override the method declared in the Car table. Pretty fun stuff. You can also inherit through as many classes as you wish with very little impact on performance.


Thanks for reading my tutorial on object oriented programming in Roblox, hope it helped!

1806 Likes
Crossing server-client boundary with custom OOP?
Most efficient way to have semi-large amount of NPCs
Optimal way to do hit detection
Triggering replication of custom objects with __newindex?
Module script help
Best way to store game items?
OOPing Guidelines
Game Development Resources [MEGA THREAD]
Data Structure Organization
What's the purpose of OOP?
Creating A Furniture Placement System
How far do you go with making objects?
Advice on how to set up script hierarchy for turned based combat system enemies
Most efficient way to create spellcasting system?
Why can I never use self?
Dungeon Generation: A Procedural Generation Guide
Can somebody give me some basic stuff I can do with OOP?
Help with Object Oriented Programming
Creating a battle system: OOP style!
What are the type of scenarios Metatables are useful for?
Need help understanding OOP
Way to put module functions into event calls
Inheritance help
Is Vector3 a object?
Modules and OOP
Inheriting in a pseudo-class-based structure
All you need to know about Metatables and Metamethods
Mass-Assigning Variables
Creating projectiles?
How would I Organize My Scripting Better
(OOP) Class Module
UI Code Spaghetti
What things should I learn?
Need some examples on how to use OOP (Object-oriented Programming)
Help with metatables
Listening to field changes in a table (OOP Object Replication)
What does self mean?
How would I return a metatable from a :connect function?
Understanding Metatables
Question about OOP!
Luau Type Checking Beta!
How do you use a metatable within a ModuleScript?
I need help with scripting
What is the best practice for this?
Can you make a "class" object in Lua?
How to datastore2 more than 1 variable?
Scripting Projects: Help
Is there a way to turn a string into a reference?
Help with Object Oriented Programming
Question about oop
Employee Door System
How to improve this current code (if-then statement with queuing(?) system)
How do I make a module return a new table everytime, so scripts get their own table instead of sharing the table of that module?
What are you working on currently? (2020)
Object Oriented Programming
What are getters and setters?
Why do you need to use global functions instead of local when using metatables and OOP?
Magic/Dunder/Meta methods
Trouble with index & newindex
I do not understand __index
How to fix my OOP raycasting module?
Any way to check how many bullets remain on ROBLOX's endorsed weapons?
Global function?
Npc/monster attributes (Tags, DataStore, ?)
OOP help needed
Npc/monster attributes (Tags, DataStore, ?)
A* Pathfinding: Don't Cross Corners
How do i fix this
What is 'self' and how can it be used?
What Are the Pros and Cons of a Module Based Game?
Controlling an OOP object on the server and client?
How to create objects in LUA?
ModuleScript not working no errors
What does table.__index = table do?
Issue with results from OOP module
How can self be useful?
How can i get obj Properties
Animation and animation script help
Quick question about object oriented programming
OOP Inheritance
Why thingy is not owrking
ModuleScripts and "local" variables
A quick question regarding OOP
Roblox Scripting Roadmap & Learning Resource List
Proper way to make a .new() function in a modulescript
Can you put events in module scripts? ( OOP )
How would I go about making a tycoon?
Working w/ ModuleScripts?
Optimizing Code (Chat GBT vs My code)
Inverse Kinematics for Tripod
Purpose of Metatables?
How do I make multiple parts form into a circle around the player?
How do I make multiple parts form into a circle around the player?
What's the best way to handle the data for a forest of choppable trees?
Little help with OOP
Player Folder Objects Dissappear Before DataStore Saving
How to make a stealth system
How can I store properties & a table of functions inside an object? (metatables?)
Efficient Object Oriented Programming Tutorial
How would I go about creating methods for objects?
How to use DateTime
Scripting Resume
Custom notifications help
Roblox Module scripts are structured differently than the wiki examples? Are they the same or am I doing it wrong?
What is "self" and how do I use it?
The Beginners Guide To All Things Game Development
Help with tweening a cooldown indicator and global cooldown
All about Entity Component System
Module scripts and metatables
How do i make a zombie?
WeaponSystem Feedback
How to Implement OOP in Rbx.Lua
Quick question, how can I sync loops?
Question regarding Modules
Module Script Help
Confused about Self
State Machine : Module Scripts and Animator Replication issues
Is there a more efficient way to do this?
OLD Metatables, Metamethods, ModuleScripts and OOP
OOP Formatting Help
What is "self"?
Question on returning
What exactly does self do?
Development Troubles
How can i make a function with arguments like this
Attempt to call a nil value
How can I run a system that the player owns some parts?
Chaining .__index in metatables
How to use oop?
OOP: Implentation Review
Concepts: Object Oriented Programming in Roblox
OOP Property Issue
Are Prototype-Based Classes a better way to use OOP?
It's possible to use S.O.L.I.D principles in Lua?
Help with an OOP problem
Custom Functions/Events
What is ".self"?
Where do I keep my local script?
Is there another option similar to Bezier Curves?
How do I use modules?
EZ Pathfinding V3 [OUTDATED]
Is there any way I can improve my local laser script and/or module?
How to fix my math.clamp() so objects when being moved stayed in a plot's area?
What is self's value here
Melody - the perfect music module for you
Object Oriented Programming on Roblox
Help with scripting properly. (SCRIPTING MORE DYNAMICALLY)
Should I use roblox built in objects or object oriented programming such as intvalue to datastore?
How can i determine the name of a function inside of a table?
Optimizing Code (Chat GBT vs My code)
How do I return multiple values in a table
Can anyone explain what OOP is and how it is better
What situations should i use __index in modules and stuff
Program your own IK (Inverse Kinematics)
What is Object Oriented Programming (OOP)?
Roblox OOP (Object Oriented Programming)
"attempt to call missing method of table" from module object
Viewmodel.lua || Viewmodel handler for FPS frameworks
Self and :Functions()
How can I make these 2 modules share some methods?
What Should I learn Next?
Inheriting Instance Methods
What are some scripting terms that can help you understand code and instructions?
How would you use .new() in a module script to create a new gui object and edit its properties
Code duplicity for multiple parts with ModuleScripts
Making Events on ModuleScript
First time using ModuleScripts, need help with something
[v2] RoRift | Create a Fortnite-like rift in an instant
How can I convert this Python code into Roblox code?
How can I learn to create a gun system?
What the hell is object oriented programming?!?!?!? [Solved]
Attempt to index nil with 'PlacedObjects'
Can someone help me understand this? (OOP/metatables) (.__index)
Rotate objects with QUATERNIONS | Definitive guide
Feedback on new iteration of game
Help with OOP Module
Method Inside A Method?
Lobby System/Party System
Attempt to call a nil value - but doesn't look nil to me when debugging, guidance for lua/Roblox novice please?
Using require() on a Module that was created using Instance.new()
Make this gun system more OOP?
Attempting to use [self]
How could I expand on this metatable
Any improvements to my module?
Best way to handle perks/buffs
Variable is not getting updated
Social Chat v2 | Roblox's #1 Open-Sourced chatting resource
Seperate modules run only for latest joined player
Unleasing the power of Object-Oriented Programming | OOP
Understanding CFrame with Matrices!
Parenting Instances to Metatables?
Accommodating for Multiple Stacks of Items in an Inventory System
Roblox custom types and classes
When to use metatables
When to use metatables
Question on Object Oriented Programming
I need help with raycast springs
How to program better: Use OOP!
Having trouble with inheritance and OOP
How should I go about making a weapon system?
OOP private functions and variables
Module Scripts and what are they?
[SOLVED] Trying to make a table with function run
Help with code for multiple weapons
Module.new() and Module:New()
[SOLVED] Trying to make a table with function run
How would I make a global shop?
How can I better use self in this function module:()?
I couldn't understand what OOP does
Better Way To Reference an Object
Should I start learning and using OOP?
Table returning nil from module scritps
OLD Metatables, Metamethods, ModuleScripts and OOP
Module function doesn't update module variables
How to sync object creation through network
Open Source Wave based game
ChildAdded works but not ChildRemoved
What is the Fastest Way to Finish Large Scripting Projects in a Neat & Clean Way?
OOP Framework check
OOP / Self in Tables?
Why is the .__index there?
BlockCast Position Problem
What are a modular framework, oop, metatables and promises
Function using while true do loop too much
Inefficient Gun System
Feedback on my first OOP script
What is a better way to get handle AI?
How to make gacha/banner system
Multiple parts hitting each other at the same time
MVC: A Practical Approach Towards Developing Games (And how to stop confusing yourself with ECS vs OOP)
Lights switch scripts
Tycoon Framework/system
What is Orianted Object Programing?
Best way to set Player States
What's the best way in Roblox Studio to reuse "objects"?
How do you make a Character Selection..?
I know the basics of scripting what do I do now?
Using OOP and CollectionService to make automated plots
Does this new function change anything?
Wouldn't it be smart placing functions of weapons inside of a module script?
I may have made a mistake
Codebase involving ModuleScripts (not exactly modular) - requesting feedback
Is this good OOP code?
Need help with OOP Gun System
Combat Frameworks
Abilties and models
How to use OOP inheritance?
Help understanding custom classes/metamethods
Am I doing OOP right?
How i can create good script?
Any recommended ways on making an inventory system that doesn't use tools?
Npcs what values should be managed by the metatable
How would I use Module scripts to manage NPC behavior?
What's the advantage of learning OOP?
Is this good practice?
OOP question: How to index objects from outside that object
ModuleScripts General queries?
OOP and __Index functions
Custom Character/NPC without Humanoid
Artificial (OOP-based) humanoids and how to create them?
Table with more then one level of metatables wont convert into JSON
Can you create actual classes and how?
How would I go about making a custom moveset for specific characters
Guide to Type-Checking with OOP
Best way of manipulating Player Data
Should I use : or . when calling a ModuleScript function?
How to apply tool pseudoclasses in practice?
Can someone teach me metatables?
What does .new do in custom functions?
What does .new do in custom functions?
What is a Metatable?
Help with OOP Class Constructors
OOP is pretty easy to mess up
Do I HAVE to include metatables in my games?
Help with Object Oriented Programming
78: attempt to perform arithmetic on local 'Value' (a nil value)
Game organization
How can i "Box" this value
Don't know why argument is nil
I crave to understand this script
Sorting, Organizing Scripts and Workplace
When is a metatable 'really' required?
How to do OOP with modules?
Questions about OOP
Question about OOP
Player = nil in module script. Why?
How to make api's
OOP in Lua, elaborate
Question about ModuleScripts
Question about ModuleScripts
Understanding OOP and all of it's functions, any help?
Help with self and OOP
Can you create your own classes in lua?
Can you create your own classes in lua?

This is also pretty useful for OOP; Programming in Lua : 16

It shows some pretty nifty ways of using inheritance, which I think is pretty cool.

54 Likes

[quote] This is also pretty useful for OOP; Programming in Lua : 16

It shows some pretty nifty ways of using inheritance, which I think is pretty cool. [/quote]

This tutorial pretty much explains that in huge detail.

23 Likes

Nice tut, was clear since Iā€™ve had courses in OOP but rather Java instead.

25 Likes

What? No audio? HAX

12 Likes

Interesting. I never called it OOP. I just called it framework and metatables.

Glad to know thereā€™s a more confusing name for everything I script :stuck_out_tongue:

55 Likes

Never considered setting a metatable to a metatable before. Nice work.

30 Likes

Great introduction to metatables

18 Likes

I want to understand this, but I donā€™t understand this and donā€™t know what to do.

I feel so below everyone here.

I need to stop being a moron with all this theory stuff and get better with all this table stuff, but god damnit itā€™s hard to find where to start.

I only came here cause thereā€™s another thread discussing OOP and Iā€™m just here like ā€˜wtf is everyone talking about?ā€™ and feeling really stupidly out of place.

61 Likes

[quote] I want to understand this, but I donā€™t understand this and donā€™t know what to do.

I feel so below everyone here.

I need to stop being a moron with all this theory stuff and get better with all this table stuff, but god damnit itā€™s hard to find where to start.

I only came here cause thereā€™s another thread discussing OOP and Iā€™m just here like ā€˜wtf is everyone talking about?ā€™ and feeling really stupidly out of place. [/quote]
http://hastebin.com/oyivihiraf.lua

6 Likes

[quote] I want to understand this, but I donā€™t understand this and donā€™t know what to do.

I feel so below everyone here.

I need to stop being a moron with all this theory stuff and get better with all this table stuff, but god damnit itā€™s hard to find where to start.

I only came here cause thereā€™s another thread discussing OOP and Iā€™m just here like ā€˜wtf is everyone talking about?ā€™ and feeling really stupidly out of place. [/quote]

Probably the best place to learn about metatables would be the Minecraft mod, Computercraft.

Itā€™s where I learned all about metatables. By accident too, I was just looking at the vector API to be able to create my own stuff similar to it.

9 Likes

Well I understand metatables fairly well now and get how they work (still dont understand their use), but this whole OOP thing is like a bloody maze. It reminds me of first trying to understand FilterEnabled.

I take it OOP is the hardest thing Lua has to offer, it sure feels like it.
If any of you have some relatively easy OOP related scripts you donā€™t mind sharing so I can route through them, Iā€™d be delighted to do so.

2 Likes

[quote] Well I understand metatables fairly well now and get how they work (still dont understand their use), but this whole OOP thing is like a bloody maze. It reminds me of first trying to understand FilterEnabled.

I take it OOP is the hardest thing Lua has to offer, it sure feels like it.
If any of you have some relatively easy OOP related scripts you donā€™t mind sharing so I can route through them, Iā€™d be delighted to do so. [/quote]

Here you go, a ModuleScript to create your own custom class:

[code]Thing = {}
Thing.__index = Thing

ā€“A function to construct the object (a constructor)
ā€“Ideally, all values you want the object to have would be initialized here.
function Thing.new(value)
local new = {}
new.Value = value

setmetatable(new,Thing)
return new
end

ā€“Define some methods. The object youā€™re calling it on will be referred to as ā€˜selfā€™ inside the methods.
function Thing:setValue(value)
self.Value = value
end

function Thing:getValue()
return self.Value
end

return Thing
[/code]

Now, to use it:

[code]Thing = require(script.Parent.ModuleScript)

obj = Thing.new(3)
print(obj:getValue()) ā†’ 3

obj:setValue(1234)
print(obj:getValue()) ā†’ 1234
[/code]

I hope this helps!

29 Likes

Iā€™d like to thank Emma for this guide because it really helped me with Redirection.

4 Likes

Iā€™m having trouble understanding the use case for this stuff, apologies, but it all seems a bit confusing.

5 Likes

Stick with it Kevin. OOP is extremely useful, not just on ROBLOX, but in the industry. One way to figure it out that Iā€™ve found helpful is using ROBLOX objects as sort of your model internally for how you expect the object you make to operate.

12 Likes

Even though this is two years old, still is a useful tutorial about Metatables (Though maybe @magnalite could reformat this to work on this forum ;)) But I do have a question and I rather not make a separate thread as it could easily be answered and added to the tutorial for future readersā€¦

Once you have made an object using OOP, how can you make your own :Destroy() method thatā€™ll remove the object by itself, so I donā€™t have to do something like:

Inventory.Primary:Destoy()
Inventory.Primary = nil

To completely remove the object, I thought doing something like:

function Object:Destroy()
local n = self.Name
self.ToolModel:Destroy()
self = nil
print(n, ā€œwas successfully destroyed!ā€)
end

Would work, however it only deletes the model and not the object as self is just a path(?) and not the object itself. How would I delete the object from a method inside the metatable?

9 Likes

Your example is pretty spot on for this. You simply create the Destroy method that internally calls Destroy on the necessary items.

However, setting self to nil isnā€™t going to do anything. What youā€™re trying to do is remove the object entirely, but we should just let the garbage collector do that for us.

When you call Destroy() on any normal object in ROBLOX, it will only be GCā€™d once all references to it are gone. The exact same thing goes for tables. Of course, you can configure your table a bit more by making it have weak keys or values.

In summary, just make your Destroy() method destroy the actual objects that need to be destroyed. And then once your other scripts get rid of any references to that object, it will eventually be GCā€™d.

21 Likes

I think Iā€™m doing this wrong, following the example of OP for creating and inheriting metatables and their methods/functions. Below in the BaseTool section I create what I call a template for my custom Tool object that all other ā€œtoolsā€ (such as a sword or a gun) use for a template. I have an odd thing going on, whenever I call Tool:Destroy() on a BaseTool (Tool) object itā€™ll destroy it, and when I call RangedWeapon:Destroy() itā€™ll also be destroyed. But when I try to call :Destroy() on the PropaneTank, whether it be done by calling PropaneTank:Destroy() (when the object is spawned by using PropaneTank.new()) or by calling self:Destroy(), the script will error and say that Destroy is a nil value.

I followed step 2.2 of the original post, except the only change I made was by adding __index inside of the PropaneTank.new() function so I could read the properties of the PropaneTankā€¦ But if I remove that then my script will also breakā€¦ Yeah Iā€™m not sure whatā€™s going on anymore. Hereā€™s my code to see what Iā€™m doing.

---------------------
-- ROBLOX Services --
---------------------
local RunService = game:GetService("RunService")
local RS = game:GetService("ReplicatedStorage")
-------------------
-- ModuleScripts --
-------------------
local Utilities = require(RS:WaitForChild("Utilities")) 
---------------
-- Variables --
---------------
local Weapons = {
	Ranged = {},
	Melee = {},
	Other = {},
}

for Index, ModuleScript in pairs(RS:WaitForChild("Weapons"):WaitForChild("Ranged"):GetChildren()) do
	if ModuleScript:IsA("ModuleScript") then
		local DataToCopy = require(ModuleScript)
		local DataToSave = Utilities:DeepCopy(DataToCopy)
		DataToSave.Type = "Weapon"
		Weapons.Ranged[#Weapons.Ranged+1] = DataToSave
	end
end

for Index, ModuleScript in pairs(RS:WaitForChild("Weapons"):WaitForChild("Melee"):GetChildren()) do
	if ModuleScript:IsA("ModuleScript") then
		local DataToCopy = require(ModuleScript)
		local DataToSave = Utilities:DeepCopy(DataToCopy)
		DataToSave.Type = "Weapon"
		Weapons.Ranged[#Weapons.Ranged+1] = DataToSave
	end
end

for Index, ModuleScript in pairs(RS:WaitForChild("Weapons"):WaitForChild("Support"):GetChildren()) do
	if ModuleScript:IsA("ModuleScript") then
		local DataToCopy = require(ModuleScript)
		local DataToSave = Utilities:DeepCopy(DataToCopy)
		DataToSave.Type = "Support"
		Weapons.Other[#Weapons.Other+1] = DataToSave
	end
end

for Index, ModuleScript in pairs(RS:WaitForChild("Weapons"):WaitForChild("Other"):GetChildren()) do
	if ModuleScript:IsA("ModuleScript") then
		local DataToCopy = require(ModuleScript)
		local DataToSave = Utilities:DeepCopy(DataToCopy)
		DataToSave.Type = "Other"
		Weapons.Other[#Weapons.Other+1] = DataToSave
	end
end


local function FindTool(ToolName)
	for FolderName, FolderContents in pairs(Weapons) do
		for Index, Tool in pairs(FolderContents) do
			-- print(Tool.Name)
			if Tool.Name == ToolName then
				return Tool, FolderName
			end
		end
	end
end

	
local function CreatToolAnimationData(Parent, IndexName, OriginalTable, ToolName)
	local Type = type(OriginalTable)
	local copy
	if Type == "table" then
		copy = {}
		for orig_key, orig_value in next, OriginalTable, nil do
			copy[CreatToolAnimationData(Parent, orig_key, orig_key, ToolName)] = CreatToolAnimationData(Parent, orig_key, orig_value, ToolName)
		end
	elseif Type == "number" then		
		local AnimationObject = Instance.new("Animation")
		AnimationObject.Name = ToolName.."_"..IndexName
		AnimationObject.AnimationId = "http://www.roblox.com/asset/?id="..OriginalTable
		AnimationObject.Parent = Parent
		
		copy = {AnimationObject, nil}
	else
		copy = OriginalTable
	end
	return copy
end

local function FindHighestFiringMode(ToolData)
	local FiringModes = ToolData.FiringModes
	if #FiringModes > 1 then
		local SelectedMode = 1
		for Index, Mode in pairs(FiringModes) do
			for _,M in pairs({"Semi", "Burst", "Auto"}) do
				if Mode == M then
					SelectedMode = Index
				end
			end
		end
		return SelectedMode
	else
		return 1		
	end
end


local Service = {}
Service.RetrieveToolData = FindTool
-------------------------
-- Base Tool Class API --
-------------------------
Service.Tool = nil
do
	local Class = {}
	Class.__index = Class
	function Class.new(ToolName, Player)
		local Data = FindTool(ToolName)
		if Data ~= nil then
			local Tool = {}
	    	setmetatable(Tool, Class)	
			Tool = Utilities:DeepCopy(Data)
			Tool.Parent = Player	
			Tool.ToolModel = Tool.ToolModel:Clone()
			Tool.ToolModel.PrimaryPart = Tool.ToolModel.Handle
			Utilities:WeldTool(Tool.ToolModel)
			Tool.SavedWeldData = Utilities:SaveWeldData(Tool.ToolModel)
			Tool.ToolModel.Parent = Tool.Parent
			Tool.Animations = CreatToolAnimationData(Tool.ToolModel, Tool.Name, Tool.Animations, Tool.Name)
			-- Tool.Equipped
			local Equipped = Instance.new("BindableEvent", Tool.ToolModel)
			Class.Equipped = Equipped.Event
			-- Tool.Unequipped
			local Unequipped = Instance.new("BindableEvent", Tool.ToolModel)
			Class.Unequipped = Unequipped.Event
			
			return Tool
		else
			spawn(function() error("Error there is no such tool named "..tostring(ToolName)..".") end)
			return nil
		end
	end
	
	function Class:Destroy()
		local n = self.Name
		self.ToolModel:Destroy()
		self = nil
		print(n, "was successfully destroyed!")
	end
	
	Service.Tool = Class
end
-----------------------------
-- Ranged Weapon Class API --
-----------------------------
Service.RangedWeapon = nil
do
	local Class = {}
	Class.__index = Class
	setmetatable(Class, Service.Tool)
	function Class.new(WeaponName, Player, AmountOfAmmo)
		local RangedWeapon = Service.Tool.new(WeaponName, Player)
		if RangedWeapon == nil then spawn(function() error("Error, BaseWeapon is nil.") end) return end
		
		local MaximumAmountAllowed = (RangedWeapon.MaxAmountOfMagazines * RangedWeapon.MagazineCapacity)
		if AmountOfAmmo >= MaximumAmountAllowed then
			AmountOfAmmo = MaximumAmountAllowed
		end
		
		setmetatable(RangedWeapon, Class)
		RangedWeapon.SelectedFiringMode = FindHighestFiringMode(RangedWeapon)
		RangedWeapon.TotalAmmo = AmountOfAmmo
		RangedWeapon.MagAmmo = RangedWeapon.MagazineCapacity
		
		return RangedWeapon, AmountOfAmmo
	end

	Service.RangedWeapon = Class
end
----------------------------
-- Melee Weapon Class API --
----------------------------
Service.PropaneTank = nil -- this is the only proper metatable...
do
	local Class = {}
	Class.__index = Class
	setmetatable(Class, Service.Tool)
	function Class.new(Parent)
				
		local PropaneTank = Service.Tool.new("Propane Tank")
		
		local DamagableTag = Instance.new("BoolValue")
		DamagableTag.Name = "Damagable"
		DamagableTag.Value = true	
		DamagableTag.Parent = PropaneTank.ToolModel
		
		PropaneTank.BackingTable = {IsDead = false}
		PropaneTank.CreationTimeStamp = tick()
		PropaneTank.Name = nil ; PropaneTank.BackingTable.Name = "Propane_Tank" ; PropaneTank.ToolModel.Name = PropaneTank.BackingTable.Name
		PropaneTank.Health = nil; PropaneTank.BackingTable.Health = 5	
		PropaneTank.Position = nil
		PropaneTank.CFrame = nil
		PropaneTank.Parent = nil ; PropaneTank.ToolModel.Parent = Parent
	
		setmetatable(PropaneTank, Class)	
		
		Class.__index = function(self, Index)
			return self.BackingTable[Index]
		end
	
		Class.__newindex = function(self, Index, Value)
			--rawset(self, Key, Value)
			self.BackingTable[Index] = Value		
			-- rawset(PropaneTank.BackingTable, Key, Value)	
			if Index == "Name" then
				self.ToolModel.Name = Value
			elseif Index == "Parent" then
				self.ToolModel.Parent = Value
			elseif Index == "CFrame" then
				self.ToolModel:SetPrimaryPartCFrame(Value)
				self.BackingTable.Position = Value.p
			elseif Index == "Position" then
				--[[
				local OldCF = self.BackingTable.CFrame
				local angles = OldCF - OldCF.p
				self.BackingTable.Position = Value.p
				--]]
				self.BackingTable.Position = Value.p
				self.ToolModel.PrimaryPart.Position = Value	
			elseif Index == "Health" then
				if self.BackingTable.Health == 0 and not self.BackingTable.IsDead then
					self.BackingTable.IsDead = true
					
					local e = Instance.new("Explosion")
					e.Position = self.ToolModel.PrimaryPart.CFrame.p
					e.DestroyJointRadiusPercent = 0
					e.BlastRadius = 15
					e.Parent = game.Workspace

					for Index, Zombie in pairs(_G.Zombies) do
						local Distance = (e.Position - Zombie.Torso.Position).Magnitude
						if Distance <= e.BlastRadius then
							Zombie:TakeDamage(1000)
						end
					end
					
					if _G.MapData then
						for Index, Tank in pairs(_G.MapData.PropaneTanks) do
							if Tank ~= self and not Tank.BackingTable.IsDead and Tank.ToolModel.PrimaryPart ~= nil then
								local Distance = (e.Position - Tank.ToolModel.PrimaryPart.CFrame.p).Magnitude
								if Distance <= e.BlastRadius then
									Tank.Health = 0
								end
							end
						end
					end
					
					-- only for testing
					self:Destroy() -- delay(0.5, function() self.BackingTable.IsDead = false end)
				end	
			end
			-- warn(self.Name.."."..(Index).." property was changed to "..tostring(Value)..".")
		end

		return PropaneTank
	end
	
	function Class:Destroy()
		self.ToolModel:Destroy()
	end
	
	Service.PropaneTank = Class
end

return Service
4 Likes

@GuestCapone

If you do

PropaneTank.BackingTable.__index = Class

Does that fix it? Im hoping it doesnā€™t cause a circular loop. If that works I can explain why if you would like me to.

1 Like