I have a default custom character (StarterCharacter), and in the game you can customize it. Like a character customization system. You can change the bodycolors, shirts, pants, etc… But, the problem is, leaving the game/resetting the character doesnt save your character, and loads you in as the default startercharacter.
Am I supposed to replicate it to the client or something? How would I do that?
I’ve been trying to fix this for so long, pls help me
Script:
local datastore = datastores:GetDataStore("DataStore")
local players = game:GetService("Players")
local properties = {"HeadColor", "LeftArmColor", "LeftLegColor", "RightArmColor", "RightLegColor", "TorsoColor"}
local playersData = {}
local function serializeData(character)
local data = {}
local bodyColors = character["Body Colors"]
for _, property in ipairs(properties) do
data[property] = bodyColors[property].Name
end
local shirt = character:FindFirstChild("Shirt")
if shirt then
data["Shirt"] = shirt.ShirtTemplate
end
local pants = character:FindFirstChild("Pants")
if pants then
data["Pants"] = pants.PantsTemplate
end
local shirtGraphic = character:FindFirstChild("ShirtGraphic")
if shirtGraphic then
data["ShirtGraphic"] = shirtGraphic.Graphic
end
return data
end
local function deserializeData(character, data)
local bodyColors = character["Body Colors"]
for _, property in ipairs(properties) do
bodyColors[property] = BrickColor.new(data[property])
end
if data["Shirt"] then
local shirt = character:FindFirstChild("Shirt")
if shirt then
shirt.ShirtTemplate = data["Shirt"]
else
shirt = Instance.new("Shirt")
shirt.Parent = character
shirt.ShirtTemplate = data["Shirt"]
end
end
if data["Pants"] then
local pants = character:FindFirstChild("Pants")
if pants then
pants.PantsTemplate = data["Pants"]
else
pants = Instance.new("Pants")
pants.Parent = character
pants.PantsTemplate = data["Pants"]
end
end
if data["ShirtGraphic"] then
local shirtGraphic = character:FindFirstChild("ShirtGraphic")
if shirtGraphic then
shirtGraphic.Graphic = data["ShirtGraphic"]
else
shirtGraphic = Instance.new("ShirtGraphic")
shirtGraphic.Parent = character
shirtGraphic.Graphic = data["ShirtGraphic"]
end
end
end
local function onPlayerAdded(player)
local function onCharacterRemoving(character)
local function onPlayerRemoving(plr)
if player == plr then
local data = serializeData(character)
local success, result = pcall(function()
return datastore:SetAsync("BodyColors_"..player.UserId, data)
end)
if success then
if result then
print(result)
end
else
warn(result)
end
end
playersData[plr] = nil
end
players.PlayerRemoving:Connect(onPlayerRemoving)
end
local function onCharacterLoaded(character)
local data = playersData[player]
if data then
deserializeData(character, data)
end
end
player.CharacterRemoving:Connect(onCharacterRemoving)
player.CharacterAppearanceLoaded:Connect(onCharacterLoaded)
local success, result = pcall(function()
return datastore:GetAsync("BodyColors_"..player.UserId)
end)
if success then
if result then
playersData[player] = result
end
else
warn(result)
end
end
players.PlayerAdded:Connect(onPlayerAdded)
I’m not getting any errors or warnings, but nothing is printing either
Also why is you doing bodyColors[property].Name instead of doing bodyColors[property]? Aren’t you just setting every color to the “Body Colors” which is the name?
Thank you! This does actually kinda save and load colors which is a huge step in the right direction. There is a new problem, though. When the player dies and respawns, the shirt/pants colors are removed and all the colors become the skin color. I’m pretty sure its due to this function:
The only datatypes you can save to a datastore are strings, numbers, and booleans. If OP just put bodyColors[property], it would store a value of the BrickColor data type. @mrfIuffywaffles, here’s a modified version of your script that I created:
local datastores = game:GetService("DataStoreService")
local datastore = datastores:GetDataStore("DataStore")
local players = game:GetService("Players")
local properties = {"HeadColor", "LeftArmColor", "LeftLegColor", "RightArmColor", "RightLegColor", "TorsoColor"}
local playersData = {}
local lastsavedChar = {}
local function serializeData(plr)
local data = playersData[plr] or {} -- check if player has data or create empty table
local character = lastsavedChar[plr] -- we reference the last saved character
local bodyColors = character["Body Colors"]
for _, property in ipairs(properties) do
data[property] = bodyColors[property].Name
end
local shirt = character:FindFirstChild("Shirt")
if shirt then
data["Shirt"] = shirt.ShirtTemplate
end
local pants = character:FindFirstChild("Pants")
if pants then
data["Pants"] = pants.PantsTemplate
end
local shirtGraphic = character:FindFirstChild("ShirtGraphic")
if shirtGraphic then
data["ShirtGraphic"] = shirtGraphic.Graphic
end
return data
end
local function deserializeData(player, data)
local character = lastsavedChar[player] -- we reference the last saved character
local bodyColors = character["Body Colors"]
for _, property in ipairs(properties) do
bodyColors[property] = BrickColor.new(data[property])
end
if data["Shirt"] then
local shirt = character:FindFirstChild("Shirt")
if shirt then
shirt.ShirtTemplate = data["Shirt"]
else
shirt = Instance.new("Shirt")
shirt.Parent = character
shirt.ShirtTemplate = data["Shirt"]
end
end
if data["Pants"] then
local pants = character:FindFirstChild("Pants")
if pants then
pants.PantsTemplate = data["Pants"]
else
pants = Instance.new("Pants")
pants.Parent = character
pants.PantsTemplate = data["Pants"]
end
end
if data["ShirtGraphic"] then
local shirtGraphic = character:FindFirstChild("ShirtGraphic")
if shirtGraphic then
shirtGraphic.Graphic = data["ShirtGraphic"]
else
shirtGraphic = Instance.new("ShirtGraphic")
shirtGraphic.Parent = character
shirtGraphic.Graphic = data["ShirtGraphic"]
end
end
serializeData(player)
end
local function onPlayerAdded(player)
local function onCharacterLoaded(character)
if (lastsavedChar[player]) then serializeData(player) end -- we check if this is the player's 2nd+ time respawning and if so, serialize the data from the old character for the new character to reference
local data = playersData[player]
lastsavedChar[player] = character -- set the new character
if data then
deserializeData(player, data)
end
end
player.CharacterAppearanceLoaded:Connect(onCharacterLoaded)
local success, result = pcall(function()
return datastore:GetAsync("BodyColor_"..player.UserId)
end)
if success then
playersData[player] = result
else
warn(result)
end
end
local function saveData(plr, attempt)
--saves the player's data and attempts 3 times if it errors
if (attempt ~= nil and attempt > 3) then return end -- reached 3 attempts: return to avoid continuing
local plrData = serializeData(plr)
if (plrData ~= nil) then
--<< Player's data exists: save the data >>--
local success, error = pcall(function()
return datastore:UpdateAsync("BodyColor_" .. plr.UserId, function(o_data) -- o_data is the previous version of the data
local prev_data = o_data or {} -- in case o_data is nil, set as default
print(plrData.Version, prev_data.Version)
print(plrData.Version == prev_data.Version)
if (plrData.Version == prev_data.Version) then -- we check if the versions of the data are the same
-- update this data to the current
plrData.Version = plrData.Version and plrData.Version + 1 or 1
print(plrData)
return plrData
else
-- do not update data, versions do not match
return nil -- returning nil prevents it from sending a write request
end
end)
end)
if (success) then
-- player's data has been saved!
print("data key: BodyColors_" .. plr.UserId .. " has been saved!")
playersData[plr] = nil
else
-- error occured, attempt to retry
warn(error)
saveData(plr, (attempt and attempt + 1 or 2)) -- add +1 to the current attempt or set as 2 if nil
end
end
end
players.PlayerAdded:Connect(onPlayerAdded)
players.PlayerRemoving:Connect(saveData)
game:BindToClose(function() -- in case of a shutdown, save the data of players before the server closes
local to_Save = 0
for _, plr in pairs(players:GetPlayers()) do
if (playersData[plr]) then to_Save += 1 end -- add +1 if the player has stored data
task.spawn(function()
saveData(plr)
to_Save -= 1 -- subtract 1 when data is finished
end)
end
while (to_Save > 0) do task.wait() end -- yield until all data has been saved
return -- shut the server down! all data has been saved!
end)
I added comments to (mostly) everything I changed - so feel free to look at that. Let me know if anything is wrong.
From the video, I see that you’re using a custom character instead of the player’s avatar. I did this myself and found that CharacterAppearanceLoaded does not trigger. No matter how many times I respawned, it never triggered. And to be honest, I don’t really know why. I replaced it with CharacterAdded and everything worked fine:
But also, I realized there were other issues other than just that. So I did some debugging and this is the final code I came up with (you can just replace everything with this):
local datastores = game:GetService("DataStoreService")
local datastore = datastores:GetDataStore("DataStore")
local players = game:GetService("Players")
local properties = {"HeadColor", "LeftArmColor", "LeftLegColor", "RightArmColor", "RightLegColor", "TorsoColor"}
local playersData = {}
local lastsavedChar = {}
local function serializeData(plr)
local data = playersData[plr] or {} -- check if player has data or create empty table
local character = lastsavedChar[plr] -- we reference the last saved character
local bodyColors = character["Body Colors"]
for _, property in ipairs(properties) do
data[property] = bodyColors[property].Name
end
local shirt = character:FindFirstChild("Shirt")
if shirt then
data["Shirt"] = shirt.ShirtTemplate
end
local pants = character:FindFirstChild("Pants")
if pants then
data["Pants"] = pants.PantsTemplate
end
local shirtGraphic = character:FindFirstChild("ShirtGraphic")
if shirtGraphic then
data["ShirtGraphic"] = shirtGraphic.Graphic
end
playersData[plr] = data -- set player's data as modified data table
return data
end
local function deserializeData(player, data)
local character = lastsavedChar[player] -- we reference the last saved character
local bodyColors = character["Body Colors"]
for _, property in ipairs(properties) do
bodyColors[property] = BrickColor.new(data[property])
end
if data["Shirt"] then
local shirt = character:FindFirstChild("Shirt")
if shirt then
shirt.ShirtTemplate = data["Shirt"]
else
shirt = Instance.new("Shirt")
shirt.Name = "Shirt"
shirt.Parent = character
shirt.ShirtTemplate = data["Shirt"]
end
end
if data["Pants"] then
local pants = character:FindFirstChild("Pants")
if pants then
pants.PantsTemplate = data["Pants"]
else
pants = Instance.new("Pants")
pants.Name = "Pants"
pants.Parent = character
pants.PantsTemplate = data["Pants"]
end
end
if data["ShirtGraphic"] then
local shirtGraphic = character:FindFirstChild("ShirtGraphic")
if shirtGraphic then
shirtGraphic.Graphic = data["ShirtGraphic"]
else
shirtGraphic = Instance.new("ShirtGraphic")
shirtGraphic.Name = "ShirtGraphic"
shirtGraphic.Parent = character
shirtGraphic.Graphic = data["ShirtGraphic"]
end
end
serializeData(player)
end
local function onPlayerAdded(player)
local function onCharacterLoaded(character)
if (lastsavedChar[player]) then serializeData(player) end -- we check if this is the player's 2nd+ time respawning and if so, serialize the data from the old character for the new character to reference
local data = playersData[player]
while (data == nil and player:IsDescendantOf(players)) do
data = playersData[player] -- we know that it will either be the player's data or an empty table
task.wait()
end
lastsavedChar[player] = character -- set the new character
if (next(data) ~= nil) then deserializeData(player, data) end -- we check if the dictionary is not empty and if it is not, then we deserialize that data
end
player.CharacterAdded:Connect(onCharacterLoaded)
local success, result = pcall(function()
return datastore:GetAsync("BodyColors_"..player.UserId)
end)
if success then
playersData[player] = result or {}
else
warn(result)
end
end
local function saveData(plr, attempt)
--saves the player's data and attempts 3 times if it errors
if (attempt ~= nil and attempt > 3) then return end -- reached 3 attempts: return to avoid continuing
local plrData = serializeData(plr)
if (plrData ~= nil) then
--<< Player's data exists: save the data >>--
local success, error = pcall(function()
return datastore:UpdateAsync("BodyColors_" .. plr.UserId, function(o_data) -- o_data is the previous version of the data
local prev_data = o_data or {} -- in case o_data is nil, set as default
if (plrData.Version == prev_data.Version) then -- we check if the versions of the data are the same
-- update this data to the current
plrData.Version = plrData.Version and plrData.Version + 1 or 1
return plrData
else
-- do not update data, versions do not match
return nil -- returning nil prevents it from sending a write request
end
end)
end)
if (success) then
-- player's data has been saved!
print("data key: BodyColors_" .. plr.UserId .. " has been saved!")
playersData[plr] = nil
else
-- error occured, attempt to retry
warn(error)
saveData(plr, (attempt and attempt + 1 or 2)) -- add +1 to the current attempt or set as 2 if nil
end
end
end
players.PlayerAdded:Connect(onPlayerAdded)
players.PlayerRemoving:Connect(saveData)
game:BindToClose(function() -- in case of a shutdown, save the data of players before the server closes
local to_Save = 0
for _, plr in pairs(players:GetPlayers()) do
if (playersData[plr]) then to_Save += 1 end -- add +1 if the player has stored data
task.spawn(function()
saveData(plr)
to_Save -= 1 -- subtract 1 when data is finished
end)
end
while (to_Save > 0) do task.wait() end -- yield until all data has been saved
return -- shut the server down! all data has been saved!
end)
Thank you so much! this works like a charm. I’ve been struggling with this issue for days now, I really appreciate it. I’ve been looking at the changes you made and I am learning how it all works. I hope you have a great day, thanks again