Startup.
So… you.. you want to make a custom camera system ey? Well too bad! You can’t! That’s awful… maybe me and my friend here who I AM going to keep a secret may be able to help you make your own camera system..
Let’s start!
Initializing
First of all, like every sensible human (unless you’re not a human and maybe just a monke on a komputer) insert a LOCAL script under StarterCharacterScripts (as shown in Fig 1.0). Exactly that okay? I know some of you little nibwits would insert a script under ServerStorage thinking it’s the same but it’s not.
Like so:
Fig 1.0
Make sure you have that, name it whatever you want, in my case I’m going to name it “tutowial UwU” (as shown in Fig 1.1) because it just sounds funny to my secret friend. I’m going to call him “Jack” as saying secret friend every now and then would be insanely tiring and I know your attention span would die.
Fig 1.1
Scripting begins!
Open the script and let’s start coding!!
Assigning Variables
First off, declare the services. For this tutorial, we’ll use two three services. RunService, UserInputService and Players (as shown in Script 1.0).
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")
Next, get the player and some other stuff (Script 1.1). We have the Camera for obvious reasons, the player, the character, and finally, the root part. The root part is one of the most important parts of any Roblox character. It’s like the heart of it. It controls every other part. Hence, let’s get it!!
local Camera = workspace.CurrentCamera
local Player = Players.LocalPlayer
local Character = Player.Character or Player.CharacterAdded:Wait()
local HumanoidRootPart = Character:WaitForChild("HumanoidRootPart")
Now, add some numbers!! For the three axis of the camera and it’s distance from the player!
local Camera_X = 0
local Camera_Y = 0
local Camera_Z = 0
local Camera_Distance = 0
You could use a Vector3 for it but I’ll leave it to you to discover. ![]()
I obviously want my students to do some discovering of their own hence, I won’t add it. Don’t worry, this won’t be on the test.
Function Assignment
Okay okay okay… NOW!!! The REALLLL THING BEGINS!!! I NEED you to focus here okay??
Make two functions. Name them… uhhh… anything that just makes sense (view script 1.2).
Joking*! Name one SetTransparency and the other UpdateCamera and give the second function an argument of deltaTime. Does it ring a bell? It’s the amount of time it takes your potato pc to render one frame! Do note, for your potato PC it should be around 1/60 seconds; which is around… 0.016666 seconds orrrrr 16.67 milliseconds. It’s… pretty small.
local function SetTransparency()
end
local function UpdateCamera(deltaTime)
end
Invisibility first!
Now, this part is a bit tricky so bare with me! I’ll explain it line by line! (view Script 1.2)
local function SetTransparency()
local transparency = 1-math.clamp(Camera_Distance,0,3)/3
for _,part in Character:GetChildren() do
if not part:IsA("BasePart") then
if not part:IsA("Accessory") then continue end
part.Handle.LocalTransparencyModifier = transparency
continue
end
part.LocalTransparencyModifier = transparency
end
end
Yikes! That’s… so bad… honestly… but uhm. Let’s get through this.
Line number 1! Just your basic function assignment.
Line number 2! Ooh.. a bit of maths! You know, I always loved maths. Said no one ever. Except one psychopath that being JACK. He’ll explain it here:
(View video 1.0 to see “Jack” give you a demonstration.)
"
“Okay so how this works is first we get the Camera_Distance and clamp it so that it ranges from 0 to 3 studs away. We then divide it by three so it now ranges from 0 to 1. We then need to invert it so rather than being 1 at a camera distance of 3+. It’s “1” at a camera distance of 0. You know, how 1-0 is 1 and 1-1 is 0. Yeah.”
"
Line 3 is just us looping through the character.
Line 4 is just a check to see if the object we encounter is the loop is NOT a basepart.
Line 5, same thing but for an accessory. If the object isn’t an accessory then we continue with the loop. That is, we don’t proceed further and go to the next part.
Line 6 is us setting the transparency of the handle of the accessory to the value we calculated.
Line 7 is us continuing to the next iteration to ensure the loop doesn’t accidentally try setting the transparency of the accessory instance itself. As it’ll just error out. (Duh…)
Line 8, let’s say we have something that’s NOT an accessory and in fact, a part! Like the head, or the body, or something else.
Line 9 to 10 are just us terminating the above statements at line 1 and 3.
The Camera Manipulation.
Okay I’m not smart enough. I quit. Let jack explain this again.
local function UpdateCamera(deltaTime)
local Delta = UserInputService:GetMouseDelta()
Camera_X -= Delta.X / UserInputService.MouseDeltaSensitivity
Camera_Y = math.clamp(Camera_Y - Delta.Y / UserInputService.MouseDeltaSensitivity,-89,89)
local Origin = (HumanoidRootPart.CFrame * CFrame.new(0,2,-0.25)).Position
Camera.CFrame = CFrame.new(Origin) * CFrame.Angles(0, math.rad(Camera_X), 0) * CFrame.Angles(math.rad(Camera_Y), 0, math.rad(Camera_Z)) * CFrame.new(0, 0, Camera_Distance)
SetTransparency()
end
"
In the script, we first define the function, next we get the mouses delta using UserInputService. Do consider that it’s only a non-zero Vector2 value when the mouse is Locked.
Next, we update the Camera_X and Camera_Y variables we set above. We subtract Delta.X / *Sensitivity* from it. Sensitivity being the value you set in the Roblox settings menu.
Same goes for Camera_Y but we enclose it within a math.clamp to ensure that the value is withing 89 degrees both up and down.
Next, we set the Origin. In our case, it’s the players head, simulated by the HumanoidRootPart. The head is about 1.5 studs above the root part but in my case I set it 2. I also moved it forward by a quarter of a stud to simulate a look through the eyes.
I’m using CFrame so we can also get the rotation of the root part. I also then take out the rotation by using .Position which just returns a Vector3.
Next part’s a bit tricky. We first use CFrame.new(Origin) to position the camera at the head. Next, we set the camera horizontal rotation in radians by using math.rad. This is IMPORTANT! I’ll explain later.
After setting horizontal rotation. It’s time for the vertical rotation as well as tilt. We set it in radians by using math.rad. Remember one thing that radians is a unit of rotation and is calculate like so: AngleInDegree x pi / 180. 1 Radian is equal to approximately 57 degrees.
After that, we simply run the SetTransparency function we established up top to update the transparency of the body properly.
# "
Wow, thanks big guy jack.
Input detection.
Anyways, next, input detection!!!
Start by getting 3 connections. Not the new “friends” rename kinda connections but the kind you get from UserInputService.Input<STATE> as shown in Script 1.5
UserInputService.InputBegan:Connect(function(Input,GameProcessedEvent)
end)
UserInputService.InputEnded:Connect(function(Input,GameProcessedEvent)
end)
UserInputService.InputChanged:Connect(function(Input,GameProcessedEvent)
end)
Now, let’s add a common line in each of them. That being in Script 1.6. It just ensures that nothing would fire if the player is… say.. writing a text… has the menu open and bla bla bla.
if GameProcessedEvent then return end
UserInputService.InputBegan:Connect(function(Input,GameProcessedEvent)
if GameProcessedEvent then return end
end)
UserInputService.InputEnded:Connect(function(Input,GameProcessedEvent)
if GameProcessedEvent then return end
end)
UserInputService.InputChanged:Connect(function(Input,GameProcessedEvent)
if GameProcessedEvent then return end
end)
Next, add the mouse Locked thing that Jackie mentioned above by using UserInputService.MouseBehaviour. It should’ve been HIM explaining this but his lazy bum won’t.. ugh.
Anywho… Just check if the player is holding down mouse button 2, that is, the right mouse button. Also detect if the players in third person by just doing Camera_Distance > 0. It just ensures that the mouse will lock and unlock if the player is in third person. In Script 1.7, we use Enum.MouseBehaviour.LockCurrentPosition/Default which as it sounds, locks the mouse to it’s current position and unlocks it respectively.
To spoil stuff, I used Camera_Distance > 0, that is, to ensure the code ONLY run when the player is in third person so that in first person, the player won’t be able to unlock their mouse. Because we have to ensure it’s always locked in first person!!
UserInputService.InputBegan:Connect(function(Input,GameProcessedEvent)
if GameProcessedEvent then return end
if Input.UserInputType == Enum.UserInputType.MouseButton2 and Camera_Distance > 0 then
UserInputService.MouseBehavior = Enum.MouseBehavior.LockCurrentPosition
end
end)
UserInputService.InputEnded:Connect(function(Input,GameProcessedEvent)
if GameProcessedEvent then return end
if Input.UserInputType == Enum.UserInputType.MouseButton2 and Camera_Distance > 0 then
UserInputService.MouseBehavior = Enum.MouseBehavior.Default
end
end)
Now, the third connection.
UserInputService.InputChanged:Connect(function(Input,GameProcessedEvent)
if GameProcessedEvent then return end
if Input.UserInputType == Enum.UserInputType.MouseWheel then
Camera_Distance = math.clamp(Camera_Distance - Input.Position.Z,0,128)
if Camera_Distance == 0 then
UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
else
UserInputService.MouseBehavior = Enum.MouseBehavior.Default
end
end
end)
Here, we check if the player used their mouse wheel. We then just update the Camera_Distance accordingly. Note one thing, Input.Position exists. It’s a Vector3 value where the X and Y value correspond to the mouses screen position and the Z value is the direction in which the mouse wheel moves.
We also clamp it so it won’t be like you seeing the entire map and stuff…
Next, we use an if-statement. You probably know what that is because we used it a lot previously and I’m just saying it now because the budget of this post is zero. Soo… WOMP WOMP!!!
Anyways, just check if the Camera_Distance equals zero, i.e first person and then just lock the mouse to the center using Enum.MouseBehaviour.LockCenter. Here is where the Camera_Distance > 0 I mentioned above comes in handy!
Camera Clipping.
Please view this. Thank you!! >W<
Finishing…
Using BindToRenderStep. Which is as advertised. It binds (connects) a function to the rendering system of Roblox. That is, the function we had assigned would run before or after game has been rendered. View Script 1.9 (a). As seen, we first give it a name. I gave it “CameraUpdate”. (I should’ve called it my cutie pie…) We give it a priority, for now, I set it to Enum.RenderPriority.Camera.Value. Which is just the priority value for the camera update. Next, we pass the UpdateCamera function.
There’s an easier way to do this as shown in Script 1.9 (b). We just connect the RenderSteppedconnectionn of RunService to the UpdateCamera function. Straight to the point, no crap.
RunService:BindToRenderStep("CameraUpdate",Enum.RenderPriority.Camera.Value,UpdateCamera)
(a)
RunService.RenderStepped:Connect(UpdateCamera)
(b)
FINISHED!! FINALLY!! 
WOOO! Great job!!! You finally completed this tutorial!! How do you feel? Is that little brain of yours hurting? I hope it is because that means that you had a brain hemorrhage! I’m joking.. I’m joking… just.. Good job. If you didn’t read through all that well then TOO BAD!!
Video 1.1 shows jackie chan demonstrating the code.
The final script. View Script 1.10.
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")
local Camera = workspace.CurrentCamera
local Player = Players.LocalPlayer
local Character = Player.Character or Player.CharacterAdded:Wait()
local HumanoidRootPart = Character:WaitForChild("HumanoidRootPart")
local Camera_X = 0
local Camera_Y = 0
local Camera_Z = 0
local Camera_Distance = 12
Camera.CameraType = Enum.CameraType.Scriptable
local function SetTransparency()
local transparency = 1-math.clamp(Camera_Distance,0,3)/3
for _,part in Character:GetChildren() do
if not part:IsA("BasePart") then
if not part:IsA("Accessory") then continue end
part.Handle.LocalTransparencyModifier = transparency
continue
end
part.LocalTransparencyModifier = transparency
end
end
local function UpdateCamera(deltaTime)
local Delta = UserInputService:GetMouseDelta()
Camera_X -= Delta.X / UserInputService.MouseDeltaSensitivity
Camera_Y = math.clamp(Camera_Y - Delta.Y / UserInputService.MouseDeltaSensitivity,-89,89)
local Origin = (HumanoidRootPart.CFrame * CFrame.new(0,2,-0.25)).Position
Camera.CFrame = CFrame.new(Origin) * CFrame.Angles(0, math.rad(Camera_X), 0) * CFrame.Angles(math.rad(Camera_Y), 0, math.rad(Camera_Z)) * CFrame.new(0, 0, Camera_Distance)
SetTransparency()
end
UserInputService.InputBegan:Connect(function(Input,GameProcessedEvent)
if GameProcessedEvent then return end
if Input.UserInputType == Enum.UserInputType.MouseButton2 and Camera_Distance > 0 then
UserInputService.MouseBehavior = Enum.MouseBehavior.LockCurrentPosition
end
end)
UserInputService.InputEnded:Connect(function(Input,GameProcessedEvent)
if GameProcessedEvent then return end
if Input.UserInputType == Enum.UserInputType.MouseButton2 and Camera_Distance > 0 then
UserInputService.MouseBehavior = Enum.MouseBehavior.Default
end
end)
UserInputService.InputChanged:Connect(function(Input,GameProcessedEvent)
if GameProcessedEvent then return end
if Input.UserInputType == Enum.UserInputType.MouseWheel then
Camera_Distance = math.clamp(Camera_Distance - Input.Position.Z,0,128)
if Camera_Distance == 0 then
UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
else
UserInputService.MouseBehavior = Enum.MouseBehavior.Default
end
end
end)
RunService:BindToRenderStep("CameraUpdate",Enum.RenderPriority.Camera.Value,UpdateCamera)
So… I ask for nothing in return but except one thing. I want YOU! Yes. YOU!! To use this tutorial and show me your own camera system! Add more things to it, like bobbling, smoothing, camera clipping, etc. Do that okay? I’ll grade it out of 10. If you fail I’m sending you to coding jail. If you need help just ask me.
Conclusion
This is a tutorial about showing new users and some intermediate users the uses of CFrames and how they can make their own basic custom camera system. More features can be documented in upcoming updates to this tutorial or completely separate tutorials.
Polls
- I loved this tutorial! Great job you and Jack.
- I didn’t like this tutorial.
- Bad tutorial.
Post script.
What next tutorial should I make and on what? I plan on covering a lot of the questions regarding CFrames and how many people don’t understand them because they are “hard”. Please, go ahead, ask me questions. I’ll gladly respond them.
Made with
by @i_dunno8p and @mirawvj.