How to make a 'First-Person-Feeling' CCTV Camera system?

How would you go about creating a CCTV Camera system that moves the Camera with your mouse (just like when playing in first person)? Below is an example of what I’m looking for… I haven’t had the best of luck finding or thinking of ways to make this system so any ideas or knowledge would be great!

3 Likes

Inside a run service loop, using CFrame.lookAt and update the CFrame of the camera to look at the mouse position, here is an example:

RunService.Heartbeat:Connect(function(dt)
    camera.CFrame = CFrame.lookAt(camera.CFrame.Position,mouse.Hit.Position)
end)
1 Like

although this does make the camera follow the mouse it doesn’t work like how first person works… the sensitivity is wayyy too high and the mouse still freely moves on the players screen…

1 Like

I am giving you the layout to start from, you cannot ask for a full working script here sadly.

1 Like

Here is some guidance:

  • Lock the mouse using UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter (You might need to call this often so it doesn’t get overridden.)
  • Create a camera overlay (e.g. the box above, time stamps in the corner, etc) in a ScreenGui and add it to the player’s GUI when they use the camera
  • You can get mouse movement with something like:
angleX = 0  -- Some starting angle
angleY = 0  -- Some starting angle
lastTime = tick()
local function onMouseMovement (actionName, inputState, inputObject)
	currTime = tick()
	dt = currTime - lastTime
	angleY += inputObject.Delta.X * dt * ySensitivity
	angleX += inputObject.Delta.Y * dt * xSensitivity
	lastTime = currTime
end
ContextActionService:BindAction("MouseMovement", onMouseMovement, false, Enum.UserInputType.MouseMovement, Enum.UserInputType.Touch)
  • You can make the camera feel more real by adding interpolation, since real cameras can’t move instantly:
-- Get the target CFrame based on the angle variables
--targetCFrame = CFrame.Angles(math.rad(angleX), math.rad(angleY), 0) + camPosition
-- Edit: the rotations need to be done separately, with the Y axis rotation first, as @SquarePapyrus12 pointed out:
local targetCFrame = CFrame.Angles(0, math.rad(angleY), 0) * CFrame.Angles(math.rad(angleX), 0, 0) + cameraPart.Position
-- Move the camera towards the target each frame
camera.CFrame = camera.CFrame:Lerp(targetCFrame, 0.05) -- Change 0.05 based on how "snappy" you want it to be, though note this speed will depend on frame rate with this equation
  • Finally, you can limit the rotation by simply locking the angle variables before setting the camera CFrame:
-- For example, to lock the horizontal rotation between 100 degrees and 25 degrees:
if angleY > 100 then angleY = 100 end
if angleY < 25 then angleY = 25 end
3 Likes

I appreciate the guidance into this however, I only want the camera to be able to pan on the X and Y axis, why are we getting the Z axis and the position of the Camera? apart from that, I understand this and will play around with things!

2 Likes

Whoops, you’re right. I meant the x axis. I’ll fix that in the post.

2 Likes

okay so after some playing around and a handful of confusion I’ve ran into a problem with this system which is shown in the video below:

:warning: ISSUE

as you can probably see… the panning of the camera works just right when you’re looking at the Front face of the Camera Part but the second you rotate to the Right, Back or Left face the entire upwards and downwards rotation of the Camera starts going a bit… whacky

:gear: CODE

local UserInputService = game:GetService("UserInputService")
local ContextActionService = game:GetService("ContextActionService")
local camera = workspace.CurrentCamera
local cameraPart = workspace:WaitForChild("CameraPart")

game["Run Service"].Heartbeat:Connect(function(deltaTime)
	UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
end)

camera.CameraType = Enum.CameraType.Scriptable
camera.CFrame = workspace.CameraPart.CFrame

--[[ disabled for the sake of the video 
local limitLR = 50
local limitUD = 50
]]


local angleY = 0
local angleX = 0

local function onMouseMovement(actionName, inputState, inputObject)
	
	angleY += inputObject.Delta.X * -1
	angleX += inputObject.Delta.Y * -1
	
	local targetCFrame = CFrame.Angles(math.rad(angleX), math.rad(angleY), 0) + cameraPart.Position
	camera.CFrame = camera.CFrame:Lerp(targetCFrame, 0.4) -- Change 0.05 based on how "snappy" you want it to be, though note this speed will depend on frame rate with this equation
	cameraPart.CFrame = camera.CFrame
	
	print(angleX, angleY)
	
	--[[ disabled for the sake of the video 
	if angleY > limitLR then angleY = limitLR end
	if angleY < -limitLR then angleY = -limitLR end
	
	if angleX > limitUD then angleX = limitUD end
	if angleX < -limitUD then angleX = -limitUD end
	]]
end

ContextActionService:BindAction("MouseMovement", onMouseMovement, false, Enum.UserInputType.MouseMovement, Enum.UserInputType.Touch)

I couldn’t find anything about creating a Camera system like this until now and i’m grateful for the help I’ve recieved, I’m sure this post will be useful for others looking for such a specific mechanic :smiley:

hopefully someone can help with this problem!

1 Like

you need to first rotate on the y axis and then on the x axis, not both at the same time:

local targetCFrame = CFrame.Angles(0, math.rad(angleY), 0) * CFrame.Angles(math.rad(angleX), 0, 0) + cameraPart.Position
2 Likes

I believe @SquarePapyrus12 has the fix for this.

The code @SquarePapyrus12 provided first creates a CFrame rotated around the Y axis, then rotates that CFrame around the X axis relatively.

You can also manually remove the roll by doing:

local cframe = ...
cframe = CFrame.lookAt(cframe.Position, cframe.Position + cframe.LookAt)

thank you man, it works perfect now! you’re a legend @BendsSpace :raised_hands:t3:

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.