Universe tower spawner script
Key RequiredUniversal

Universe tower spawner script

Universal ScriptPikangi*Pikangi* 755 views 53.3K executesUpdated Sep 9, 2026
2.5/ 5(2 votes)
1150% Positive
Lua Script•1189 lines (26846 chars)
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UserInputService = game:GetService("UserInputService")
local TweenService = game:GetService("TweenService")

local player = Players.LocalPlayer
if not player then
	warn("TowerHub must run as a LocalScript.")
	return
end

local playerGui = player:WaitForChild("PlayerGui", 15)

if not playerGui then
	warn("TowerHub: PlayerGui was not found.")
	return
end

print("TowerHub: script started")

local oldGui = playerGui:FindFirstChild("TowerHub")

if oldGui then
	oldGui:Destroy()
end

--==================================================
-- FIND UNIT / TOWER FOLDERS
--==================================================

local folderNames = {
	tower = true,
	towers = true,

	unit = true,
	units = true,

	troop = true,
	troops = true,

	character = true,
	characters = true,

	npc = true,
	npcs = true,

	unitsfolder = true,
	towerfolder = true,
}

local function isUnitTowerFolder(object)
	return object:IsA("Folder")
		and folderNames[object.Name:lower()] == true
end

local function getAllUnitsAndTowers()
	local objects = {}
	local seen = {}

	for _, folder in ipairs(ReplicatedStorage:GetDescendants()) do
		if isUnitTowerFolder(folder) then
			for _, object in ipairs(folder:GetChildren()) do
				if not seen[object] then
					seen[object] = true
					table.insert(objects, object)
				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

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.fromOffset(560, 400)
main.BackgroundColor3 = Color3.fromRGB(17, 18, 24)
main.BorderSizePixel = 0
main.Visible = true
main.Parent = gui

local mainCorner = Instance.new("UICorner")
mainCorner.CornerRadius = UDim.new(0, 16)
mainCorner.Parent = main

local mainStroke = Instance.new("UIStroke")
mainStroke.Color = Color3.fromRGB(62, 65, 80)
mainStroke.Thickness = 1
mainStroke.Parent = main

--==================================================
-- RESPONSIVE SCALE
--==================================================

local uiScale = Instance.new("UIScale")
uiScale.Scale = 1
uiScale.Parent = main

local function updateScale()
	local camera = workspace.CurrentCamera

	if not camera then
		return
	end

	local viewport = camera.ViewportSize
	local width = viewport.X
	local height = viewport.Y

	local scaleX = (width - 20) / 560
	local scaleY = (height - 20) / 400
	local screenScale = math.min(scaleX, scaleY)

	if width <= 390 then
		uiScale.Scale = math.min(screenScale, 0.82)
	elseif width <= 600 then
		uiScale.Scale = math.min(screenScale, 0.90)
	else
		uiScale.Scale = math.min(screenScale, 1)
	end

	if uiScale.Scale <= 0 then
		uiScale.Scale = 0.5
	end
end

local function connectCamera()
	local camera = workspace.CurrentCamera

	if not camera then
		return
	end

	camera:GetPropertyChangedSignal("ViewportSize"):Connect(updateScale)
	updateScale()
end

connectCamera()

workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(function()
	task.defer(connectCamera)
end)

--==================================================
-- TOP BAR
--==================================================

local top = Instance.new("Frame")
top.Name = "TopBar"
top.Size = UDim2.new(1, 0, 0, 50)
top.BackgroundColor3 = Color3.fromRGB(25, 27, 35)
top.BorderSizePixel = 0
top.Parent = main

local topCorner = Instance.new("UICorner")
topCorner.CornerRadius = UDim.new(0, 16)
topCorner.Parent = top

local title = Instance.new("TextLabel")
title.BackgroundTransparency = 1
title.Position = UDim2.fromOffset(14, 0)
title.Size = UDim2.new(1, -65, 1, 0)
title.Text = "universe spawner tower"
title.TextColor3 = Color3.fromRGB(255, 255, 255)
title.TextSize = 18
title.Font = Enum.Font.GothamBold
title.TextXAlignment = Enum.TextXAlignment.Left
title.TextTruncate = Enum.TextTruncate.AtEnd
title.Parent = top

local hideButton = Instance.new("TextButton")
hideButton.Size = UDim2.fromOffset(38, 38)
hideButton.Position = UDim2.new(1, -45, 0, 6)
hideButton.BackgroundColor3 = Color3.fromRGB(47, 49, 61)
hideButton.Text = "×"
hideButton.TextColor3 = Color3.fromRGB(255, 255, 255)
hideButton.TextSize = 23
hideButton.Font = Enum.Font.GothamBold
hideButton.AutoButtonColor = false
hideButton.Parent = top

local hideCorner = Instance.new("UICorner")
hideCorner.CornerRadius = UDim.new(0, 9)
hideCorner.Parent = hideButton

--==================================================
-- LEFT PANEL
--==================================================

local left = Instance.new("Frame")
left.Name = "UnitTowerPanel"
left.Position = UDim2.fromOffset(8, 58)
left.Size = UDim2.new(0.42, -8, 1, -66)
left.BackgroundColor3 = Color3.fromRGB(24, 26, 34)
left.BorderSizePixel = 0
left.Parent = main

local leftCorner = Instance.new("UICorner")
leftCorner.CornerRadius = UDim.new(0, 12)
leftCorner.Parent = left

local listTitle = Instance.new("TextLabel")
listTitle.BackgroundTransparency = 1
listTitle.Position = UDim2.fromOffset(11, 7)
listTitle.Size = UDim2.new(1, -22, 0, 28)
listTitle.Text = "UNITS + TOWERS"
listTitle.TextColor3 = Color3.fromRGB(225, 227, 235)
listTitle.TextSize = 11
listTitle.Font = Enum.Font.GothamBold
listTitle.TextXAlignment = Enum.TextXAlignment.Left
listTitle.TextTruncate = Enum.TextTruncate.AtEnd
listTitle.Parent = left

local towerList = Instance.new("ScrollingFrame")
towerList.Name = "UnitTowerList"
towerList.Position = UDim2.fromOffset(7, 39)
towerList.Size = UDim2.new(1, -14, 1, -46)
towerList.BackgroundTransparency = 1
towerList.BorderSizePixel = 0
towerList.ScrollBarThickness = 5
towerList.ScrollBarImageColor3 = Color3.fromRGB(90, 94, 112)
towerList.ScrollBarImageTransparency = 0.15
towerList.ScrollingDirection = Enum.ScrollingDirection.Y
towerList.ScrollingEnabled = true
towerList.ElasticBehavior = Enum.ElasticBehavior.Always
towerList.AutomaticCanvasSize = Enum.AutomaticSize.Y
towerList.CanvasSize = UDim2.new(0, 0, 0, 0)
towerList.Parent = left

local towerPadding = Instance.new("UIPadding")
towerPadding.PaddingTop = UDim.new(0, 3)
towerPadding.PaddingBottom = UDim.new(0, 10)
towerPadding.PaddingLeft = UDim.new(0, 2)
towerPadding.PaddingRight = UDim.new(0, 2)
towerPadding.Parent = towerList

local towerLayout = Instance.new("UIListLayout")
towerLayout.Padding = UDim.new(0, 6)
towerLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center
towerLayout.SortOrder = Enum.SortOrder.LayoutOrder
towerLayout.Parent = towerList

--==================================================
-- RIGHT PANEL
--==================================================

local right = Instance.new("Frame")
right.Name = "StatsPanel"
right.Position = UDim2.new(0.42, 4, 0, 58)
right.Size = UDim2.new(0.58, -12, 1, -66)
right.BackgroundColor3 = Color3.fromRGB(20, 21, 29)
right.BorderSizePixel = 0
right.Parent = main

local rightCorner = Instance.new("UICorner")
rightCorner.CornerRadius = UDim.new(0, 12)
rightCorner.Parent = right

local selectedTitle = Instance.new("TextLabel")
selectedTitle.BackgroundTransparency = 1
selectedTitle.Position = UDim2.fromOffset(13, 8)
selectedTitle.Size = UDim2.new(1, -26, 0, 28)
selectedTitle.Text = "SELECT A UNIT"
selectedTitle.TextColor3 = Color3.fromRGB(255, 255, 255)
selectedTitle.TextSize = 16
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(14, 34)
subtitle.Size = UDim2.new(1, -28, 0, 16)
subtitle.Text = "Select an existing unit or tower"
subtitle.TextColor3 = Color3.fromRGB(125, 130, 145)
subtitle.TextSize = 9
subtitle.Font = Enum.Font.Gotham
subtitle.TextXAlignment = Enum.TextXAlignment.Left
subtitle.TextTruncate = Enum.TextTruncate.AtEnd
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"
	},

	DPS = {
		"DPS",
		"DamagePerSecond",
		"DamagePerSec"
	},
}

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

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

local function findStatInModule(moduleScript, statName)
	if not moduleScript or not moduleScript:IsA("ModuleScript") then
		return nil
	end

	local success, data = pcall(require, moduleScript)

	if not success or 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

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
--==================================================

local statsScroll = Instance.new("ScrollingFrame")
statsScroll.Name = "StatsScroll"
statsScroll.Position = UDim2.fromOffset(7, 56)
statsScroll.Size = UDim2.new(1, -14, 1, -116)
statsScroll.BackgroundTransparency = 1
statsScroll.BorderSizePixel = 0
statsScroll.ScrollBarThickness = 5
statsScroll.ScrollBarImageColor3 = Color3.fromRGB(85, 90, 110)
statsScroll.ScrollBarImageTransparency = 0.15
statsScroll.ScrollingDirection = Enum.ScrollingDirection.Y
statsScroll.ScrollingEnabled = true
statsScroll.ElasticBehavior = Enum.ElasticBehavior.Always
statsScroll.AutomaticCanvasSize = Enum.AutomaticSize.Y
statsScroll.CanvasSize = UDim2.new(0, 0, 0, 0)
statsScroll.Parent = right

local statsPadding = Instance.new("UIPadding")
statsPadding.PaddingTop = UDim.new(0, 3)
statsPadding.PaddingBottom = UDim.new(0, 10)
statsPadding.PaddingLeft = UDim.new(0, 4)
statsPadding.PaddingRight = UDim.new(0, 4)
statsPadding.Parent = statsScroll

local statsLayout = Instance.new("UIListLayout")
statsLayout.Padding = UDim.new(0, 7)
statsLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center
statsLayout.SortOrder = Enum.SortOrder.LayoutOrder
statsLayout.Parent = statsScroll

local statCards = {}

local function createStatCard(name, icon, color)
	local card = Instance.new("Frame")
	card.Name = name .. "Card"
	card.Size = UDim2.new(1, 0, 0, 50)
	card.BackgroundColor3 = Color3.fromRGB(31, 33, 43)
	card.BorderSizePixel = 0
	card.Parent = statsScroll

	local corner = Instance.new("UICorner")
	corner.CornerRadius = UDim.new(0, 9)
	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(6, 6)
	iconLabel.Size = UDim2.fromOffset(38, 38)
	iconLabel.BackgroundColor3 = color
	iconLabel.Text = icon
	iconLabel.TextColor3 = Color3.new(1, 1, 1)
	iconLabel.TextSize = 17
	iconLabel.Font = Enum.Font.GothamBold
	iconLabel.Parent = card

	local iconCorner = Instance.new("UICorner")
	iconCorner.CornerRadius = UDim.new(0, 8)
	iconCorner.Parent = iconLabel

	local nameLabel = Instance.new("TextLabel")
	nameLabel.BackgroundTransparency = 1
	nameLabel.Position = UDim2.fromOffset(52, 5)
	nameLabel.Size = UDim2.new(1, -62, 0, 14)
	nameLabel.Text = name:upper()
	nameLabel.TextColor3 = Color3.fromRGB(140, 145, 160)
	nameLabel.TextSize = 8
	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(52, 18)
	valueLabel.Size = UDim2.new(1, -62, 0, 26)
	valueLabel.Text = "--"
	valueLabel.TextColor3 = Color3.fromRGB(248, 248, 252)
	valueLabel.TextSize = 16
	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))
createStatCard("DPS", "⚡", Color3.fromRGB(220, 150, 45))

--==================================================
-- SELECTION
--==================================================

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.2,
			Enum.EasingStyle.Quint,
			Enum.EasingDirection.Out
		),
		{
			TextTransparency = 0
		}
	):Play()
end

local function calculateDPS(unit)
	local directDPS = findStat(unit, "DPS")

	if directDPS ~= "N/A" then
		return directDPS
	end

	local damage = findStat(unit, "Damage")
	local cooldown = findStat(unit, "Cooldown")

	if type(damage) == "number"
		and type(cooldown) == "number"
		and cooldown > 0 then

		return math.round((damage / cooldown) * 100) / 100
	end

	return "N/A"
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"))
	animateStat("DPS", calculateDPS(unit))

	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
end

local function createTowerButton(unit)
	local button = Instance.new("TextButton")

	button.Name = unit.Name
	button.Size = UDim2.new(1, -4, 0, 42)
	button.BackgroundColor3 = Color3.fromRGB(40, 42, 53)
	button.BorderSizePixel = 0
	button.Text = unit.Name
	button.TextColor3 = Color3.fromRGB(240, 240, 245)
	button.TextSize = 11
	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, 7)
	corner.Parent = button

	towerButtons[button] = unit

	button.MouseEnter:Connect(function()
		if selectedUnit ~= unit then
			TweenService:Create(
				button,
				TweenInfo.new(0.1),
				{
					BackgroundColor3 = Color3.fromRGB(50, 53, 66)
				}
			):Play()
		end
	end)

	button.MouseLeave:Connect(function()
		if selectedUnit ~= unit then
			TweenService:Create(
				button,
				TweenInfo.new(0.1),
				{
					BackgroundColor3 = Color3.fromRGB(40, 42, 53)
				}
			):Play()
		end
	end)

	button.Activated:Connect(function()
		showUnitStats(unit)
	end)
end

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 TOWERS FOUND"
		return
	end

	for _, object in ipairs(objects) do
		createTowerButton(object)
	end

	listTitle.Text = "TOWERS  •  " .. tostring(#objects)

	task.defer(function()
		towerList.CanvasPosition = Vector2.new(0, 0)
	end)
end

refreshTowers()

--==================================================
-- AUTO REFRESH
--==================================================

local function isInsideUnitTowerFolder(object)
	local current = object

	while current and current ~= ReplicatedStorage do
		if isUnitTowerFolder(current) then
			return true
		end

		current = current.Parent
	end

	return false
end

ReplicatedStorage.DescendantAdded:Connect(function(object)
	if isUnitTowerFolder(object)
		or isInsideUnitTowerFolder(object) then

		task.defer(refreshTowers)
	end
end)

ReplicatedStorage.DescendantRemoving:Connect(function(object)
	if isUnitTowerFolder(object)
		or isInsideUnitTowerFolder(object) then

		task.defer(refreshTowers)
	end
end)

--==================================================
-- SPAWN AREA
--==================================================

local spawnArea = Instance.new("Frame")
spawnArea.Name = "SpawnArea"
spawnArea.AnchorPoint = Vector2.new(0, 1)
spawnArea.Position = UDim2.new(0, 7, 1, -7)
spawnArea.Size = UDim2.new(1, -14, 0, 50)
spawnArea.BackgroundColor3 = Color3.fromRGB(25, 27, 36)
spawnArea.BorderSizePixel = 0
spawnArea.Parent = right

local spawnAreaCorner = Instance.new("UICorner")
spawnAreaCorner.CornerRadius = UDim.new(0, 9)
spawnAreaCorner.Parent = spawnArea

local spawnButton = Instance.new("TextButton")
spawnButton.Name = "SpawnButton"
spawnButton.Position = UDim2.fromOffset(6, 6)
spawnButton.Size = UDim2.new(1, -12, 1, -12)
spawnButton.BackgroundColor3 = Color3.fromRGB(55, 165, 95)
spawnButton.BorderSizePixel = 0
spawnButton.Text = "SPAWN SELECTED"
spawnButton.TextColor3 = Color3.new(1, 1, 1)
spawnButton.TextSize = 11
spawnButton.Font = Enum.Font.GothamBold
spawnButton.AutoButtonColor = false
spawnButton.Parent = spawnArea

local spawnCorner = Instance.new("UICorner")
spawnCorner.CornerRadius = UDim.new(0, 8)
spawnCorner.Parent = spawnButton

--==================================================
-- SPAWN NAMES
--==================================================

local spawnNames = {
	"SpawnTower",
	"spawnTower",
	"SPAWNTOWER",

	"PlaceTower",
	"placeTower",
	"PLACETOWER",

	"SpawnUnit",
	"spawnUnit",
	"SPAWNUNIT",

	"PlaceUnit",
	"placeUnit",
	"PLACEUNIT",

	"DeployTower",
	"deployTower",
	"DEPLOYTOWER",

	"DeployUnit",
	"deployUnit",
	"DEPLOYUNIT",

	"CreateTower",
	"createTower",
	"CREATETOWER",

	"CreateUnit",
	"createUnit",
	"CREATEUNIT",

	"BuildTower",
	"buildTower",
	"BUILDTOWER",

	"BuildUnit",
	"buildUnit",
	"BUILDUNIT",

	"TowerPlace",
	"towerPlace",
	"TOWERPLACE",

	"UnitPlace",
	"unitPlace",
	"UNITPLACE",

	"TowerSpawn",
	"towerSpawn",
	"TOWERSPAWN",

	"UnitSpawn",
	"unitSpawn",
	"UNITSPAWN",

	"TroopSpawn",
	"troopSpawn",
	"TROOPSPAWN",

	"SpawnTroop",
	"spawnTroop",
	"SPAWNTROOP",

	"PlaceTroop",
	"placeTroop",
	"PLACETROOP",

	"DeployTroop",
	"deployTroop",
	"DEPLOYTROOP",

	"CreateTroop",
	"createTroop",
	"CREATETROOP",

	"BuildTroop",
	"buildTroop",
	"BUILDTROOP",

	"RequestSpawn",
	"requestSpawn",
	"REQUESTSPAWN",

	"SpawnRequest",
	"spawnRequest",
	"SPAWNREQUEST",

	"RequestTower",
	"requestTower",
	"REQUESTTOWER",

	"RequestUnit",
	"requestUnit",
	"REQUESTUNIT",

	"RequestTroop",
	"requestTroop",
	"REQUESTTROOP",

	"SummonTower",
	"summonTower",
	"SUMMONTOWER",

	"SummonUnit",
	"summonUnit",
	"SUMMONUNIT",

	"SummonTroop",
	"summonTroop",
	"SUMMONTROOP",

	"SpawnRequestTower",
	"spawnRequestTower",
	"SPAWNREQUESTTOWER",

	"SpawnRequestUnit",
	"spawnRequestUnit",
	"SPAWNREQUESTUNIT",

	"RequestSpawnTower",
	"requestSpawnTower",
	"REQUESTSPAWNTOWER",

	"RequestSpawnUnit",
	"requestSpawnUnit",
	"REQUESTSPAWNUNIT",

	"Spawn_Tower",
	"spawn_tower",

	"Spawn_Unit",
	"spawn_unit",

	"SpawnRequest_Tower",
	"spawnRequest_Tower",

	"SpawnRequest_Unit",
	"spawnRequest_Unit",
}

local function findSpawnRemote()
	local containers = {}

	local functionsFolder = ReplicatedStorage:FindFirstChild("Functions")

	if functionsFolder then
		table.insert(containers, functionsFolder)
	end

	local remoteFunctions = ReplicatedStorage:FindFirstChild("RemoteFunctions")

	if remoteFunctions then
		table.insert(containers, remoteFunctions)
	end

	local remoteEvents = ReplicatedStorage:FindFirstChild("RemoteEvents")

	if remoteEvents then
		table.insert(containers, remoteEvents)
	end

	table.insert(containers, ReplicatedStorage)

	for _, container in ipairs(containers) do
		for _, wantedName in ipairs(spawnNames) do
			local remote = container:FindFirstChild(wantedName, true)

			if remote
				and (
					remote:IsA("RemoteFunction")
					or remote:IsA("RemoteEvent")
				) then

				return remote
			end
		end
	end

	return nil
end

--==================================================
-- SPAWN SELECTED
--==================================================

spawnButton.Activated:Connect(function()
	if not selectedUnitName then
		selectedTitle.Text = "SELECT A UNIT OR TOWER"
		return
	end

	local character = player.Character

	if not character then
		character = player.CharacterAdded:Wait()
	end

	local root = character:FindFirstChild("HumanoidRootPart")

	if not root then
		selectedTitle.Text = "CHARACTER NOT READY"
		return
	end

	local spawnRemote = findSpawnRemote()

	if not spawnRemote then
		warn("No tower/unit spawn RemoteFunction or RemoteEvent was found.")
		selectedTitle.Text = "SPAWN REMOTE NOT FOUND"
		return
	end

	local success, result = pcall(function()
		if spawnRemote:IsA("RemoteFunction") then
			return spawnRemote:InvokeServer(
				selectedUnitName,
				root.CFrame
			)

		elseif spawnRemote:IsA("RemoteEvent") then
			spawnRemote:FireServer(
				selectedUnitName,
				root.CFrame
			)

			return true
		end
	end)

	if not success then
		warn("Spawn request failed:", result)
		selectedTitle.Text = "SPAWN REQUEST FAILED"
	else
		print(
			"Spawn request sent:",
			spawnRemote:GetFullName(),
			selectedUnitName
		)
	end
end)

--==================================================
-- SPAWN BUTTON ANIMATION
--==================================================

spawnButton.MouseEnter:Connect(function()
	TweenService:Create(
		spawnButton,
		TweenInfo.new(0.1),
		{
			BackgroundColor3 = Color3.fromRGB(65, 185, 105)
		}
	):Play()
end)

spawnButton.MouseLeave:Connect(function()
	TweenService:Create(
		spawnButton,
		TweenInfo.new(0.1),
		{
			BackgroundColor3 = Color3.fromRGB(55, 165, 95)
		}
	):Play()
end)

--==================================================
-- HIDE / SHOW
--==================================================

local showButton = Instance.new("TextButton")
showButton.Name = "ShowHub"
showButton.AnchorPoint = Vector2.new(0, 0.5)
showButton.Position = UDim2.new(0, 10, 0.5, 0)
showButton.Size = UDim2.fromOffset(52, 52)
showButton.BackgroundColor3 = Color3.fromRGB(35, 38, 49)
showButton.BorderSizePixel = 0
showButton.Text = "☰"
showButton.TextColor3 = Color3.new(1, 1, 1)
showButton.TextSize = 22
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)

--==================================================
-- PC + MOBILE DRAGGING
--==================================================

local dragging = false
local dragStart = nil
local startPosition = nil
local dragInput = nil

top.InputBegan:Connect(function(input)
	if input.UserInputType == Enum.UserInputType.MouseButton1
		or input.UserInputType == Enum.UserInputType.Touch then

		dragging = true
		dragStart = input.Position
		startPosition = main.Position
		dragInput = input
	end
end)

top.InputEnded:Connect(function(input)
	if input == dragInput
		or input.UserInputType == Enum.UserInputType.MouseButton1
		or input.UserInputType == Enum.UserInputType.Touch then

		dragging = false
		dragInput = nil
	end
end)

UserInputService.InputChanged:Connect(function(input)
	if not dragging then
		return
	end

	if input.UserInputType ~= Enum.UserInputType.MouseMovement
		and input.UserInputType ~= Enum.UserInputType.Touch 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)

--==================================================
-- FINALIZE
--==================================================

main.Visible = true
gui.Enabled = true

task.defer(function()
	updateScale()
end)

print("TowerHub loaded.")

Features & Information

Spawn towers in any Tower defense games but not all tower defense username (Tanner_v44)
#Tower defense

Community Comments (8)

Pikangi*Pikangi*Sep 9, 2026

My script got accidentally deleted or something

Replying to @Pikangi*
jp05gloriajp05gloriaSep 12, 2026

@Pikangi i thought you delete your script(your script is W

jp05gloriajp05gloriaSep 19, 2026

Yo Pikangi The Gui Does not show up anymore could you Pls Fix It

Replying to @jp05gloria
Pikangi*Pikangi*Sep 19, 2026

@jp05gloria bc the script is not Approved probably

User_57482967User_57482967Sep 19, 2026

Gui won't show please fix it daddy pikangi

jp05gloriajp05gloriaSep 23, 2026

Ohhh ok

gng like 3 hours ago the gui won't appear I'm absolutely sure this isn't an executor problem

User_57482967User_57482967Sep 28, 2026

Omg bro gui won't show up again

More Scripts for Universal Scripts