Saving items to an inventory

Are your skins groups of Accessories, Shirts, and Pants?
Do they live inside your game, or have you uploaded them to the website?

You can use an ID system to keep track of the skins, or you can use the skin’s unique name. If you choose to use IDs you can have multiple skins with the same name, but I usually opt to use the skin’s name as a key in my shops most of the time, because I know my shop will never contain two skins of the same name.

Here is a suggestion for how to structure your skin system.
Well, it’s a bit more of a guide than a suggestion

-- how to store the skin data, should be kept in a module or some globally accessible directory
-- server script:
local skins = {
    ["Robot"] = {
        pants_id = "1234567" -- content id for pants
        shirt_id = "1234567" -- content id for the shirt
        accessories = { -- an array of all content ids for relevant accessories
            12345,
            12345,
            12345, 
        },
        cost = 500, -- cost for the skin, subtract from wherever you store their currency
    },

    ["Army Soldier"] = 
        pants_id = "1234567"
        shirt_id = "1234567"
        accessories = {
            12345,
            12345,
            12345,
        },
    cost = 1000,
    },

-- etc
}
-- code to generate the skins, it should be able to access the skin data table above
-- server script
local InsertService = game:GetService("InsertService")

local function ClearSkin(character)
  for _,object in next,character:GetChildren() do
        local typesToRemove = {"Accoutrement","Accessory","Hat","Shirt","T-Shirt","Pants"}
        if table.find(typesToRemove,object.Class) then
            object:Destroy()
        end
    end
end

local function GenerateSkin(character,skinName)
    local skinData = skins[skinName]
    ClearSkin(character)

    local function GenerateShirt()
        local shirt = Instance.new("Shirt")
        shirt.ShirtTemplate = skinData.shirt
        shirt.Parent = character
    end

    local function GeneratePants()
        pants.PantsTemplate = skinData.pants
        pants.Parent = character
    end

    local function GenerateAccessories()
        for _,assetId in next,skinData.accessories do
            local success, model = pcall(InsertService.LoadAsset, InsertService, assetId)
            if success and model then
              accessory = model:GetChildren()[1]
              accessory.Parent = character
            else     
                -- this accessory could not be loaded for some reason
            end
    end

    GenerateShirt();GeneratePants();GenerateAccessories()
end

-- this server code will give players a skin when they join
game.Players.PlayerAdded:connect(function(player)
    player.CharacterAdded:connect(function(character)
        GenerateSkin(character)
    end)
end)
-- this server code controls saving/loading/making comparisons to a player's table of owned skins, it should be able to call the functions in the server code above
local DS = game:GetService("DataStoreService")
local SkinsData = DS:GetDataStore("SkinsData")

local sessionSkinsData = {} -- this table holds

local function LoadPlayerSkinsData(player)
  local playerSkinsData = SkinsData:GetAsync(player.UserId) -- you should pcall this but I am pressed for time writing, so please make your own optimizations :)
sessionSkinsData[player] = playerSkinsData
end

game.Players.PlayerAdded:connect(LoadPlayerSkinsData)

local function SavePlayerSkinsData(player)
    local playerSkinsData = sessionSkinsData[player]
    if playerSkinsData then
        SkinsData:SetAsync(player.UserId,playerSkinsData)
    end
    if not player:IsDescendantOf(game.Players) then -- they have left the game
       sessionSkinsData[player] = nil -- clear the key
    end
end

game:BindToClose(function() -- save when the game closes
    for _,player in next,game.Players:GetPlayers() do
        SavePlayerSkinsData(player)
    end
end)

game.Players.PlayerRemoving:connect(SavePlayerSkinsData)

local SkinEquip = Instance.new("RemoteEvent")
skinEquip.Name = "SkinEquip"
skinEquip.Parent = game.ReplicatedStorage
skinEquip.OnServerEvent:connect(function(player,skinName)
    local character = player.Character
        GenerateSkin(character,skinName)
    end
end)

local SkinPurchase = Instance.new("RemoteFunction")
skinPurchase.Name = "SkinPurchase"
skinPurchase.Parent = game.ReplicatedStorage
skinPurchase.OnServerInvoke = function(player,skinName)
    local skinData = skins[skinName]
  
   local ownsSkin = playerSkinsData[skinName]
   local playerCurrencyPointer -- define this, set to the amount of currency they have
   local canAfford = skinData.cost <= playerCurrency

   if not (ownsSkin or canAfford) then
       playerCurrencyPointer -= skinData.cost -- subtract the player's currency here, make sure you define this correctly!
       
       SkinsData:UpdateAsync(player.UserId,function()
            local newData =  sessionSkinsData[player]
            newData[skinName] = true
            return newData
       end)
   else
    return nil -- maybe throw an error message in the return so you know what to tell the client (i.e. cannot afford, already owns, etc)
end

-- this local code in conjunction with the Remotes above will allow players to change their skins or buy new ones.

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local skinPurchase = ReplicatedStorage:WaitForChild("SkinPurchase")
local skinEquip = ReplicatedStorage:WaitForChild("SkinEquip")

-- example purchase call
local result = skinPurchase:InvokeServer("exampleSkinName")
if result, errorMessage == true then
    -- we bought the skin successfully, update UI
    else
    -- we could not buy the skin
end

-- example equip call
skinEquip:FireServer("skinName")

As for visualizing skins in the shop, I recommend creating another event that returns information about whether or not a player owns a skin, and then displaying a different type of UI for equipping the skin if they own it, or purchasing it if they don’t, using the sessionSkinData table to avoid querying DataStores for that info.

This is just a general structure for how to equip skins, please pardon any errors, I only briefly reviewed the code. The biggest structural shortcoming of this system is ensuring that data saves and loads properly. I would highly recommend wrapping those calls inside of a pcall function where they are not already.

This guide is not completely comprehensive, as it relies on you to fetch the player’s currency information and update it correctly.

I would recommend using ProfileService by loleris to avoid all of these shortcomings, but again, I just wanted to show you what a general skin purchasing/equipping infrastructure should look like.

5 Likes