Data Saving using Custom Characters?


#1

I was working on my character customization system these days and finally I’ve came up with Data Stores, which is a really hard part for me, since I am not that great programmer. Anyways I’ve been using Custom Character (“StarterCharacter”) and placed him in StarterPlayer. Every hair and clothes are custom-made, so they are not ROBLOX assets or anything like that. What am I trying to do? I want to save my character’s clothes and hair using Data Store Service. How am I doing it? I simply made a “Save” button in a ScreenGUI which contains a local script. The following code is in this local script.
local Player = game.Players.LocalPlayer

    script.Parent.MouseButton1Click:Connect(function()
    	--Arrays
    	local Character = {}
    	
    	for _, v in pairs(Player.Character:GetChildren()) do
    	    if string.sub(v.Name, 1, 4) == "Clot" or string.sub(v.Name, 1, 4) == "Hair" then
    	        if v.ClassName == "Model" then
    		        table.insert(Character, v.Name)
    		        print(table.concat(Character, ' '))
    		    end
    	    end
    	end
    	game.ReplicatedStorage.Events.CharacterEvent:FireServer(Character)
    	print("Event is fired!")
    end) 

So, I am simply firing an event to the server, that contains everything that character is wearing currently. After that I am “receiving” that event using following DataStore script.

local DataStoreService = game:GetService("DataStoreService")
local PlayerData = DataStoreService:GetDataStore("CharacterData")
local stuff = game.ReplicatedStorage.Accessories

game.ReplicatedStorage.Events.CharacterEvent.OnServerEvent:Connect(function(player, data)
    pcall(function()
        PlayerData:SetAsync(player.UserId, function(old)
            local newdata = data or old
            return newdata
        end)
    end)
end)

game.Players.PlayerAdded:Connect(function(player)
	print("1. Player is added!")
    player.CharacterAdded:Connect(function(character)
	print("2. Character is added!")
        local data = PlayerData:GetAsync(player.UserId)
        print("3. Data Exists.")
        if data then
	          print("4. Data is here.")
              for _,accessory in pairs(stuff:GetChildren()) do
	            print("5. Finding accessories...")
                for _,wearing in pairs(PlayerData:GetAsync(player.UserId)) do
	                  print("6. Finding wearing..")
                      if wearing == accessory.Name then
	                    print("7. Wearing exists.")
		                local clone = accessory:Clone()
		                print("8. Cloning the accessory!")
		                clone.Parent = character
		                print("9. Clone is in the character.")
						if string.match(clone.Name, "Clot") then
						print("10. Welding clothes to character!")
                                            --Welding part
                        elseif string.match(clone.Name, "Hair") then
                        print("11. Welding hair to character.")
                         --Welding part
			            end
                    end
                end
            end
        end
    end)
end)

And here is the following output when player joins the game.

1

And here is the output again when player dies in the game.

2

For some reason my script doesn’t save my character’s accessories ( Clothes, Hair etc.). I made prints to guide me where does code ends. Looks like it can’t find items that character saved. Is there any way I can fix this?


#2

Did you use SetAsync or UpdateAsync?


#3

SetAsync, looking at the code


#4

I didn’t notice the event there, well you should change setasync to updateasync since it doesn’t support function calls.

Edit: Also, change the key to tostring(Player.userId) since you can’t user integers in the data store.

Edit 2: You’re probably not erroring because of the pcall.


#5

Your LocalScript isn’t the problem, it’s your server script. Specifically this line:

for _,accessory in pairs(stuff:GetChildren()) do
    print("5. Finding accessories...")
    for _,wearing in pairs(PlayerData:GetAsync(player.UserId)) do --> This here
        print("6. Finding wearing..")

I’d assume that the interval of what’s being returned from PlayerData:GetAsync(player.UserId) is nil (have you tried printing that out?) so it’s not looping through anything and therefore has no reason to pass any of the following code through.

You also should NOT be calling GetAsync in a loop like this: call it once and iterate through those results. Repeated calls of GetAsync is bad practice and a good way to unnecessarily waste your request budget. You already have a line that does the GetAsync right below CharacterAdded, local data = PlayerData:GetAsync(player.UserId), so just loop through this.

All in all, I think a rewrite may be necessary. I’m not going to do the rewriting, but there are several issues with your code.

  • Exploiters can pass whatever they want to the CharacterEvent and save new data. If any of this data is relevant to game play, you also open up the opportunity for exploiters to up and call the remote to save data they shouldn’t be.
  • Fetching data from a DataStore in CharacterAdded is bad practice. This again has to do with DataStore limitations. You should only poll for data once and then hold session data in a module, with methods to help you update the data. When a save is needed, fetch the session data and save it.
  • Your object hierarchy may potentially need to be redone, though I haven’t a clue what exactly your object hierarchy looks like.

#6

I’m pretty sure you can, since DataStore keys are coerced into strings. Using a UserId as a key should be fine. Though yeah, I do agree - tostring(Player.UserId) is great for keys.


#7
							if string.match(clone.Name, "Clot") then
							print("10. Welding clothes to character!")
							local LeftUpperArmPart = clone.LeftUpperArmPart
							local LowerTorsoPart = clone.LowerTorsoPart
							local RightUpperArmPart = clone.RightUpperArmPart
							local UpperTorsoPart = clone.UpperTorsoPart
							local Weld = Instance.new("Weld", UpperTorsoPart)
						    Weld.Part0 = UpperTorsoPart.PrimaryPart
						    Weld.Part1 = game.Players.LocalPlayer.Character:FindFirstChild("UpperTorso")
						    Weld.C0 = CFrame.new(0, 0, 0) *CFrame.new(0, 0, 0)
						
						    local Weld1 = Instance.new("Weld", LowerTorsoPart)
						    Weld1.Part0 = LowerTorsoPart.PrimaryPart
						    Weld1.Part1 = game.Players.LocalPlayer.Character:FindFirstChild("LowerTorso")
						    Weld1.C0 = CFrame.new(0, 0, 0) *CFrame.new(0, 0, 0)
						
						    local Weld2 = Instance.new("Weld", LeftUpperArmPart)
						    Weld2.Part0 = LeftUpperArmPart.PrimaryPart
						    Weld2.Part1 = game.Players.LocalPlayer.Character:FindFirstChild("LeftUpperArm")
						    Weld2.C0 = CFrame.new(0, 0, 0) *CFrame.new(0, 0, 0)
						
						    local Weld3 = Instance.new("Weld", RightUpperArmPart)
						    Weld3.Part0 = RightUpperArmPart.PrimaryPart
	--Line 40			    Weld3.Part1 = game.Players.LocalPlayer.Character:FindFirstChild("RightUpperArm")
						    Weld3.C0 = CFrame.new(0, 0, 0) *CFrame.new(0, 0, 0)
							elseif string.match(clone.Name, "Hair") then
							print("11. Welding hair to character.")
							local Head = clone	
						    local Weld = Instance.new("Weld", Head)
							Weld.Part0 = Head.PrimaryPart
							Weld.Part1 = game.Players.LocalPlayer.Character:FindFirstChild("Head")
							Weld.C0 = CFrame.new(0, 0, 0) *CFrame.new(0, 0, 0)
							elseif data == nil then
							print("Data doesn't exist.")

Looks like the code works, but the welding part doesn’t, which I didn’t mention on this post. I received an error in the output that says that LocalPlayer is a nil value in the line 40. However as you can see it passed through all codes that contains the same code as on the line 40. It’s really weird.


#8

You can’t get a player that way, if the welding is done on the server as it should.

local player = Players:FindFirstChild(playerName)
local character = player.Character or player.CharacterAdded:Wait()


#9

Oh yeah… I completely forgot.