How to Create a Simple Security Camera System

Hello! I’m making this tutorial to teach others about how to make a security system. They are used in a variety of games, but I mostly see them in the horror/scp genre. It’s actually really simple to make once you break it down.

GUI Setup

The first step is to create a GUI. You can make one however you want. I am going to make mine look like a spectate bar, where players can select the next camera and also go back to the previous one. I put a TextLabel at the center of the screen, and moved it down a little. Then, I added two ImageButtons on both sides of the TextLabel. The TextLabel is going to tell the player which camera they are viewing. I added two images of arrows from the toolbox into the ImageButtons. The end result, for me, looks like this:


I’m not good at UI designing. Don’t judge. Now that we have our GUI, let’s put it into workspace so it doesn’t block our screen. Next, we need to make the chair.

Chair Setup

The chair can be any chair, it doesn’t really matter, the only rule is that it has to have a seat. Any old chair will work, normally it would be the chair sitting in front of the camera monitors, but when you first make it, any chair will work. I just grabbed a random chair from the toolbox. Insert a script into the seat, not the chair.
Capture2
In the script, type this:

script.Parent.ChildAdded:Connect(function(weld) --connects to the function if someone sits on the seat
	if weld.Name == "SeatWeld" then --it checks to make sure the player is sitting
		local Character = weld.Part1.Parent --it gets the character of the player
		if Character then
			local ActualPlayer = game.Players:GetPlayerFromCharacter(Character) --it gets the player from the character
			if ActualPlayer then
				script.CameraGui:Clone().Parent = ActualPlayer.PlayerGui --clones the gui to the player
			end
			end
	end
end)

This script connects to the function when a child is added. The child would be a seatweld, which only appear when a player sits on the seat. Then, it clones the GUI into the player’s PlayerGui. After you type in the script, take the GUI from workspace and put it into the script.
Capture3
Go ahead and test it.


It successfully cloned the GUI and gave it to the player, but the player can just jump off, still have the GUI, and view the cameras whenever they please. Lets fix that. A few lines underneath the ChildAdded script, add this:

script.Parent.DescendantRemoving:Connect(function(weld) --connects to the function if someone is trying to jump off the seat
	local Character = weld.Part1.Parent --gets the character
	if Character then
		local ActualPlayer = game.Players:GetPlayerFromCharacter(Character) --gets the player
		if ActualPlayer then
			ActualPlayer.PlayerGui.CameraGui:Destroy() --removes the gui from the player so the player can't view the cameras anymore
			end
			end
end)

Now, when you test it, it should work perfectly, so when the player jumps off, the GUI disappears.


The chair is now set up!

Camera Setup

Now that we have the chair and the GUI set up, lets set up the “camera.” To do this I’m going to grab a random camera from the toolbox, and put the actual “camera” we’ll be using in front of it. You could also put the camera part in the camera instead of the front, but that’s your choice.
The first thing to do is to create a folder in workspace named Cameras.
Capture4
Once you have that ready, lets make our camera. I didn’t add any actual cameras to make it look like an actual camera, I’m just using a part. You can make yours however you want.


That was pretty simple, and that’s all there is to make the camera parts! Mine looks weird, but it’s just a example, so I didn’t spend a lot of time on it.

Scripting the GUI system

We’ve finished building everything, we’ve scripted the chair to give the GUI, it’s finally time to script the actual camera system. The way I’m doing it is with simple if/then loops with a lot of elseifs. The best way to do this while you’re scripting this is to put the GUI back into StarterGui so you can test it easily. Here is what my setup looks like:
Capture5
Add a LocalScript into both ImageButtons. Open the one in the left arrow first, and type this:

local Player = game.Players.LocalPlayer
local Character = Player.Character or Player.CharacterAdded:Wait()
local Camera = workspace.CurrentCamera

script.Parent.Activated:Connect(function() --connects whenever the player clicks on the arrow
	if script.Parent.Parent.CameraName.Text == "Camera:None"  then --if the player isn't viewing a camera then
		repeat wait() Camera.CameraType = Enum.CameraType.Custom until Camera.CameraType == Enum.CameraType.Custom --this line makes it so that the player cant move their camera
	elseif script.Parent.Parent.CameraName.Text == "Camera:Left" then --if the camera is supposed to be viewing this camera, it changes the position of the camera to where that part is.
		repeat wait() Camera.CameraType = Enum.CameraType.Scriptable until Camera.CameraType == Enum.CameraType.Scriptable
		Camera.CFrame = workspace.Cameras.LeftCamera.CFrame
	elseif script.Parent.Parent.CameraName.Text == "Camera:Front" then
		repeat wait() Camera.CameraType = Enum.CameraType.Scriptable until Camera.CameraType == Enum.CameraType.Scriptable
		Camera.CFrame = workspace.Cameras.FrontCamera.CFrame
	elseif script.Parent.Parent.CameraName.Text == "Camera:Right" then
		repeat wait() Camera.CameraType = Enum.CameraType.Scriptable until Camera.CameraType == Enum.CameraType.Scriptable
		Camera.CFrame = workspace.Cameras.RightCamera.CFrame
	elseif script.Parent.Parent.CameraName.Text == "Camera:Back" then
		repeat wait() Camera.CameraType = Enum.CameraType.Scriptable until Camera.CameraType == Enum.CameraType.Scriptable
		Camera.CFrame = workspace.Cameras.BackCamera.CFrame
	end
end)

script.Parent.Activated:Connect(function()
	if script.Parent.Parent.CameraName.Text == "Camera:None" then --if the player clicks while they're not viewing a camera, it goes to the camera on the right of the camera the player is viewing
		script.Parent.Parent.CameraName.Text = "Camera:Back"
	elseif script.Parent.Parent.CameraName.Text == "Camera:Back" then
		script.Parent.Parent.CameraName.Text= "Camera:Right"
	elseif script.Parent.Parent.CameraName.Text == "Camera:Right" then
		script.Parent.Parent.CameraName.Text = "Camera:Front" 
	elseif script.Parent.Parent.CameraName.Text == "Camera:Front" then
		script.Parent.Parent.CameraName.Text = "Camera:Left"
	elseif script.Parent.Parent.CameraName.Text == "Camera:Left" then
		script.Parent.Parent.CameraName.Text = "Camera:None"
	end
end)

This may look like a lot, but it’s because there’s 4 CameraParts. Let’s break it down into the two main parts of the script.
The first part of the script handles the camera manipulation. Camera manipulation is when the player’s camera is altered. If you don’t know how workspace.CurrentCamera works, here is the devhub link to it:Workspace.CurrentCamera. When the button is clicked, the script checks what camera the TextLabel in the GUI says the player is supposed to be viewing. If the TextLabel, for example, is “Camera:Front” then the first part of the script changes the player’s camera in two steps.

  1. It makes the camera type Scriptable, using workspace.CurrentCamera.CameraType. The CameraType is, well, the type of camera the player has. There are many cameratypes, but the default is Custom. The script changes it to scriptable, so when the player is viewing on a camera, they can’t move it, and it’s stuck in place. The only way they can move their camera if the CameraType is scriptable is if a script allows it. The only exception is when the TextLabel reads Camera:None. This means that the player isn’t viewing a camera, and so the script sets it back to Custom.
  2. It sets the Camera CFrame.

The second part of the script changes the TextLabel’s text so the 1st part can check which part to set the camera CFrame to.

Now that the left arrow is fully scripted, lets move on to the right arrow. Open the right arrow’s LocalScript, and paste this:

local CameraName = script.Parent.Parent.CameraName
local Camera = workspace.CurrentCamera

script.Parent.Activated:Connect(function() --connects whenever the player clicks on the arrow
	if script.Parent.Parent.CameraName.Text == "Camera:None"  then --if the player isn't viewing a camera then
		repeat wait() Camera.CameraType = Enum.CameraType.Custom until Camera.CameraType == Enum.CameraType.Custom --this line makes it so that the player cant move their camera
	elseif script.Parent.Parent.CameraName.Text == "Camera:Left" then --if the camera is supposed to be viewing this camera, it changes the position of the camera to where that part is.
			repeat wait() Camera.CameraType = Enum.CameraType.Scriptable until Camera.CameraType == Enum.CameraType.Scriptable
		Camera.CFrame = workspace.Cameras.LeftCamera.CFrame
	elseif script.Parent.Parent.CameraName.Text == "Camera:Front" then
		repeat wait() Camera.CameraType = Enum.CameraType.Scriptable until Camera.CameraType == Enum.CameraType.Scriptable
		Camera.CFrame = workspace.Cameras.FrontCamera.CFrame
	elseif script.Parent.Parent.CameraName.Text == "Camera:Right" then
		repeat wait() Camera.CameraType = Enum.CameraType.Scriptable until Camera.CameraType == Enum.CameraType.Scriptable
		Camera.CFrame = workspace.Cameras.RightCamera.CFrame
	elseif script.Parent.Parent.CameraName.Text == "Camera:Back" then
		repeat wait() Camera.CameraType = Enum.CameraType.Scriptable until Camera.CameraType == Enum.CameraType.Scriptable
		Camera.CFrame = workspace.Cameras.BackCamera.CFrame
	end
end)

script.Parent.Activated:Connect(function()
	if script.Parent.Parent.CameraName.Text == "Camera:None" then --if the player clicks while they're not viewing a camera, it goes to the camera on the right of the camera the player is viewing
		script.Parent.Parent.CameraName.Text = "Camera:Left"
	elseif script.Parent.Parent.CameraName.Text == "Camera:Left" then
		script.Parent.Parent.CameraName.Text = "Camera:Front"
	elseif script.Parent.Parent.CameraName.Text == "Camera:Front" then
		script.Parent.Parent.CameraName.Text= "Camera:Right"
	elseif script.Parent.Parent.CameraName.Text == "Camera:Right" then
		script.Parent.Parent.CameraName.Text = "Camera:Back" 
	elseif script.Parent.Parent.CameraName.Text == "Camera:Back" then
		script.Parent.Parent.CameraName.Text = "Camera:None"
	end
end)

You might notice these two scripts are the same, except that the order of the cameras is flipped, so if you click the right arrow, you go to the next camera, but if you click the left arrow, you go to the previous camera.
Now that we have finished scripting both, put the GUI back in the seat script, and let’s test it out!
I would recommend that if you want to add more cameras than just 4, add them first and don’t edit it after you finish making it, it makes it really easy to mess up. Believe me, I’ve tried.

Enjoy your new camera system!

Rate my tutorial, please.

  • Awesome
  • Great
  • Alright
  • Could be Better
  • Needs Improvement
  • Garbage

0 voters

39 Likes

This is an alright tutorial, however it needs a decent amount of improvement. There are a lot of mistakes.

So I will go over that :stuck_out_tongue:


Regarding when a player jumps

First there is no way to reset the camera if the player jumps out.
This can be problematic and enraging to players who don’t know this.

So now moving to your script here’s what we should do is utilize remote events
So what are remote events? They are client to server / server to client communication. You should put the camera UI into StarterGui and utilize a remote event to open and close it

local remoteEvent = game.ReplicatedStorage:WaitForChild('CameraEvent')
script.Parent.ChildAdded:Connect(function(weld) 
	if weld.Name == "SeatWeld" then 
		local Character = weld.Part1.Parent 
		if Character then
			local ActualPlayer = game.Players:GetPlayerFromCharacter(Character)
			if ActualPlayer then
				remoteEvent:FireClient(ActualPlayer, true)
			end
		end
	end
end)

On the client (Note I will talk about merging both scripts down the line)

local remoteEvent = game.ReplicatedStorage:WaitForChild('CameraEvent')
local Player = game.Players.LocalPlayer
local Character = Player.Character or Player.CharacterAdded:Wait()
local Camera = workspace.CurrentCamera

remoteEvent.OnClientEvent:Connect(function(OpenOrClose)
    if OpenOrClose then -- If true
        script.Parent.Visible = true
    else
        script.Parent.Visible = false
    end
end)

An alternative solution is to remove the player's ability to jump when they are in the camera

Regarding Repeating

Correct me if wrong, but I am pretty sure you don’t need to use repeats. It is just wasting memory

Before

if script.Parent.Parent.CameraName.Text == "Camera:None"  then --if the player isn't viewing a camera then
		repeat wait() Camera.CameraType = Enum.CameraType.Custom until Camera.CameraType == Enum.CameraType.Custom 
	elseif script.Parent.Parent.CameraName.Text == "Camera:Left" then 
		repeat wait() Camera.CameraType = Enum.CameraType.Scriptable until Camera.CameraType == Enum.CameraType.Scriptable
		Camera.CFrame = workspace.Cameras.LeftCamera.CFrame

After

if script.Parent.Parent.CameraName.Text == "Camera:None"  then
		Camera.CameraType = Enum.CameraType.Custom
        Camera.CameraSubject = Character.Humanoid-- Return the subject
	elseif script.Parent.Parent.CameraName.Text == "Camera:Left" then 
		Camera.CameraType = Enum.CameraType.Scriptable
		Camera.CFrame = workspace.Cameras.LeftCamera.CFrame
Don't be a Yandere dev

Now the biggest issue with this is the use of if then statements

Now what’s wrong with that you may ask? Well once you start wanting to add camera’s you begin to have a HUGE problem. Right now you are utilizing 2 different scripts. With 2 sets of activating functions meaning that in order to add ONE camera you need to change the script in 4 places. If I wanted a camera system with 10 camera’s then I would have a nightmare doing that

Also don’t forget about the hassle that one wrong name could have

So what can we do about this?

Well you’re goal should be to create code that is easy to expand upon. It should be as simple as just putting in a new camera block into a folder

First merge both the camera button scripts into one main script

You should put the script under the frame
image

local remoteEvent = game.ReplicatedStorage:WaitForChild('CameraEvent')
local Player = game.Players.LocalPlayer
local Character = Player.Character or Player.CharacterAdded:Wait()
local Camera = workspace.CurrentCamera

local LeftArrow = script.Parent.LeftArrow -- Create extra definitions for easy use
local RightArrow = script.Parent.RightArrow
local CameraName = script.Parent.CameraName

remoteEvent.OnClientEvent:Connect(function(OpenOrClose)
    if OpenOrClose then -- If true
        script.Parent.Visible = true
    else
        script.Parent.Visible = false
    end
end)

-- Continue below

Now what we want to do is create an index of all the cameras and put them into a table
After we have put them into a table we can use the current camera’s index in order to determine the next camera


-- Continue below

local Cameras = game.Workspace.Cameras:GetChildren() -- this is the table of all the cameras
local index = 0

function ChangeCamera() -- This function will be fired when a button is pressed
    if index < 0 then -- The index is less than 0 so we will put the last camera in the table
        index = #Cameras + 1
    elseif index > (#Cameras + 1) then -- You reached the last camera
        index = 0 
    end

    if index == 0 then -- When the index is 0 set the camera to the player
        CameraName.Text = "Camera:None"
        Camera.CameraType = Enum.CameraType.Custom
        Camera.CameraSubject = Character.Humanoid
    else
        CameraName.Text = "Camera:"..Cameras[index].Name -- This is the camera part's name
        Camera.CameraType = Enum.CameraType.Scriptable 
        Camera.CFrame = Cameras[index].CFrame -- The Camera Part's Cframe
    end

end

LeftArrow.Activiated:Connect(function() --Event for when the button is pressed
    index -= 1 --Changing the index -1
    ChangeCamera() --Fire the function
end)
RightArrow.Activiated:Connect(function()
    index += 1 
    ChangeCamera() 
end)

And boom, we have simplified the script and fixed a lot of the issues. Now in order to add a camera just add it to the folder and forget about it.


Make sure to give those a read, also good attempt and keep up the good work :slight_smile:
~ kingerman88

Note I wrote all that on the fly those may have some errors in the script just reply to the post if so!

15 Likes

Thank you, I was having trouble simplifying the scripts, but the repeat is needed. I completely forgot about the fact that the player could jump, I forgot about it. I’ll fix that as well.

5 Likes

I took a look at one of my former scripts

local camera = workspace.CurrentCamera

camera.CameraType = Enum.CameraType.Scriptable

local cameraBlock = workspace.CameraBlock
local cameraPos = workspace.CameraPos

local postition = cameraPos.Position
local lookAt = cameraBlock.Position

camera.CFrame = CFrame.new(postition,lookAt)

To this day this script has worked, so I don’t believe the repeat is needed

7 Likes

Hello!
Are you using a local to define Character in your fixed script?
If so, please include it in the fixed version. Thanks!

4 Likes

That’s correct it defines it right here :stuck_out_tongue:

5 Likes