How would I code in a skin system?

Hello!

One of my main desires during the production of my TD game is for skins. I have currently been thinking, restarting and clearing my mind each day on how to make it. I have all the skins and my idea for accessing it has sort of been:

image

I can’t really think of any ways on how to do this. My TD game is in full working condition but its mainly just adding the skins.

Heres one of the scripts that manages the shop…

local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")

local Events = ReplicatedStorage:WaitForChild("Events")
local Modules = ReplicatedStorage:WaitForChild("Modules")
local Functions = ReplicatedStorage:WaitForChild("Functions")
local Remotes = ReplicatedStorage:WaitForChild("Remotes")

local database = DataStoreService:GetDataStore("teststore1")
local towers = require(Modules:WaitForChild("TowerShop"))

local MAX_SELECTED_TOWERS = 5

local data = {}

-- Load the players data
local function LoadData(player)
	local success = nil
	local playerData = nil
	local attempt = 1
	
	repeat
		success, playerData = pcall(function()
			return database:GetAsync(player.UserId)
		end)
		
		attempt += 1
		if not success then
			warn(playerData)
			task.wait()
		end
		
	until success or attempt == 3
	
	if success then
		print("Connection success")
		if not playerData then
			print("New player, giving default data")
			playerData = {
				["Bucks"] = 100,
				["SelectedTowers"] = {"Rookie"},
				["OwnedTowers"] = {"Rookie"},
			}
		end
		data[player.UserId] = playerData
	else
		warn("Unable to get data for player", player.UserId)
		player:Kick("There was a problem getting your data")
	end
end
Players.PlayerAdded:Connect(LoadData)

-- Save the players data
local function SaveData(player)
	if data[player.UserId] then
		local success = nil
		local playerData = nil
		local attempt = 1

		repeat
			success, playerData = pcall(function()
				return database:UpdateAsync(player.UserId, function()
					return data[player.UserId]
				end)
			end)

			attempt += 1
			if not success then
				warn(playerData)
				task.wait()
			end

		until success or attempt == 3

		if success then
			print("Data saved successfully")
		else
			warn("Unable to save data for", player.UserId)
		end
	else
		warn("No session data for", player.UserId)
	end
	
end
Players.PlayerRemoving:Connect(function(player)
	SaveData(player)
	data[player.UserId] = nil
end)

game:BindToClose(function()
	if not RunService:IsStudio() then
		for index, player in pairs(Players:GetPlayers()) do
			task.spawn(function()
				SaveData(player)
			end)
		end
	else
		print("Shutting down inside studio")
	end
end)

local function getItemStatus(player, itemName)
	local playerData = data[player.UserId]
	if table.find(playerData.SelectedTowers, itemName) then
		return "Equipped"
	elseif table.find(playerData.OwnedTowers, itemName) then
		return "Owned"
	else
		return "For Sale"
	end
end

Functions.InteractItem.OnServerInvoke = function(player, itemName, Unequip)
	local shopItem = towers[itemName]
	local playerData = data[player.UserId]
	if shopItem and playerData then
		local status = getItemStatus(player, itemName)
		
		if Unequip then
			if #playerData.SelectedTowers > 1 then
				local towerToRemove = table.find(playerData.SelectedTowers, itemName)
				table.remove(playerData.SelectedTowers, towerToRemove)
			end
			
		else
			if status == "For Sale" and shopItem.Price <= playerData.Bucks then
				-- purchase
				playerData.Bucks -= shopItem.Price
				table.insert(playerData.OwnedTowers, shopItem.Name)

			elseif status == "Owned" then
				-- equip the tower
				table.insert(playerData.SelectedTowers, shopItem.Name)

				if #playerData.SelectedTowers > MAX_SELECTED_TOWERS then
					table.remove(playerData.SelectedTowers, 1)
				end

			elseif status == "Equipped" then
				-- unselect the tower (if more than 1 selected)
				if #playerData.SelectedTowers > 1 then
					local towerToRemove = table.find(playerData.SelectedTowers, itemName)
					table.remove(playerData.SelectedTowers, towerToRemove)
				end
			end
		end
		return playerData
	else
		warn("Tower/player data does not exist")
	end

	return false
end

Functions.GetData.OnServerInvoke = function(player)	
	return data[player.UserId]
end

Events.SetBucks.Event:Connect(function(player, bucks)
	data[player.UserId].Bucks += bucks
end)

Events.RewardTower.Event:Connect(function(player, tower)
	if tower and player ~= nil then
		local OwnedTable = data[player.UserId].OwnedTowers
		table.insert(OwnedTable, tower)
	end
end)

(if you have any questions about the scripts for my game, go ahead and ask)

18 Likes

One of my possibilities as I also wanted to do something similar but with something else is to create a new datastore only for skins, and to move your skins in ServerStorage, and create multiple IntValues for each skin. if you dont have the skin bought, the int value will be 0, but if you own the skin, the int value will be 1. You can do this to all the skins. The default skins will have their intvalues to 1. If you have set it that you can change your skins only from the lobby, then you can set it to the script that “if SoldierGoldenSkinIntValue = 1 then SoldierModel = game.ServerStorage.SoldierGoldenSkinModel” or something like that. then you can import your skin database to the ingame rounds alongside with the “if intvalue” script, and there you go

this might sound weird and very hard to make as it requires to make an intvalue for every skin in the game, but you can modify. hope I helped you!

7 Likes

I genuinely like this idea! Would this involve duplicating the shop script and changing it?

Also, wouldn’t this be quite difficult since you have to go through each value?

5 Likes

you would be able to buy and equip skins only from the lobby, then the skins database of the values of the intvalues will be imported to ingame rounds, then in the ingame place you add the “if intvalue = 1 then” script, so that the skins will be equiped in round

7 Likes

hmmmm… ill see if this could work and ill start trying to code it.

5 Likes

I haven’t started yet, since im finishing off this UI

EDIT: I’m currently having difficulty starting it all of again, i’ll try to make something hopefully…

11 Likes

Hey, im having trouble starting it off since what ive done is copy and pasted my shop-code into a new script. and now im at this part and puzzled…

local function LoadData(player)
	local success = nil
	local playerData = nil
	local attempt = 1
	
	repeat
		success, playerData = pcall(function()
			return database:GetAsync(player.UserId)
		end)
		
		attempt += 1
		if not success then
			warn(playerData)
			task.wait()
		end
		
	until success or attempt == 3
	
	if success then
		print("Connection success")
		if not playerData then
			print("New player, giving default data")
			playerData = {
				["SelectedTowers"] = {"Rookie"},
				["OwnedTowers"] = {"Rookie"},
			}
		end
		data[player.UserId] = playerData
	else
		warn("Unable to get data for player", player.UserId)
		player:Kick("There was a problem getting your data")
	end
end

what would the data be?

6 Likes

the data should be a folder where all the intvalues would be stored

4 Likes

How would you tell which ones owned and which ones not?

2 Likes

the owned ones alongside the default ones will have the value of 1, and those that arent owned will have the value of 0

3 Likes

so something like this maybe?

SkinData > OwnedSkins > Rookie > Rookie.Value = “Skin1” > Rookie.Equipped.Value = true

SkinData > AllSkins > Rookie > Skin1 > Skin1.Value = 1

4 Likes

yeah or one alternative if you want is to make the intvalue values: 0 for unowned, 1 for owned but not equipped and 2 for owned and equipped

4 Likes

ive sort of given up, i don’t know how i can do it :pensive:

EDIT: Please give me some ideas on how to script this since my brain is just dead. no joke

6 Likes

Why not use boolean values for that case ? (instead of “if v == 1 then” it would be “if v then”)

5 Likes

If I was you I would use this architecture, a dictionnary :

local Skins = {
	["SkinName"] = {
		["Owned"] = false,
		["Equipped"] = false
	}
}

After that you only have to automatically create the table when adding a skin, if you have any trouble while doing that ask me !

6 Likes

ok right. I’ll try this out. tysm you are literally saving my braincells

7 Likes

Consider that you will have to send to client the dictionnary to update the shop and set his backpack I believe !

6 Likes

how would I check if theres no skins equipped when im doing this?

would it just be nil?

			playerData = {
				["Bucks"] = 100,
				["SelectedSkins"] = {"Rookie"},
				["OwnedTowers"] = {"Rookie"},
			}

(im copy and pasting the shop script, and thinking about how you make it have no skins)

6 Likes

Ok so the player can equip several skins ?

5 Likes

Theres different towers, and each of those towers can have skins made for them. meaning that there could be this:

Tower1 = Skin2
Tower2 = Skin3

However the skins can’t cross over so each tower will have skins made for them. so what I did was this:


	["Office"] = {
		["Owned"] = false,
		["Equipped"] = false,
		["MadeFor"] = "Rookie",
		["Price"] = 150,
	}

5 Likes