Recreating Entry Point's camera restrains

In Entry Point, whenever the player is interacting with something, the camera’s X axis movement is restricted to a certain extent as (probably really badly) shown.


I’ve searched around many topics relating to restricting the camera over the summer, but none are exactly how Entry Point does it. Any help is appreciated.

3 Likes
local x,y,z = camera.CFrame:ToOrientation()
x = math.rad(math.clamp(math.deg(x),-120,120))
y = math.rad(math.clamp(math.deg(y),-120,120))
camera.CFrame = CFrame.new(camera.CFrame.Position) * CFrame.fromOrientation(x,y,z)

may be helpful to you :slight_smile:

Hi! Thank you for the code, but I’m rather confused as to where it would go? I’ve experimented with connecting it to RunService.HeartBeat and RunService.RenderStepped and both did not behave how I expected them to.

This is the code for Heartbeat, which behaves like below

local x,y,z = camera.CFrame:ToOrientation()
connection = RunService.Heartbeat:Connect(function()
	x = math.rad(math.clamp(math.deg(x),-120,120))
	y = math.rad(math.clamp(math.deg(y),-120,120))
	camera.CFrame = CFrame.new(camera.CFrame.Position) * CFrame.fromOrientation(x,y,z)
end)

For RunService, the code is mostly the same, but with slightly different behavior

local x,y,z = camera.CFrame:ToOrientation()
connection = RunService.RenderStepped:Connect(function()
	x = math.rad(math.clamp(math.deg(x),-120,120))
	y = math.rad(math.clamp(math.deg(y),-120,120))
	camera.CFrame = CFrame.new(camera.CFrame.Position) * CFrame.fromOrientation(x,y,z)
end)

If it’s not clear, both of them return the camera to its original position when the mouse stops moving.

Are you using a Scriptable camera?

I’m not, the videos above are all with Roblox’s default camera.

That explains the jittering, overwriting the roblox camera itself is a bit difficult, im not sure how certain games do it but afaik this is what causes your problems there

Hello! I did a little more testing, and putting the entire code block inside the loop like below seems to have worked a little better

renderStep = RunService.RenderStepped:Connect(function()
	local x,y,z = camera.CFrame:ToOrientation()
	x = math.rad(math.clamp(math.deg(x),-120,120))
	y = math.rad(math.clamp(math.deg(y),-120,120))
	camera.CFrame = CFrame.new(camera.CFrame.Position) * CFrame.fromOrientation(x,y,z)
end)


However a new issue arose, where the camera seems to be restrained to the same 120 degrees around the same direction towards global negative Z like shown. I don’t have much experience working with CFrame or Camera but I assume it’s due to camera.CFrame:ToOrientation() not using the camera’s orientation/lookVector?

Yes, the camera CFrame is in world space, I forgot how to turn it into local space. I will look at my own code again and try to help you with this.

Hi! A little update, because I’ve made a lot of progress and it’s gotten close to working perfectly. This is the current code(a little jank, I apologise).

local maxTilt = 60

local iniX,iniY,_ = camera.CFrame:ToEulerAnglesYXZ()
local minY = math.deg(iniY) - maxTilt
local maxY = math.deg(iniY) + maxTilt

renderStep = RunService.RenderStepped:Connect(function(deltaTime)
	local x,y,z = camera.CFrame:ToEulerAnglesYXZ()
	y = math.rad(math.clamp(math.deg(y),minY, maxY))

	camera.CFrame = CFrame.new(camera.CFrame.Position) * CFrame.fromOrientation(x,y,z)
end)

Works as intended! Sort of… There’s still a few kinks to iron out.

It’s kind of difficult for me to interpret the issue, so here are some Paint illustrations.


This is a rough sketch of the top down view of the player/camera, Negative Z is the direction looking at the “Front” part in my videos.


The purple line(henceforth called vP for Vector Purple) is the direction the player was looking before starting the loop. From vP, we draw 2 more lines, each 60 degrees apart in different directions from vP, these line will be the bounds which limit the player’s camera’s movement.
As can be seen, there is no issue with the system when vP is on the upper half of the circle, and, depending on the maxTilt, a little under the lower half as well(seen below).

So far so good! It’s only until the area between the black bars go through the 180 degree point that problems arise.


Let’s assume vP is pointing at 170 degrees, already there are problems. Looking back at the math for the area, the issue is obvious.

local minY = math.deg(iniY) - maxTilt
local maxY = math.deg(iniY) + maxTilt

With vP at 170 or -170, the range between min and max would be [110; 230] and [-230; 110] respectively, which is impossible and requires a bit more math to calculate the “overflow”(?), but would not be too hard to implement.

The main issue comes from when the player moves the mouse.

The turquoise line(referred as lV for Look Vector) stands in for the direction the player is looking at. Let’s look back at the code again to see where the problem lies.

y = math.rad(math.clamp(math.deg(y),minY, maxY))

or, in this case

y = math.rad(math.clamp(math.deg(y), -130, 110)) --Correct parameters of [110; 230]

Now, when the player moves the lV past the 180 degree mark, its orientation goes from positive to negative, let’s assume that negative orientation is -170 degrees. Because it is lower than the min(-130), it immediately jumps to -130 and creates a “dead zone” of sorts between 180 and -130 where the camera cannot turn to, which would restrict the camera’s field of movement to somewhere around this.


I don’t believe I’m even half as smart enough to find a solution to this. If you could help that would be amazing.

Hi, sorry to bother, have you found a solution yet? I’ve been waiting on you lol

What I recommend is, you know dot products?

You can get a look vector (the point from where the dot product is created, basically in which direction it goes) then specify how large it is and clamp the camera within this radius, this fixes your issue of it breaking in different rotations I think.