Freecam script supported on every device
Key RequiredUniversal

Freecam script supported on every device

Universal Scriptboinoweeboinowee 612 views 1.1K executesUpdated Feb 12, 2026
Unrated
00
Lua Script•699 lines (31050 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 -- Size of the camera hitbox

-- 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 -- For PC Rotation

-- 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_Full"
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
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 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.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.Size = UDim2.new(1, 0, 0, 6)
seekBarBg.Position = UDim2.new(0, 0, 0, 18)
seekBarBg.BackgroundColor3 = Color3.fromRGB(60,60,60)
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.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.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.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.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.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
		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.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.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
	
	-- // FIXED COLLISION SYSTEM (Iterative) //
	if collisionEnabled and moveStep.Magnitude > 0.001 then
		local params = RaycastParams.new()
		params.FilterDescendantsInstances = {player.Character}
		params.FilterType = Enum.RaycastFilterType.Exclude
		
		-- Iterate 3 times to handle corners (Wall -> Floor -> Slide)
		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
				-- Project velocity onto the wall plane (slide)
				local normal = cast.Normal
				local proj = moveStep - (normal * moveStep:Dot(normal))
				moveStep = proj
			else
				break -- No collision found
			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 -- Reset mouse
	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
local isDraggingSeek = false
local function updateSeek(input)
	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 not isPlaying and #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; updateSeek(i) end end)
seekBarBg.InputEnded:Connect(function(i) if i.UserInputType == Enum.UserInputType.MouseButton1 or i.UserInputType == Enum.UserInputType.Touch then isDraggingSeek = false 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")

-- // FIXED PC 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 -- LOCK MOUSE
	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 -- UNLOCK MOUSE
	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
		if not isDraggingSeek and not (isDpadMode and input.UserInputType == Enum.UserInputType.Touch and input.Position.X < (camera.ViewportSize.X * 0.45)) then
			
			-- Allow rotation if: It is Touch OR (It is Mouse AND Right Button is Held)
			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

Includes a heck ton I can't be bothered to list allat
#freecam#universal#Keyless#Free#Freecam script supported on every device

Community Comments (0)

No comments yet for this script.

More Scripts for Universal Scripts