How to serialize avatars?

I’m trying to make a game where you can upload your current avatar and it will be saved (even if the player changes it in the future) meaning that I cannot just import the avatar from Roblox servers.

I tried just cloning the model and importing that into a datastore but obviously that didn’t work. How would I go about serializing a character model, including all of the hats/gear and everything

2 Likes

Assuming the character model does not have anything placed on it by the game iteself, then you can just use HumanoidDescription. Just get all the values in the HumanoidDescription in a table and save it.

2 Likes

You can use Humanoid:GetAppliedDescription() and upload that into a datastore with a random number as the key.

1 Like

Is there an easy way to loop through all of the properties in a HumanoidDescription

1 Like

No and yes. You can make a table with all the properties and use for I loops to get each property of that Description?

I wrote down the properties here so just use them in a table? There may be another better way but that’s all that I can think of currently.

local humanoid_descriptionProperties = {
"AccessoryBlob",
"BackAccessory",
"BodyTypeScale",
"ClimbAnimation",
"DepthScale",
"Face",
"FaceAccessory",
"FallAnimation",
"FrontAccessory",
"GraphicTShirt",
"HairAccessory",
"HatAccessory",
"Head",
"HeadColor",
"HeadScale",
"HeightScale",
"IdleAnimation",
"JumpAnimation",
"LeftArm",
"LeftArmColor",
"LeftLeg",
"LeftLegColor",
"MoodAnimation",
"NeckAccessory",
"Pants",
"ProportionScale",
"RightArm",
"RightArmColor",
"RightLeg",
"RightLegColor",
"RunAnimation",
"Shirt",
"ShouldersAccessory",
"SwimAnimation",
"Torso",
"TorsoColor",
"WaistAccessory",
"WalkAnimation",
"WidthScale"
}

-- Loop through these properies.
-- Make another table and set the values like a dictionary to serialize them.
-- For more optimization, I would suggest to either remove some properties or just see if any property is nil then not set it.
-- To deserialize, just loop through this again.

I am sorry to burst your bubble but Humanoid | Documentation - Roblox Creator Hub. (:GetAppliedDescription() returns a HumanoidDescription which isn’t compatible with datastores.)

4 Likes

Painful but thank you

30 =-=-=-=

1 Like

It won’t be too painful as I have mentioned steps on how you could do it.

2 Likes

You can also use this:

1 Like

It’s not letting me upload this as a datastore even though I’m positive it’s all numeric values and strings in arrays/dictionaries

ReplicatedStorage.SendAvatar.OnServerEvent:Connect(function(player)
	local mii = player.Character:FindFirstChild("Humanoid"):GetAppliedDescription()
	mii.Parent = ServerStorage

	local humanoidDescriptionProperties = {
		"AccessoryBlob",
		"BackAccessory",
		"BodyTypeScale",
		"ClimbAnimation",
		"DepthScale",
		"Face",
		"FaceAccessory",
		"FallAnimation",
		"FrontAccessory",
		"GraphicTShirt",
		"HairAccessory",
		"HatAccessory",
		"Head",
		"HeadColor",
		"HeadScale",
		"HeightScale",
		"IdleAnimation",
		"JumpAnimation",
		"LeftArm",
		"LeftArmColor",
		"LeftLeg",
		"LeftLegColor",
		"MoodAnimation",
		"NeckAccessory",
		"Pants",
		"ProportionScale",
		"RightArm",
		"RightArmColor",
		"RightLeg",
		"RightLegColor",
		"RunAnimation",
		"Shirt",
		"ShouldersAccessory",
		"SwimAnimation",
		"Torso",
		"TorsoColor",
		"WaistAccessory",
		"WalkAnimation",
		"WidthScale"
	}

	local miiInfo = {}

	for i = 1, #humanoidDescriptionProperties do
		pcall(function()
			--print(humanoidDescriptionProperties[i])
			--print(mii[humanoidDescriptionProperties[i]])
			miiInfo[humanoidDescriptionProperties[i]] = mii[humanoidDescriptionProperties[i]]
		end)
	end
	for key, value in pairs(miiInfo) do
		print(key, value)
	end

	table.insert(miiList, miiInfo)
	
	ServerStorage.TotalAvatars.Value += 1
	gameStats = {
		ServerStorage.TotalAvatars.Value
	}
	
	dataStore:SetAsync("game", gameStats)
	dataStore:SetAsync("list", miiList)
	
	dataStore:GetAsync("list")
	for k, v in pairs(miiList) do
		print(k,v)
	end
end)
1 Like

That’s a common issue with datastores. You can use HTTPService:JSonEncode() to send the data and then later on decode it using HTTPService:JSonDecode(). This is usually because DataStores face a problem saving Dictionaries.

2 Likes

New problem: [“HeadColor”] and other Color3 properties are saving as nil whenever I’m trying to add them to the dictionary.

print(mii[humanoidDescriptionProperties[i]]) returns (0.972579, 0.972579, 0.972579)
but print(miiList[i]["HeadColor"]) returns nil

1 Like

just found my old module from my avatar editor game

i didnt consider converting emotes in this module cuz i have a custom made emote system in my game before but if you really wish to also convert the emotes you can get the emotes from using :GetEmotes() on humanoiddesc i believe

local Module = {}

Module.HumanoidDescriptionProperties = {
	"BackAccessory", "FaceAccessory", "FrontAccessory", "HairAccessory", "HatAccessory", "NeckAccessory", "ShouldersAccessory", "WaistAccessory",
	"ClimbAnimation", "FallAnimation", "IdleAnimation", "JumpAnimation", "MoodAnimation", "RunAnimation", "SwimAnimation", "WalkAnimation",
	"HeadColor", "LeftArmColor", "LeftLegColor", "RightArmColor", "RightLegColor", "TorsoColor",
	"BodyTypeScale", "DepthScale", "HeadScale", "HeightScale", "ProportionScale", "WidthScale",
	"Face", "Head", "LeftArm", "LeftLeg", "RightArm", "RightLeg", "Torso",
	"GraphicTShirt", "Pants", "Shirt",
}

function Module:HumanoidDescriptionToTable(HumanoidDescription:HumanoidDescription):{string}
	local HumanoidDescriptionData = {}
	HumanoidDescriptionData.LayeredClothing = {}
	for Index, Value in ipairs(Module.HumanoidDescriptionProperties) do
		if HumanoidDescription[Value] then
			if table.find({"HeadColor", "LeftArmColor", "LeftLegColor", "RightArmColor", "RightLegColor", "TorsoColor"}, Value) then
				HumanoidDescriptionData[Value] = {R = HumanoidDescription[Value].R * 255, G = HumanoidDescription[Value].G * 255, B = HumanoidDescription[Value].B * 255}
			else
				HumanoidDescriptionData[Value] = HumanoidDescription[Value]
			end
		end
	end
	for Index, Value in ipairs(HumanoidDescription:GetAccessories(false)) do
		table.insert(HumanoidDescriptionData.LayeredClothing, {
			AccessoryType = Value.AccessoryType.Name,
			AssetId = Value.AssetId,
			Order = Value.Order,
		})
	end
	return HumanoidDescriptionData
end

function Module:TableToHumanoidDescription(HumanoidDescriptionData:{string}):HumanoidDescription
	local HumanoidDescription = Instance.new("HumanoidDescription")
	for Index, Value in ipairs(Module.HumanoidDescriptionProperties) do
		if HumanoidDescriptionData[Value] then
			if table.find({"HeadColor", "LeftArmColor", "LeftLegColor", "RightArmColor", "RightLegColor", "TorsoColor"}, Value) then
				HumanoidDescription[Value] = Color3.fromRGB(HumanoidDescriptionData[Value].R, HumanoidDescriptionData[Value].G, HumanoidDescriptionData[Value].B)
			else 
				HumanoidDescription[Value] = HumanoidDescriptionData[Value]
			end
		end
	end
	local LayeredClothingData = {}
	for Index, Value in ipairs(HumanoidDescriptionData.LayeredClothing) do
		table.insert(LayeredClothingData, {
			AccessoryType = Enum.AccessoryType[Value.AccessoryType],
			AssetId = Value.AssetId,
			Order = Value.Order
		})
	end	
	HumanoidDescription:SetAccessories(LayeredClothingData, false)
	return HumanoidDescription
end

function Module:CheckWearingClothes(Character:Model)
	local Humanoid = Character:FindFirstChild("Humanoid")
	if Humanoid then
		local ShirtNeeded = true
		local PantsNeeded = true

		local HumanoidDescription = Humanoid:GetAppliedDescription()

		for Index, Value in ipairs(HumanoidDescription:GetAccessories(false)) do
			if Value.AccessoryType == Enum.AccessoryType.DressSkirt then
				ShirtNeeded = false
				PantsNeeded = false
			elseif Value.AccessoryType == Enum.AccessoryType.Pants then
				PantsNeeded = false
				ShirtNeeded = false
			elseif Value.AccessoryType == Enum.AccessoryType.Shorts then
				PantsNeeded = false
				ShirtNeeded = false
			elseif Value.AccessoryType == Enum.AccessoryType.Shirt then
				ShirtNeeded = false
			elseif Value.AccessoryType == Enum.AccessoryType.TShirt then
				ShirtNeeded = false
			elseif Value.AccessoryType == Enum.AccessoryType.Sweater then
				ShirtNeeded = false
			elseif Value.AccessoryType == Enum.AccessoryType.Jacket then
				ShirtNeeded = false			
			end
		end

		if ShirtNeeded and HumanoidDescription.Shirt ~= 0 then
			ShirtNeeded = false
		end
		if PantsNeeded and HumanoidDescription.Pants ~= 0 then
			PantsNeeded = false
			ShirtNeeded = false
		end

		if PantsNeeded then
			local function ColorSimilarity(Color1, Color2)
				local R1, G1, B1 = Color1.R, Color1.G, Color1.B
				local R2, G2, B2 = Color2.R, Color2.G, Color2.B
				return math.sqrt((R1 - R2)^2 + (G1 - G2)^2 + (B1 - B2)^2)
			end
			if ColorSimilarity(HumanoidDescription.TorsoColor, HumanoidDescription.LeftLegColor) >= 0.125 and ColorSimilarity(HumanoidDescription.TorsoColor, HumanoidDescription.RightLegColor) >= 0.125 then
				PantsNeeded = false
				ShirtNeeded = false
			end
		end

		for Index, Value in ipairs(Character:GetChildren()) do
			if Value:IsA("Shirt") then
				if ShirtNeeded then
					Value:Destroy()
				elseif Value.Name == "DefaultShirt" then
					Value:Destroy()
				end
			end
			if Value:IsA("Pants") then
				if PantsNeeded then
					Value:Destroy()
				elseif Value.Name == "DefaultPants" then
					Value:Destroy()
				end
			end
		end

		if ShirtNeeded then
			local DefaultShirt = Instance.new("Shirt")
			DefaultShirt.Name = "DefaultShirt"
			DefaultShirt.Parent = Character
			DefaultShirt.ShirtTemplate = "http://www.roblox.com/asset/?id=855768337"
		end
		if PantsNeeded then
			local DefaultPants = Instance.new("Pants")
			DefaultPants.Name = "DefaultPants"
			DefaultPants.Parent = Character
			DefaultPants.PantsTemplate = "http://www.roblox.com/asset/?id=867830078"
		end
	end	
end

return Module
3 Likes

This is usually because DataStores can’t save Color3 values. You have to store them in a string or a table instead.

local function SerializeColor(Color)
	return {R = Color.R,G = Color.G,B = Color.B}
end
local function DeserializeColor(Color:{R:number,G:number,B:number})
	return Color3.new(Color.R,Color.G,Color.B)
end
1 Like

Just a little note for your CheckWearingClothes function, you can also use AvatarEditorService’s CheckApplyDefaultClothing method, it is a bit more dynamic in a sense and tries to capture all use cases i.e. I’ve noticed if a user has a beach wear it would be a bit more allowing if appropriate.

2 Likes

i was aware of that service having that kind of fuction almost a year ago, it was my old module having 3k lines

edit: basically i just extracted it on my module having 3k lines i didnt meant to add that extra function

i made that function cuz i mainly only using http on my avatar editor before to get catalog items and didnt know about that avatar service

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.