Fixing spectate code

The problem: I have a spectate script with an ‘auto spectate’-mode, which will make the camera look at certain points and follow random players around. When following a player around in auto-spectate, the camera acts really jittery. I’ve tried many methods to fix the code, but I am still unable to fix it so I’m asking if someone can do it for me.

The code: you can find the code below. I know it looks like a lot, but it’s just the ‘SpectateMap’ function which doesn’t function properly.

while script.Parent.Parent.PlayerGui:findFirstChild("ScreenGui")==nil do
	wait()
end

function GetMapSpectateAngles()
	local Table = {
		CFrame.new(43.259758, 27.0027161, 81.7025681, 0.98601532, 0.130608171, -0.1035157, -0, 0.621136606, 0.783702433, 0.166655317, -0.772742629, 0.612450063),
		CFrame.new(123.095901, 27.4490452, 10.6504364, 0.255268425, -0.741772413, 0.620170712, -7.4505806e-009, 0.641420901, 0.767189205, -0.966870248, -0.195839182, 0.163734481),
		CFrame.new(153.451706, 23.9135094, 21.0269623, 0.674503803, 0.558915854, -0.482346207, -0, 0.653345406, 0.757060111, 0.738271415, -0.510639906, 0.440683931)
	}
	-- make sure Table is filled up in some way with CFrame's which are stored somewhere in the map hierarchy
	return Table
end

--------------------------------------- Initialise everything

local Cam = game.workspace.CurrentCamera
local AutoId = 0 -- don't mind this
CurrentIndex = 1
RunnerColor = "Bright blue"
KillerColor = "Bright red"
PreviousSpectateMode = nil
CurrentSpectateMode = nil
--MapSpectatePoints = {Vector3.new(0, 0, 0)} -- this should be filled with Vector3's for map spectate angles

local SpectateOrder = {}
local P = game.Players:GetPlayers()
for i = 1, #P do
	if P[i].Name~=script.Parent.Parent.Name and (P[i].TeamColor.Name == RunnerColor or P[i].TeamColor.Name == KillerColor) then
		SpectateOrder[#SpectateOrder+1] = P[i]
	end
end
--SpectateOrder[#SpectateOrder+1] = unpack(GetMapSpectateAngles())
SpectateOrder[#SpectateOrder+1] = "AutoSpectate"

--------------------------------------- Functions

function SpectatePlayer(PlayerName)
	game.workspace.CurrentCamera.CameraType = "Custom"
	if game.Players:findFirstChild(PlayerName)~=nil then
		local Plr = game.Players:findFirstChild(PlayerName)
		if Plr.Character:findFirstChild("Humanoid")~=nil then
			game.workspace.CurrentCamera.CameraSubject = Plr.Character.Humanoid
		else
			game.workspace.CurrentCamera.CameraSubject = script.Parent.Parent.Character.Humanoid
		end
	else
		game.workspace.CurrentCamera.CameraSubject = script.Parent.Parent.Character.Humanoid
	end
end

function FindTorso(Model)
	local P = Model:GetChildren()
	for i = 1, #P do
		if P[i]:IsA("BasePart") then
			if P[i].Size == Vector3.new(2, 2, 1) then
				return P[i]
			end
		end
	end
end

function SpectateMap(Obj)
	
	print("testing")
	game.workspace.CurrentCamera.CameraType = "Scriptable"
	local SpectateModes = {"SpectateKiller", "SpectateRandomRunner", "SpectateBestAngle"}
	MapSpectateCFrames = GetMapSpectateAngles()
	
	local function GetSumRunnerDistanceToPoint(Vec)
		local Sum = 0
		local SampleSize = 0
		local P = game.Players:GetPlayers()
		for i = 1, #P do
			print(1)
			if P[i].TeamColor.Name == RunnerColor then
				print(2)
				if P[i].Character then
					print(3)
					local Tor = FindTorso(P[i].Character)
					if Tor~=nil then
						print(4)
						Sum = Sum+(Tor.Position-Vec).magnitude
						SampleSize = SampleSize+1
					end
				end
			end
		end
		--print("ok", Sum, SampleSize)
		return Sum, SampleSize
	end
	
	local PreviousSpectateMode = CurrentSpectateMode
	repeat
		local Done = false
		CurrentSpectateMode = SpectateModes[math.random(1, #SpectateModes)]
		if CurrentSpectateMode ~= PreviousSpectateMode then
			Done = true
		end
	until Done == true
	AutoId = AutoId+1
	local Cur = AutoId
	
	if CurrentSpectateMode == "SpectateKiller" then
		
		local P = game.Players:GetPlayers()
		local T = nil
		for i = 1, #P do
			if P[i].TeamColor.Name == KillerColor then
				if P[i].Character then
					T = FindTorso(P[i].Character)
				end
			end
		end
		if T == nil then
			QuitSpectate()
		end
		local Distance = 24
		local CF = T.CFrame
		local Time = 3+math.random()*2
		local Now = game.workspace.DistributedGameTime
		CF = CF*CFrame.Angles(0, math.pi*2*math.random(), 0)*CFrame.Angles(math.pi/4*math.random(), 0, 0)*CFrame.new(0, 0, -Distance)
		Cam.CoordinateFrame = CFrame.new(CF.X, CF.Y, CF.Z)
		local CallAgain = false
		while Cur == AutoId do
			Cam.CoordinateFrame = CFrame.new(Cam.CoordinateFrame.p.X, Cam.CoordinateFrame.p.Y, Cam.CoordinateFrame.p.Z)
			Cam.CoordinateFrame = CFrame.new(Cam.CoordinateFrame.p, T.Position)
			local Dis = (Cam.CoordinateFrame.p-T.Position).magnitude
			Cam.CoordinateFrame = Cam.CoordinateFrame*CFrame.new(0, 0, Distance-Dis)
			game:GetService("RunService").Renderstepped:wait()
			if game.workspace.DistributedGameTime-Time > Now then
				AutoId = AutoId+1
				CallAgain = true
			end
		end
		if CallAgain == true then
			Spectate(Obj)
		end
		
	elseif CurrentSpectateMode == "SpectateRandomRunner" then
		
		local P = game.Players:GetPlayers()
		local N = 0
		for i = 1, #P do
			N = N+1
			if P[N].TeamColor.Name ~= RunnerColor then
				table.remove(P, N)
				N = N-1
			end
		end
		if #P <= 0 then
			QuitSpectate()
			return
		end
		local T = nil
		repeat
			local Success = false
			local R = P[math.random(1, #P)]
			if R.Character then
				if FindTorso(R.Character) then
					T = FindTorso(R.Character)
					Success = true
				end
			end
		until Success == true
		local Distance = 24
		local CF = T.CFrame
		local Time = 3+math.random()*2
		local Now = game.workspace.DistributedGameTime
		CF = CF*CFrame.Angles(0, math.pi*2*math.random(), 0)*CFrame.Angles(math.pi/4*math.random(), 0, 0)*CFrame.new(0, 0, -Distance)
		Cam.CoordinateFrame = CFrame.new(CF.X, CF.Y, CF.Z)
		local CallAgain = false
		while Cur == AutoId do
			Cam.CoordinateFrame = CFrame.new(Cam.CoordinateFrame.p.X, Cam.CoordinateFrame.p.Y, Cam.CoordinateFrame.p.Z)
			Cam.CoordinateFrame = CFrame.new(Cam.CoordinateFrame.p, T.Position)
			local Dis = (Cam.CoordinateFrame.p-T.Position).magnitude
			Cam.CoordinateFrame = Cam.CoordinateFrame*CFrame.new(0, 0, Distance-Dis)
			game:GetService("RunService").Heartbeat:wait()
			if game.workspace.DistributedGameTime-Time > Now then
				AutoId = AutoId+1
				CallAgain = true
			end
		end
		if CallAgain == true then
			Spectate(Obj)
		end
		
	elseif CurrentSpectateMode == "SpectateBestAngle" then
		local Distances = {}
		for i = 1, #MapSpectateCFrames do
			Distances[i] = GetSumRunnerDistanceToPoint((MapSpectateCFrames[i]*CFrame.new(0, 0, -20)).p)
		end
		local BestAngle = CFrame.new(0, 0, 0)
		local Lowest = math.min(unpack(Distances))
		for i = 1, #Distances do
			if Distances[i] == Lowest then
				BestAngle = MapSpectateCFrames[i]
			end
		end
		
		local Time = 3+math.random()*2
		local Now = game.workspace.DistributedGameTime
		game.workspace.CurrentCamera.CoordinateFrame = BestAngle
		local CallAgain = false
		--print("f", Cur, AutoId)
		while Cur == AutoId do
			--print("g", Cur, AutoId)
			game:GetService("RunService").Heartbeat:wait()
			if game.workspace.DistributedGameTime-Time > Now then
				AutoId = AutoId+1
				CallAgain = true
				--print("h")
			end
		end
		--print("i", Cur, AutoId)
		if CallAgain == true then
			Spectate(Obj)
		end
	end
	
end

function Spectate(Obj)
	if type(Obj)~="string" then
		SpectatePlayer(Obj.Name)
	else
		SpectateMap(Obj)
	end
end

function QuitSpectate()
	AutoId = AutoId+1
	game.workspace.CurrentCamera.CameraType = "Custom"
	game.workspace.CurrentCamera.CameraSubject = script.Parent.Parent.Character.Humanoid
end

function ScrollSpectateList(Direction) -- Direction should be '1' if you want to spectate the next item, -1 for previous
	AutoId = AutoId+1
	print(#SpectateOrder, CurrentIndex, SpectateOrder[1], SpectateOrder[2], SpectateOrder[3])
	--CurrentIndex = (CurrentIndex+Direction >= 1 and (math.fmod(CurrentIndex, #SpectateOrder)+Direction)) or #SpectateOrder
	CurrentIndex = CurrentIndex+Direction
	if CurrentIndex > #SpectateOrder then
		CurrentIndex = 1
	elseif CurrentIndex <= 0 then
		CurrentIndex = #SpectateOrder
	end
	print(#SpectateOrder, CurrentIndex, SpectateOrder[1], SpectateOrder[2], SpectateOrder[3])
	Spectate(SpectateOrder[CurrentIndex])
end

function EnterSpectate()
	ScrollSpectateList(0)
end

--[[function AddPlayerToList(p)
	table.insert(SpectateOrder, 1, p)
end]]

--[[function RemovePlayerFromList(p)
	local Num = 1
	for i = 1, #SpectateOrder do
		if SpectateOrder[i] == p then
			Num = i
		end
	end
	table.remove(SpectateOrder, Num)
	if Num >= CurrentIndex then
		ScrollSpectateList(-1)
	end
end]]

function ResetSpectateList()
	print("Reset")
	SpectateOrder = {}
	local P = game.Players:GetPlayers()
	for i = 1, #P do
		if P[i]~=script.Parent.Parent.Name and (P[i].TeamColor.Name == RunnerColor or P[i].TeamColor.Name == KillerColor) then
			SpectateOrder[#SpectateOrder+1] = P[i]
		end
	end
	--SpectateOrder[#SpectateOrder+1] = unpack(GetMapSpectateAngles())
	SpectateOrder[#SpectateOrder+1] = "AutoSpectate"
end

--------------------------------------- Player functions

local Players = game.Players:GetChildren()
for t = 1, #Players do
	--AddPlayerToList(Players[t])
	ResetSpectateList()
	Players[t].CharacterAdded:connect(function(char)
		char:WaitForChild("Humanoid").Died:connect(function()
			--RemovePlayerFromList(Players[t])
			ResetSpectateList()
		end)
	end)
	Players[t].Changed:connect(function()
		--print("a")
		ResetSpectateList()
	end)
end

game.Players.ChildAdded:connect(function(plr)
	--AddPlayerToList(plr)
	print("PlayerAdded!")
	ResetSpectateList()
	plr.CharacterAdded:connect(function(char)
		char:WaitForChild("Humanoid").Died:connect(function()
			--RemovePlayerFromList(plr)
			ResetSpectateList()
		end)
	end)
	plr.Changed:connect(function()
		--print("a")
		ResetSpectateList()
	end)
end)

game.Players.PlayerRemoving:connect(function(plr)
	--RemovePlayerFromList(plr)
	ResetSpectateList()
end)

wait(.5)
ResetSpectateList()

--------------------------------------- Connectors

script.Parent.Parent.PlayerGui.ScreenGui.Left.MouseButton1Down:connect(function() ScrollSpectateList(-1) end)
script.Parent.Parent.PlayerGui.ScreenGui.Right.MouseButton1Down:connect(function() ScrollSpectateList(1) end)
script.Parent.Parent.PlayerGui.ScreenGui.Toggle.MouseButton1Down:connect(function()
	if script.Parent.Parent.PlayerGui.ScreenGui.Toggle.Text == "Off" then
		script.Parent.Parent.PlayerGui.ScreenGui.Toggle.Text = "On"
		EnterSpectate()
	else
		script.Parent.Parent.PlayerGui.ScreenGui.Toggle.Text = "Off"
		QuitSpectate()
	end
end)


Other info: Copy and paste the code above into a LocalScript inside the ‘StarterPlayerScripts’. The problem is probably the ‘game:GetService(“RunService”).Heartbeat:wait()’ on line 178 and 208 and the RenderStepped on line 130. You will need at least 3 players in 3 different Teams to test this: ‘Bright blue’-team, ‘Bright red’-team and ‘White’-team. You will also need a Gui with TextButtons that looks like this to test the code:

External Media

My thanks in advance.

http://wiki.roblox.com/index.php?title=API:Class/RunService/Heartbeat

Heartbeat fires once every ~1/30 second, and you have 60fps, so that’s where the jitter is coming from.

Replace all your Heartbeat:wait()s with RenderStepped:wait()s and see if that fixes it.

EDIT: also I cannot reproduce your issue in Studio, haven’t checked online, but maybe I just don’t understand all of these camera modes I suppose.

You could also try using GetRenderCFrame instead of raw torso positions.

Characters are interpolated on online servers, but the position values are not. Replacing T.Position with T:GetRenderCFrame().p should make the camera smoother.

GetRenderCFrame fixed it! :smiley:
@Gl0in2, can you make a shirt for 5k robux so I can buy it?

@builthomas, I used RenderStepped beforehand but that also didn’t do the job. Luckily it works now though.

Glad it worked :smiley:

You can keep the money.