Universal spectate
Key RequiredUniversal

Universal spectate

Universal Scriptsantinokool07santinokool07 4.5K views 5.1K executesUpdated Aug 29, 2025
5.0/ 5(1 votes)
10100% Positive
Lua Script•215 lines (6911 chars)
local TweenService = game:GetService("TweenService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local player = Players.LocalPlayer
local camera = workspace.CurrentCamera

-- GUI Setup (bottom-left)
local screenGui = Instance.new("ScreenGui")
screenGui.ResetOnSpawn = false
screenGui.Parent = player:WaitForChild("PlayerGui")

local spectateButton = Instance.new("TextButton")
spectateButton.Size = UDim2.new(0, 120, 0, 40)
spectateButton.Position = UDim2.new(0, 10, 1, -50)
spectateButton.BackgroundColor3 = Color3.fromRGB(40, 40, 40)
spectateButton.TextColor3 = Color3.fromRGB(255, 255, 255)
spectateButton.Text = "Spectate"
spectateButton.Font = Enum.Font.SourceSansBold
spectateButton.TextScaled = true
spectateButton.AutoButtonColor = true
Instance.new("UICorner", spectateButton)
spectateButton.Parent = screenGui

local spectateFrame = Instance.new("Frame")
spectateFrame.Size = UDim2.new(0, 250, 0, 60)
spectateFrame.Position = UDim2.new(0, 10, 1, -120)
spectateFrame.BackgroundColor3 = Color3.fromRGB(25, 25, 25)
spectateFrame.Visible = false
Instance.new("UICorner", spectateFrame)
spectateFrame.Parent = screenGui

local leftArrow = Instance.new("TextButton")
leftArrow.Size = UDim2.new(0, 40, 1, 0)
leftArrow.Position = UDim2.new(0, 0, 0, 0)
leftArrow.BackgroundColor3 = Color3.fromRGB(50, 50, 50)
leftArrow.TextColor3 = Color3.fromRGB(255, 255, 255)
leftArrow.Text = "<"
leftArrow.Font = Enum.Font.SourceSansBold
leftArrow.TextScaled = true
Instance.new("UICorner", leftArrow)
leftArrow.Parent = spectateFrame

local rightArrow = Instance.new("TextButton")
rightArrow.Size = UDim2.new(0, 40, 1, 0)
rightArrow.Position = UDim2.new(1, -40, 0, 0)
rightArrow.BackgroundColor3 = Color3.fromRGB(50, 50, 50)
rightArrow.TextColor3 = Color3.fromRGB(255, 255, 255)
rightArrow.Text = ">"
rightArrow.Font = Enum.Font.SourceSansBold
rightArrow.TextScaled = true
Instance.new("UICorner", rightArrow)
rightArrow.Parent = spectateFrame

local playerNameLabel = Instance.new("TextLabel")
playerNameLabel.Size = UDim2.new(1, -80, 1, 0)
playerNameLabel.Position = UDim2.new(0, 40, 0, 0)
playerNameLabel.BackgroundTransparency = 1
playerNameLabel.TextColor3 = Color3.fromRGB(255, 255, 255)
playerNameLabel.Font = Enum.Font.SourceSansBold
playerNameLabel.TextScaled = true
playerNameLabel.Text = "None"
playerNameLabel.Parent = spectateFrame

-- Teleport Button
local teleportButton = Instance.new("TextButton")
teleportButton.Size = UDim2.new(0, 30, 0, 30)
teleportButton.Position = UDim2.new(1, -35, 1, -35)
teleportButton.BackgroundColor3 = Color3.fromRGB(70, 70, 70)
teleportButton.TextColor3 = Color3.fromRGB(255, 255, 255)
teleportButton.Text = "TP"
teleportButton.Font = Enum.Font.SourceSansBold
teleportButton.TextScaled = true
Instance.new("UICorner", teleportButton)
teleportButton.Parent = spectateFrame

-- Variables
local spectating = false
local currentIndex = 1
local spectatePlayers = {}

-- Update list of players
local function updatePlayerList()
	spectatePlayers = {}
	for _, plr in ipairs(Players:GetPlayers()) do
		if plr ~= player then
			table.insert(spectatePlayers, plr)
		end
	end
end

local function isAlive(target)
	return target 
	   and target.Character 
	   and target.Character:FindFirstChild("Humanoid") 
	   and target.Character.Humanoid.Health > 0
end

local function updateNameColor(target)
	if target and target.Team ~= player.Team then
		playerNameLabel.TextColor3 = Color3.fromRGB(255, 0, 0)
	else
		playerNameLabel.TextColor3 = Color3.fromRGB(255, 255, 255)
	end
end

-- Smooth name animation
local function animateNameChange(newName)
	local tweenInfo = TweenInfo.new(0.3, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
	local fadeOut = TweenService:Create(playerNameLabel, tweenInfo, {TextTransparency = 1})
	fadeOut:Play()
	fadeOut.Completed:Wait()
	playerNameLabel.Text = newName
	local fadeIn = TweenService:Create(playerNameLabel, tweenInfo, {TextTransparency = 0})
	fadeIn:Play()
end

local function spectate(target)
	if isAlive(target) then
		camera.CameraSubject = target.Character.Humanoid
		camera.CameraType = Enum.CameraType.Custom
		animateNameChange(target.Name)
		updateNameColor(target)
	else
		if #spectatePlayers > 0 then
			local startIndex = currentIndex
			repeat
				currentIndex = currentIndex + 1
				if currentIndex > #spectatePlayers then currentIndex = 1 end
				if currentIndex == startIndex then break end
			until isAlive(spectatePlayers[currentIndex])
			if isAlive(spectatePlayers[currentIndex]) then
				spectate(spectatePlayers[currentIndex])
			else
				playerNameLabel.Text = "None"
				playerNameLabel.TextColor3 = Color3.fromRGB(255, 255, 255)
				camera.CameraSubject = player.Character:FindFirstChild("Humanoid")
			end
		else
			playerNameLabel.Text = "None"
			playerNameLabel.TextColor3 = Color3.fromRGB(255, 255, 255)
			camera.CameraSubject = player.Character:FindFirstChild("Humanoid")
		end
	end
end

-- Toggle spectate
spectateButton.MouseButton1Click:Connect(function()
	spectating = not spectating
	spectateFrame.Visible = spectating
	updatePlayerList()
	if spectating then
		if spectatePlayers[currentIndex] and isAlive(spectatePlayers[currentIndex]) then
			spectate(spectatePlayers[currentIndex])
		else
			for i, plr in ipairs(spectatePlayers) do
				if isAlive(plr) then
					currentIndex = i
					spectate(plr)
					break
				end
			end
		end
	else
		if player.Character and player.Character:FindFirstChild("Humanoid") then
			camera.CameraSubject = player.Character.Humanoid
		end
	end
end)

-- Arrow buttons
leftArrow.MouseButton1Click:Connect(function()
	if #spectatePlayers > 0 then
		repeat
			currentIndex = currentIndex - 1
			if currentIndex < 1 then currentIndex = #spectatePlayers end
		until isAlive(spectatePlayers[currentIndex])
		spectate(spectatePlayers[currentIndex])
	end
end)

rightArrow.MouseButton1Click:Connect(function()
	if #spectatePlayers > 0 then
		repeat
			currentIndex = currentIndex + 1
			if currentIndex > #spectatePlayers then currentIndex = 1 end
		until isAlive(spectatePlayers[currentIndex])
		spectate(spectatePlayers[currentIndex])
	end
end)

-- Teleport
teleportButton.MouseButton1Click:Connect(function()
	local target = spectatePlayers[currentIndex]
	if target and isAlive(target) and target.Character and target.Character:FindFirstChild("HumanoidRootPart") then
		local hrp = player.Character and player.Character:FindFirstChild("HumanoidRootPart")
		if hrp then
			hrp.CFrame = target.Character.HumanoidRootPart.CFrame + Vector3.new(0,3,0)
		end
	end
end)

Players.PlayerAdded:Connect(updatePlayerList)
Players.PlayerRemoving:Connect(updatePlayerList)

RunService.RenderStepped:Connect(function()
	if spectating and #spectatePlayers > 0 then
		if not isAlive(spectatePlayers[currentIndex]) then
			spectate(spectatePlayers[currentIndex])
		else
			updateNameColor(spectatePlayers[currentIndex])
		end
	end
end)

updatePlayerList()

Features & Information

*NEW ANIMATIONS* Middle-left “Spectate” toggle button. Pop-up UI with left/right arrows and player name label. Cycle through all alive players. Remembers last spectated player when toggled off/on. Automatically skips dead players. Name turns red if the spectated player is on a different team. Updates dynamically when players join, leave, or die. Smooth edges on all GUI elements. Camera follows the Humanoid of the spectated player.
#roblox#spectate

Community Comments (3)

santinokool07santinokool07Aug 29, 2025

This script works because it has been tested 7 different times and if you have suggestions then tell me and I can maybe add them

santinokool07santinokool07Aug 29, 2025

Also this script is free to use no credit needed

Tsunami_Tsunami_Oct 7, 2025

tysm man

More Scripts for Universal Scripts