Can't Rotate Character Vertically While Implementing Over-The-Shoulder Camera System

I am working on an Over-The-Shoulder Camera system.
What is the problem you are facing?

Well, I have my Over-The-Shoulder Camera script ready but the problem is that I can only rotate the character on the X-Axis. I want to be able to rotate it on the Y-Axis too. As you can see below, I am only able to rotate my character on the X-axis.
https://gyazo.com/3a93e007c2654a9133011f63ecd64df7

This is my Local Script placed inside the StarterPlayer → StarterCharacterScripts:

local UIS = game:GetService("UserInputService")

local Character = script.Parent
local Camera = workspace.CurrentCamera
local RunService = game:GetService("RunService")
local Root = Character:WaitForChild("HumanoidRootPart")
local Humanoid = Character:WaitForChild("Humanoid")
local BodyGyro = Instance.new("BodyGyro")

local CameraXSensitivity = 0.5

local IsMouseLocked = true

Humanoid.AutoRotate = false
BodyGyro.MaxTorque = Vector3.new(0, 4e5, 0)
BodyGyro.P = 4e5
BodyGyro.Parent = Root

UIS.InputBegan:Connect(function(input)
	if (input.UserInputType == Enum.UserInputType.Keyboard) then
		if (input.KeyCode == Enum.KeyCode.LeftAlt) then
			print("Unlocked")
			IsMouseLocked = not IsMouseLocked
		end
	end
end)

UIS.InputChanged:Connect(function(input)
	if (input.UserInputType == Enum.UserInputType.MouseMovement) then
		
		local xDelta = input.Delta.X
		
		local DegreeChange = math.rad(xDelta * CameraXSensitivity) -- This could probably be the problem since we are getting the degree changed for the x value only but I can't seem to find a way to get the y axis and place it in DegreeChange.

		BodyGyro.CFrame = BodyGyro.CFrame * CFrame.fromAxisAngle(Vector3.new(0, 1, 0), -DegreeChange)
	end
end)

RunService:BindToRenderStep("otsCamera", 201, function()
	local lookAt = (Root.CFrame * CFrame.new(0, 0, -20)).Position
	local CamStartPos = (Root.CFrame * CFrame.new(4, 4, 10)).Position
	if IsMouseLocked then
		UIS.MouseBehavior = Enum.MouseBehavior.LockCenter
	else
		UIS.MouseBehavior = Enum.MouseBehavior.Default
	end
	Camera.CameraType = Enum.CameraType.Scriptable
	Camera.CFrame = CFrame.new(CamStartPos, lookAt)
end)

In simple words, I also want to be able to look up-down. Not only right-left.
Thanks for any help

That video shows your character rotating around the Y-axis. Rotating on the X axis would mean that your character is doing a flip.

So when you say:

What do you mean?

Have you tried using Humanoid.CameraOffset?

In simple words, I want to also be able to look up-down.

Do you want to bend your neck?

You can get the vertical angle like:

local angle = math.asin(workspace.CurrentCamera.LookVector.Y)

Then apply that to your neck or something:

local player = game.Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local human = character:WaitForChild("Humanoid")

local neck
if human.RigType == Enum.HumanoidRigType.R6 then
	neck = character:WaitForChild("Torso"):WaitForChild("Neck")
else
	neck = character:WaitForChild("Head"):WaitForChild("Neck")
end

local original = neck.C0

game:GetService("RunService").Stepped:Connect(function()
	local angle = math.asin(workspace.CurrentCamera.CFrame.LookVector.Y)
	neck.C0 = original * CFrame.Angles(angle, 0, 0)
end)
1 Like

This does not work :frowning:
Probably because my mouse has been locked to the centre I guess. The neck isn’t moving neither can I look up or down.
For your reference, this video is where I learnt how to do it:

Oh I understand your question now.

So what do you want to happen when you look up and down?

Do you just want the camera to move, but the player to remain level?

Do you want the neck to turn towards the camera?

Do you want the whole body to swing up and down (probably not that one :slight_smile: )?

I want the whole character to follow the camera. Take the game Jailbreak as an example.
Something like this:
https://gyazo.com/d92c14aadfb3d04ac78f335bd7b33ebe
Compare this above one to the one in the original post. You will see that in this one, I am able to rotate anywhere I want. In any direction. It really doesn’t matter if the head moves or not. The body should.

No, in this newest video, your body is still only rotating on the Y axis (left-to-right). Only the camera is different. I assume that’s what you want, though.

Anyways, I would store the yaw and pitch as separate variables instead of manually manipulating the CFrame when the mouse moves.

You just set the yaw and pitch, then update your bodygyro, then update your camera:

local UIS = game:GetService("UserInputService")

local Character = script.Parent
local Camera = workspace.CurrentCamera
local RunService = game:GetService("RunService")
local Root = Character:WaitForChild("HumanoidRootPart")
local Humanoid = Character:WaitForChild("Humanoid")
local BodyGyro = Instance.new("BodyGyro")

local CameraXSensitivity = 0.005

local IsMouseLocked = true

Humanoid.AutoRotate = false
BodyGyro.MaxTorque = Vector3.new(0, 4e5, 0)
BodyGyro.P = 4e5
BodyGyro.Parent = Root

Camera.CameraType = Enum.CameraType.Scriptable

UIS.InputBegan:Connect(function(input)
	if (input.UserInputType == Enum.UserInputType.Keyboard) then
		if (input.KeyCode == Enum.KeyCode.LeftAlt) then
			print("Unlocked")
			IsMouseLocked = not IsMouseLocked
		end
	end
end)

local yaw = 0
local pitch = 0

UIS.InputChanged:Connect(function(input)
	if (input.UserInputType == Enum.UserInputType.MouseMovement) then
		yaw = yaw - input.Delta.X * CameraXSensitivity
		pitch = math.clamp(pitch - input.Delta.Y * CameraXSensitivity, -math.pi/2 + 0.01, math.pi/2 - 0.01)
		
		BodyGyro.CFrame = CFrame.Angles(0, yaw, 0)
	end
end)

RunService:BindToRenderStep("otsCamera", 201, function()
	Camera.CFrame = CFrame.fromOrientation(pitch, yaw, 0) * CFrame.new(4, 4, 10) + Root.Position
	if IsMouseLocked then
		UIS.MouseBehavior = Enum.MouseBehavior.LockCenter
	else
		UIS.MouseBehavior = Enum.MouseBehavior.Default
	end
end)
1 Like

Yes this is exactly what I want. But there is a slight problem. You see, the player can see a little bit too much. Like this:
https://gyazo.com/b2e87e883ad2fc4a10b93654be5820eb
Can you somehow reduce the amount the player can see upwards or downwards? So that they can’t see through the floor etc.?

Sure, you have options for that.

The easiest is to change the up/down limits on this line:

pitch = math.clamp(pitch - input.Delta.Y * CameraXSensitivity, -math.pi/2 + 0.01, math.pi/2 - 0.01)

The second and third parameter are what you want. Currently they’re -math.pi/2 + 0.01 on the low end and math.pi/2 - 0.01 on the high end, or about +/-89 degrees.

Slightly fancier is to raycast from the head to the camera, and pop the camera forward as necessary if the ray hits something.

Example of the raycast method:

local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Blacklist
params.FilterDescendantsInstances = {Character}

RunService:BindToRenderStep("otsCamera", 201, function()
	local focus = Root.Position
	local camCf = CFrame.fromOrientation(pitch, yaw, 0) * CFrame.new(4, 4, 10) + Root.Position
	local result = workspace:Raycast(focus, camCf.Position - focus, params)
	
	if result then
		-- copies the position of the result, but the rotation of the original
		camCf = CFrame.fromMatrix(result.Position, camCf.RightVector, camCf.UpVector, -camCf.LookVector)
	end
	
	Camera.CFrame = camCf
	
	if IsMouseLocked then
		UIS.MouseBehavior = Enum.MouseBehavior.LockCenter
	else
		UIS.MouseBehavior = Enum.MouseBehavior.Default
	end
end)

Edit: also just for fun, the whole script with neck/waist bending and the raycast:

local UIS = game:GetService("UserInputService")

local Character = script.Parent
local Camera = workspace.CurrentCamera
local RunService = game:GetService("RunService")
local Root = Character:WaitForChild("HumanoidRootPart")
local Humanoid = Character:WaitForChild("Humanoid")
local BodyGyro = Instance.new("BodyGyro")

local neck, waist
local originalWaist
if Humanoid.RigType == Enum.HumanoidRigType.R6 then
	neck = Character:WaitForChild("Torso"):WaitForChild("Neck")
else
	neck = Character:WaitForChild("Head"):WaitForChild("Neck")
	waist = Character:WaitForChild("UpperTorso"):WaitForChild("Waist")
	originalWaist = waist.C0
end
local originalNeck = neck.C0

local CameraXSensitivity = 0.005

local IsMouseLocked = true

Humanoid.AutoRotate = false
BodyGyro.MaxTorque = Vector3.new(0, 4e5, 0)
BodyGyro.P = 4e5
BodyGyro.Parent = Root

Camera.CameraType = Enum.CameraType.Scriptable

UIS.InputBegan:Connect(function(input)
	if (input.UserInputType == Enum.UserInputType.Keyboard) then
		if (input.KeyCode == Enum.KeyCode.LeftAlt) then
			print("Unlocked")
			IsMouseLocked = not IsMouseLocked
		end
	end
end)

local yaw = 0
local pitch = 0

UIS.InputChanged:Connect(function(input)
	if (input.UserInputType == Enum.UserInputType.MouseMovement) then
		yaw = yaw - input.Delta.X * CameraXSensitivity
		pitch = math.clamp(pitch - input.Delta.Y * CameraXSensitivity, -math.pi/2 + 0.01, math.pi/2 - 0.01)
		
		BodyGyro.CFrame = CFrame.Angles(0, yaw, 0)
		if waist then
			neck.C0 = originalNeck * CFrame.Angles(pitch / 2, 0, 0)
			waist.C0 = originalWaist * CFrame.Angles(pitch / 2, 0, 0)
		else
			neck.C0 = originalNeck * CFrame.Angles(pitch, 0, 0)
		end
	end
end)

local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Blacklist
params.FilterDescendantsInstances = {Character}

RunService:BindToRenderStep("otsCamera", 201, function()
	local focus = Root.Position
	local camCf = CFrame.fromOrientation(pitch, yaw, 0) * CFrame.new(4, 4, 10) + Root.Position
	local result = workspace:Raycast(focus, camCf.Position - focus, params)
	
	if result then
		camCf = CFrame.fromMatrix(result.Position, camCf.RightVector, camCf.UpVector, -camCf.LookVector)
	end
	
	Camera.CFrame = camCf
	
	if IsMouseLocked then
		UIS.MouseBehavior = Enum.MouseBehavior.LockCenter
	else
		UIS.MouseBehavior = Enum.MouseBehavior.Default
	end
end)
2 Likes