Spectate Client Camera Gets Stuck

I shouldn’t have to explain what my spectate UI does, but I have an issue where attempting to spectate a player will just make the player’s camera get stuck in the last position the player was.

https://gyazo.com/51fd70f40ef625f571a971d3dac3e640

I haven’t been able to find any similar issues to mine (which is surprising because I feel like this is common, or I’m just overlooking it)

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")

local player = Players.LocalPlayer
local camera = workspace.CurrentCamera

local GetActivePlayers = ReplicatedStorage.Storage.Remotes.ServerFunctions:WaitForChild("GetActivePlayers")

local gui = script.Parent
local container = gui:WaitForChild("Container")

local nextButton = gui:WaitForChild("Next")
local prevButton = gui:WaitForChild("Previous")

local headshot = container:WaitForChild("PlayerHeadshot")
local displayName = container:WaitForChild("DisplayName")
local userName = container:WaitForChild("UserName")
local healthLabel = container:WaitForChild("Health")

local activePlayers = {}
local index = 1
local currentTarget = nil

local function isValidPlayer(plr)
	return plr
		and plr.Parent == Players
		and plr.Character
		and plr.Character:FindFirstChildOfClass("Humanoid")
end

local function getHumanoid(plr)
	if not plr or not plr.Character then return nil end
	return plr.Character:FindFirstChildOfClass("Humanoid")
end

local function getHeadshot(plr)
	local ok, result = pcall(function()
		return Players:GetUserThumbnailAsync(
			plr.UserId,
			Enum.ThumbnailType.HeadShot,
			Enum.ThumbnailSize.Size420x420
		)
	end)

	return ok and result or ""
end

local function setSpectateTarget(plr)
	if not isValidPlayer(plr) then return end

	local hum = getHumanoid(plr)
	if not hum then return end

	camera.CameraType = Enum.CameraType.Custom
	camera.CameraSubject = hum

	currentTarget = plr
end

local function updateUI(plr)
	if not plr then return end

	local hum = getHumanoid(plr)
	if not hum then return end

	displayName.Text = plr.DisplayName
	userName.Text = "@" .. plr.Name
	healthLabel.Text = tostring(math.floor(hum.Health)) .. "%"

	headshot.Image = getHeadshot(plr)
end

local function sanitizeList(list)
	local cleaned = {}

	for _, plr in ipairs(list) do
		if isValidPlayer(plr) and plr ~= player then
			table.insert(cleaned, plr)
		end
	end

	return cleaned
end

local function applySpectate()
	activePlayers = sanitizeList(activePlayers)

	if #activePlayers == 0 then
		local hum = player.Character and player.Character:FindFirstChildOfClass("Humanoid")
		if hum then
			camera.CameraType = Enum.CameraType.Custom
			camera.CameraSubject = hum
		end

		displayName.Text = "No Players"
		userName.Text = ""
		healthLabel.Text = ""
		headshot.Image = ""

		currentTarget = nil
		return
	end

	index = math.clamp(index, 1, #activePlayers)

	local target = activePlayers[index]

	if target ~= currentTarget then
		setSpectateTarget(target)
	end

	updateUI(target)
end

local function nextPlayer()
	if #activePlayers == 0 then return end

	index = index + 1
	if index > #activePlayers then
		index = 1
	end

	applySpectate()
end

local function prevPlayer()
	if #activePlayers == 0 then return end

	index = index - 1
	if index < 1 then
		index = #activePlayers
	end

	applySpectate()
end

local function refreshActivePlayers()
	local ok, result = pcall(function()
		return GetActivePlayers:InvokeServer()
	end)

	if ok and typeof(result) == "table" then
		activePlayers = sanitizeList(result)
	else
		activePlayers = {}
	end

	applySpectate()
end

RunService.RenderStepped:Connect(function()
	if currentTarget and not isValidPlayer(currentTarget) then
		currentTarget = nil
		applySpectate()
	end
end)

nextButton.MouseButton1Click:Connect(nextPlayer)
prevButton.MouseButton1Click:Connect(prevPlayer)

task.spawn(function()
	while true do
		refreshActivePlayers()
		task.wait(2)
	end
end)

refreshActivePlayers()

You didn’t share the console, but I’m pretty sure it should return the image and not a boolean

Nothing printed out, but I figured out the issue, it was because I had the lobby (where the player spectates from) too far from the main map, which caused it to break.

you should turn off streaming enabled if your game doesn’t need it. Streaming enabled removes parts, objects that are too far from you and requires the server to give you the parts. Think of it kinda like the Client is your memory, and your Server is your Hard drive.

1 Like