So I’m trying to make a character system that saves, and recently I’ve received a tip saying it would work if i “set an attribute to the player and store it in datastore”. Obviously, being the weak developer that I am, I have no idea how to implement this. Now, I know what a datastore is but I’ve never set an attribute to a player in my life. If anyone is willing to help, it would be greatly appreciated. Thank you!
To set a attrbrute to a player you first need get the player the most slimplest option for a tutoral is game.Players.playeradded:Connect(function(player)
to set a atttibute via script is player:SetAttrbrute("money", 0")
then you just refence the attrbyte value when saving by do player:GetAttbrute("money")
you now have a attrbute on the player.
I will make a more on depth script in 7 hours just not on studio rn
Idk if it’s really best way, it’s the simpliest, attributes are string → any map, which isn’t great if you want a lot of data cuz each character is 1 byte!
To save attributes you need only to retrieve this string and it’s value, and save it inside table to a datastore
To load attributes you only need to take that string and value, and set new attribute to a player
Note: There is limit of iirc 100 attributes per instance, so below is more complex solution
Solution number 2 is more complex, as you save player’s data inside a module script and use remote events to replicate it to client, this way you can update the value when some action is taken (for instance, when player collects a coin) or even only if client asks, this way you remove unnecesary replication of instances and reduce lag in your game
You actually did it. Omgosh.
To set and get attributes:
Instance:SetAttribute(Name, Value)
Instance:GetAttribute(Name)
That’s the easy part. Just one function.
Now you have to load this when the player joins.
First, include DataStoreService in a serverscript:
local DataStoreService = game:GetService("DataStoreService")
Now we have to get your data store using GetDataStore.
local store = DataStoreService:GetDataStore("this is the name of the store, can be anything")
This is just a place where your game can store information.
You can get the a value from a datastore using GetAsync.
You can once again just pick a name for the key you want to read, but I suggest you add some form of player identifier to it to not mix up different players’ data.
--something like character + userid
store:GetAsync("Character"..plr.UserId)
--.. adds two strings together.
But watch out! This is a network call, it may fail, so wrap it in pcall (protected call) or xpcall (if you want a custom fn to run when an error occurs). pcall returns two objects, a boolean, indicating whether the call succeeded, and either the return value from the fn wrapped in pcall if it succeeded, or the error message if it failed.
pcall(function()
return store:GetAsync("Character"..plr.UserId)
end
Finally, you assign the value retrieved from your key to the player’s attribute, or a default value if it didn’t succeed:
plr:SetAttribute("Character", success and result or "Default value")
Or you might just choose to kick the player. It’s up to you on how you will handle failure. Combining the snippets, we can assemble a PlayerAdded handler:
local players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local store = DataStoreService:GetDataStore("your name")
players.PlayerAdded:Connect(function(plr)
local success, result = pcall(function()
return store:GetAsync("Character"..plr.UserId)
end
if not success then
warn(result)--output error msg
--kick the player here if you will
-- or you could retry here
end
plr:SetAttribute("Character", success and result or "Default value")
end)
Now, once we have an attribute, we can connect a changed event handler to it so whenever you set the attribute it will change the player’s character to the corresponding character. Don’t worry attributes are only replicated from server to clients so an exploiter can’t set the attribute and suddenly become any character.
plr:GetAttributeChangedSignal("Character"):Connect(function()
local targetName = plr:GetAttribute("Character")
local char = --use the name to retrieve the model
if not char then return end
local cframe = plr.Character and plr.Character.PrimaryPart.CFrame or CFrame.new()
--switch characters, then move the new character to the old chars position if it exists.
plr.Character = char
char:PivotTo(cframe)
end)
Now whenever you set this attribute, the character will change.
Finally, we need a way to save our character.
We can use SetAsync for this. We will not use UpdateAsync, since there’s no reason to read the old value(unless you want to).
store:SetAsync(Name, Value)
--so you just write to the store:
store:SetAsync("Character"..plr.UserId, plr:GetAttribute("Character"))
Set async can fail, so handle this error gracefully, wrap it in pcall
, or xpcall
.
When the player leaves we want to save their data, when the game closes we also want to save the data.
So we use game:BindToClose
and players.PlayerRemoving
:
local function save(plr)
local success, result = pcall(function()
store:SetAsync("Character"..plr.UserId, plr:GetAttribute("Character"))
end)
if not success then
warn(result)--print error msg
--maybe add retrying here?
end
end
players.PlayerRemoving:Connect(save)
game:BindToClose(function()
for _, plr in ipairs(players:GetPlayers()) do
save(plr)
end
end)
Now if you don’t kick the player when their data failed to load you cannot save the data now because it is a default value, so you should add a boolean in the player.PlayerAdded handler to indicate whether to save data or not:
players.PlayerAdded:Connect(function(plr)
--other code
--maybe like an attribute?
plr:SetAttribute("CanSaveData", success)
end)
And then in the save function add a check to see if the data isn’t the default value:
local function save(plr)
if not plr:GetAttribute("CanSaveData") then return end
--other code
end
This ensures the player’s data isn’t overriden with the default value.
Combining the snippets we get:
local players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local store = DataStoreService:GetDataStore("your name")
local function save(plr)
if not plr:GetAttribute("CanSaveData") then return end
local success, result = pcall(function()
store:SetAsync("Character"..plr.UserId, plr:GetAttribute("Character"))
end)
if not success then
warn(result)
--handle error
end
end
players.PlayerAdded:Connect(function(plr)
local success, result = pcall(function()
return store:GetAsync("Character"..plr.UserId)
end)
if not success then
warn(result)
--handle error
end
plr:SetAttribute("CanSaveData", success)
plr:SetAttribute("Character", success and result or "default value")
plr:GetAttributeChangedSignal("Character"):Connect(function()
local targetName = plr:GetAttribute("Character")
local char = -- get char from name
if not char then return end
local cframe = plr.Character and plr.Character.PrimaryPart.CFrame or CFrame.new()
plr.Character = char
char:PivotTo(cframe)
end)
end)
players.PlayerRemoving:Connect(save)
game:BindToClose(function()
for _, plr in ipairs(players:GetPlayers()) do
save(plr)
end
end)
Now, you’re basically done. You have a very simple system. Now what if you want to store more data than only the character?
Well we can use tables to store the data instead!
We just need to change the code a bit:
local cache = {}
First we can add a cache, so when we need the data again we don’t need to perform another network call.
Then change stuff to match tables:
local function load(plr)
if cache[plr] then return cache[plr] end
local success, result = pcall(function()
return store:GetAsync(tostring(plr.UserId))
end)
if success then
cache[plr] = result
plr:SetAttribute("CanSaveData", true)
return result
end
--simple retry mechanism
for i = 1, 5 do
if success then
cache[plr] = result
plr:SetAttribute("CanSaveData", true)
return result
end
success, result = pcall(function()
return store:GetAsync(tostring(plr.UserId))
end
end
warn(result)
-maybe kick plr
plr:SetAttribute("CanSaveData", false)
return {["Character"] = "default"}--default value
end)
plr.PlayerAdded:Connect(function(plr)
local result = load(plr)
plr:SetAttribute("Character", result["Character"])
end)
local function save(plr, data)
if not plr:GetAttribute("CanSaveData") then return end
cache[plr] = data
local success, result = pcall(function()
store:SetAsync(tostring(plr.UserId), data)
end)
if success then return end
for i=1, 5 do
if success then return end
success, result = pcall(function()
store:SetAsync(tostring(plr.UserId), data)
end)
end
warn(result)
end)
So that’s basically how you save and load data
And my post is becoming too long so I’ll end it here, hope this helps!
Edit: I typed this on mobile and now my hand hurts cus of the typing
I’ve changed the script to match what I think its supposed to match like. If I changed anything that I wasn’t supposed to, please let me know and correct me.
local players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local store = DataStoreService:GetDataStore("Characters")
local function save(plr)
if not plr:GetAttribute("CanSaveData") then return end
local success, result = pcall(function()
store:SetAsync("Character"..plr.UserId, plr:GetAttribute("Character"))
end)
if not success then
warn(result)
end
end
players.PlayerAdded:Connect(function(plr)
local success, result = pcall(function()
return store:GetAsync("Character"..plr.UserId)
end)
if not success then
warn(result)
--handle error
end
plr:SetAttribute("CanSaveData", success)
plr:SetAttribute("Character", success and result or "default value")
plr:GetAttributeChangedSignal("Character"):Connect(function()
local targetName = plr:GetAttribute("Character")
local char = "PlaceholderCharacter"
if not char then return end
local cframe = plr.Character and plr.Character.PrimaryPart.CFrame or CFrame.new()
plr.Character = char
char:PivotTo(cframe)
end)
end)
players.PlayerRemoving:Connect(save)
game:BindToClose(function()
for _, plr in ipairs(players:GetPlayers()) do
save(plr)
end
end)
also can you please tell me how I would change change the attribute? Thanks !
Uhh char is suppposed to be a model. If you set the Character you’d want to switch to the corresponding model right? So the string is I’ m assuming the name of the model, so you’d maybe look in replicatedstorage or wherever you store characters, for the character with the given name.
so
local char = someservice:FindFirstChild(targetName)
Also you forgot to change the default value to like a name of a character that you have
And you can set the attribute again using SetAttribute again.