Playerdata management with OOP

So I recently wanted to change the way I am managing playerData using OOP, buy lets say I have a rebirth system that should change a value for the player, how would I do that? Since I dont have self, I cant do self:Update(). Should I use bindable events? Or should I use a completely different way of managing playerdata?

You don’t have to abandon OOP or use bindable events for this
As If you’re using OOP for playerData, you should store the object class instance for each player, maybe in a dictionary like PlayerData[player] and when your rebirth system triggers, just access the instance and call its method like

PlayerData[player]:UpdateRebirth()

or modify values directly

PlayerData[player].rebirths += 1

The key is to keep track of the player’s data object and I’d say there’s really no need for bindables

Ohhh so a seperate module for each player?

No, not a separate module, a separate instance of a class (from a module for each player) because the module defines the class and you create a new object from it per player

Ok could you give an example of how this would like because i am still kinda confused

Module:

local playerDataModule = {}
playerDataModule.__index = playerDataModule

function playerDataModule:initalize(player:Player)
	local self = setmetatable({}, playerDataModule)
	playerDataModule[player.UserId] = self
	--
	self.coins = 0
	--
	return self
end

function playerDataModule:update(value)
	self[value] = self[value] + 1
end

return playerDataModule

Changer

local players = game:GetService("Players")
local replicatedStorage = game:GetService("ReplicatedStorage")

local modules = replicatedStorage:WaitForChild("modules")
local playerDataModule = require(modules:WaitForChild("playerDataModule"))

local part = script.Parent

local debounce = {}

part.Touched:Connect(function(otherPart)
	local humanoid = otherPart.Parent:FindFirstChildOfClass("Humanoid")
	if not humanoid then return end
	local character = otherPart.Parent
	local player = players:GetPlayerFromCharacter(character)
	local self = playerDataModule[player.UserId]
	self:update("coins")
	print(playerDataModule)
end)

Like this?

No

You are turning self into a number…Why?
Why do yall begginers want to turn everything into a class anyway?
OOP in such cases makes code harder,more unoptimized and harder to manage.
All you need to do is having one table always alive per player and having backend system that saves it upon player leaving/autosaving.

Also you absolutelly never use self,there no any point of using methods here anyway

Also if you are creating a constructur please either name it new or separate it from main table.

like:

local module = {}
module.__index = module

local function module:Method():string
return self.Name
end

return function(name:string)
local class = {Name=name}

return setmetatable(class,module)
end

Anyway if you were to use OOP, i recomend you to either use C like OOP or closures/closure OOP:

Example of closure OOP:

type closureClass = {
Increase:(num:number)->();
Print:()->();
Destroy: ()->();
}
local function NewClosure():closureClass
local num:number = 0
local connection = Script.Destroying:Connect(function():()
print(num)
end)
return {
Increase = function():()
num+=1
end;

Print = function():()
print(`Closure scope: {num}`)
end;

Destroy = function(self:closureClass):()
connection:Disconnect()
table.clear(self::{[any]:any})
end;
}::closureClass
end

local closure = NewClosure()
local Increase,Print = closure.Increase,closure.Print
Increase()
Increase()
Increase()
Increase()
Increase()
Print()
task.wait(5)
closure:Destroy()
1 Like

Agreed. There is so little instances where this would be practical.

No, it’s because you are overwriting the playerDataModule table with some self instance for every player, losing previous player data and breaking the module’s functions

Ok I am sorry for being stupid, so from your response the conclusion is:

No OOP

From this response I conclude:

No OOP

From this response I conclude:

Still use OOP?

If everyone that replied here just please could tell me how they would do it without OOP, or send an example of a script with OOP, it would be very usefull

Sorry for being stupid, but I cant improve if I dont know how too, so please help me out if you can

You could do something pretty simple like this

local datas = {} :: {[Player] : {any}}

local function updateStat(player : Player, stat : string, value : any)
	datas[player][stat] = value
end

game.Players.PlayerAdded:Connect(function(player)
	datas[player] = {
		Coins = 0
	}
end)

updateStat(player, "Coins", 100)

print(datas[player].Coins) -- prints 100

Sorry I havent written out something properly, but I hope you get the jist of it.

And then to access it from other scripts you could put it in a ModuleScript and have it return a table with the updateStat function within it.

yes ok thats what I thought at first too

You could make a class that is created when a player joins, and saved and destroyed when they leave.

Anything related to changing a player’s data should be encapsulated inside the class as a method, like rebirthing.

-- Defining a type for the object to make it easier to read
export type PlayerData = {
    Owner: Player,
    Coins: Number,
    Rebirths: Number,
    Rebirth: (self: PlayerData) -> (),
    Destroy: (self: PlayerData) -> (),
}

local PlayerData = {}
PlayerData.__index = PlayerData

function PlayerData.new(Owner: Player): PlayerData
    local self = setmetatable({}, PlayerData)

    self.Owner = Owner
    self.Coins = 0 -- Could replace with a call to grab saved data, if any, so coins is assigned to the data
    self.Rebirths = 0 -- Same here

    return self
end

function PlayerData:Rebirth()
    self.Coins = 0
    -- Whatever other values need to be reset

    self.Rebirths += 1
end

function PlayerData:Destroy()
    table.clear(self)
end

return PlayerData

If you want to handle multiple players, you can create something called a singleton/handler (also a module):

local PlayerData = require(script.Parent) -- or whatever path PlayerData is in

local PlayerDataHandler = {}
local PlayerDatas: {[Player]: PlayerData.PlayerData} = {} -- Dictionary that holds all the players objects

-- Stores PlayerData objects in a dictionary, if a player doesn't have a PlayerData object yet it makes a new one
function PlayerDataHandler.Get(Owner: Player): PlayerData
    local result = PlayerDatas[Owner]
    if not result then
        result = PlayerData.new(Owner)
        PlayerDatas[Owner] = result
    end

    return result
end

return PlayerDataHandler

This is just a really basic template. In this case, you should only be requiring the PlayerDataHandler in other scripts, and then calling functions from the value it returns, like:

local PlayerDataHandler = require(yourPath.PlayerData.PlayerDataHandler)

local PlrData = PlayerDataHandler.Get(player) -- Assuming you have a reference to a player
PlrData:Rebirth()

Let me know if this was helpful!