I’m trying to get a insight of how OOP works, and I gotta say it’s fun but I couldn’t figure out a way to clear the metatable upon on playerremove, I know how to set it up on playerAdded but not quite on playerremoved since it’s linked to player.
local RisTal = {}
RisTal.__index = RisTal
function RisTal.New(Player)
local PlayerMorph = {}
PlayerMorph.Name = Player.Name
PlayerMorph.Morph = ""
setmetatable(PlayerMorph, RisTal)
return PlayerMorph
end
-- example function
function RisTal:Get(Player: Player?)
print(self)
print(self.Morph)
end
return RisTal
I’m confused on what you mwan by “clear the metatable”. Are you really sure you want to clear the keys of the metatable? This will cause the class to stop working entirely.
Correct me if i’m wrong, but wouldn’t that clear every metatable for each player assigned to it?
This is how I set up the metatable btw.
local Handler = require(game:GetService("ServerScriptService").GameHandler.OOPTest)
Players.PlayerAdded:Connect(function(Player)
Handler.New(Player)
end)
script.Parent.Touched:Connect(function(Player)
local PlayerReal = Players:GetPlayerFromCharacter(Player.Parent)
if PlayerReal ~= nil then
Handler:Get(true)
end
end)
Well I tried it, it kind off works? But another problem occurs is that table now applies to everybody. Like a player wants to change their Morph to “Test”, it would change for every player morph.
Players.PlayerAdded:Connect(function(Player)
Handler.New(Player)
end)
script.Parent.ClickDetector.MouseClick:Connect(function(Player)
Handler:Get("string")
end)
script.Parent.ProximityPrompt.TriggerEnded:Connect(function(Player)
Handler:Change("Lol")
end)
---
function RisTal.New(Player)
local PlayerMorph = {}
PlayerMorph.Name = Player.Name
PlayerMorph.Morphed = false
PlayerMorph.Morph = ""
setmetatable(PlayerMorph, RisTal)
return PlayerMorph
end
function RisTal:Change(Morph: string?)
self.Morphed = true
self.Morph = Morph
end
If you be confused, i can explain what happens in the script. Here is the script:
--Module
local RisTal = {}
RisTal.__index = RisTal
function RisTal.New(Player)
local PlayerMorph = {}
PlayerMorph.Name = nil
PlayerMorph.Morph = nil
setmetatable(PlayerMorph, RisTal)
return PlayerMorph
end
-- example function
function RisTal:Get()
print(self)
print(self.Morph)
end
return RisTal
Tried that, however :Change() applies for all players.
function RisTal:Change(Morph: string?)
self.Morphed = true
self.Morph = Morph
end
--
local Connection = Handler.New()
Players.PlayerAdded:Connect(function(Player)
Connection.Name = Player.Name
Player.CharacterAdded:Connect(function()
Connection.Morph = ""
end)
end)
script.Parent.ProximityPrompt.TriggerEnded:Connect(function(Player)
Connection:Change(Player.Name.. "test") -- Player1 Triggered proximity meaning it will replace morph for all of players metatable.
end)
@TheH0meLands I’m aware of that, it was just a quick demonstration.
The class you’ve created is fine but how you’re using it is wrong: in subsequent code samples you’ve shared in the replies, you’re calling the methods at the class-level when you instead want them called at the object-level. What you need is to have a reference to the object.
You can make this as simple or complex as you want, such as storing your cache in the class module itself (not entirely sure I recommend for readability’s sake?) or somewhere else. As an example of how you can write your script’s cache:
local PlayerMorphClass = require(player_morph_class)
local playerMorphs = {}
local function playerAdded(player)
playerMorphs[player] = PlayerMorphClass.New()
end
local function playerRemoving(player)
playerMorph[player] = nil
end
Players.PlayerAdded:Connect(playerAdded)
Players.PlayerRemoving:Connect(playerRemoving)
for _, player in Players:GetPlayers() do
playerAdded(player)
end
You can then reference the playerMorphs table to get the player. I recommend keeping your cache in a ModuleScript if you need multiple scripts to access it.
An example of how you could have a ProximityPrompt handled in the same script:
local function promptTriggered(player)
local playerMorph = playerMorphs[player]
if not playerMorph then return end -- No morph object for player
playerMorph:Change("foobar")
end
ProximityPrompt.Triggered:Connect(promptTriggered)
Personally, I change how I write my classes from the standard paradigm to one that uses a prototype table. For me it’s more of an organisational thing but for others I think it could help identify some very common problems while using OOP, especially when it comes to confusing the class- and object-level. Though really the latter thing is more about deploying your class properly and getting a good explanation rather than having to change the way you approach Luau OOP.
-- Standard paradigm
local Class = {}
Class.__index = Class
-- My edition
local Class = {}
Class.prototype = {}
Class.__index = Class.prototype
function Class.new()
return setmetatable({}, Class)
end
function Class.prototype:Foobar()
print("Foobar")
end
local object = Class.new()
object:Foobar() --> Foobar
Class:Foobar() -- This will error
Very good explanation, thank you so much but I am confusd on this, i thought only one __index is?
Also this. Is it possible to change it to:
function Class.prototype:Foobar(string)
Class.prototype = string
end
function Class:GetFoobar()
print(Class.prototype) -- return Class.prototype
end
---
object:Foobar("Test")
object:GetFooBar()
__index by itself means nothing because we’re just setting it as a member of the Class table. __index’s relevance comes into play when you call setmetatable; whatever table is passed as the second argument becomes the metatable, so this makes __index point towards the prototype table.
Regarding the bottom half: the prototype table is only meant to make sure that object-level methods can’t be called from the class-level. This can prevent common mistakes but it’s not necessarily the answer; the main issue for you is that you’re not caching the objects you create from the class for players.
You don’t need to change that bit and you shouldn’t. Foobar is an example function that would be able to be called on an object returned from Class.new. For you, you’d be writing something along the lines of Class.prototype:ChangeMorph. It’s just an alternate suggestion though; ultimately, again, the way you wrote your original class wasn’t wrong, but rather how you deployed it.
I don’t know where you get off accusing me of using ChatGPT. If we’re going to make unsubstantiated accusations of using AI against people for attempting to help, then I worry about the future state of helping users on the forum. Please don’t do this to me or anyone else.
Your method worked! I had to rewrote the .New function to meet the new standards!
Edit: Accidentally marked your other comment as solution, my bad. Changed it to the correct one.
function RisTal.New()
local PlayerMorph = {}
PlayerMorph.Morphed = false
PlayerMorph.Morph = ""
setmetatable(PlayerMorph, RisTal)
return PlayerMorph
end
Before this topic closes, I have one more question, will this work with global tables? Because I wanna have only one script that sets up classes while allowing others to use the class without the need of creating a new table. Like this:
_G.Classes = {}
Players.PlayerAdded:Connect(function(Player)
_G.Classes[Player] = CLN.New()
end)
Players.PlayerRemoving:Connect(function(Player)
_G.Classes[Player] = nil
end)
--different script
local CLN = require(game:GetService("ServerScriptService").Handlers.CFEngine)
script.Parent.ProximityPrompt.TriggerEnded:Connect(function(Player)
local Class = _G.Classes[Player]
Class:Get(true)
end)
Although my personal advice to developers is to stay away from using the global table because of the practice implications it brings, namely but not limited to namespace conflictions, yes this will work there as well – it’ll work in any place that supports accepting a table value. I mean you’re still creating a new table at the end of the day though.