FREECAMTEST
Key RequiredUniversal

FREECAMTEST

Universal Scriptboinoweeboinowee 224 views 17 executesUpdated Feb 12, 2026
Unrated
00
Lua Script•735 lines (31684 chars)
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local TweenService = game:GetService("TweenService")
local HttpService = game:GetService("HttpService")

local player = Players.LocalPlayer
local camera = workspace.CurrentCamera
local playerModule = require(player:WaitForChild("PlayerScripts"):WaitForChild("PlayerModule"))
local controls = playerModule:GetControls()

-- // SETTINGS & STATE //
local currentSpeed, currentSens, currentFOV = 1.5, 0.4, 70
local slideEnabled, isFreecamEnabled, collisionEnabled = false, false, false
local is180Mode, isDpadMode, rotationLocked = false, false, false
local slipPower, targetFPS, fpsCapEnabled = 1.0, 60, false
local CAM_RADIUS = 1.5

-- Playback State
local isRecording, isPlaying = false, false
local playbackSpeed, isReverse = 1.0, false
local isLooping = false
local playbackTime, totalDuration = 0, 0
local recordedPath = {} 
local recStartTime = 0

-- Movement State
local cameraCFrame = CFrame.new()
local yaw, pitch = 0, 0
local velocity = Vector3.new(0,0,0)
local joystickTouch = nil
local gamepadRotation = Vector2.zero
local dpadMove = Vector3.zero
local verticalMove = 0 
local dpadState = {Up=false, Down=false, Left=false, Right=false}
local isRightMouseDown = false 

-- File System State
local SAVES_FILE = "UltraFreecam_Saves.json"
local savedRecordings = {} 

-- // IO HELPER FUNCTIONS //
local function serializeCFrame(cf)
	local x, y, z = cf.Position.X, cf.Position.Y, cf.Position.Z
	local rx, ry, rz = cf:ToEulerAnglesXYZ()
	return {x, y, z, rx, ry, rz}
end

local function deserializeCFrame(data)
	return CFrame.new(data[1], data[2], data[3]) * CFrame.Angles(data[4], data[5], data[6])
end

local function saveToDisk()
	if not writefile then return end
	local dataToSave = {}
	for _, rec in ipairs(savedRecordings) do
		local cleanPath = {}
		for _, node in ipairs(rec.path) do
			table.insert(cleanPath, {t = node.t, cf = serializeCFrame(node.cf)})
		end
		table.insert(dataToSave, {name = rec.name, date = rec.date, dur = rec.dur, path = cleanPath})
	end
	pcall(function() writefile(SAVES_FILE, HttpService:JSONEncode(dataToSave)) end)
end

local function loadFromDisk()
	if not readfile or not isfile or not isfile(SAVES_FILE) then return end
	local success, result = pcall(function() return HttpService:JSONDecode(readfile(SAVES_FILE)) end)
	if success and result then
		savedRecordings = {}
		for _, rec in ipairs(result) do
			local realPath = {}
			for _, node in ipairs(rec.path) do
				table.insert(realPath, {t = node.t, cf = deserializeCFrame(node.cf)})
			end
			table.insert(savedRecordings, {name = rec.name, date = rec.date, dur = rec.dur, path = realPath})
		end
	end
end

-- // UI SETUP //
local screenGui = Instance.new("ScreenGui")
screenGui.Name = "UltraFreecam_V4_2"
screenGui.ResetOnSpawn = false
screenGui.IgnoreGuiInset = true
screenGui.DisplayOrder = 10
screenGui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
screenGui.Parent = player:WaitForChild("PlayerGui")

loadFromDisk()

local mainFrame = Instance.new("Frame")
mainFrame.Name = "MainFrame"
mainFrame.Size = UDim2.new(0, 160, 0, 450)
mainFrame.Position = UDim2.new(1, -180, 0.5, -225)
mainFrame.BackgroundColor3 = Color3.fromRGB(15, 15, 15)
mainFrame.BackgroundTransparency = 0.1
mainFrame.Active = true -- Important for stopping rotation
mainFrame.Parent = screenGui
Instance.new("UICorner", mainFrame)

local dragHandle = Instance.new("TextButton")
dragHandle.Size = UDim2.new(1, 0, 0, 25)
dragHandle.BackgroundColor3 = Color3.fromRGB(45, 45, 45)
dragHandle.Text = ":: V4.2 MENU ::"
dragHandle.TextColor3 = Color3.new(0.8,0.8,0.8)
dragHandle.Font = Enum.Font.GothamBold
dragHandle.TextSize = 12
dragHandle.Parent = mainFrame
Instance.new("UICorner", dragHandle)

local scrollFrame = Instance.new("ScrollingFrame")
scrollFrame.Size = UDim2.new(1, -10, 1, -40)
scrollFrame.Position = UDim2.new(0, 5, 0, 30)
scrollFrame.BackgroundTransparency = 1
scrollFrame.ScrollBarThickness = 4
scrollFrame.AutomaticCanvasSize = Enum.AutomaticSize.Y
scrollFrame.CanvasSize = UDim2.new(0,0,0,0)
scrollFrame.Active = true
scrollFrame.Parent = mainFrame

local uiList = Instance.new("UIListLayout")
uiList.Parent = scrollFrame
uiList.Padding = UDim.new(0, 5)
uiList.HorizontalAlignment = Enum.HorizontalAlignment.Center
uiList.SortOrder = Enum.SortOrder.LayoutOrder

local function createBtn(text, color, layoutOrder)
	local b = Instance.new("TextButton")
	b.Size = UDim2.new(0.95, 0, 0, 28)
	b.BackgroundColor3 = color or Color3.fromRGB(35, 35, 35)
	b.TextColor3 = Color3.new(1,1,1)
	b.Text = text
	b.Font = Enum.Font.GothamBold
	b.TextSize = 12
	b.LayoutOrder = layoutOrder or 0
	b.Parent = scrollFrame
	Instance.new("UICorner", b)
	return b
end

local function createBox(defText, layoutOrder)
	local b = Instance.new("TextBox")
	b.Size = UDim2.new(0.95, 0, 0, 24)
	b.BackgroundColor3 = Color3.new(0,0,0)
	b.BackgroundTransparency = 0.5
	b.TextColor3 = Color3.new(1,1,1)
	b.Text = defText
	b.Font = Enum.Font.Gotham
	b.TextSize = 12
	b.LayoutOrder = layoutOrder or 0
	b.Parent = scrollFrame
	Instance.new("UICorner", b)
	return b
end

local function createHeader(text, layoutOrder)
	local l = Instance.new("TextLabel")
	l.Size = UDim2.new(1, 0, 0, 15)
	l.BackgroundTransparency = 1
	l.TextColor3 = Color3.fromRGB(150, 150, 150)
	l.Text = text
	l.Font = Enum.Font.GothamBold
	l.TextSize = 10
	l.LayoutOrder = layoutOrder
	l.Parent = scrollFrame
	return l
end

local order = 0
local function inc() order = order + 1 return order end

createHeader("- CAMERA -", inc())
local toggleBtn = createBtn("CAM: OFF", Color3.fromRGB(180, 50, 50), inc())
local modeBtn = createBtn("180°: OFF", nil, inc())
local lockRotBtn = createBtn("ROT LOCK: OFF", nil, inc())

createHeader("- SETTINGS -", inc())
local speedIn = createBox("Speed: 1.5", inc())
local sensIn = createBox("Sens: 0.4", inc())
local fovIn = createBox("FOV: 70", inc())
local colTog = createBtn("COLLIDE: OFF", nil, inc())

createHeader("- MOVEMENT -", inc())
local slideBtn = createBtn("SLIDE: OFF", nil, inc())
local slipIn = createBox("Slip: 1.0", inc())
local dpadTog = createBtn("DPAD: OFF", Color3.fromRGB(150, 80, 0), inc())

createHeader("- RECORDER -", inc())
local recBtn = createBtn("RECORD", Color3.fromRGB(200, 50, 50), inc())
local playBtn = createBtn("PLAY", Color3.fromRGB(50, 150, 50), inc())
local loopBtn = createBtn("LOOP: OFF", nil, inc())
local revBtn = createBtn("REV: OFF", nil, inc())
local savesBtn = createBtn("SHOW SAVES", Color3.fromRGB(200, 120, 0), inc())
local pbSpeedIn = createBox("PB Speed: 1.0", inc())

local seekContainer = Instance.new("Frame")
seekContainer.Size = UDim2.new(0.95, 0, 0, 30)
seekContainer.BackgroundTransparency = 1
seekContainer.LayoutOrder = inc()
seekContainer.Parent = scrollFrame
local seekTimeLabel = Instance.new("TextLabel")
seekTimeLabel.Size = UDim2.new(1, 0, 0, 10)
seekTimeLabel.BackgroundTransparency = 1
seekTimeLabel.Text = "0.0s / 0.0s"
seekTimeLabel.TextColor3 = Color3.new(1,1,1)
seekTimeLabel.TextSize = 10
seekTimeLabel.Font = Enum.Font.Code
seekTimeLabel.Parent = seekContainer
local seekBarBg = Instance.new("Frame")
seekBarBg.Name = "SeekBg"
seekBarBg.Size = UDim2.new(1, 0, 0, 6)
seekBarBg.Position = UDim2.new(0, 0, 0, 18)
seekBarBg.BackgroundColor3 = Color3.fromRGB(60,60,60)
seekBarBg.Active = true -- Block rotation on seek bar
seekBarBg.Parent = seekContainer
Instance.new("UICorner", seekBarBg)
local seekFill = Instance.new("Frame")
seekFill.Size = UDim2.new(0, 0, 1, 0)
seekFill.BackgroundColor3 = Color3.fromRGB(200, 50, 50)
seekFill.Parent = seekBarBg
Instance.new("UICorner", seekFill)
local seekKnob = Instance.new("TextButton")
seekKnob.Size = UDim2.new(0, 14, 0, 14)
seekKnob.Position = UDim2.new(0, 0, 0.5, 0)
seekKnob.AnchorPoint = Vector2.new(0.5, 0.5)
seekKnob.Text = ""
seekKnob.Active = true
seekKnob.Parent = seekBarBg
Instance.new("UICorner", seekKnob)

createHeader("- MISC -", inc())
local fpsTog = createBtn("FPS CAP: OFF", nil, inc())
local fpsIn = createBox("FPS: 60", inc())
local tpBtn = createBtn("TELEPORT", Color3.fromRGB(80, 50, 150), inc())
local hideBtn = createBtn("HIDE MENU", Color3.fromRGB(30, 30, 30), inc())

local resizeHandle = Instance.new("ImageButton")
resizeHandle.Size = UDim2.new(0, 20, 0, 20)
resizeHandle.Position = UDim2.new(1, -20, 1, -20)
resizeHandle.BackgroundTransparency = 1
resizeHandle.Image = "rbxassetid://3577448866"
resizeHandle.ImageColor3 = Color3.fromRGB(100, 100, 100)
resizeHandle.Parent = mainFrame

local showBtn = Instance.new("TextButton")
showBtn.Size = UDim2.new(0, 70, 0, 30)
showBtn.Position = UDim2.new(1, -80, 0, 10)
showBtn.BackgroundColor3 = Color3.new(0,0,0)
showBtn.Text = "SHOW"
showBtn.TextColor3 = Color3.new(1,1,1)
showBtn.Visible = false
showBtn.Active = true
showBtn.Parent = screenGui
Instance.new("UICorner", showBtn)

-- // SAVES UI SYSTEM //
local savesFrame = Instance.new("Frame")
savesFrame.Name = "SavesFrame"
savesFrame.Size = UDim2.new(0, 250, 0, 300)
savesFrame.Position = UDim2.new(0.5, -125, 0.5, -150)
savesFrame.BackgroundColor3 = Color3.fromRGB(20, 20, 20)
savesFrame.Visible = false
savesFrame.Active = true
savesFrame.Parent = screenGui
Instance.new("UICorner", savesFrame)

local savesHeader = Instance.new("TextButton")
savesHeader.Size = UDim2.new(1, 0, 0, 30)
savesHeader.BackgroundColor3 = Color3.fromRGB(40, 40, 40)
savesHeader.Text = "RECORDINGS"
savesHeader.TextColor3 = Color3.new(1,1,1)
savesHeader.Font = Enum.Font.GothamBold
savesHeader.TextSize = 14
savesHeader.Parent = savesFrame
Instance.new("UICorner", savesHeader)

local closeSaves = Instance.new("TextButton")
closeSaves.Size = UDim2.new(0, 25, 0, 25)
closeSaves.Position = UDim2.new(1, -28, 0, 2)
closeSaves.BackgroundColor3 = Color3.fromRGB(180, 50, 50)
closeSaves.Text = "X"
closeSaves.TextColor3 = Color3.new(1,1,1)
closeSaves.Parent = savesHeader
Instance.new("UICorner", closeSaves)

local savesScroll = Instance.new("ScrollingFrame")
savesScroll.Size = UDim2.new(1, -10, 1, -40)
savesScroll.Position = UDim2.new(0, 5, 0, 35)
savesScroll.BackgroundTransparency = 1
savesScroll.ScrollBarThickness = 4
savesScroll.Active = true
savesScroll.Parent = savesFrame
local savesList = Instance.new("UIListLayout")
savesList.Parent = savesScroll
savesList.Padding = UDim.new(0, 4)
savesList.SortOrder = Enum.SortOrder.LayoutOrder

-- Confirm Delete UI
local confirmFrame = Instance.new("Frame")
confirmFrame.Size = UDim2.new(0, 200, 0, 100)
confirmFrame.Position = UDim2.new(0.5, -100, 0.5, -50)
confirmFrame.BackgroundColor3 = Color3.fromRGB(30,30,30)
confirmFrame.Visible = false
confirmFrame.ZIndex = 10
confirmFrame.Active = true
confirmFrame.Parent = screenGui
Instance.new("UICorner", confirmFrame)

local confirmText = Instance.new("TextLabel")
confirmText.Size = UDim2.new(1, 0, 0, 40)
confirmText.BackgroundTransparency = 1
confirmText.Text = "Delete this recording?"
confirmText.TextColor3 = Color3.new(1,1,1)
confirmText.Font = Enum.Font.GothamBold
confirmText.TextSize = 14
confirmText.ZIndex = 11
confirmText.Parent = confirmFrame

local yesBtn = Instance.new("TextButton")
yesBtn.Size = UDim2.new(0, 80, 0, 30)
yesBtn.Position = UDim2.new(0, 10, 0.6, 0)
yesBtn.BackgroundColor3 = Color3.fromRGB(150, 50, 50)
yesBtn.Text = "YES"
yesBtn.TextColor3 = Color3.new(1,1,1)
yesBtn.ZIndex = 11
yesBtn.Parent = confirmFrame
Instance.new("UICorner", yesBtn)

local noBtn = Instance.new("TextButton")
noBtn.Size = UDim2.new(0, 80, 0, 30)
noBtn.Position = UDim2.new(1, -90, 0.6, 0)
noBtn.BackgroundColor3 = Color3.fromRGB(50, 150, 50)
noBtn.Text = "NO"
noBtn.TextColor3 = Color3.new(1,1,1)
noBtn.ZIndex = 11
noBtn.Parent = confirmFrame
Instance.new("UICorner", noBtn)

local itemToDeleteIdx = nil
local function refreshSaves()
	for _, v in pairs(savesScroll:GetChildren()) do if v:IsA("Frame") then v:Destroy() end end
	for i, save in ipairs(savedRecordings) do
		local row = Instance.new("Frame")
		row.Size = UDim2.new(1, 0, 0, 40)
		row.BackgroundColor3 = Color3.fromRGB(30, 30, 30)
		row.Parent = savesScroll
		row.Active = true -- Block rotation
		Instance.new("UICorner", row)
		
		local nameBox = Instance.new("TextBox")
		nameBox.Size = UDim2.new(0.5, 0, 0, 20)
		nameBox.Position = UDim2.new(0, 5, 0, 5)
		nameBox.BackgroundTransparency = 1
		nameBox.Text = save.name
		nameBox.TextColor3 = Color3.new(1,1,1)
		nameBox.Font = Enum.Font.GothamBold
		nameBox.TextXAlignment = Enum.TextXAlignment.Left
		nameBox.TextSize = 12
		nameBox.Parent = row
		nameBox.FocusLost:Connect(function() save.name = nameBox.Text; saveToDisk() end)
		
		local dateLbl = Instance.new("TextLabel")
		dateLbl.Size = UDim2.new(0.5, 0, 0, 15)
		dateLbl.Position = UDim2.new(0, 5, 0, 22)
		dateLbl.BackgroundTransparency = 1
		dateLbl.Text = save.date .. " ("..string.format("%.1fs", save.dur)..")"
		dateLbl.TextColor3 = Color3.fromRGB(150,150,150)
		dateLbl.Font = Enum.Font.Code
		dateLbl.TextSize = 10
		dateLbl.TextXAlignment = Enum.TextXAlignment.Left
		dateLbl.Parent = row
		
		local pBtn = Instance.new("TextButton")
		pBtn.Size = UDim2.new(0, 30, 0, 30)
		pBtn.Position = UDim2.new(1, -75, 0.5, -15)
		pBtn.BackgroundColor3 = Color3.fromRGB(50, 150, 50)
		pBtn.Text = "▶"
		pBtn.TextColor3 = Color3.new(1,1,1)
		pBtn.Parent = row
		Instance.new("UICorner", pBtn)
		
		local dBtn = Instance.new("TextButton")
		dBtn.Size = UDim2.new(0, 30, 0, 30)
		dBtn.Position = UDim2.new(1, -35, 0.5, -15)
		dBtn.BackgroundColor3 = Color3.fromRGB(150, 50, 50)
		dBtn.Text = "🗑"
		dBtn.TextColor3 = Color3.new(1,1,1)
		dBtn.Parent = row
		Instance.new("UICorner", dBtn)
		
		pBtn.MouseButton1Click:Connect(function()
			recordedPath = save.path; totalDuration = save.dur; playbackTime = 0
			isPlaying = true; isRecording = false; playBtn.Text = "PAUSE"
			playBtn.BackgroundColor3 = Color3.fromRGB(100, 200, 100)
		end)
		
		dBtn.MouseButton1Click:Connect(function()
			itemToDeleteIdx = i
			confirmFrame.Visible = true
		end)
	end
	savesScroll.CanvasSize = UDim2.new(0, 0, 0, #savedRecordings * 44)
end

yesBtn.MouseButton1Click:Connect(function()
	if itemToDeleteIdx then
		table.remove(savedRecordings, itemToDeleteIdx)
		saveToDisk(); refreshSaves()
		itemToDeleteIdx = nil
		confirmFrame.Visible = false
	end
end)
noBtn.MouseButton1Click:Connect(function()
	itemToDeleteIdx = nil
	confirmFrame.Visible = false
end)

-- // VERTICAL UI //
local vertFrame = Instance.new("Frame")
vertFrame.Size = UDim2.new(0, 50, 0, 110)
vertFrame.Position = UDim2.new(0, 10, 0.5, -55)
vertFrame.BackgroundTransparency = 1
vertFrame.Parent = screenGui

local function makeVertBtn(txt, pos, val)
	local b = Instance.new("TextButton")
	b.Size = UDim2.new(0, 40, 0, 40)
	b.Position = pos
	b.BackgroundColor3 = Color3.new(0,0,0)
	b.BackgroundTransparency = 0.4
	b.Text = txt; b.TextColor3 = Color3.new(1,1,1); b.TextSize = 20; b.Font = Enum.Font.GothamBold
	b.Active = true
	b.Parent = vertFrame
	Instance.new("UICorner", b)
	b.InputBegan:Connect(function(i) if i.UserInputType == Enum.UserInputType.MouseButton1 or i.UserInputType == Enum.UserInputType.Touch then verticalMove = val; b.BackgroundTransparency = 0.1 end end)
	b.InputEnded:Connect(function(i) if i.UserInputType == Enum.UserInputType.MouseButton1 or i.UserInputType == Enum.UserInputType.Touch then if verticalMove == val then verticalMove = 0 end; b.BackgroundTransparency = 0.4 end end)
end
makeVertBtn("▲", UDim2.new(0,0,0,0), 1)
makeVertBtn("▼", UDim2.new(0,0,1,-40), -1)

-- // DPAD UI //
local dpadFrame = Instance.new("Frame")
dpadFrame.Size = UDim2.new(0, 180, 0, 180)
dpadFrame.Position = UDim2.new(0, 30, 1, -220)
dpadFrame.BackgroundTransparency = 1
dpadFrame.Visible = false
dpadFrame.Parent = screenGui
local function makeArrow(name, pos, rot)
	local btn = Instance.new("ImageButton")
	btn.Size = UDim2.new(0, 55, 0, 55); btn.Position = pos
	btn.BackgroundColor3 = Color3.new(0,0,0); btn.BackgroundTransparency = 0.4
	btn.Image = "rbxassetid://6031091004"; btn.Rotation = rot; btn.Active = true; btn.Parent = dpadFrame
	Instance.new("UICorner", btn)
	return btn
end
local btnU, btnD, btnL, btnR = makeArrow("U", UDim2.new(0.5, -27, 0, 0), 0), makeArrow("D", UDim2.new(0.5, -27, 1, -55), 180), makeArrow("L", UDim2.new(0, 0, 0.5, -27), -90), makeArrow("R", UDim2.new(1, -55, 0.5, -27), 90)

-- // LOGIC: FPS CAPPER //
task.spawn(function()
	while true do
		if fpsCapEnabled then
			local t = os.clock()
			RunService.Heartbeat:Wait()
			while (os.clock() - t) < (1/targetFPS) do end
		else
			RunService.Heartbeat:Wait()
		end
	end
end)

-- // LOGIC: MOVEMENT & RECORDER //
local function applyRot(delta)
	if rotationLocked then return end
	if is180Mode then
		yaw = yaw - math.rad(delta.X)
		pitch = math.clamp(pitch - math.rad(delta.Y), math.rad(-89), math.rad(89))
		cameraCFrame = CFrame.new(cameraCFrame.Position) * CFrame.Angles(0, yaw, 0) * CFrame.Angles(pitch, 0, 0)
	else
		cameraCFrame = cameraCFrame * CFrame.Angles(0, -math.rad(delta.X), 0) * CFrame.Angles(-math.rad(delta.Y), 0, 0)
	end
end

local function getFrameAtTime(t)
	if #recordedPath < 2 then return cameraCFrame end
	t = math.clamp(t, 0, totalDuration)
	local idx1, idx2 = 1, #recordedPath
	for i = 1, #recordedPath - 1 do if recordedPath[i+1].t >= t then idx1 = i; idx2 = i + 1; break end end
	local f1, f2 = recordedPath[idx1], recordedPath[idx2]
	local duration = f2.t - f1.t
	if duration <= 0 then return f1.cf end
	return f1.cf:Lerp(f2.cf, (t - f1.t) / duration)
end

RunService:BindToRenderStep("FreecamCore", Enum.RenderPriority.Camera.Value + 101, function(dt)
	if isRecording then
		local t = os.clock() - recStartTime
		table.insert(recordedPath, {cf = camera.CFrame, t = t})
		totalDuration = t
		seekTimeLabel.Text = string.format("%.1fs / %.1fs", t, t)
	elseif isPlaying then
		if totalDuration > 0 then
			playbackTime = playbackTime + (dt * playbackSpeed * (isReverse and -1 or 1))
			if isLooping then
				if playbackTime > totalDuration then playbackTime = 0 elseif playbackTime < 0 then playbackTime = totalDuration end
			else
				if playbackTime > totalDuration then playbackTime = totalDuration; isPlaying = false; playBtn.Text = "PLAY" end
				if playbackTime < 0 then playbackTime = 0; isPlaying = false; playBtn.Text = "PLAY" end
			end
			camera.CFrame = getFrameAtTime(playbackTime); cameraCFrame = camera.CFrame
			local percent = math.clamp(playbackTime / totalDuration, 0, 1)
			seekKnob.Position = UDim2.new(percent, 0, 0.5, 0); seekFill.Size = UDim2.new(percent, 0, 1, 0)
			seekTimeLabel.Text = string.format("%.1fs / %.1fs", playbackTime, totalDuration)
		end
		camera.CameraType = Enum.CameraType.Scriptable; return
	end

	if not isFreecamEnabled then return end
	camera.CameraType = Enum.CameraType.Scriptable
	local mv = isDpadMode and dpadMove or controls:GetMoveVector()
	local yForce = verticalMove
	if UserInputService:IsKeyDown(Enum.KeyCode.Q) then yForce = 1 elseif UserInputService:IsKeyDown(Enum.KeyCode.E) then yForce = -1 end
	if UserInputService:GetGamepadConnected(Enum.UserInputType.Gamepad1) then
		local state = UserInputService:GetGamepadState(Enum.UserInputType.Gamepad1)
		for _, input in pairs(state) do
			if input.KeyCode == Enum.KeyCode.ButtonL2 and input.Position.Z > 0.1 then yForce = 1 end
			if input.KeyCode == Enum.KeyCode.ButtonR2 and input.Position.Z > 0.1 then yForce = -1 end
		end
	end
	if gamepadRotation.Magnitude > 0 then applyRot(gamepadRotation * currentSens) end
	
	-- Calculate Velocity
	local dir = (cameraCFrame.RightVector * mv.X) + (cameraCFrame.LookVector * -mv.Z) + (camera.CFrame.UpVector * yForce)
	local targetVel = dir * currentSpeed
	if slideEnabled then velocity = velocity:Lerp(targetVel, math.clamp(dt * (10/slipPower), 0.0001, 1.0)) else velocity = targetVel end
	
	local moveStep = velocity
	
	-- // COLLISION SOLVER //
	if collisionEnabled and moveStep.Magnitude > 0.001 then
		local params = RaycastParams.new()
		params.FilterDescendantsInstances = {player.Character}
		params.FilterType = Enum.RaycastFilterType.Exclude
		for i = 1, 3 do
			if moveStep.Magnitude < 0.0001 then break end
			local cast = workspace:Spherecast(cameraCFrame.Position, CAM_RADIUS, moveStep + (moveStep.Unit * 0.1), params)
			if cast and cast.Instance and cast.Instance.CanCollide then
				local normal = cast.Normal
				moveStep = moveStep - (normal * moveStep:Dot(normal))
			else
				break
			end
		end
	end
	
	cameraCFrame = cameraCFrame + moveStep
	camera.CFrame = cameraCFrame; camera.FieldOfView = currentFOV
end)

-- // UI INTERACTION //
local function sync(box, var, prefix)
	box.FocusLost:Connect(function()
		local val = tonumber(box.Text:match("[%d%.]+"))
		if val then
			if var == "speed" then currentSpeed = val elseif var == "sens" then currentSens = val elseif var == "fov" then currentFOV = val
			elseif var == "slip" then slipPower = math.max(val, 0.01) elseif var == "fps" then targetFPS = math.clamp(val, 1, 240)
			elseif var == "pbs" then playbackSpeed = val end
		end
		box.Text = prefix .. tostring(val or "??")
	end)
end
sync(speedIn, "speed", "Speed: "); sync(sensIn, "sens", "Sens: "); sync(fovIn, "fov", "FOV: ")
sync(slipIn, "slip", "Slip: "); sync(fpsIn, "fps", "FPS: "); sync(pbSpeedIn, "pbs", "PB Speed: ")

toggleBtn.MouseButton1Click:Connect(function()
	isFreecamEnabled = not isFreecamEnabled
	toggleBtn.Text = isFreecamEnabled and "CAM: ON" or "CAM: OFF"
	toggleBtn.BackgroundColor3 = isFreecamEnabled and Color3.new(0, 0.7, 0.3) or Color3.fromRGB(180, 50, 50)
	if isFreecamEnabled then
		cameraCFrame = camera.CFrame; local look = cameraCFrame.LookVector
		yaw = math.atan2(-look.X, -look.Z); pitch = math.asin(math.clamp(look.Y, -0.999, 0.999))
		if player.Character then for _, v in pairs(player.Character:GetDescendants()) do if v:IsA("BasePart") then v.Anchored = true end end end
	else
		isRecording = false; isPlaying = false; camera.CameraType = Enum.CameraType.Custom
		if player.Character then for _, v in pairs(player.Character:GetDescendants()) do if v:IsA("BasePart") then v.Anchored = false end end end
		UserInputService.MouseBehavior = Enum.MouseBehavior.Default
	end
end)

recBtn.MouseButton1Click:Connect(function()
	if isRecording then
		isRecording = false; recBtn.Text = "RECORD"; recBtn.BackgroundColor3 = Color3.fromRGB(200, 50, 50)
		table.insert(savedRecordings, {name = "Rec_"..#savedRecordings+1, date = os.date("%d/%m/%y %H:%M"), dur = totalDuration, path = recordedPath})
		saveToDisk(); refreshSaves()
	else
		isRecording = true; isPlaying = false; recBtn.Text = "STOP"; recBtn.BackgroundColor3 = Color3.new(0.8, 0, 0)
		recordedPath = {}; recStartTime = os.clock(); playbackTime = 0
	end
end)

playBtn.MouseButton1Click:Connect(function()
	if isPlaying then isPlaying = false; playBtn.Text = "PLAY"; playBtn.BackgroundColor3 = Color3.fromRGB(50, 150, 50)
	elseif #recordedPath > 0 then isPlaying = true; isRecording = false; playBtn.Text = "PAUSE"; playBtn.BackgroundColor3 = Color3.fromRGB(100, 200, 100)
	if playbackTime >= totalDuration then playbackTime = 0 end end
end)

loopBtn.MouseButton1Click:Connect(function() isLooping = not isLooping; loopBtn.Text = isLooping and "LOOP: ON" or "LOOP: OFF"; loopBtn.BackgroundColor3 = isLooping and Color3.fromRGB(200, 150, 0) or Color3.fromRGB(35, 35, 35) end)
revBtn.MouseButton1Click:Connect(function() isReverse = not isReverse; revBtn.Text = isReverse and "REV: ON" or "REV: OFF"; revBtn.BackgroundColor3 = isReverse and Color3.fromRGB(200, 100, 0) or Color3.fromRGB(35, 35, 35) end)
savesBtn.MouseButton1Click:Connect(function() savesFrame.Visible = true; refreshSaves() end)
closeSaves.MouseButton1Click:Connect(function() savesFrame.Visible = false end)

-- // SEEK LOGIC (UPDATED) //
local isDraggingSeek = false
local wasPlayingBeforeSeek = false

local function updateSeek(input)
	-- FIX: Check vertical distance (100 pixels threshold)
	local barCenterY = seekBarBg.AbsolutePosition.Y + (seekBarBg.AbsoluteSize.Y / 2)
	local distY = math.abs(input.Position.Y - barCenterY)
	
	if distY > 100 then return end -- Stop scrubbing if finger is too far away
	
	local pct = math.clamp((input.Position.X - seekBarBg.AbsolutePosition.X) / seekBarBg.AbsoluteSize.X, 0, 1)
	playbackTime = totalDuration * pct
	seekKnob.Position = UDim2.new(pct, 0, 0.5, 0); seekFill.Size = UDim2.new(pct, 0, 1, 0)
	seekTimeLabel.Text = string.format("%.1fs / %.1fs", playbackTime, totalDuration)
	if #recordedPath > 0 then camera.CameraType = Enum.CameraType.Scriptable; camera.CFrame = getFrameAtTime(playbackTime) end
end

seekBarBg.InputBegan:Connect(function(i) 
	if i.UserInputType == Enum.UserInputType.MouseButton1 or i.UserInputType == Enum.UserInputType.Touch then 
		isDraggingSeek = true
		wasPlayingBeforeSeek = isPlaying
		isPlaying = false -- Pause while dragging so it doesn't fight the user
		playBtn.Text = "PAUSE"
		updateSeek(i) 
	end 
end)

seekBarBg.InputEnded:Connect(function(i) 
	if i.UserInputType == Enum.UserInputType.MouseButton1 or i.UserInputType == Enum.UserInputType.Touch then 
		isDraggingSeek = false 
		if wasPlayingBeforeSeek then isPlaying = true; playBtn.Text = "PAUSE" else playBtn.Text = "PLAY" end
	end 
end)

UserInputService.InputChanged:Connect(function(i) 
	if isDraggingSeek and (i.UserInputType == Enum.UserInputType.MouseMovement or i.UserInputType == Enum.UserInputType.Touch) then 
		updateSeek(i) 
	end 
end)

-- Misc Buttons
modeBtn.MouseButton1Click:Connect(function() is180Mode = not is180Mode; modeBtn.Text = is180Mode and "180°: ON" or "180°: OFF" end)
lockRotBtn.MouseButton1Click:Connect(function() rotationLocked = not rotationLocked; lockRotBtn.Text = rotationLocked and "ROT LOCK: ON" or "ROT LOCK: OFF"; lockRotBtn.BackgroundColor3 = rotationLocked and Color3.fromRGB(200, 50, 50) or Color3.fromRGB(35,35,35) end)
colTog.MouseButton1Click:Connect(function() collisionEnabled = not collisionEnabled; colTog.Text = collisionEnabled and "COLLIDE: ON" or "COLLIDE: OFF" end)
slideBtn.MouseButton1Click:Connect(function() slideEnabled = not slideEnabled; slideBtn.Text = slideEnabled and "SLIDE: ON" or "SLIDE: OFF" end)
dpadTog.MouseButton1Click:Connect(function() isDpadMode = not isDpadMode; dpadTog.Text = isDpadMode and "DPAD: ON" or "DPAD: OFF"; dpadFrame.Visible = isDpadMode; dpadTog.BackgroundColor3 = isDpadMode and Color3.fromRGB(255, 120, 0) or Color3.fromRGB(150, 80, 0); player.DevTouchMovementMode = isDpadMode and Enum.DevTouchMovementMode.Scriptable or Enum.DevTouchMovementMode.UserChoice end)
fpsTog.MouseButton1Click:Connect(function() fpsCapEnabled = not fpsCapEnabled; fpsTog.Text = fpsCapEnabled and "FPS CAP: ON" or "FPS CAP: OFF" end)
tpBtn.MouseButton1Click:Connect(function() if player.Character then player.Character:MoveTo(cameraCFrame.Position) end end)
hideBtn.MouseButton1Click:Connect(function() mainFrame.Visible = false; showBtn.Visible = true end)
showBtn.MouseButton1Click:Connect(function() mainFrame.Visible = true; showBtn.Visible = false end)

-- Dragging Logic
local isDrag, isResize, activeFrame, dStart, sPos, sSize = false, false, nil, nil, nil, nil
dragHandle.InputBegan:Connect(function(i) if i.UserInputType == Enum.UserInputType.MouseButton1 or i.UserInputType == Enum.UserInputType.Touch then isDrag=true; activeFrame=mainFrame; dStart=i.Position; sPos=mainFrame.Position end end)
savesHeader.InputBegan:Connect(function(i) if i.UserInputType == Enum.UserInputType.MouseButton1 or i.UserInputType == Enum.UserInputType.Touch then isDrag=true; activeFrame=savesFrame; dStart=i.Position; sPos=savesFrame.Position end end)
resizeHandle.InputBegan:Connect(function(i) if i.UserInputType == Enum.UserInputType.MouseButton1 or i.UserInputType == Enum.UserInputType.Touch then isResize=true; dStart=i.Position; sSize=mainFrame.Size end end)
UserInputService.InputChanged:Connect(function(i)
	if i.UserInputType == Enum.UserInputType.MouseMovement or i.UserInputType == Enum.UserInputType.Touch then
		if isDrag and activeFrame then local delta = i.Position - dStart; activeFrame.Position = UDim2.new(sPos.X.Scale, sPos.X.Offset + delta.X, sPos.Y.Scale, sPos.Y.Offset + delta.Y)
		elseif isResize then local delta = i.Position - dStart; mainFrame.Size = UDim2.new(0, math.max(160, sSize.X.Offset + delta.X), 0, math.max(200, sSize.Y.Offset + delta.Y)) end
	end
end)
UserInputService.InputEnded:Connect(function(i) if i.UserInputType == Enum.UserInputType.MouseButton1 or i.UserInputType == Enum.UserInputType.Touch then isDrag=false; isResize=false; activeFrame=nil end end)

local function upDpad()
	local x, z = (dpadState.Right and 1 or 0) - (dpadState.Left and 1 or 0), (dpadState.Down and 1 or 0) - (dpadState.Up and 1 or 0)
	dpadMove = Vector3.new(x, 0, z); if dpadMove.Magnitude > 0 then dpadMove = dpadMove.Unit end
end
local function bindD(b, d)
	b.InputBegan:Connect(function(i) if i.UserInputType == Enum.UserInputType.Touch or i.UserInputType == Enum.UserInputType.MouseButton1 then dpadState[d] = true; upDpad(); b.BackgroundTransparency = 0.1 end end)
	b.InputEnded:Connect(function(i) if i.UserInputType == Enum.UserInputType.Touch or i.UserInputType == Enum.UserInputType.MouseButton1 then dpadState[d] = false; upDpad(); b.BackgroundTransparency = 0.4 end end)
end
bindD(btnU, "Up"); bindD(btnD, "Down"); bindD(btnL, "Left"); bindD(btnR, "Right")

-- // PC & MOBILE INPUTS //
UserInputService.InputBegan:Connect(function(input, gpe)
	if not isFreecamEnabled then return end
	if input.UserInputType == Enum.UserInputType.MouseButton2 then
		isRightMouseDown = true
		UserInputService.MouseBehavior = Enum.MouseBehavior.LockCurrentPosition
	end
	if isDpadMode and input.UserInputType == Enum.UserInputType.Gamepad1 then
		local k = input.KeyCode
		if k == Enum.KeyCode.DPadUp then dpadState.Up = true elseif k == Enum.KeyCode.DPadDown then dpadState.Down = true elseif k == Enum.KeyCode.DPadLeft then dpadState.Left = true elseif k == Enum.KeyCode.DPadRight then dpadState.Right = true end
		upDpad()
	end
	if input.UserInputType == Enum.UserInputType.Touch and not isDpadMode and input.Position.X < (camera.ViewportSize.X * 0.45) then joystickTouch = input end
end)

UserInputService.InputEnded:Connect(function(input)
	if input.UserInputType == Enum.UserInputType.MouseButton2 then
		isRightMouseDown = false
		UserInputService.MouseBehavior = Enum.MouseBehavior.Default
	end
	if input == joystickTouch then joystickTouch = nil end
	if input.KeyCode == Enum.KeyCode.Thumbstick2 then gamepadRotation = Vector2.zero end
	if isDpadMode and input.UserInputType == Enum.UserInputType.Gamepad1 then
		local k = input.KeyCode
		if k == Enum.KeyCode.DPadUp then dpadState.Up = false elseif k == Enum.KeyCode.DPadDown then dpadState.Down = false elseif k == Enum.KeyCode.DPadLeft then dpadState.Left = false elseif k == Enum.KeyCode.DPadRight then dpadState.Right = false end
		upDpad()
	end
end)

UserInputService.InputChanged:Connect(function(input, gpe)
	if not isFreecamEnabled then return end
	
	-- Gamepad Rotation
	if input.KeyCode == Enum.KeyCode.Thumbstick2 then 
		local p = input.Position; gamepadRotation = (p.Magnitude > 0.1) and Vector2.new(p.X * 5, -p.Y * 5) or Vector2.zero
	
	-- PC & Mobile Rotation
	elseif input.UserInputType == Enum.UserInputType.MouseMovement or (input.UserInputType == Enum.UserInputType.Touch and input ~= joystickTouch) then
		
		-- // STOP ROTATION IF INTERACTING WITH UI //
		if gpe then return end
		
		if not isDraggingSeek and not (isDpadMode and input.UserInputType == Enum.UserInputType.Touch and input.Position.X < (camera.ViewportSize.X * 0.45)) then
			if input.UserInputType == Enum.UserInputType.Touch or (input.UserInputType == Enum.UserInputType.MouseMovement and isRightMouseDown) then
				applyRot(Vector2.new(input.Delta.X * currentSens, input.Delta.Y * currentSens))
			end
		end
	end
end)

Features & Information

Test
#localplayer

Community Comments (0)

No comments yet for this script.

More Scripts for Universal Scripts