How to make an Easy Freecam Script! [MOBILE SUPPORT]

Since there aren’t too many freecam tutorials/scripts on here, I decided to make an easy one that anyone can use. If you are here for just the script and not to learn, here is the model..

Tutorial:

First off, create a script in StarterGui named whatever you want, but I just used the name “CamScript”. At the top add these variables:

local cam = workspace.CurrentCamera
local UIS = game:GetService("UserInputService")
local RS = game:GetService("RunService")
local onMobile = not UIS.KeyboardEnabled
local keysDown = {}
local rotating = false

cam is the camera, UIS and RS are different services we are going to use, onMobile just lets us know if the player is using a device that has no keyboard (phone, tablet, etc) and the rest are used to control the camera. Next, add these two lines:

if not game:IsLoaded() then game.Loaded:Wait() end

cam.CameraType = Enum.CameraType.Scriptable 

The first line waits until the game is loaded so the second line can set the camera type to scriptable. Otherwise, we wouldn’t be able to control the camera with a script. The last lines you should add to the variable section are these:

local speed = 5 
local sens = .3

speed /= 10
if onMobile then sens*=2 end

“speed” is how fast the camera moves, and sens (sensitivity) is how fast the camera turns. If the player is on mobile, the sensitivity is increased to compensate for the smaller screen size. Now, on to the render stepped function.

local function renderStepped() -- runs every frame
	-- rotate camera
	if rotating then
		local delta = UIS:GetMouseDelta()
		local cf = cam.CFrame
		local deltaY = delta.Y
		local yAngle = cf:ToEulerAngles(Enum.RotationOrder.YZX)
		local newAmount = math.deg(yAngle)+deltaY
		if newAmount > 65 or newAmount < -65 then
		if not (yAngle<0 and delta.Y<0) and not (yAngle>0 and delta.Y>0) then
		    delta = Vector2.new(delta.X,0)
		end end
		cf *= CFrame.Angles(-math.rad(deltaY),0,0)
		cf = CFrame.Angles(0,-math.rad(delta.X),0) * (cf - cf.Position) + cf.Position
		cf = CFrame.lookAt(cf.Position, cf.Position + cf.LookVector)
		if delta ~= Vector2.new(0,0) then cam.CFrame = cam.CFrame:Lerp(cf,sens) end
		UIS.MouseBehavior = Enum.MouseBehavior.LockCurrentPosition
	else
		UIS.MouseBehavior = Enum.MouseBehavior.Default
	end

	if keysDown["Enum.KeyCode.W"] then
		cam.CFrame *= CFrame.new(Vector3.new(0,0,-speed))
	end
	if keysDown["Enum.KeyCode.A"] then
		cam.CFrame *= CFrame.new(Vector3.new(-speed,0,0))
	end
	if keysDown["Enum.KeyCode.S"] then
		cam.CFrame *= CFrame.new(Vector3.new(0,0,speed))
	end
	if keysDown["Enum.KeyCode.D"] then
		cam.CFrame *= CFrame.new(Vector3.new(speed,0,0))
	end
end

RS.RenderStepped:Connect(renderStepped)

Lets breakdown the rotating part first.

local delta = UIS:GetMouseDelta()
local cf = cam.CFrame
local deltaY = delta.Y
local yAngle = cf:ToEulerAngles(Enum.RotationOrder.YZX)
local newAmount = math.deg(yAngle)+deltaY

“delta” is how far the mouse moved in pixels since the last frame (i think it is since the last frame, i’m not completely sure though).
“cf” is the camera’s current cf, pretty self-explanatory.
“deltaY” is how far the mouse moved up/down (positive = up, negative = down)
“yAngle” is the current angle of how far up/down the camera is looking (straight forward is 0, up is positive down is negative).
“newAmount” is the angle the camera will be looking after this function has run. It is used to determine whether the camera should be able to move up or down more.

if newAmount > 65 or newAmount < -65 then
if not (yAngle<0 and delta.Y<0) and not (yAngle>0 and delta.Y>0) then
        delta = Vector2.new(delta.X,0)
end end

These lines of code basically just check if the new angle is more/less than the y look limit (-65 and 65) and if it is, it changes the y delta to 0 so it doesn’t move.

		cf *= CFrame.Angles(-math.rad(deltaY),0,0)
		cf = CFrame.Angles(0,-math.rad(delta.X),0) * (cf - cf.Position) + cf.Position
		cf = CFrame.lookAt(cf.Position, cf.Position + cf.LookVector)
		if delta ~= Vector2.new(0,0) then cam.CFrame = cam.CFrame:Lerp(cf,sens) end

These lines are where the changes to the CFrame actually happen. The first line rotates the camera on its local x axis using the delta y to look up and down. The second line does the same thing, but on the global y axis using the delta x to look left and right. The third line prevents the camera from tilting too much and/or flipping on its side. The last line checks if the mouse has moved (delta x and y are not equal to 0) and then sets the cameras CFrame to the new one if it has. The lerp is to apply the sensitivity.

		UIS.MouseBehavior = Enum.MouseBehavior.LockCurrentPosition
	else
		UIS.MouseBehavior = Enum.MouseBehavior.Default
	end

These last lines for the rotation part justn either lock the mouse’s position while rotating or letting it freely move if not rotating. Lets look at the “move camera” part now.

if keysDown["Enum.KeyCode.W"] then
	cam.CFrame *= CFrame.new(Vector3.new(0,0,-speed))
end
if keysDown["Enum.KeyCode.A"] then
	cam.CFrame *= CFrame.new(Vector3.new(-speed,0,0))
end
if keysDown["Enum.KeyCode.S"] then
	cam.CFrame *= CFrame.new(Vector3.new(0,0,speed))
end
if keysDown["Enum.KeyCode.D"] then
	cam.CFrame *= CFrame.new(Vector3.new(speed,0,0))
end

All the move part does it checks whether a key is pressed down, and if it is, is moves the camera “speed” amount on the axis determined by what key is pressed down. (w = forward, a = left, s = backward, and d = right).

Okay, now that we know how the camera moves and rotates, lets get to the input part so we can actually control the camera.

First, add this chunk of code below.

local validKeys = {"Enum.KeyCode.W","Enum.KeyCode.A","Enum.KeyCode.S","Enum.KeyCode.D"}

UIS.InputBegan:Connect(function(Input)
	for i, key in pairs(validKeys) do
		if key == tostring(Input.KeyCode) then
			keysDown[key] = true
		end
	end
	if Input.UserInputType == Enum.UserInputType.MouseButton2 or (Input.UserInputType == Enum.UserInputType.Touch and UIS:GetMouseLocation().X>(cam.ViewportSize.X/2)) then
		rotating = true
	end
	if Input.UserInputType == Enum.UserInputType.Touch then
		if Input.Position.X < cam.ViewportSize.X/2 then
			touchPos = Input.Position
		end
	end
end)

The first for loop in this function is for keyboard users. It goes through the valid keys (w,a,s,and d) and if it is the key that was pressed to call the function, the key is set as true in the keysDown table from the variable section. The if statement after the for loop checks if “mousebutton2” is the button that was pressed to call the function or if it was a finger touching the right side of the screen on a mobile device. If either of these are true, it sets rotating the camera to true. The last if statement in the InputBegan function checks if the input was a finger touching the screen, and if the finger touched the screen on the left side, then it sets the touchPos variable to the position of where the finger is touching.

Now that we got through the InputBegan function, we have to make the InputEnded function.

UIS.InputEnded:Connect(function(Input)
	for key, v in pairs(keysDown) do
		if key == tostring(Input.KeyCode) then
			keysDown[key] = false
		end
	end
	if Input.UserInputType == Enum.UserInputType.MouseButton2 or (Input.UserInputType == Enum.UserInputType.Touch and UIS:GetMouseLocation().X>(cam.ViewportSize.X/2)) then
		rotating = false
	end
	if Input.UserInputType == Enum.UserInputType.Touch and touchPos then
		if Input.Position.X < cam.ViewportSize.X/2 then
			touchPos = nil
			keysDown["Enum.KeyCode.W"] = false
			keysDown["Enum.KeyCode.A"] = false
			keysDown["Enum.KeyCode.S"] = false
			keysDown["Enum.KeyCode.D"] = false
		end
	end
end)

The for loop at the beginning of the function is basically the same as the one at the beginning of the InputBegan function, however this one sets the key to not down in the keysDown table. The if statement after is also the same as the one in the InputBegan function but turns rotating off instead of on. The last if statement in this function sets all keys to not down if the touch was ended on the left side of the screen on mobile.

Lastly, we have to make a TouchMoved function so mobile players can control the camera with their joystick. To do this, write the code below.

UIS.TouchMoved:Connect(function(input)
	if touchPos then
		if input.Position.X < cam.ViewportSize.X/2 then
			if input.Position.Y < touchPos.Y then
				keysDown["Enum.KeyCode.W"] = true
				keysDown["Enum.KeyCode.S"] = false
			else
				keysDown["Enum.KeyCode.W"] = false
				keysDown["Enum.KeyCode.S"] = true
			end
			if input.Position.X < (touchPos.X-15) then
				keysDown["Enum.KeyCode.A"] = true
				keysDown["Enum.KeyCode.D"] = false
			elseif input.Position.X > (touchPos.X+15) then
				keysDown["Enum.KeyCode.A"] = false
				keysDown["Enum.KeyCode.D"] = true
			else
				keysDown["Enum.KeyCode.A"] = false
				keysDown["Enum.KeyCode.D"] = false
			end
		end
	end
end)

This function fires when a mobile player moves their finger while touching the screen. It checks if the current position of the touch is above/below the starting position of the touch (forward/backward) and if the current position of the touch is to the left, to the right, or in the center of where the starting position of the touch was and sets the keys equivalent to the direction the joystick is in to down.

This should be the whole script:

local cam = workspace.CurrentCamera
local UIS = game:GetService("UserInputService")
local RS = game:GetService("RunService")
local onMobile = not UIS.KeyboardEnabled
local keysDown = {}
local rotating = false

if not game:IsLoaded() then game.Loaded:Wait() end

cam.CameraType = Enum.CameraType.Scriptable

local speed = 5
local sens = .3

speed /= 10
if onMobile then sens*=2 end

local function renderStepped()
	if rotating then
		local delta = UIS:GetMouseDelta()
		local cf = cam.CFrame
		local yAngle = cf:ToEulerAngles(Enum.RotationOrder.YZX)
		local newAmount = math.deg(yAngle)+delta.Y
		if newAmount > 65 or newAmount < -65 then
			if not (yAngle<0 and delta.Y<0) and not (yAngle>0 and delta.Y>0) then
				delta = Vector2.new(delta.X,0)
			end 
		end
		cf *= CFrame.Angles(-math.rad(delta.Y),0,0)
		cf = CFrame.Angles(0,-math.rad(delta.X),0) * (cf - cf.Position) + cf.Position
		cf = CFrame.lookAt(cf.Position, cf.Position + cf.LookVector)
		if delta ~= Vector2.new(0,0) then cam.CFrame = cam.CFrame:Lerp(cf,sens) end
		UIS.MouseBehavior = Enum.MouseBehavior.LockCurrentPosition
	else
		UIS.MouseBehavior = Enum.MouseBehavior.Default
	end

	if keysDown["Enum.KeyCode.W"] then
		cam.CFrame *= CFrame.new(Vector3.new(0,0,-speed))
	end
	if keysDown["Enum.KeyCode.A"] then
		cam.CFrame *= CFrame.new(Vector3.new(-speed,0,0))
	end
	if keysDown["Enum.KeyCode.S"] then
		cam.CFrame *= CFrame.new(Vector3.new(0,0,speed))
	end
	if keysDown["Enum.KeyCode.D"] then
		cam.CFrame *= CFrame.new(Vector3.new(speed,0,0))
	end
end

RS.RenderStepped:Connect(renderStepped)

local validKeys = {"Enum.KeyCode.W","Enum.KeyCode.A","Enum.KeyCode.S","Enum.KeyCode.D"}

UIS.InputBegan:Connect(function(Input)
	for i, key in pairs(validKeys) do
		if key == tostring(Input.KeyCode) then
			keysDown[key] = true
		end
	end
	if Input.UserInputType == Enum.UserInputType.MouseButton2 or (Input.UserInputType == Enum.UserInputType.Touch and UIS:GetMouseLocation().X>(cam.ViewportSize.X/2)) then
		rotating = true
	end
	if Input.UserInputType == Enum.UserInputType.Touch then
		if Input.Position.X < cam.ViewportSize.X/2 then
			touchPos = Input.Position
		end
	end
end)

UIS.InputEnded:Connect(function(Input)
	for key, v in pairs(keysDown) do
		if key == tostring(Input.KeyCode) then
			keysDown[key] = false
		end
	end
	if Input.UserInputType == Enum.UserInputType.MouseButton2 or (Input.UserInputType == Enum.UserInputType.Touch and UIS:GetMouseLocation().X>(cam.ViewportSize.X/2)) then
		rotating = false
	end
	if Input.UserInputType == Enum.UserInputType.Touch and touchPos then
		if Input.Position.X < cam.ViewportSize.X/2 then
			touchPos = nil
			keysDown["Enum.KeyCode.W"] = false
			keysDown["Enum.KeyCode.A"] = false
			keysDown["Enum.KeyCode.S"] = false
			keysDown["Enum.KeyCode.D"] = false
		end
	end
end)

UIS.TouchMoved:Connect(function(input)
	if touchPos then
		if input.Position.X < cam.ViewportSize.X/2 then
			if input.Position.Y < touchPos.Y then
				keysDown["Enum.KeyCode.W"] = true
				keysDown["Enum.KeyCode.S"] = false
			else
				keysDown["Enum.KeyCode.W"] = false
				keysDown["Enum.KeyCode.S"] = true
			end
			if input.Position.X < (touchPos.X-15) then
				keysDown["Enum.KeyCode.A"] = true
				keysDown["Enum.KeyCode.D"] = false
			elseif input.Position.X > (touchPos.X+15) then
				keysDown["Enum.KeyCode.A"] = false
				keysDown["Enum.KeyCode.D"] = true
			else
				keysDown["Enum.KeyCode.A"] = false
				keysDown["Enum.KeyCode.D"] = false
			end
		end
	end
end)

Thanks for reading and let me know if you have any questions!

24 Likes

There is a simple Magikc trick which allows you to do this without polling which is called

if not game:IsLoaded() then
    game.Loaded:Wait()
end
3 Likes

You can add a “cameraEnabled” variable and put an if statement around everything in the renderStepped function where it only goes through when “cameraEnabled” is true and if it isnt set the cameraType to “Custom” like this:

local function renderStepped()
    if cameraEnabled then
        cam.CameraType = Enum.CameraType.Scriptable
        -- all the code that is in the "renderStepped" function
    else
        cam.CameraType = Enum.CameraType.Custom
    end
end

After this you would just make a remote event and add a function in the camera script where when it is fired, it changes the “cameraEnabled” variable to true or false like this:

RemoteEvent.OnClientEvent:Connect(function(enabled)
    cameraEnabled = enabled
end)

lmk if you have any questions

4 Likes

Thanks, Ill change that real quick

Hi @Joephiss , How do i use this Mobile Support? And how to use it on PC?

1 Like

If you use the script and put it in StarterGui, it should work on both pc and mobile already. All you have to do is move and turn like you normally would (Joystick for mobile and WASD for pc).

So i type on chat is? (For Mobile)

No, you don’t need to type anything in chat you should just be able to move like you normally would on mobile like how you move your character.

i think this is a dumb question but, how do i actually make it freecam? Do i just remove the camera subject?

EDIT: oh my god i am an idiot you can just simply set camera mode to scriptable

I know this is probably a dead thread, but how could I alter the script so that the mouse could still click on things like ClickDetectors and the like?

Edit: Nevermind! You just need to set the ActivationDistance to a really high number!