Is this bad practice when creating a new class?

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
1 Like

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

And then do like

PlayerSwords

return {
["Fire Sword"] = {
    Damage = 10,
    Range = 30,
    Speed = 50,
},

["Metal Sword"] = {
    Damage = 5,
    Range = 20,
    Speed = 30,
},
}

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
3 Likes

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:

local replicatedStorage = game:GetService("ReplicatedStorage")

local swordInformation = {}
swordInformation.__index = swordInformation
swordInformation.Model = replicatedStorage.swordModel

-- [[Linked Sword]]

swordInformation.Linked_Sword = {}
setmetatable(swordInformation.Linked_Sword, swordInformation)

swordInformation.Linked_Sword.Damage = 10
swordInformation.Linked_Sword.Range = Vector3.new(5,5,5)
swordInformation.Linked_Sword.Speed = 1

return swordInformation

(I’m aware this syntax is atrocious but it’s all for learning purposes mwahahah)

I’ll be picking this one apart for the next few days : D

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.)

Idk, just messing around ig? Watch some yt tutorials maybe?

Metatables/OOP are good for reusable things, they are better if you don’t want redundant code like this:

function foo(a : number) : number
    return a * 2
end

function foo2(a : number, b : number) : number
    return a * b
end

not the best examples, but you get the idea.

i don’t get it, i mean isn’t that the point of the functions? to avoid duplicate code

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

1 Like

I’m not sure if this is worded correctly, but I’ve another question about OOP practices; how do you generally store new class objects?

do they generally stay within the script that they’re called from?

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)

(omg i deleted it by accident)

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
1 Like

Just wanted to add onto this with a heavily simplified diagram from Roblox themselves which is so perfect.


It’s from an official Roblox source on OOP, of all places: https://www.youtube.com/watch?v=fByFKZarNiI&t=270s&ab_channel=RobloxLearn