Saving Model Properties

Hi, I’m making a Star Wars droid companion system where the primary, secondary and tertiary colours are fully customisable. The model is structured like this:
image

This is the current code I have, though it doesn’t seem to work and I just wanted to know if there was a better and actually functional way of doing this:

-- Services
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")

-- Droid Module
local DroidModule = require(ReplicatedStorage:FindFirstChild("Droids").DroidModule)

-- RemoteEvent
local DroidEvent = ReplicatedStorage:FindFirstChild("Droids").DroidEvent

-- Datastore
local DataStore = DataStoreService:GetDataStore("Development1")

-- Save Function
local function SaveData(Player, Character)
	local DroidName
	print("Data saved")
	for i,v in pairs(DroidModule) do
		if Character:FindFirstChild(v.Title) then
			DroidName = Character:FindFirstChild(v.Title)
		end
	end
	
	local DroidColours = {
		["DroidName"] = DroidName.Name,
		
		["PrimaryRed"] = DroidName.Body.Primary.Head.Color.R,
		["PrimaryGreen"] = DroidName.Body.Primary.Head.Color.G,
		["PrimaryBlue"] = DroidName.Body.Primary.Head.Color.B,
		
		["SecondaryRed"] = DroidName.Body.Secondary.Head.Color.R,
		["SecondaryGreen"] = DroidName.Body.Secondary.Head.Color.G,
		["SecondaryBlue"] = DroidName.Body.Secondary.Head.Color.B,
		
		["TertiaryRed"] = DroidName.Body.Tertiary.Head.Color.R,
		["TertiaryGreen"] = DroidName.Body.Tertiary.Head.Color.G,
		["TertiaryBlue"] = DroidName.Body.Tertiary.Head.Color.B,
	}
	
	DataStore:SetAsync(Player.UserId, function()
		return DroidColours
	end)
end

-- Load Function
local function LoadData(Player, Character)
	local DroidColours
	
	local success, err = pcall(function()
		DroidColours = DataStore:GetAsync(Player.UserId)
	end)
	
	if success then
		for i,v in ipairs(DroidColours) do
			local NewDroid = ReplicatedStorage.Droids:FindFirstChild(DroidColours[1]):Clone()
			
			for i,primary in (NewDroid.Body.Primary:GetChildren()) do
				primary.Color = Color3.fromRGB(DroidColours[2], DroidColours[3], DroidColours[4])
			end
			
			for i,secondary in (NewDroid.Body.Secondary:GetChildren()) do
				secondary.Color = Color3.fromRGB(DroidColours[5], DroidColours[6], DroidColours[7])
			end
			
			for i,tertiary in (NewDroid.Body.Secondary:GetChildren()) do
				tertiary.Color = Color3.fromRGB(DroidColours[8], DroidColours[9], DroidColours[10])
			end
			
			NewDroid.Parent = Character
		end
	end
	
end

-- Player
game.Players.PlayerAdded:Connect(function(Player)
	local Character = Player.Character or Player.CharacterAdded:Wait()
	
	LoadData(Player, Character)
	
end)

game.Players.PlayerRemoving:Connect(function(Player)
	local Character = Player.Character or Player.CharacterAdded:Wait()
	
	SaveData(Player, Character)
end)

Could you try printing the table after setting it?

Do you have a BindToClose function? When testing in studio it could be that the game closes before it had time to save.
Also it’s a lot more efficient to store your colors as hex instead of R G and B values

How would I convert it to hex values?

using the Color3:ToHex() method

1 Like

Printing doesn’t seem to work.

-- Save Function
local function SaveData(Player, Character)
	local DroidName
	print("Data saved")
	for i,v in pairs(DroidModule) do
		if Character:FindFirstChild(v.Title) then
			DroidName = Character:FindFirstChild(v.Title)
		end
	end
	
	local DroidColours = {
		["DroidName"] = DroidName.Name,
		
		["PrimaryRed"] = DroidName.Body.Primary.Head.Color.R,
		["PrimaryGreen"] = DroidName.Body.Primary.Head.Color.G,
		["PrimaryBlue"] = DroidName.Body.Primary.Head.Color.B,
		
		["SecondaryRed"] = DroidName.Body.Secondary.Head.Color.R,
		["SecondaryGreen"] = DroidName.Body.Secondary.Head.Color.G,
		["SecondaryBlue"] = DroidName.Body.Secondary.Head.Color.B,
		
		["TertiaryRed"] = DroidName.Body.Tertiary.Head.Color.R,
		["TertiaryGreen"] = DroidName.Body.Tertiary.Head.Color.G,
		["TertiaryBlue"] = DroidName.Body.Tertiary.Head.Color.B,
	}
	
	print(DroidColours.DroidName)
	
	DataStore:SetAsync(Player.UserId, function()
		return DroidColours
	end)
end

Print the whole table and check if anything is nil

Doesn’t print anything when saving data, though when loading data I get invalid argument #1 to 'ipairs' (table expected, got nil) for line for i,v in ipairs(DroidColours) do

Function:

-- Load Function
local function LoadData(Player, Character)
	local DroidColours
	
	local success, err = pcall(function()
		DroidColours = DataStore:GetAsync(Player.UserId)
	end)
	
	if success then
		for i,v in ipairs(DroidColours) do
			local NewDroid = ReplicatedStorage.Droids:FindFirstChild(DroidColours[1]):Clone()
			
			for i,primary in (NewDroid.Body.Primary:GetChildren()) do
				primary.Color = Color3.fromRGB(DroidColours[2], DroidColours[3], DroidColours[4])
			end
			
			for i,secondary in (NewDroid.Body.Secondary:GetChildren()) do
				secondary.Color = Color3.fromRGB(DroidColours[5], DroidColours[6], DroidColours[7])
			end
			
			for i,tertiary in (NewDroid.Body.Secondary:GetChildren()) do
				tertiary.Color = Color3.fromRGB(DroidColours[8], DroidColours[9], DroidColours[10])
			end
			
			NewDroid.Parent = Character
		end
	end
	
end

It should print, if it is not printing then your data is nil which means loading it will pass nil and will not pass a table with the data.

I rewrote the code to a much simpler version, I think it is an issue with defining Character when the player is leaving, any ideas?

-- DataStoreService
local DataStoreService = game:GetService("DataStoreService")
local DataStore = DataStoreService:GetDataStore("Development")

-- Services
local Players = game:GetService("Players")

-- Save Function
local function SaveData(Player, Character)
	local key = Player.UserId
	
	local data = {
		Player.Droids.Equipped.Value,
		Player.Droids.Owned.Value
	}
	
	local success, err = pcall(function()
		DataStore:SetAsync(key, data)
	end)
	
	if success then
		print("Data saved")
	end
end

game.Players.PlayerRemoving:Connect(function(Player)
	local Character = Player.CharacterRemoving:Wait()
	SaveData(Player, Character)
end)

game:BindToClose(function()
	for i,Player in (Players:GetPlayers()) do
		local Character = Player.CharacterRemoving:Wait()
		SaveData(Player, Character)
	end
end)

-- Load Function
local function LoadData(Player, Character)
	local key = Player.UserId
	local data
	
	local success, err = pcall(function()
		data = DataStore:GetAsync(key)
	end)
	
	if success and data then
		Player.Droids.Equipped.Value = data[1]
		Player.Droids.Owned.Value = data[2]
	end
	
	print(Character.Name)
end

game.Players.PlayerAdded:Connect(function(Player)
	local Character = Player.CharacterAdded:Wait()
	LoadData(Player, Character)
end)

No need for the character here.

-- DataStoreService
local DataStoreService = game:GetService("DataStoreService")
local DataStore = DataStoreService:GetDataStore("Development")

-- Services
local Players = game:GetService("Players")

-- Save Function
local function SaveData(Player)
	local key = Player.UserId
	
	local data = {
		Player.Droids.Equipped.Value,
		Player.Droids.Owned.Value
	}
	
	local success, err = pcall(function()
		DataStore:SetAsync(key, data)
	end)
	
	if success then
		print("Data saved")
	end
end

game.Players.PlayerRemoving:Connect(function(Player)
	SaveData(Player)
end)

game:BindToClose(function()
	for _, Player in pairs(Players:GetPlayers()) do
		SaveData(Player)
	end
end)

-- Load Function
local function LoadData(Player)
	local key = Player.UserId
	local data
	
	local success, err = pcall(function()
		data = DataStore:GetAsync(key)
	end)
	
	if success and data then
		Player.Droids.Equipped.Value = data[1]
		Player.Droids.Owned.Value = data[2]
	else
		warn("Failed to load data: " .. err)
	end
end

game.Players.PlayerAdded:Connect(function(Player)
	Player.CharacterAdded:Connect(function(Character)
		LoadData(Player)
	end)
end)

I understand, but for a much later version where it’ll save colours of parts inside the Character it’ll be necessary to use Character.

You could use Player.CharacterRemoving which will give you the Character but this will be triggered when the character respawns.