I’m new to OOP, so there are still things about it which I do not understand - I created this simple new “Sword” class which creates and attaches the swords model to the players’ hand. My question is:
Is it bad practice to do anything outside of creating the object itself inside of class.new?
Should the creation of the weld and assigning of the weld to the players’ hand be a separate function/method?
Here is my script:
local swordInformation = require(game:GetService("ReplicatedStorage").swordInformation)
local Sword = {}
Sword.__index = Sword
setmetatable(Sword, swordInformation)
function Sword.new(Character, playerSword)
local newSword = {}
setmetatable(newSword, Sword)
newSword.Model = Sword.Model
newSword.Damage = Sword[playerSword].Damage
newSword.Range = Sword[playerSword].Range
newSword.Speed = Sword[playerSword].Speed
local swordModelClone = newSword.Model:Clone()
swordModelClone.Parent = Character
swordModelClone.Handle.CFrame = Character.RightHand.CFrame
local newWeld = Instance.new("WeldConstraint")
newWeld.Parent = swordModelClone.Handle
newWeld.Part0 = swordModelClone.Handle
newWeld.Part1 = Character.RightHand
return newSword
end
function Sword:primarySwing()
print("The swing did "..self.Damage.." damage.")
print(self)
end
I mean, it really depends on what your trying to achieve.
I don’t really understand the question, what do you mean by “doing anything outside the object”? I mean, yeah, you should define methods outside the actual constructor, for readability at least.
Again, depends, if you think its going to be easier making 2 methods for each, then go ahead, but just using one method I think works just fine and doesn’t overcomplicate things, just my take though.
Actually it depends. If you’re going to use those welding creation lines more than once, for example if player dies and you don’t recreate the sword class, so you’ll need to create the weld again. If it’s the only time you’re going to use those lines than you can do that during creating the class.
The way it worked in my head was that I’d be creating a new sword class every time the players respawns.
Are you saying it would be more efficient to create the sword class once (per player that joins?) and then reweld a clone of the model to the character each time they respawn with a separate method?
It’s bad practice because it’s unorganised, and defeats the purpose of being able to reuse code. Another bad practice is storing the Sword data inside the class itself. What I would do personally is
local Sword = {}
Sword.__index = Sword
local PlayerSwords = require(--path to player swords)
function Sword.new(character, swordIndex)
local self = setmetatable({}, Sword)
self.Character = character
self:Set(swordIndex)
return self
end
function Sword:Set(swordIndex)
local swordData = PlayerSwords [swordIndex]
assert(playerSword, swordIndex .. " not found")
self.Damage = swordData.Damage
self.Range = swordData.Range
self.Speed = swordData.Speed
self.CurrentSwordIndex = swordIndex
end
function Sword:Equip()
local swordModelClone = newSword.Model:Clone()
swordModelClone.Parent = self.Character
swordModelClone.Handle.CFrame = Character.RightHand.CFrame
local newWeld = Instance.new("WeldConstraint")
newWeld.Parent = swordModelClone.Handle
newWeld.Part0 = swordModelClone.Handle
newWeld.Part1 = self.Character.RightHand
end
return Sword
Which’d allow for more functionality, like changing the sword dynamically rather than being limited to when its created. On top of that add hundreds of swords without it being overwhelming.
Another thing, if your using swordInformation as a way to do like inheritance, I find it’s much easier to do so by setting a custom __index function.
local baseSword = require("Example Base class path")
local ClassInheritance = {baseSword}
function Sword:__index(index)
local value = rawget(self, index) or Sword[index]
if value then
return value
else
for, _, inherit in ClassInheritance do
if inherit[index] then
return inherit[index]
end
end
end
return nil -- not needed but gives more visual context (personal preference.)
end
This is incredibly helpful! tysm! If I’m getting this correct then…
Each player upon joining is given their own “Sword” object
The sword, based on let’s say a string, is Indexed in playerSwords and the attributes of that sword are correctly set to the players sword object, which is able to be updated at anytime with the Set() method
Equip handles all the physical stuffs
Please correct me if any of this seems wrong!
On a side note, the Sword module is actually the base module here, swordInformation is basically what you said PlayerSwords should be:
I have two questions, the first is: What helped you learn about metatables?, and the second is How do I know when to use metatables instead of regular functions? (Anyone reading this and having the answer would also help with your answer.)
In the case of OOP, it allows you to encapsulate everything inside a class that represents a real-world object
for example, in a vehicle system, instead of having:
local function driveTruck()
end
local function driveCar()
end
you would create a Vehicle class and then create different objects from it
local Vehicle = {}
Vehicle.__index = Vehicle
function Vehicle.new()
local self = setmetatable({}, Vehicle)
return self
end
function Vehicle:Drive()
end
return Vehicle
---
local car = Vehicle.new()
local truck = Vehicle.new()
car:Drive()
truck:Drive()
i’m not sure if that’s what you meant, but I hope it helps
I usually store them in a variable within the script where I’m going to use them, so they stay active during runtime.
If there are multiple objects (for example, instances of a Tycoon or Plot class), I store them in a table and create a function to retrieve a specific one, like getPlayerTycoon(player).
I’m not sure if it’s the best way, but it works for me x)
Metatables became easy when I realized everyone overcomplicates them.
Say we have a regular table. When we interact with it, like adding a value, or retrieving a value, we are firing hidden ‘meta’ methods that handle it all behind the scenes.
local newTable = {Value = 10}
print(newTable.Value)
“newTable.Value” is actually firing a function like this
-- This is HELLA simplified, the real one will not look like this at all.
function myTable.__index(_table, index) -- index is when you retrieve a value.
return rawget(_table, index)
end
But we can also swap these hidden methods for our own functions by giving our table a Metatable. A Metatable litterally just a regular plain old table that contains our custom methods.
local myTable = {Value = 10}
print(myTable.Value) -- prints: 10
local myMetaTable = {
__index = function(_table, index)
return "I don't feel like finding it sorry."
end
}
setmetatable(myTable, myMetaTable)
print(myTable.Value) -- prints: "I don't feel like finding it sorry."
-- Even when you try to find nil values.
print(myTable.DidntSetThisValue) -- prints: "I don't feel like finding it sorry."
We don’t even have to use functions, we can make it index another table. Although, the functionality for this, is if the value isn’t found in the base table, then look in the __index table instead. Which is how me make Classes.
local baseClass = {}
baseClass.__index = baseClass -- because we're using it as the metatable
baseClass.InheritedValue = 10
local newClassObject = setmetatable({}, baseClass)
print(baseClass.InheritedValue) -- prints: 10