How to show gui frame with description and statistics?

I am creating a Tower Defense game, I have a storage system and I want that when I select a certain tower, I have a frame with a description and statistics that corresponds to the selected tower.

It should be like this
image

But when I click on another tower, the frames begin to layer on top of each other and create even more copies.

function updateItems()
	StatsGui.MainFrame.Coins.TextLabel.Text = playerData.Coins
	StatsGui.MainFrame.Towers.TextLabel.Text = #playerData.SelectedTowers.."/5"
	
	for i, tower in pairs(towers) do

		local oldButton = CoinTowerList:FindFirstChild(tower.Name)
		if oldButton then
			oldButton:Destroy()
		end
		
		local oldDesc = TowerDescFrame:FindFirstChild(tower.Name)
		if oldDesc then
			oldDesc:Destroy(tower.Name)
		end
		
		local newButton = CoinTowerList.TowerTemplate:Clone()
		newButton.Name = tower.Name
		newButton.TowerName.Text = tower.Name
		newButton.TowerImage.Image = tower.ImageAsset
		newButton.Visible = true
		newButton.LayoutOrder = tower.Price
		newButton.Parent = CoinTowerList
		
		
		local newDescriptionTower = TowerDescFrame.Frame2:Clone()
		newDescriptionTower.Name = tower.Name
		newDescriptionTower.TowerName.Text = tower.Name
		newDescriptionTower.ImageLabel.Image = tower.ImageAsset
		newDescriptionTower.ScrollingFrame.Description.StatsText.Text = tower.Description
		newDescriptionTower.ScrollingFrame.Stats.CooldownText.Text = tower.Cooldown
		newDescriptionTower.ScrollingFrame.Stats.DamageText.Text = tower.Damage
		newDescriptionTower.ScrollingFrame.Stats.RangeText.Text = tower.Range
		newDescriptionTower.ScrollingFrame.Stats.MoneyText.Text = tower.MoneyPrice
		newDescriptionTower.Visible = false
		newDescriptionTower.Parent = gui.MainShopFrame.ShopFrame.ShopDesc.TowerDesc.Frame
		
		local status = getItemStatus(tower.Name)
		if status == "For Sale" then
			newButton.TowerImage.Price.Text = tower.Price
			newButton.TowerImage.CoinIcon.Image = "rbxassetid://"
		elseif status == "Equipped" then
			newButton.TowerImage.Price.Text = "Equipped"
			newDescriptionTower.TextButton.TextLabel.Text = "Equipped"
			newButton.TowerImage.CoinIcon.Image = "rbxassetid://"
			newButton.TowerImage.Price.PriceGradient.Enabled = false
			newButton.TowerImage.Price.EquippedGradient.Enabled = true
		else
			newButton.TowerImage.Price.Text = ""
			newButton.TowerImage.CoinIcon.Visible = false
		end
		
		newButton.Activated:Connect(function()
			interactItem(tower.Name)
			if newDescriptionTower.Name == newButton.Name then
				newDescriptionTower.Visible = true
			end
		end)
	end
end

Is it possible to make it so that when a certain tower is selected, only one frame with a description and statistics is created that corresponds to the selected tower, and then deleted if another tower was selected?

2 Likes

There is a property called ZIndex. I highly recommend trying to use it. If that doesn’t work, post here.

Another solution to try is create a variable called currentStatsFrame for your currently selected frame. If another tower is selected, simply call :Destroy() if currentStatsFrame ~= nil. (Make sure to assign it with :Copy, for example currentStatsFrame = towerStatsFrame:Copy())

I know about ZIndex, but that’s not what I need. As I described earlier, I need that when I select a tower, I have only one frame corresponding to the selected tower, which will exist until another tower is selected.

Could you post the full code?
For example “interactItem” isn’t in your current code.

local function interactItem(itemName)
	local data = interactItemFunc:InvokeServer(itemName)
	if data then
		playerData = data
		updateItems()
	end
end

I assume updateItems() is called once?

Not only that, there is also a toggleShop function

local function toggleShop(player)
	gui.MainShopFrame.Visible = not gui.MainShopFrame.Visible
	if gui.MainShopFrame.Visible then
		playerData = getDataFunc:InvokeServer()
		updateItems()
	end
end

Please post the full code for everything related to the TowerGUI, without it we can’t really help you since new related code keeps popping up. But i think i see the issue, give me a moment.

EDIT:
Could you adjust your oldDesc code to this?
Maybe its used incorrectly.

local oldDesc = TowerDescFrame:FindFirstChild(tower.Name)
if oldDesc then
	oldDesc:Destroy()
end

I indicated everything that is responsible for the interface with towers at the beginning of the topic, but if it’s easier, then here’s the full code

local ReplicatedStorage = game:GetService('ReplicatedStorage')
local towers = require(ReplicatedStorage:WaitForChild("TowerShop"))

local getDataFunc = ReplicatedStorage.RemoteFunctions:WaitForChild("GetData")
local interactItemFunc = ReplicatedStorage.RemoteFunctions:WaitForChild("InteractItem")

local gui = script.Parent
local exit = gui.MainShopFrame.ShopFrame.CloseButton
local menu = gui.MainShopFrame.ShopFrame.ShopMenuButton

local CoinTowerList = gui.MainShopFrame.ShopFrame.ShopDesc.BuyList.ScrollingFrame.CoinTowers.TowerList
local TowerDescFrame = gui.MainShopFrame.ShopFrame.ShopDesc.TowerDesc.Frame

local shopPromt = workspace.ShopZone.TowerShop.VenoxDevs:WaitForChild("Head")
local StatsGui = gui.StatsGui

local selectedTower = nil

local playerData = {}

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

local function interactItem(itemName)
	local data = interactItemFunc:InvokeServer(itemName)
	if data then
		playerData = data
		updateItems()
	end
end

function updateItems()
	StatsGui.MainFrame.Coins.TextLabel.Text = playerData.Coins
	StatsGui.MainFrame.Towers.TextLabel.Text = #playerData.SelectedTowers.."/5"
	
	for i, tower in pairs(towers) do

		local oldButton = CoinTowerList:FindFirstChild(tower.Name)
		if oldButton then
			oldButton:Destroy()
		end
		
		local oldDesc = TowerDescFrame:FindFirstChild(tower.Name)
		if oldDesc then
			--oldDesc:Destroy(tower.Name)
		end
		
		local oldDesc2 = TowerDescFrame.Parent.FrameOld:FindFirstChild(tower.Name)
		if oldDesc2 then
			oldDesc2:Destroy(tower.Name)
		end
		
		local newButton = CoinTowerList.TowerTemplate:Clone()
		newButton.Name = tower.Name
		newButton.TowerName.Text = tower.Name
		newButton.TowerImage.Image = tower.ImageAsset
		newButton.Visible = true
		newButton.LayoutOrder = tower.Price
		newButton.Parent = CoinTowerList
		
		
		local newDescriptionTower = TowerDescFrame.Frame2:Clone()
		newDescriptionTower.Name = tower.Name
		newDescriptionTower.TowerName.Text = tower.Name
		newDescriptionTower.ImageLabel.Image = tower.ImageAsset
		newDescriptionTower.ScrollingFrame.Description.StatsText.Text = tower.Description
		newDescriptionTower.ScrollingFrame.Stats.CooldownText.Text = tower.Cooldown
		newDescriptionTower.ScrollingFrame.Stats.DamageText.Text = tower.Damage
		newDescriptionTower.ScrollingFrame.Stats.RangeText.Text = tower.Range
		newDescriptionTower.ScrollingFrame.Stats.MoneyText.Text = tower.MoneyPrice
		newDescriptionTower.Visible = false
		newDescriptionTower.Parent = gui.MainShopFrame.ShopFrame.ShopDesc.TowerDesc.Frame
		
		local status = getItemStatus(tower.Name)
		if status == "For Sale" then
			newButton.TowerImage.Price.Text = tower.Price
			newButton.TowerImage.CoinIcon.Image = "rbxassetid://"
		elseif status == "Equipped" then
			newButton.TowerImage.Price.Text = "Equipped"
			newDescriptionTower.TextButton.TextLabel.Text = "Equipped"
			newButton.TowerImage.CoinIcon.Image = "rbxassetid://"
			newButton.TowerImage.Price.PriceGradient.Enabled = false
			newButton.TowerImage.Price.EquippedGradient.Enabled = true
		else
			newButton.TowerImage.Price.Text = ""
			newButton.TowerImage.CoinIcon.Visible = false
		end
		
		newButton.Activated:Connect(function()
			interactItem(tower.Name)
			if newDescriptionTower.Name == newButton.Name then
				newDescriptionTower.Visible = true
			elseif newDescriptionTower.Visible == false then
				newDescriptionTower.Parent = TowerDescFrame.Parent.FrameOld
			end
		end)
	end
end

local function toggleShop(player)
	gui.MainShopFrame.Visible = not gui.MainShopFrame.Visible
	if gui.MainShopFrame.Visible then
		playerData = getDataFunc:InvokeServer()
		updateItems()
	end
end

local function setupShop()
	local promt = Instance.new("ProximityPrompt")
	promt.RequiresLineOfSight = false
	promt.ActionText = "Shop"
	promt.Parent = shopPromt
	
	promt.Triggered:Connect(toggleShop)
	exit.Activated:Connect(toggleShop)
	menu.Activated:Connect(function()
		gui.MainShopFrame.MenuFrame.Visible = not gui.MainShopFrame.MenuFrame.Visible
	end)
end
setupShop()

Could you try adjusting your oldDesc code?

local oldDesc = TowerDescFrame:FindFirstChild(tower.Name)
if oldDesc then
	oldDesc:Destroy()
end

In general, every time the store is updated, and this includes clicking on any tower in the list, the list of towers begins to clone itself, so duplicates appear. This code block removes duplicates.
I tried to do it by analogy with oldButton, but it doesn’t quite work properly

I know what it does, this is your original code:

local oldButton = CoinTowerList:FindFirstChild(tower.Name)
if oldButton then
	oldButton:Destroy() -- This gets destroyed every time
end
		
local oldDesc = TowerDescFrame:FindFirstChild(tower.Name)
if oldDesc then
	oldDesc:Destroy(tower.Name) -- You're having issues with this
end

See how the oldButton uses :Destroy() and the oldDesc uses :Destroy(tower.Name)?
Now since the oldButton does get destroyed every time, try also just making the oldDesc:Destroy().
This would be the final code for it:

local oldDesc = TowerDescFrame:FindFirstChild(tower.Name)
if oldDesc then
	oldDesc:Destroy() -- No more tower.name, It might work
end

I tried it earlier, but then I don’t get frames with the description of the tower at all


They are only in the interface, but they are not shown

Are there any errors in the output?

There are no errors, absolutely

Could you paste your whole code again?

local ReplicatedStorage = game:GetService('ReplicatedStorage')
local towers = require(ReplicatedStorage:WaitForChild("TowerShop"))

local getDataFunc = ReplicatedStorage.RemoteFunctions:WaitForChild("GetData")
local interactItemFunc = ReplicatedStorage.RemoteFunctions:WaitForChild("InteractItem")

local gui = script.Parent
local exit = gui.MainShopFrame.ShopFrame.CloseButton
local menu = gui.MainShopFrame.ShopFrame.ShopMenuButton

local CoinTowerList = gui.MainShopFrame.ShopFrame.ShopDesc.BuyList.ScrollingFrame.CoinTowers.TowerList
local TowerDescFrame = gui.MainShopFrame.ShopFrame.ShopDesc.TowerDesc.Frame

local shopPromt = workspace.ShopZone.TowerShop.VenoxDevs:WaitForChild("Head")
local StatsGui = gui.StatsGui

local selectedTower = nil

local playerData = {}

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

local function interactItem(itemName)
	local data = interactItemFunc:InvokeServer(itemName)
	if data then
		playerData = data
		updateItems()
	end
end

function updateItems()
	StatsGui.MainFrame.Coins.TextLabel.Text = playerData.Coins
	StatsGui.MainFrame.Towers.TextLabel.Text = #playerData.SelectedTowers.."/5"
	
	for i, tower in pairs(towers) do

		local oldButton = CoinTowerList:FindFirstChild(tower.Name)
		if oldButton then
			oldButton:Destroy()
		end
		
		local oldDesc = TowerDescFrame:FindFirstChild(tower.Name)
		if oldDesc then
			oldDesc:Destroy()
		end
		
		local newButton = CoinTowerList.TowerTemplate:Clone()
		newButton.Name = tower.Name
		newButton.TowerName.Text = tower.Name
		newButton.TowerImage.Image = tower.ImageAsset
		newButton.Visible = true
		newButton.LayoutOrder = tower.Price
		newButton.Parent = CoinTowerList
		
		
		local newDescriptionTower = TowerDescFrame.Frame2:Clone()
		newDescriptionTower.Name = tower.Name
		newDescriptionTower.TowerName.Text = tower.Name
		newDescriptionTower.ImageLabel.Image = tower.ImageAsset
		newDescriptionTower.ScrollingFrame.Description.StatsText.Text = tower.Description
		newDescriptionTower.ScrollingFrame.Stats.CooldownText.Text = tower.Cooldown
		newDescriptionTower.ScrollingFrame.Stats.DamageText.Text = tower.Damage
		newDescriptionTower.ScrollingFrame.Stats.RangeText.Text = tower.Range
		newDescriptionTower.ScrollingFrame.Stats.MoneyText.Text = tower.MoneyPrice
		newDescriptionTower.Visible = false
		newDescriptionTower.Parent = gui.MainShopFrame.ShopFrame.ShopDesc.TowerDesc.Frame
		
		local status = getItemStatus(tower.Name)
		if status == "For Sale" then
			newButton.TowerImage.Price.Text = tower.Price
			newButton.TowerImage.CoinIcon.Image = "rbxassetid://"
		elseif status == "Equipped" then
			newButton.TowerImage.Price.Text = "Equipped"
			newDescriptionTower.TextButton.TextLabel.Text = "Equipped"
			newButton.TowerImage.CoinIcon.Image = "rbxassetid://"
			newButton.TowerImage.Price.PriceGradient.Enabled = false
			newButton.TowerImage.Price.EquippedGradient.Enabled = true
		else
			newButton.TowerImage.Price.Text = ""
			newButton.TowerImage.CoinIcon.Visible = false
		end
		
		newButton.Activated:Connect(function()
			local selectedTowerFrame = nil
			
			interactItem(tower.Name)
			if newDescriptionTower.Name == newButton.Name then
				newDescriptionTower.Visible = true
			end
		end)
	end
end

local function toggleShop(player)
	gui.MainShopFrame.Visible = not gui.MainShopFrame.Visible
	if gui.MainShopFrame.Visible then
		playerData = getDataFunc:InvokeServer()
		updateItems()
	end
end

local function setupShop()
	local promt = Instance.new("ProximityPrompt")
	promt.RequiresLineOfSight = false
	promt.ActionText = "Shop"
	promt.Parent = shopPromt
	
	promt.Triggered:Connect(toggleShop)
	exit.Activated:Connect(toggleShop)
	menu.Activated:Connect(function()
		gui.MainShopFrame.MenuFrame.Visible = not gui.MainShopFrame.MenuFrame.Visible
	end)
end
setupShop()

So i assume the reason it won’t delete it is because the updateItems() keeps being used so much.
The function updateItems() calls another function called interactItem(x).

interactItem(x):

 local function interactItem(itemName)
	 local data = interactItemFunc:InvokeServer(itemName)
	 if data then
		 playerData = data
		 updateItems() -- Update items called again
	 end
 end

updateItems():

 function updateItems()
	StatsGui.MainFrame.Coins.TextLabel.Text = playerData.Coins
	StatsGui.MainFrame.Towers.TextLabel.Text = #playerData.SelectedTowers.."/5"
	
	for i, tower in pairs(towers) do

		-- Rest of code
		
		newButton.Activated:Connect(function()
			local selectedTowerFrame = nil
			
			interactItem(tower.Name) -- The other function is called, and that function also calls the 'updateItems' function.
			if newDescriptionTower.Name == newButton.Name then
				newDescriptionTower.Visible = true
			end
		end)
	end
end

Its basically a not ending loop. therefor it keeps getting cloned. This is my theory.
I wrote some code which might work (hopefully, i wont be able to test it though)
Let me know if this works or not! :+1:

local ReplicatedStorage = game:GetService('ReplicatedStorage')
local towers = require(ReplicatedStorage:WaitForChild("TowerShop"))

local getDataFunc = ReplicatedStorage.RemoteFunctions:WaitForChild("GetData")
local interactItemFunc = ReplicatedStorage.RemoteFunctions:WaitForChild("InteractItem")

local gui = script.Parent
local exit = gui.MainShopFrame.ShopFrame.CloseButton
local menu = gui.MainShopFrame.ShopFrame.ShopMenuButton

local CoinTowerList = gui.MainShopFrame.ShopFrame.ShopDesc.BuyList.ScrollingFrame.CoinTowers.TowerList
local TowerDescFrame = gui.MainShopFrame.ShopFrame.ShopDesc.TowerDesc.Frame

local shopPromt = workspace.ShopZone.TowerShop.VenoxDevs:WaitForChild("Head")
local StatsGui = gui.StatsGui

local selectedTower = nil

local playerData = {}

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

local function updateTowerDescription(tower)
    local descriptionFrame = TowerDescFrame:FindFirstChild(tower.Name)
    if not descriptionFrame then
        return
    end

    descriptionFrame.TowerName.Text = tower.Name
    descriptionFrame.ImageLabel.Image = tower.ImageAsset
    descriptionFrame.ScrollingFrame.Description.StatsText.Text = tower.Description
    descriptionFrame.ScrollingFrame.Stats.CooldownText.Text = tower.Cooldown
    descriptionFrame.ScrollingFrame.Stats.DamageText.Text = tower.Damage
    descriptionFrame.ScrollingFrame.Stats.RangeText.Text = tower.Range
    descriptionFrame.ScrollingFrame.Stats.MoneyText.Text = tower.MoneyPrice
end

local function interactItem(itemName)
    local data = interactItemFunc:InvokeServer(itemName)
    if data then
        playerData = data
        updateItems()
        updateTowerDescription(towers[itemName])
    end
end

function updateItems()
    StatsGui.MainFrame.Coins.TextLabel.Text = playerData.Coins
    StatsGui.MainFrame.Towers.TextLabel.Text = #playerData.SelectedTowers.."/5"

    for i, tower in pairs(towers) do
        local oldButton = CoinTowerList:FindFirstChild(tower.Name)
        if oldButton then
            oldButton:Destroy()
        end

        local newButton = CoinTowerList.TowerTemplate:Clone()
        newButton.Name = tower.Name
        newButton.TowerName.Text = tower.Name
        newButton.TowerImage.Image = tower.ImageAsset
        newButton.Visible = true
        newButton.LayoutOrder = tower.Price
        newButton.Parent = CoinTowerList

        local status = getItemStatus(tower.Name)
        if status == "For Sale" then
            newButton.TowerImage.Price.Text = tower.Price
            newButton.TowerImage.CoinIcon.Image = "rbxassetid://"
        elseif status == "Equipped" then
            newButton.TowerImage.Price.Text = "Equipped"
            newButton.TowerImage.CoinIcon.Image = "rbxassetid://"
            newButton.TowerImage.Price.PriceGradient.Enabled = false
            newButton.TowerImage.Price.EquippedGradient.Enabled = true
        else
            newButton.TowerImage.Price.Text = ""
            newButton.TowerImage.CoinIcon.Visible = false
        end

        newButton.Activated:Connect(function()
            interactItem(tower.Name)
            if selectedTower ~= tower.Name then
                updateTowerDescription(tower)
                selectedTower = tower.Name
            end
        end)
    end
end

local function toggleShop(player)
    gui.MainShopFrame.Visible = not gui.MainShopFrame.Visible
    if gui.MainShopFrame.Visible then
        playerData = getDataFunc:InvokeServer()
        updateItems()
    end
end

local function setupShop()
    local promt = Instance.new("ProximityPrompt")
    promt.RequiresLineOfSight = false
    promt.ActionText = "Shop"
    promt.Parent = shopPromt

    promt.Triggered:Connect(toggleShop)
    exit.Activated:Connect(toggleShop)
    menu.Activated:Connect(function()
        gui.MainShopFrame.MenuFrame.Visible = not gui.MainShopFrame.MenuFrame.Visible
    end)
end
setupShop()

Now it does not create frames with a description of the tower, which should be cloned
image
Maybe I see a problem, give me some time

Okay, I tried, it didn’t work. I see a problem that a frame with a description of the tower is simply not created when updating the store or clicking on the tower

My template is TowerDescFrame.Frame2 which was previously used for cloning

Maybe it’s because you don’t use my template anywhere.
image