🛒 Shop System

Lag en komplett butikk hvor spillere kan bruke myntene sine til å kjøpe verktøy, tilbehør og andre gjenstander!

RemoteFunction Attributes Folder Clone() Humanoid.AddAccessory

🎯 Mål for dette systemet

  • Lage en butikk med en katalog av varer.
  • Vise varer i et UI med bilder, navn og priser.
  • La spillere kjøpe varer med myntene sine.
  • Gi varer til spillere (Tools, Accessories, Models).
  • Forhindre dobbeltkjøp av varer som kun kan kjøpes én gang.
Resultat: En fullt fungerende butikk hvor spillere kan bruke myntene sine!

📦 Komplett oppsett - Oversikt

Dette er strukturen vi skal bygge i dette systemet:

ReplicatedStorage ├─ ShopItems (Folder) │ ├─ Sword (Tool) │ │ └─ [Attributes: Price=100, DisplayName="Sverd", IconId=123456, OneTime=false] │ ├─ Hat (Accessory) │ │ └─ [Attributes: Price=50, DisplayName="Hatt", IconId=789012, OneTime=true] │ └─ Chair (Model) │ └─ [Attributes: Price=25, DisplayName="Stol", IconId=345678, OneTime=false] │ └─ ShopRemotes (Folder) ├─ GetCatalog (RemoteFunction) └─ Purchase (RemoteFunction) ServerScriptService └─ ShopServer (Script) StarterGui └─ ShopUI (ScreenGui) └─ LocalScript
Enkelt forklart: Server holder katalogen → klient henter og viser varer → spiller kjøper → server leverer varen!

🧱 Del 1: Opprett Shop Items

Først må vi lage varer som kan kjøpes i butikken.

1 Opprett ShopItems Folder

  1. Åpne ReplicatedStorage i Explorer.

    Høyreklikk ReplicatedStorage → Insert Object → Folder

    Navn: ShopItems

2 Opprett en Tool (Sverd)

  1. Legg til en Tool i ShopItems-folderen.

    Høyreklikk ShopItems → Insert Object → Tool

    Navn: Sword (dette blir ID for varen)

  2. Lag en Handle (Part) inne i Sword.

    VIKTIG: Den MÅ hete Handle (med stor H)

    Anchored: false

    CanCollide: false

    Size: 0.5, 4, 0.5 (lang og tynn som et sverd)

  3. Legg til Attributes til Sword:

    Price: 100 (heltall)

    DisplayName: Sverd (tekst)

    IconId: 123456 (valgfritt, bilde-ID)

    OneTime: false (bool, kan kjøpes flere ganger)

3 Opprett en Accessory (Hatt)

  1. Legg til en Accessory i ShopItems-folderen.

    Høyreklikk ShopItems → Insert Object → Accessory

    Navn: Hat

  2. Legg til Attributes til Hat:

    Price: 50

    DisplayName: Hatt

    IconId: 789012 (valgfritt)

    OneTime: true (kan kun kjøpes én gang)

4 Opprett en Model (Stol)

  1. Legg til en Model i ShopItems-folderen.

    Høyreklikk ShopItems → Insert Object → Model

    Navn: Chair

  2. Legg til en Part inne i Chair.

    Navn: Seat

    Anchored: true

    Size: 2, 0.5, 2

  3. Sett PrimaryPart til Seat:

    Høyreklikk Chair → Set Primary Part → Seat

  4. Legg til Attributes til Chair:

    Price: 25

    DisplayName: Stol

    IconId: 345678 (valgfritt)

    OneTime: false

⚠️ Viktig: Navnet på varen (Sword, Hat, Chair) blir ID-en som brukes for å kjøpe den. Hver vare må ha et unikt navn!

🧱 Del 2: Shop Server

Nå skal vi lage server-scriptet som håndterer kjøp og levering av varer.

1 Opprett ShopRemotes Folder

  1. Åpne ReplicatedStorage i Explorer.

    Høyreklikk ReplicatedStorage → Insert Object → Folder

    Navn: ShopRemotes

  2. Legg til en RemoteFunction i ShopRemotes.

    Høyreklikk ShopRemotes → Insert Object → RemoteFunction

    Navn: GetCatalog

  3. Legg til en annen RemoteFunction i ShopRemotes.

    Høyreklikk ShopRemotes → Insert Object → RemoteFunction

    Navn: Purchase

2 Opprett ShopServer Script

  1. Åpne ServerScriptService i Explorer.

    Høyreklikk ServerScriptService → Insert Object → Script

    Navn: ShopServer

  2. Lim inn følgende kode:
    -- ServerScriptService/ShopServer.lua
    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    local Players = game:GetService("Players")
    
    -- === FOLDERS & REMOTES ===
    local shopFolder = ReplicatedStorage:FindFirstChild("ShopItems")
    if not shopFolder then
    	shopFolder = Instance.new("Folder")
    	shopFolder.Name = "ShopItems"
    	shopFolder.Parent = ReplicatedStorage
    end
    
    local remotes = ReplicatedStorage:FindFirstChild("ShopRemotes")
    if not remotes then
    	remotes = Instance.new("Folder")
    	remotes.Name = "ShopRemotes"
    	remotes.Parent = ReplicatedStorage
    end
    
    local getCatalog = remotes:FindFirstChild("GetCatalog") :: RemoteFunction
    if not getCatalog then
    	getCatalog = Instance.new("RemoteFunction")
    	getCatalog.Name = "GetCatalog"
    	getCatalog.Parent = remotes
    end
    
    local purchaseFn = remotes:FindFirstChild("Purchase") :: RemoteFunction
    if not purchaseFn then
    	purchaseFn = Instance.new("RemoteFunction")
    	purchaseFn.Name = "Purchase"
    	purchaseFn.Parent = remotes
    end
    
    -- === UTILS ===
    local function ensureOwnedFolder(player: Player)
    	local owned = player:FindFirstChild("OwnedItems")
    	if not owned then
    		owned = Instance.new("Folder")
    		owned.Name = "OwnedItems"
    		owned.Parent = player
    	end
    	return owned
    end
    
    local function getWalletCoins(player: Player): IntValue?
    	local wallet = player:FindFirstChild("wallet")
    	if not wallet then return nil end
    	local coins = wallet:FindFirstChild("coins")
    	if not coins then return nil end
    	return coins :: IntValue
    end
    
    local function guessItemType(inst: Instance): string
    	if inst:IsA("Tool") then
    		return "Tool"
    	elseif inst:IsA("Accessory") then
    		return "Accessory"
    	elseif inst:IsA("Model") then
    		return "Model"
    	else
    		return "Other"
    	end
    end
    
    local function getIconFromAttributes(item: Instance): string?
    	local iconId = item:GetAttribute("IconId")
    	if typeof(iconId) == "number" and iconId > 0 then
    		return "rbxassetid://" .. iconId
    	end
    	return nil
    end
    
    local function buildCatalog()
    	local list = {}
    	for _, item in ipairs(shopFolder:GetChildren()) do
    		-- Krev unik Name per vare
    		local price = tonumber(item:GetAttribute("Price")) or 0
    		if price > 0 then
    			table.insert(list, {
    				id = item.Name, -- må være unik
    				displayName = item:GetAttribute("DisplayName") or item.Name,
    				price = price,
    				icon = getIconFromAttributes(item),
    				type = guessItemType(item),
    				oneTime = item:GetAttribute("OneTime") == true,
    			})
    		end
    	end
    	table.sort(list, function(a,b) return a.price < b.price end)
    	return list
    end
    
    local function alreadyOwned(player: Player, itemId: string): boolean
    	local owned = ensureOwnedFolder(player)
    	return owned:FindFirstChild(itemId) ~= nil
    end
    
    local function markOwned(player: Player, itemId: string)
    	local owned = ensureOwnedFolder(player)
    	if not owned:FindFirstChild(itemId) then
    		local flag = Instance.new("BoolValue")
    		flag.Name = itemId
    		flag.Value = true
    		flag.Parent = owned
    	end
    end
    
    local function giveItem(player: Player, template: Instance, itemType: string)
    	local character = player.Character
    	if itemType == "Tool" then
    		local clone = template:Clone()
    		clone.Parent = player:WaitForChild("Backpack")
    		return true, "Levert til ryggsekken."
    	elseif itemType == "Accessory" then
    		local clone = template:Clone()
    		local char = character or player.CharacterAdded:Wait()
    		local hum = char:WaitForChild("Humanoid")
    		-- Accessory må være Accessory
    		if clone:IsA("Accessory") then
    			hum:AddAccessory(clone)
    			return true, "Tilbehør utstyrt."
    		else
    			clone:Destroy()
    			return false, "Varen er ikke en Accessory."
    		end
    	elseif itemType == "Model" then
    		local clone = template:Clone()
    		local char = character or player.CharacterAdded:Wait()
    		local root = char:WaitForChild("HumanoidRootPart")
    		local putCF = root.CFrame * CFrame.new(0, 0, -4)
    		if clone.PrimaryPart == nil then
    			-- Prøv å sette PrimaryPart automatisk
    			for _, p in ipairs(clone:GetDescendants()) do
    				if p:IsA("BasePart") then
    					clone.PrimaryPart = p
    					break
    				end
    			end
    		end
    		if clone.PrimaryPart then
    			clone:PivotTo(putCF)
    		end
    		clone.Parent = workspace
    		return true, "Plassert foran spilleren."
    	else
    		return false, "Ukjent varetype."
    	end
    end
    
    -- === REMOTES ===
    getCatalog.OnServerInvoke = function(player: Player)
    	-- Kan legge på tilgangskontroll her om ønskelig
    	return buildCatalog()
    end
    
    purchaseFn.OnServerInvoke = function(player: Player, itemId: string)
    	if typeof(itemId) ~= "string" then
    		return { ok = false, message = "Ugyldig forespørsel." }
    	end
    
    	local template = shopFolder:FindFirstChild(itemId)
    	if not template then
    		return { ok = false, message = "Varen finnes ikke." }
    	end
    
    	local price = tonumber(template:GetAttribute("Price")) or 0
    	if price <= 0 then
    		return { ok = false, message = "Varen mangler pris." }
    	end
    
    	local oneTime = template:GetAttribute("OneTime") == true
    	if oneTime and alreadyOwned(player, itemId) then
    		return { ok = false, message = "Du eier allerede denne varen." }
    	end
    
    	local coins = getWalletCoins(player)
    	if not coins then
    		return { ok = false, message = "Fant ikke mynter hos spilleren." }
    	end
    
    	if coins.Value < price then
    		return { ok = false, message = "For lite Coins." }
    	end
    
    	-- Trekk og gi vare (transaksjon)
    	coins.Value -= price
    
    	local itemType = guessItemType(template)
    	local ok, msg = giveItem(player, template, itemType)
    	if not ok then
    		-- Rull tilbake coins om utdeling feiler
    		coins.Value += price
    		return { ok = false, message = "Kunne ikke levere: " .. (msg or "Ukjent feil") }
    	end
    
    	if oneTime then
    		markOwned(player, itemId)
    	end
    
    	return { ok = true, message = ("Kjøpt %s for %d Coins. %s"):format(template.Name, price, msg or "") }
    end
    
    -- Opprett OwnedItems for alle
    Players.PlayerAdded:Connect(function(plr)
    	ensureOwnedFolder(plr)
    end)
✅ Ferdig! Server-scriptet håndterer nå alle kjøp og leverer varer til spillere.

🧱 Del 3: Shop UI

Nå skal vi lage et brukergrensesnitt som viser butikken og lar spillere kjøpe varer.

1 Opprett ShopUI

  1. Åpne StarterGui i Explorer.

    Høyreklikk StarterGui → Insert Object → ScreenGui

    Navn: ShopUI

  2. Legg til en LocalScript i ShopUI.

    Høyreklikk ShopUI → Insert Object → LocalScript

  3. Lim inn følgende kode i LocalScript:
    -- StarterGui/ShopUI.client.lua  (LocalScript)
    local Players = game:GetService("Players")
    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    local player = Players.LocalPlayer
    
    local remotes = ReplicatedStorage:WaitForChild("ShopRemotes")
    local getCatalog = remotes:WaitForChild("GetCatalog") :: RemoteFunction
    local purchase = remotes:WaitForChild("Purchase") :: RemoteFunction
    
    -- ===== UI BY CODE =====
    local screen = Instance.new("ScreenGui")
    screen.Name = "AutoShopGui"
    screen.ResetOnSpawn = false
    screen.Parent = player:WaitForChild("PlayerGui")
    
    local openBtn = Instance.new("TextButton")
    openBtn.Name = "OpenShop"
    openBtn.Size = UDim2.fromOffset(140, 44)
    openBtn.Position = UDim2.new(0, 20, 1, -64)
    openBtn.AnchorPoint = Vector2.new(0,1)
    openBtn.TextScaled = true
    openBtn.Text = "🛒 Shop"
    openBtn.BackgroundColor3 = Color3.fromRGB(50, 50, 50)
    openBtn.TextColor3 = Color3.fromRGB(255,255,255)
    openBtn.Parent = screen
    
    local frame = Instance.new("Frame")
    frame.Name = "ShopFrame"
    frame.Size = UDim2.fromScale(0.38, 0.6)
    frame.Position = UDim2.new(0.5, 0, 0.5, 0)
    frame.AnchorPoint = Vector2.new(0.5,0.5)
    frame.BackgroundColor3 = Color3.fromRGB(22,22,22)
    frame.Visible = false
    frame.Parent = screen
    
    local uiCorner = Instance.new("UICorner", frame)
    uiCorner.CornerRadius = UDim.new(0,12)
    
    local title = Instance.new("TextLabel")
    title.Size = UDim2.new(1, -20, 0, 40)
    title.Position = UDim2.new(0, 10, 0, 8)
    title.BackgroundTransparency = 1
    title.Text = "Shop"
    title.TextXAlignment = Enum.TextXAlignment.Left
    title.TextScaled = true
    title.TextColor3 = Color3.new(1,1,1)
    title.Parent = frame
    
    local closeBtn = Instance.new("TextButton")
    closeBtn.Size = UDim2.fromOffset(36, 36)
    closeBtn.Position = UDim2.new(1, -46, 0, 8)
    closeBtn.BackgroundColor3 = Color3.fromRGB(60,60,60)
    closeBtn.Text = "X"
    closeBtn.TextScaled = true
    closeBtn.TextColor3 = Color3.new(1,1,1)
    closeBtn.Parent = frame
    Instance.new("UICorner", closeBtn).CornerRadius = UDim.new(0,8)
    
    local listHolder = Instance.new("Frame")
    listHolder.Name = "ListHolder"
    listHolder.Size = UDim2.new(1, -20, 1, -64)
    listHolder.Position = UDim2.new(0, 10, 0, 54)
    listHolder.BackgroundTransparency = 1
    listHolder.Parent = frame
    
    local scroller = Instance.new("ScrollingFrame")
    scroller.Size = UDim2.fromScale(1,1)
    scroller.CanvasSize = UDim2.new(0,0,0,0)
    scroller.ScrollBarThickness = 8
    scroller.BackgroundTransparency = 1
    scroller.Parent = listHolder
    
    local layout = Instance.new("UIGridLayout")
    layout.CellPadding = UDim2.fromOffset(10,10)
    layout.CellSize = UDim2.new(0.5, -10, 0, 100)
    layout.SortOrder = Enum.SortOrder.LayoutOrder
    layout.Parent = scroller
    
    local function createItemCard(item)
    	local card = Instance.new("Frame")
    	card.BackgroundColor3 = Color3.fromRGB(35,35,35)
    	card.Parent = scroller
    	Instance.new("UICorner", card).CornerRadius = UDim.new(0,10)
    
    	local nameLbl = Instance.new("TextLabel")
    	nameLbl.Size = UDim2.new(1, -110, 0, 36)
    	nameLbl.Position = UDim2.new(0, 10, 0, 8)
    	nameLbl.BackgroundTransparency = 1
    	nameLbl.TextXAlignment = Enum.TextXAlignment.Left
    	nameLbl.TextScaled = true
    	nameLbl.TextColor3 = Color3.new(1,1,1)
    	nameLbl.Text = item.displayName or item.id
    	nameLbl.Parent = card
    
    	local priceLbl = Instance.new("TextLabel")
    	priceLbl.Size = UDim2.new(1, -110, 0, 24)
    	priceLbl.Position = UDim2.new(0, 10, 0, 50)
    	priceLbl.BackgroundTransparency = 1
    	priceLbl.TextXAlignment = Enum.TextXAlignment.Left
    	priceLbl.TextScaled = true
    	priceLbl.TextColor3 = Color3.fromRGB(200,200,200)
    	priceLbl.Text = ("Pris: %d"):format(item.price)
    	priceLbl.Parent = card
    
    
    	local icon = Instance.new("ImageLabel")
    	icon.Size = UDim2.fromOffset(90, 90)
    	icon.Position = UDim2.new(1, -100, 0, 5)
    	icon.BackgroundTransparency = 1
    	icon.Parent = card
    	if item.icon then icon.Image = item.icon end
    
    	local buyBtn = Instance.new("TextButton")
    	buyBtn.Size = UDim2.fromOffset(90, 32)
    	buyBtn.Position = UDim2.new(1, -100, 1, -38)
    	buyBtn.BackgroundColor3 = Color3.fromRGB(80,160,80)
    	buyBtn.Text = "Kjøp"
    	buyBtn.TextScaled = true
    	buyBtn.TextColor3 = Color3.new(1,1,1)
    	buyBtn.Parent = card
    	Instance.new("UICorner", buyBtn).CornerRadius = UDim.new(0,6)
    
    	buyBtn.MouseButton1Click:Connect(function()
    		buyBtn.Active = false
    		buyBtn.AutoButtonColor = false
    		buyBtn.Text = "..."
    		local result = nil
    		local ok, err = pcall(function()
    			result = purchase:InvokeServer(item.id)
    		end)
    		if not ok then
    			result = { ok = false, message = "Feil: " .. tostring(err) }
    		end
    		buyBtn.Text = "Kjøp"
    		buyBtn.Active = true
    		buyBtn.AutoButtonColor = true
    
    		-- Liten tilbakemelding
    		local msg = Instance.new("TextLabel")
    		msg.Size = UDim2.new(1, -20, 0, 28)
    		msg.Position = UDim2.new(0, 10, 1, -32)
    		msg.BackgroundColor3 = result.ok and Color3.fromRGB(60,110,60) or Color3.fromRGB(110,60,60)
    		msg.TextColor3 = Color3.new(1,1,1)
    		msg.TextScaled = true
    		msg.Text = result.message or (result.ok and "Kjøp fullført!" or "Kjøp feilet")
    		msg.Parent = frame
    		Instance.new("UICorner", msg).CornerRadius = UDim.new(0,6)
    		task.delay(2, function() msg:Destroy() end)
    	end)
    end
    
    local function populate()
    	for _, c in ipairs(scroller:GetChildren()) do
    		if c:IsA("Frame") then c:Destroy() end
    	end
    	local catalog = {}
    	local ok, err = pcall(function()
    		catalog = getCatalog:InvokeServer()
    	end)
    	if not ok then
    		warn("Kunne ikke hente katalog:", err)
    		return
    	end
    	for _, item in ipairs(catalog) do
    		createItemCard(item)
    	end
    	-- Oppdater scroll-høyde
    	task.defer(function()
    		local contentSize = layout.AbsoluteContentSize
    		scroller.CanvasSize = UDim2.new(0, 0, 0, contentSize.Y + 20)
    	end)
    end
    
    openBtn.MouseButton1Click:Connect(function()
    	frame.Visible = not frame.Visible
    	if frame.Visible then
    		populate()
    	end
    end)
    
    closeBtn.MouseButton1Click:Connect(function()
    	frame.Visible = false
    end)
✅ Ferdig! Nå har du et komplett shop UI som viser alle varer og lar spillere kjøpe dem!

🧪 Test systemet

  1. Sørg for at du har Wallet og Coin System installert.
  2. Start spillet ved å trykke Play i Roblox Studio.
  3. Du skal se en "🛒 Shop"-knapp nede til venstre.
  4. Klikk på knappen for å åpne butikken.
  5. Du skal se alle varer du har laget med bilder, navn og priser.
  6. Prøv å kjøpe en vare (du trenger nok mynter!).
  7. Varen skal leveres til deg (Tool i ryggsekk, Accessory på hodet, eller Model foran deg).
  8. Prøv å kjøpe en vare med OneTime=true to ganger – det skal ikke være mulig.
🎉 Gratulerer! Du har nå et komplett shop system!

📚 Konsepter du har lært

  • RemoteFunction: To-veis kommunikasjon mellom klient og server.
  • Attributes: Egendefinerte data knyttet til objekter (Price, DisplayName, etc.).
  • Clone(): Lager en kopi av et objekt.
  • Humanoid.AddAccessory: Legger til tilbehør på en karakter.
  • UIGridLayout: Arrangerer UI-elementer i et rutenett.
  • ScrollingFrame: En container som kan scrolles.
  • pcall: Beskytter mot feil ved å kjøre kode trygt.
  • task.defer: Utfører kode i neste frame.

🚀 Neste steg

Nå som du har et shop system, kan du:

  • Legge til flere varer i butikken med forskjellige typer.
  • Lage spesielle tilbud eller rabatter.
  • Legge til kategorier for å organisere varer.
  • Lagre kjøpshistorikk i DataStore.
  • Lage en "My Items"-side som viser hva spilleren eier.