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!