local Players = game:GetService("Players") local ReplicatedStorage = game:GetService("ReplicatedStorage") local UserInputService = game:GetService("UserInputService") local TweenService = game:GetService("TweenService") local player = Players.LocalPlayer local playerGui = player:WaitForChild("PlayerGui") -- -- FIND EXISTING UNITS + TOWERS -- local unitFolders = { ReplicatedStorage:FindFirstChild("Towers"), ReplicatedStorage:FindFirstChild("Tower"), ReplicatedStorage:FindFirstChild("Units"), } local function getAllUnitsAndTowers() local objects = {} local seen = {} for _, folder in ipairs(unitFolders) do if folder then for _, object in ipairs(folder:GetChildren()) do if ( object:IsA("Model") or object:IsA("Folder") or object:IsA("Configuration") ) then if not seen[object] then seen[object] = true table.insert(objects, object) end end end end end table.sort(objects, function(a, b) return a.Name:lower() < b.Name:lower() end) return objects end -- -- GUI -- local gui = Instance.new("ScreenGui") gui.Name = "TowerHub" gui.ResetOnSpawn = false gui.IgnoreGuiInset = true gui.DisplayOrder = 999 gui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling gui.Parent = playerGui -- -- MAIN WINDOW -- local main = Instance.new("Frame") main.Name = "Main" main.AnchorPoint = Vector2.new(0.5, 0.5) main.Position = UDim2.fromScale(0.5, 0.5) main.Size = UDim2.fromScale(0.92, 0.82) main.BackgroundColor3 = Color3.fromRGB(17, 18, 24) main.BorderSizePixel = 0 main.Parent = gui local mainCorner = Instance.new("UICorner") mainCorner.CornerRadius = UDim.new(0, 18) mainCorner.Parent = main local mainStroke = Instance.new("UIStroke") mainStroke.Color = Color3.fromRGB(62, 65, 80) mainStroke.Thickness = 1 mainStroke.Parent = main -- -- TOP BAR -- local top = Instance.new("Frame") top.Name = "TopBar" top.Size = UDim2.new(1, 0, 0, 58) top.BackgroundColor3 = Color3.fromRGB(25, 27, 35) top.BorderSizePixel = 0 top.Parent = main local topCorner = Instance.new("UICorner") topCorner.CornerRadius = UDim.new(0, 18) topCorner.Parent = top local title = Instance.new("TextLabel") title.BackgroundTransparency = 1 title.Position = UDim2.fromOffset(17, 0) title.Size = UDim2.new(1, -75, 1, 0) title.Text = "universe spawner tower" title.TextColor3 = Color3.fromRGB(255, 255, 255) title.TextSize = 21 title.Font = Enum.Font.GothamBold title.TextXAlignment = Enum.TextXAlignment.Left title.Parent = top local hideButton = Instance.new("TextButton") hideButton.Size = UDim2.fromOffset(42, 42) hideButton.Position = UDim2.new(1, -50, 0, 8) hideButton.BackgroundColor3 = Color3.fromRGB(47, 49, 61) hideButton.Text = "×" hideButton.TextColor3 = Color3.fromRGB(255, 255, 255) hideButton.TextSize = 25 hideButton.Font = Enum.Font.GothamBold hideButton.AutoButtonColor = false hideButton.Parent = top local hideCorner = Instance.new("UICorner") hideCorner.CornerRadius = UDim.new(0, 10) hideCorner.Parent = hideButton -- -- LEFT UNIT / TOWER PANEL -- local left = Instance.new("Frame") left.Name = "UnitTowerPanel" left.Position = UDim2.fromOffset(10, 68) left.Size = UDim2.new(0.42, -10, 1, -78) left.BackgroundColor3 = Color3.fromRGB(24, 26, 34) left.BorderSizePixel = 0 left.Parent = main local leftCorner = Instance.new("UICorner") leftCorner.CornerRadius = UDim.new(0, 13) leftCorner.Parent = left local listTitle = Instance.new("TextLabel") listTitle.BackgroundTransparency = 1 listTitle.Position = UDim2.fromOffset(13, 8) listTitle.Size = UDim2.new(1, -26, 0, 32) listTitle.Text = "UNITS + TOWERS" listTitle.TextColor3 = Color3.fromRGB(225, 227, 235) listTitle.TextSize = 13 listTitle.Font = Enum.Font.GothamBold listTitle.TextXAlignment = Enum.TextXAlignment.Left listTitle.Parent = left local towerList = Instance.new("ScrollingFrame") towerList.Name = "UnitTowerList" towerList.Position = UDim2.fromOffset(8, 45) towerList.Size = UDim2.new(1, -16, 1, -52) towerList.BackgroundTransparency = 1 towerList.BorderSizePixel = 0 towerList.ScrollBarThickness = 3 towerList.ScrollBarImageColor3 = Color3.fromRGB(90, 94, 112) towerList.CanvasSize = UDim2.new(0, 0, 0, 0) towerList.ScrollingDirection = Enum.ScrollingDirection.Y towerList.Parent = left local towerLayout = Instance.new("UIListLayout") towerLayout.Padding = UDim.new(0, 7) towerLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center towerLayout.Parent = towerList -- -- RIGHT STATS PANEL -- local right = Instance.new("Frame") right.Name = "StatsPanel" right.Position = UDim2.new(0.42, 5, 0, 68) right.Size = UDim2.new(0.58, -15, 1, -78) right.BackgroundColor3 = Color3.fromRGB(20, 21, 29) right.BorderSizePixel = 0 right.Parent = main local rightCorner = Instance.new("UICorner") rightCorner.CornerRadius = UDim.new(0, 13) rightCorner.Parent = right -- -- SELECTED UNIT / TOWER HEADER -- local selectedTitle = Instance.new("TextLabel") selectedTitle.BackgroundTransparency = 1 selectedTitle.Position = UDim2.fromOffset(16, 10) selectedTitle.Size = UDim2.new(1, -32, 0, 32) selectedTitle.Text = "SELECT A UNIT" selectedTitle.TextColor3 = Color3.fromRGB(255, 255, 255) selectedTitle.TextSize = 19 selectedTitle.Font = Enum.Font.GothamBold selectedTitle.TextXAlignment = Enum.TextXAlignment.Left selectedTitle.TextTruncate = Enum.TextTruncate.AtEnd selectedTitle.Parent = right local subtitle = Instance.new("TextLabel") subtitle.BackgroundTransparency = 1 subtitle.Position = UDim2.fromOffset(17, 38) subtitle.Size = UDim2.new(1, -34, 0, 18) subtitle.Text = "Select an existing unit or tower" subtitle.TextColor3 = Color3.fromRGB(125, 130, 145) subtitle.TextSize = 10 subtitle.Font = Enum.Font.Gotham subtitle.TextXAlignment = Enum.TextXAlignment.Left subtitle.Parent = right -- -- STAT ALIASES -- local aliases = { Damage = { "Damage", "AttackDamage", "Attack", "DMG" }, Range = { "Range", "AttackRange", "Radius" }, Cooldown = { "Cooldown", "AttackCooldown", "FireRate", "AttackSpeed" }, Price = { "Price", "Cost", "Cash" } } -- -- VALUE READER -- local function readValueObject(object) if object:IsA("NumberValue") or object:IsA("IntValue") or object:IsA("StringValue") or object:IsA("BoolValue") then return object.Value end return nil end -- -- OBJECT STAT READER -- local function findStatInObject(object, statName) if not object then return nil end local attribute = object:GetAttribute(statName) if attribute ~= nil then return attribute end local direct = object:FindFirstChild(statName) if direct then local value = readValueObject(direct) if value ~= nil then return value end end for _, alias in ipairs(aliases[statName] or {}) do local child = object:FindFirstChild(alias) if child then local value = readValueObject(child) if value ~= nil then return value end end local attr = object:GetAttribute(alias) if attr ~= nil then return attr end end for _, descendant in ipairs(object:GetDescendants()) do local attr = descendant:GetAttribute(statName) if attr ~= nil then return attr end local value = readValueObject(descendant) if value ~= nil then if descendant.Name:lower() == statName:lower() then return value end for _, alias in ipairs(aliases[statName] or {}) do if descendant.Name:lower() == alias:lower() then return value end end end end return nil end -- -- MODULESCRIPT STAT READER -- local function findStatInModule(moduleScript, statName) if not moduleScript then return nil end if not moduleScript:IsA("ModuleScript") then return nil end local success, data = pcall(require, moduleScript) if not success then return nil end if type(data) ~= "table" then return nil end if data[statName] ~= nil then return data[statName] end for _, alias in ipairs(aliases[statName] or {}) do if data[alias] ~= nil then return data[alias] end end for key, value in pairs(data) do if type(key) == "string" then if key:lower() == statName:lower() then return value end for _, alias in ipairs(aliases[statName] or {}) do if key:lower() == alias:lower() then return value end end end end return nil end -- -- COMPLETE STAT SEARCH -- local function findStat(unit, statName) local result = findStatInObject(unit, statName) if result ~= nil then return result end for _, configName in ipairs({ "Config", "Configuration", "Stats", "Data", "TowerData", "UnitData" }) do local config = unit:FindFirstChild(configName) if config then result = findStatInObject(config, statName) if result ~= nil then return result end end end for _, object in ipairs(unit:GetDescendants()) do if object:IsA("ModuleScript") then result = findStatInModule(object, statName) if result ~= nil then return result end end end return "N/A" end -- -- STATS SCROLL -- local statsScroll = Instance.new("ScrollingFrame") statsScroll.Name = "StatsScroll" statsScroll.Position = UDim2.fromOffset(8, 62) statsScroll.Size = UDim2.new(1, -16, 1, -135) statsScroll.BackgroundTransparency = 1 statsScroll.BorderSizePixel = 0 statsScroll.ScrollBarThickness = 3 statsScroll.ScrollBarImageColor3 = Color3.fromRGB(85, 90, 110) statsScroll.CanvasSize = UDim2.new(0, 0, 0, 0) statsScroll.ScrollingDirection = Enum.ScrollingDirection.Y statsScroll.Parent = right local statsLayout = Instance.new("UIListLayout") statsLayout.Padding = UDim.new(0, 8) statsLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center statsLayout.Parent = statsScroll local statsPadding = Instance.new("UIPadding") statsPadding.PaddingLeft = UDim.new(0, 6) statsPadding.PaddingRight = UDim.new(0, 6) statsPadding.PaddingBottom = UDim.new(0, 8) statsPadding.Parent = statsScroll -- -- STAT CARDS -- local statCards = {} local function createStatCard(name, icon, color) local card = Instance.new("Frame") card.Name = name .. "Card" card.Size = UDim2.new(1, 0, 0, 58) card.BackgroundColor3 = Color3.fromRGB(31, 33, 43) card.BorderSizePixel = 0 card.Parent = statsScroll local corner = Instance.new("UICorner") corner.CornerRadius = UDim.new(0, 10) corner.Parent = card local stroke = Instance.new("UIStroke") stroke.Color = Color3.fromRGB(45, 47, 59) stroke.Thickness = 1 stroke.Parent = card local iconLabel = Instance.new("TextLabel") iconLabel.Position = UDim2.fromOffset(7, 7) iconLabel.Size = UDim2.fromOffset(44, 44) iconLabel.BackgroundColor3 = color iconLabel.Text = icon iconLabel.TextColor3 = Color3.new(1, 1, 1) iconLabel.TextSize = 20 iconLabel.Font = Enum.Font.GothamBold iconLabel.Parent = card local iconCorner = Instance.new("UICorner") iconCorner.CornerRadius = UDim.new(0, 10) iconCorner.Parent = iconLabel local nameLabel = Instance.new("TextLabel") nameLabel.BackgroundTransparency = 1 nameLabel.Position = UDim2.fromOffset(62, 7) nameLabel.Size = UDim2.new(1, -75, 0, 17) nameLabel.Text = name:upper() nameLabel.TextColor3 = Color3.fromRGB(140, 145, 160) nameLabel.TextSize = 9 nameLabel.Font = Enum.Font.GothamBold nameLabel.TextXAlignment = Enum.TextXAlignment.Left nameLabel.Parent = card local valueLabel = Instance.new("TextLabel") valueLabel.BackgroundTransparency = 1 valueLabel.Position = UDim2.fromOffset(62, 24) valueLabel.Size = UDim2.new(1, -75, 0, 27) valueLabel.Text = "--" valueLabel.TextColor3 = Color3.fromRGB(248, 248, 252) valueLabel.TextSize = 18 valueLabel.Font = Enum.Font.GothamBold valueLabel.TextXAlignment = Enum.TextXAlignment.Left valueLabel.TextTruncate = Enum.TextTruncate.AtEnd valueLabel.Parent = card statCards[name] = { Card = card, Value = valueLabel } end createStatCard( "Damage", "⚔", Color3.fromRGB(190, 60, 70) ) createStatCard( "Range", "◎", Color3.fromRGB(60, 125, 205) ) createStatCard( "Cooldown", "◷", Color3.fromRGB(145, 90, 205) ) createStatCard( "Price", "$", Color3.fromRGB(50, 165, 95) ) statsLayout:GetPropertyChangedSignal("AbsoluteContentSize"):Connect(function() statsScroll.CanvasSize = UDim2.fromOffset( 0, statsLayout.AbsoluteContentSize.Y + 15 ) end) -- -- SELECTED UNIT / TOWER -- local selectedUnit = nil local selectedUnitName = nil local towerButtons = {} local function animateStat(name, value) local data = statCards[name] if not data then return end data.Value.Text = tostring(value) data.Value.TextTransparency = 1 TweenService:Create( data.Value, TweenInfo.new( 0.25, Enum.EasingStyle.Quint, Enum.EasingDirection.Out ), { TextTransparency = 0 } ):Play() end local function showUnitStats(unit) selectedUnit = unit selectedUnitName = unit.Name selectedTitle.Text = unit.Name subtitle.Text = "EXISTING UNIT / TOWER" animateStat( "Damage", findStat(unit, "Damage") ) animateStat( "Range", findStat(unit, "Range") ) animateStat( "Cooldown", findStat(unit, "Cooldown") ) animateStat( "Price", findStat(unit, "Price") ) statsScroll.CanvasPosition = Vector2.new(0, 0) for button, buttonUnit in pairs(towerButtons) do if buttonUnit == unit then button.BackgroundColor3 = Color3.fromRGB(62, 100, 175) else button.BackgroundColor3 = Color3.fromRGB(40, 42, 53) end end for _, data in pairs(statCards) do local originalSize = data.Card.Size data.Card.Size = UDim2.new( 1, 0, 0, 54 ) TweenService:Create( data.Card, TweenInfo.new( 0.22, Enum.EasingStyle.Back, Enum.EasingDirection.Out ), { Size = originalSize } ):Play() end end -- -- CREATE UNIT / TOWER BUTTON -- local function createTowerButton(unit) local button = Instance.new("TextButton") button.Name = unit.Name button.Size = UDim2.new(1, -4, 0, 46) button.BackgroundColor3 = Color3.fromRGB(40, 42, 53) button.BorderSizePixel = 0 button.Text = unit.Name button.TextColor3 = Color3.fromRGB(240, 240, 245) button.TextSize = 13 button.Font = Enum.Font.GothamMedium button.TextTruncate = Enum.TextTruncate.AtEnd button.AutoButtonColor = false button.Parent = towerList local corner = Instance.new("UICorner") corner.CornerRadius = UDim.new(0, 8) corner.Parent = button towerButtons[button] = unit button.MouseEnter:Connect(function() if selectedUnit ~= unit then TweenService:Create( button, TweenInfo.new(0.12), { BackgroundColor3 = Color3.fromRGB(50, 53, 66) } ):Play() end end) button.MouseLeave:Connect(function() if selectedUnit ~= unit then TweenService:Create( button, TweenInfo.new(0.12), { BackgroundColor3 = Color3.fromRGB(40, 42, 53) } ):Play() end end) button.Activated:Connect(function() showUnitStats(unit) end) end -- -- REFRESH ALL UNITS + TOWERS -- local function refreshTowers() for button in pairs(towerButtons) do if button and button.Parent then button:Destroy() end end table.clear(towerButtons) local objects = getAllUnitsAndTowers() if #objects == 0 then listTitle.Text = "NO UNITS OR TOWERS FOUND" towerList.CanvasSize = UDim2.fromOffset(0, 0) return end for _, object in ipairs(objects) do createTowerButton(object) end listTitle.Text = "UNITS + TOWERS • " .. tostring(#objects) task.wait() towerList.CanvasSize = UDim2.fromOffset( 0, towerLayout.AbsoluteContentSize.Y + 10 ) end refreshTowers() -- -- AUTO REFRESH -- for _, folder in ipairs(unitFolders) do if folder then folder.ChildAdded:Connect(function() task.defer(refreshTowers) end) folder.ChildRemoved:Connect(function() task.defer(refreshTowers) end) end end -- -- SPAWN AREA -- local spawnArea = Instance.new("Frame") spawnArea.Name = "SpawnArea" spawnArea.AnchorPoint = Vector2.new(0, 1) spawnArea.Position = UDim2.new(0, 8, 1, -8) spawnArea.Size = UDim2.new(1, -16, 0, 58) spawnArea.BackgroundColor3 = Color3.fromRGB(25, 27, 36) spawnArea.BorderSizePixel = 0 spawnArea.Parent = right local spawnAreaCorner = Instance.new("UICorner") spawnAreaCorner.CornerRadius = UDim.new(0, 11) spawnAreaCorner.Parent = spawnArea local spawnButton = Instance.new("TextButton") spawnButton.Name = "SpawnButton" spawnButton.Position = UDim2.fromOffset(7, 7) spawnButton.Size = UDim2.new(1, -14, 1, -14) spawnButton.BackgroundColor3 = Color3.fromRGB(55, 165, 95) spawnButton.BorderSizePixel = 0 spawnButton.Text = "SPAWN SELECTED" spawnButton.TextColor3 = Color3.new(1, 1, 1) spawnButton.TextSize = 12 spawnButton.Font = Enum.Font.GothamBold spawnButton.AutoButtonColor = false spawnButton.Parent = spawnArea local spawnCorner = Instance.new("UICorner") spawnCorner.CornerRadius = UDim.new(0, 9) spawnCorner.Parent = spawnButton spawnButton.MouseEnter:Connect(function() TweenService:Create( spawnButton, TweenInfo.new(0.12), { BackgroundColor3 = Color3.fromRGB(65, 185, 105) } ):Play() end) spawnButton.MouseLeave:Connect(function() TweenService:Create( spawnButton, TweenInfo.new(0.12), { BackgroundColor3 = Color3.fromRGB(55, 165, 95) } ):Play() end) -- -- EXISTING SPAWN FUNCTION -- spawnButton.Activated:Connect(function() if not selectedUnitName then selectedTitle.Text = "SELECT A UNIT OR TOWER" return end local character = player.Character or player.CharacterAdded:Wait() local root = character:FindFirstChild("HumanoidRootPart") if not root then return end local functions = ReplicatedStorage:FindFirstChild("Functions") if not functions then warn("Functions folder was not found.") return end local spawnFunction = functions:FindFirstChild("SpawnTower") if not spawnFunction then warn("SpawnTower was not found.") return end if not spawnFunction:IsA("RemoteFunction") then warn("SpawnTower is not a RemoteFunction.") return end local args = { [1] = selectedUnitName, [2] = root.CFrame } local success, result = pcall(function() return spawnFunction:InvokeServer( unpack(args) ) end) if not success then warn("SpawnTower error:", result) end end) -- -- HIDE / SHOW -- local showButton = Instance.new("TextButton") showButton.Name = "ShowHub" showButton.AnchorPoint = Vector2.new(0, 0.5) showButton.Position = UDim2.new(0, 12, 0.5, 0) showButton.Size = UDim2.fromOffset(54, 54) showButton.BackgroundColor3 = Color3.fromRGB(35, 38, 49) showButton.BorderSizePixel = 0 showButton.Text = "☰" showButton.TextColor3 = Color3.new(1, 1, 1) showButton.TextSize = 24 showButton.Font = Enum.Font.GothamBold showButton.Visible = false showButton.Parent = gui local showCorner = Instance.new("UICorner") showCorner.CornerRadius = UDim.new(1, 0) showCorner.Parent = showButton local showStroke = Instance.new("UIStroke") showStroke.Color = Color3.fromRGB(65, 68, 82) showStroke.Parent = showButton hideButton.Activated:Connect(function() main.Visible = false showButton.Visible = true end) showButton.Activated:Connect(function() main.Visible = true showButton.Visible = false end) -- -- MOBILE SCALE -- local uiScale = Instance.new("UIScale") uiScale.Parent = main local function updateScale() local camera = workspace.CurrentCamera if not camera then return end local viewport = camera.ViewportSize if viewport.X <= 390 then uiScale.Scale = 0.82 elseif viewport.X <= 500 then uiScale.Scale = 0.90 else uiScale.Scale = 1 end end updateScale() if workspace.CurrentCamera then workspace.CurrentCamera:GetPropertyChangedSignal( "ViewportSize" ):Connect(updateScale) end -- -- TOUCH / MOUSE DRAGGING -- local dragging = false local dragStart = nil local startPosition = nil top.InputBegan:Connect(function(input) if input.UserInputType == Enum.UserInputType.Touch or input.UserInputType == Enum.UserInputType.MouseButton1 then dragging = true dragStart = input.Position startPosition = main.Position end end) top.InputEnded:Connect(function(input) if input.UserInputType == Enum.UserInputType.Touch or input.UserInputType == Enum.UserInputType.MouseButton1 then dragging = false end end) UserInputService.InputChanged:Connect(function(input) if not dragging then return end if input.UserInputType ~= Enum.UserInputType.Touch and input.UserInputType ~= Enum.UserInputType.MouseMovement then return end if not dragStart or not startPosition then return end local delta = input.Position - dragStart main.Position = UDim2.new( startPosition.X.Scale, startPosition.X.Offset + delta.X, startPosition.Y.Scale, startPosition.Y.Offset + delta.Y ) end) -- -- FINISHED -- print("Tower Hub - Units + Towers loaded.")