ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 로블록스 코딩 실습 31 - 상점에 아이템 등록
    Roblox 코딩 2026. 1. 6. 21:58

    상점에 아이템 “등록(추가)”하는 구조 만들기: 상품 테이블 + 자동 UI 생성 + 서버 검증

    실습 30에서 상점을 “한 품목”으로 만들었다면, 실습 31에서는 상점이 실제로 운영 가능한 형태가 되도록 아이템 등록(상품 추가) 구조를 만든다.
    핵심 목표는 다음 3가지다.

    1. 상품 목록(PRODUCTS)에 아이템을 등록하면
    2. UI가 자동으로 버튼을 생성하고
    3. 서버는 등록된 상품만 구매 처리한다

    이 구조가 잡히면 이후에는 상품이 5개든 50개든 “등록만”으로 확장된다.


    0. 준비물 (30편 구성 그대로 사용)

    • ReplicatedStorage/ShopPurchase (RemoteEvent)
    • ServerStorage에 판매할 Tool 또는 지급 대상(예: Sword, Potion 등)
    • StarterGui/ShopGui (ScreenGui)
    • leaderstats.Coins 존재

    이번 실습에서는 UI 구조를 “목록형”으로 바꾼다.


    1. UI 구조 변경 (목록 자동 생성용)

    StarterGui/ShopGui에 아래처럼 구성한다.

    • ShopGui (ScreenGui)
      • Frame
        • Title (TextLabel)
        • Close (TextButton)
        • ItemList (ScrollingFrame) ← 상품 버튼이 들어갈 컨테이너
          • UIListLayout ← 세로 정렬
        • ItemButtonTemplate (TextButton) ← 템플릿 버튼 (Visible=false)

    중요 포인트

    • ItemButtonTemplate.Visible = false로 숨겨둔다
    • 코드가 템플릿을 Clone하여 ItemList에 붙인다

    2. “상품 등록” 데이터 구조 설계

    상품 등록의 중심은 클라이언트와 서버가 “같은 상품 목록”을 공유하는 것이다.
    가장 안전한 방식은 상품 목록을 ModuleScript로 만들고 ReplicatedStorage에 둬서 공유하는 방식이다.

    (1) ReplicatedStorage에 ModuleScript 생성

    이름: ShopCatalog

    -- ReplicatedStorage/ShopCatalog
    local ShopCatalog = {}
    
    -- 상품 등록은 여기서만 한다.
    -- id: 구매 식별자(중복 금지)
    -- name: UI 표시명
    -- price: 가격
    -- grant: 지급 방식(여기서는 Tool 지급)
    -- toolName: ServerStorage에 있는 Tool 이름
    
    ShopCatalog.Products = {
    	{
    		id = "Sword",
    		name = "Sword",
    		price = 50,
    		grant = "Tool",
    		toolName = "Sword",
    	},
    	{
    		id = "Shield",
    		name = "Shield",
    		price = 75,
    		grant = "Tool",
    		toolName = "Shield",
    	},
    	{
    		id = "SpeedBoots",
    		name = "Speed Boots (10s)",
    		price = 40,
    		grant = "Effect",
    		effectName = "SpeedBoost",
    		duration = 10,
    	},
    }
    
    return ShopCatalog
    

    이제 상점 아이템 등록은 이 파일에서 한 줄 추가로 끝난다.


    3. 실습 31의 핵심: 자동 UI 생성 (클라이언트)

    StarterPlayer > StarterPlayerScripts에 LocalScript 생성: ShopClient31

    local Players = game:GetService("Players")
    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    
    local player = Players.LocalPlayer
    
    local purchaseEvent = ReplicatedStorage:WaitForChild("ShopPurchase")
    local catalog = require(ReplicatedStorage:WaitForChild("ShopCatalog"))
    
    local shopGui = player:WaitForChild("PlayerGui"):WaitForChild("ShopGui")
    local frame = shopGui:WaitForChild("Frame")
    local closeBtn = frame:WaitForChild("Close")
    
    local itemList = frame:WaitForChild("ItemList")
    local templateBtn = frame:WaitForChild("ItemButtonTemplate")
    
    -- 초기화: 기존 생성 버튼 정리(템플릿 제외)
    local function clearItemButtons()
    	for _, child in ipairs(itemList:GetChildren()) do
    		if child:IsA("TextButton") then
    			child:Destroy()
    		end
    	end
    end
    
    -- UI 생성: 상품 등록 목록을 읽어 자동 버튼 생성
    local function buildShopUI()
    	clearItemButtons()
    
    	for _, product in ipairs(catalog.Products) do
    		local btn = templateBtn:Clone()
    		btn.Visible = true
    		btn.Name = "Item_" .. product.id
    		btn.Text = string.format("%s (%d)", product.name, product.price)
    		btn.Parent = itemList
    
    		btn.MouseButton1Click:Connect(function()
    			purchaseEvent:FireServer(product.id)
    		end)
    	end
    end
    
    -- 열고 닫기
    shopGui.Enabled = false
    
    local shopButton = workspace:WaitForChild("ShopButton")
    local clickDetector = shopButton:WaitForChild("ClickDetector")
    
    clickDetector.MouseClick:Connect(function(clickedPlayer)
    	if clickedPlayer ~= player then return end
    	buildShopUI()
    	shopGui.Enabled = true
    end)
    
    closeBtn.MouseButton1Click:Connect(function()
    	shopGui.Enabled = false
    end)
    

    이제 상품을 추가할 때 UI를 손댈 필요가 없다.
    ShopCatalog.Products에 등록만 하면 된다.


    4. 서버 구매 처리: “등록된 상품만” 처리하도록 검증 강화

    ServerScriptService에 Script 생성: ShopServer31

    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    local ServerStorage = game:GetService("ServerStorage")
    local Players = game:GetService("Players")
    
    local purchaseEvent = ReplicatedStorage:WaitForChild("ShopPurchase")
    local catalog = require(ReplicatedStorage:WaitForChild("ShopCatalog"))
    
    -- id로 상품 찾기
    local function findProduct(productId)
    	for _, product in ipairs(catalog.Products) do
    		if product.id == productId then
    			return product
    		end
    	end
    	return nil
    end
    
    local function getCoinsValue(player)
    	local leaderstats = player:FindFirstChild("leaderstats")
    	if not leaderstats then return nil end
    	return leaderstats:FindFirstChild("Coins")
    end
    
    local function grantTool(player, toolName)
    	local template = ServerStorage:FindFirstChild(toolName)
    	if not template then return end
    
    	local backpack = player:FindFirstChild("Backpack")
    	if not backpack then return end
    
    	local tool = template:Clone()
    	tool.Parent = backpack
    end
    
    local function grantEffect(player, effectName, duration)
    	-- 예시: SpeedBoost 효과
    	-- 캐릭터 Humanoid WalkSpeed를 duration 동안 증가
    	if effectName ~= "SpeedBoost" then return end
    
    	local character = player.Character
    	if not character then return end
    
    	local humanoid = character:FindFirstChildOfClass("Humanoid")
    	if not humanoid then return end
    
    	local original = humanoid.WalkSpeed
    	humanoid.WalkSpeed = original + 8
    
    	task.delay(duration, function()
    		-- 캐릭터가 바뀌거나 humanoid가 없을 수 있으므로 안전 체크
    		local c = player.Character
    		local h = c and c:FindFirstChildOfClass("Humanoid")
    		if h then
    			h.WalkSpeed = original
    		end
    	end)
    end
    
    purchaseEvent.OnServerEvent:Connect(function(player, productId)
    	-- 서버 검증 1: 등록된 상품인지 확인
    	local product = findProduct(productId)
    	if not product then
    		return
    	end
    
    	-- 서버 검증 2: 코인 존재/충분 여부
    	local coins = getCoinsValue(player)
    	if not coins then return end
    	if coins.Value < product.price then
    		return
    	end
    
    	-- 차감
    	coins.Value -= product.price
    
    	-- 지급
    	if product.grant == "Tool" then
    		grantTool(player, product.toolName)
    	elseif product.grant == "Effect" then
    		grantEffect(player, product.effectName, product.duration or 5)
    	end
    end)
    

    서버는 productId를 믿지 않고, 반드시 ShopCatalog.Products에서 조회해 처리한다.
    이 검증이 상점 시스템의 안전성을 결정한다.


    5. “아이템 등록” 방법 정리 (실습 31의 결론)

    상점에 아이템을 등록하는 절차는 다음 2단계로 고정된다.

    1) ServerStorage에 지급할 Tool 준비

    • ServerStorage/Sword
    • ServerStorage/Shield

    2) ReplicatedStorage/ShopCatalog에 상품 추가

    {
    	id = "Shield",
    	name = "Shield",
    	price = 75,
    	grant = "Tool",
    	toolName = "Shield",
    },
    

    끝이다.
    UI 버튼 생성과 구매 처리는 자동으로 따라온다.

     

    반응형
Designed by Tistory.