Camera in first person placed incorrectly

Hey you all. I’m currently trying to get a first person script to work while the player can see their own character (With the right camera placement), but when I sprint, the camera goes behind the player for some reason, making it turn into 3rd person. Even when I’m idle, the camera is placed slightly behind the character, which makes it look weird.
After I turn on transparency modifiers for the character, I noticed that this was happening with the camera, because I could actually see the character of course.
I’m asking here because this was a resource that I applied to my game.

I tried modifying the different CFrames, by modifying this value: (x, y, Here) where negative values were the only way to put the camera in place, which did place the camera a bit better, but for some reason the player’s character would constantly look backwards.

Also, when looking directly down, the character looks backward and forward constantly (Similar to how the negative values made the character look backwards), which is another issue.

Here’s an image of how the player’s view looks like:

While Idle:

While Running:

The credits to the scripts made by others are comments in the scripts.
So with that out of the way, here is the code:

The First Person System (Local Script)

Located in StarterCharacterScripts

--!strict

--[[
Move script to:
	StarterCharacterScripts
	
Made by:
	@isPauI
	YT: @Paul1Rb


Updated: 10.07.2023 - 10:35 CET
Log:
	- Changed the Sway effect to work on CameraOffset because it caused the player to rotate on Y axis since they were in first person.
	
	- Changed line 65 [Cam.CameraType = Enum.CameraType.Scriptable] to .Custom
]]

local Settings : SettingsType = require(script:WaitForChild("Settings"))

type SettingsType = {
	BaseRot : number,
	SetRot : number,
	BaseFreq : number,
	SetFreq : number,
	BaseMult : number,
	BaseNumLerp: number,
	SwayStrenght : number,
	BaseSway : number,
	SetSway : number,
	CustomSwayZVal : number,
	DriftMin : number,
	DriftMax : number,
	Rate : number,
	MaxBlur : number,
	BlurMult : number,
}

local sway : CFrame
local MouseDelta : Vector2
local Vel,t,x,y : number

local sin = math.sin
local cos = math.cos
local rad = math.rad
local abs = math.abs
local sqrt = math.sqrt
local clamp = math.clamp
local round = math.round
local clock = os.clock

local CurrentVel = 0
local Drift = 0
local Limiter = 0

local RunServ = game:GetService("RunService")
local UIS = game:GetService("UserInputService")
local PlrServ = game:GetService("Players")
local Workspace = game:GetService("Workspace")
local LightingServ = game:GetService("Lighting")

local LocalPlr : Player = PlrServ.LocalPlayer

local Cam : Camera = Workspace.CurrentCamera
--local BaseFov = Cam.FieldOfView

Cam.CameraType = Enum.CameraType.Custom
LocalPlr.CameraMode = Enum.CameraMode.LockFirstPerson

local char : Model = LocalPlr.Character or LocalPlr.CharacterAdded:Wait()
local hum = char:WaitForChild("Humanoid") :: Humanoid
local HRP = char:WaitForChild("HumanoidRootPart") :: BasePart


function NumLerp(num1: number, num2: number, rate: number) : number
	return num1 + (num2-num1)*rate
end

function CalculateCurve(Base : number, Set : number) : number
	return sin(clock() * Base) * Set
end

function GetVelMag() : number
	return round(Vector3.new(HRP.AssemblyLinearVelocity.X, HRP.AssemblyLinearVelocity.Y, HRP.AssemblyLinearVelocity.Z).Magnitude)
end

function GetMouseDrift(Drift : number, MouseDelta : Vector2, dt : number) : number
	return NumLerp(Drift, clamp(MouseDelta.X, Settings.DriftMin, Settings.DriftMax), (Settings.BaseMult * dt))
end

function GetSwayVal(x:number, y:number) : CFrame
	return CFrame.new(Vector3.new(x, y, 0), Vector3.new(x*.95, y*.95, Settings.CustomSwayZVal)) + Cam.CFrame.Position
end

local function setBlur() : BlurEffect
	local MotionBlur : BlurEffect = LightingServ:FindFirstChild("MotionBlur")
	
	if not MotionBlur then
		local newMotionBlur = Instance.new("BlurEffect")
		newMotionBlur.Size = 0
		newMotionBlur.Name = 'MotionBlur'
		newMotionBlur.Enabled = true
		newMotionBlur.Parent = LightingServ
		
		MotionBlur = newMotionBlur
	end
	
	return MotionBlur
end

local MotionBlur = setBlur()

function ConvCFrameToOrientation(_CFrame: CFrame)
	local setX, setY, setZ = _CFrame:ToOrientation()
	return Vector3.new(math.deg(setX), math.deg(setY), math.deg(setZ))
end

local function CameraUpdt(dt)
	Limiter += dt
	if Limiter >= 1/Settings.Rate then
		t = clock()
		MouseDelta = UIS:GetMouseDelta()
		
		Vel = NumLerp(CurrentVel, GetVelMag(), Settings.BaseNumLerp)
		
		x = cos(t * Settings.BaseSway) * Settings.SetSway
		y = sin(t * Settings.BaseSway) * Settings.SetSway 
		sway = GetSwayVal(x,y)
		
		Drift = GetMouseDrift(Drift, MouseDelta, dt)

		--Cam.FieldOfView = BaseFov + sqrt(Vel)
		
		MotionBlur.Size = clamp(abs(Drift*Settings.BlurMult) --[[ & Vel with lower mult]], 0, Settings.MaxBlur)
		
		CurrentVel = Vel
		
		Cam.CFrame = Cam.CFrame--:Lerp(sway * Cam.CFrame.Rotation, Settings.SwayStrenght)
			* CFrame.new(0, CalculateCurve(Settings.BaseFreq, Settings.SetFreq) * Vel / Settings.BaseMult, 0)
			* CFrame.Angles(0, 0, rad(CalculateCurve(Settings.BaseRot, Settings.SetRot) * Vel / Settings.BaseMult) + rad(Drift))
		
		hum.CameraOffset = ConvCFrameToOrientation(sway)
		
		Limiter -= 1/Settings.Rate
	end
end

RunServ:BindToRenderStep("CamEffect", Enum.RenderPriority.Camera.Value, CameraUpdt)

local function reset() : ()
	LocalPlr.CameraMode = Enum.CameraMode.Classic
	--Cam.CameraType = Enum.CameraType.Custom
end

--hum.Died:Once(reset)

Here is the settings module related to this script:

return{
	--static
	BaseRot = 2.5, -- rotation speed
	SetRot = .4, -- rotation strenght
	BaseFreq = 15, -- view bobbing frequency      (Was 15)
	SetFreq = 0.12, -- view bobbing strenght       (Was .16)
	BaseMult = 12, -- base multiplier: less -> stronger effects
	BaseNumLerp = 0.25, -- lerp time value
	
	--sway settings
	SwayStrenght = .2, -- effect strenght   (Was .2)
	BaseSway = 2.3, -- axis strenght
	SetSway = 0.7, -- axis multiplier        (Was .5)
	CustomSwayZVal = -10, -- vector z value  (Was -10)
	
	--drift min/max
	DriftMin = -3,  --(Was -3)
	DriftMax = 1,	--(Was 1)
	
	--fps
	Rate = 60,
	
	--blur
	MaxBlur = 3, -- max blur value
	BlurMult = 3, -- blur multiplier
}
Sprint System

Module Script in Replicated Storage

--[[
 ========================================
  By: luisgamercooI231fan, 
  Time: tue march 29 21:15:00 2022,
  Description: sprint,
  ======================================== 
  ]]--

local module = {}
local data = {}
local player = game:GetService("Players").LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local UIS = game:GetService("UserInputService")
local TS = game:GetService("TweenService")
local CAS = game:GetService("ContextActionService")


local Anim = Instance.new('Animation')
Anim.AnimationId = "rbxassetid://16754777947" ----------animation id here
PlayAnim = character.Humanoid:LoadAnimation(Anim)


local function scale_button(CurrentCamera, name:string)
	local MinAxis = math.min(CurrentCamera.ViewportSize.X, CurrentCamera.ViewportSize.Y)
	local IsSmallScreen = MinAxis <= 500
	local ActionButtonSize = IsSmallScreen and 70 or 120

	local function RescaleActionButton(Name)
		local ActionButton = CAS:GetButton(Name)

		if ActionButton then
			local ActionTitle = ActionButton:WaitForChild("ActionTitle")

			if not IsSmallScreen then
				ActionTitle.TextSize = 36
			else
				ActionTitle.TextSize = 18
			end
			ActionButton.Size = UDim2.fromOffset(ActionButtonSize, ActionButtonSize)
		end
	end
	RescaleActionButton(name)
end
function module.new()
	if game:GetService("RunService"):IsClient() then
		local plr = game:GetService("Players").LocalPlayer
		local self = setmetatable({}, {})
		local start_time = nil
		self.phone_button_offset = 45 -- the offset of the default mobile button on phones
		self.tablet_button_offset = 2.45 -- the offset of the default mobile button on tablets
		self.mobile_button_icon = "rbxassetid://1921587812" -- the icon for the default mobile button
		local sprinted = Instance.new("BindableEvent")
		local unsprinted = Instance.new("BindableEvent")
		self.camera = workspace.CurrentCamera
		self.mobile_button_title = nil -- the mobile button title 
		self.run_animation = "rbxassetid://16754777947" -- I just started scripting and idk how to use this. ,Mr_baconhair839
		self.default_FOV = 90
		self.FOV_tween_speed = 0.5 -- how long it takes for the fov to tween
		self.FOV_tween_style = Enum.EasingStyle.Quad -- the easing style of which the tween is tweened at
		self.FOV_tween_direction = Enum.EasingDirection.Out -- the easing direction of which the tween is tweened at.
		self.FOV_tween_info = TweenInfo.new(self.FOV_tween_speed, self.FOV_tween_style, self.FOV_tween_direction, 0, false, 0)
		self.default_speed = 21 --game:GetService("StarterPlayer").CharacterWalkSpeed -- the default speed for when you stop sprinting
		self.run_speed = 32 --self.default_speed * 1.7-- the speed of which the player sprints at
		self.uses_default_ui = true -- if the mobile ui uses the default mobile ui
		self.sprint_ui = nil -- if the mobile ui is set to false, you can set this to add a custom ui(image button or text button).
		self.Sprinted = sprinted.Event
		self.Unsprinted = unsprinted.Event
		self.use_run_FOV = true -- if the player's fov changes when sprinting
		self.running_FOV = 130 -- the FOV for the player when they run
		local goals = {
			FieldOfView = self.running_FOV
		}
		local stop_goals = {
			FieldOfView = self.default_FOV
		}
		local FOV_tween = TS:Create(self.camera, self.FOV_tween_info, goals)
		local FOV_stop_tween = TS:Create(self.camera,self.FOV_tween_info, stop_goals)
		self.valid_keys = { -- the keys that can be used to start sprinting
			--[PC]--
			Enum.KeyCode.LeftShift, 
			Enum.KeyCode.RightShift,
			--[Xbox]--
			Enum.KeyCode.ButtonX -- the X button on controller
		}
		
		
		local function sprint(name, state)
			if state == Enum.UserInputState.Begin then
				start_time = tick()
				sprinted:Fire(start_time)
			else
				if start_time then
					unsprinted:Fire(tick(),tick() - start_time)
				else
					unsprinted:Fire(tick(),0)
				end

				start_time = nil
			end
		end
		self.Sprint = function(state:Enum.UserInputState)
			sprint(nil, state)
		end
		self.Sprinted:Connect(function(start_time)
			local char = plr.Character
			if char then
				local hum = char:FindFirstChildWhichIsA("Humanoid")
				if hum then
					hum.WalkSpeed = self.run_speed
					if hum.WalkSpeed > 30 then --Was 15 instead of 20
						PlayAnim:Play()
					end
					if self.use_run_FOV then
						FOV_tween:Play()
					end

				end
			end
		end)
		self.Unsprinted:Connect(function(end_time, activation_time)
			local char = plr.Character
			if char then
				local hum = char:FindFirstChildWhichIsA("Humanoid")
				if hum then
					hum.WalkSpeed = self.default_speed
				
					PlayAnim:Stop()
					FOV_stop_tween:Play()
				end
			end
		end)

		function self:Init(id:string)
			table.insert(data, self)
			local CurrentCamera = self.camera
			if self.uses_default_ui == true and self.sprint_ui == nil then
				local MinAxis = math.min(CurrentCamera.ViewportSize.X, CurrentCamera.ViewportSize.Y)
				local IsSmallScreen = MinAxis <= 500
				local ActionButtonSize = IsSmallScreen and 70 or 120
				CAS:BindActionAtPriority(id, sprint, true, 10000, unpack(self.valid_keys))
				CAS:SetPosition(id, IsSmallScreen and UDim2.new(1, -(ActionButtonSize * 2.52), 1, -ActionButtonSize - self.phone_button_offset) or
					UDim2.new(1, -(ActionButtonSize * 2.52 - 10), 1, -ActionButtonSize  * self.tablet_button_offset))
				scale_button(CurrentCamera, id)
				if self.mobile_button_title then
					CAS:SetTitle(id, self.mobile_button_title)
				else
					CAS:SetImage(id, self.mobile_button_icon)
				end	
			elseif self.uses_default_ui == false and self.sprint_ui ~= nil then
				local ui:TextButton | ImageButton = self.sprint_ui
				if ui:IsA("TextButton") or ui:IsA("ImageButton") then
					CAS:BindActionAtPriority("sprint-action", sprint, false, 10000, unpack(self.valid_keys))
					if not UIS.KeyboardEnabled then
						ui.Visible = true
						ui.InputBegan:Connect(function(input)
							sprint(nil, input.UserInputState)
						end)
						ui.InputEnded:Connect(function(input)
							sprint(nil, input.UserInputState)				
						end)
					else --user has keyboard
						ui.Visible = false
					end
				end
			end
		end
		return self
	else
		error('please run sprinting module on client; exit code 1')
	end


end
function module:Get(id:string)
	for _, sprint_data in ipairs(data) do
		if sprint_data.id == id then
			return sprint_data
		end
	end


end
return module

Local Script in StarterPlayerScripts

local module = require(game.ReplicatedStorage:WaitForChild("SprintModule"))
local sprint_data = module.new() -- creates our sprinting data
sprint_data:Init("sprint") -- initialize for it to start being used, sprint is the id of the sprint data, this is required. 
The First Person Visible Character Script (Just if you need it)
for i, v in script.Parent:GetChildren() do
	
	if not v:IsA("BasePart") then
		continue
	end
	
	v:GetPropertyChangedSignal("LocalTransparencyModifier"):Connect(function()
		v.LocalTransparencyModifier = 0
	end)

	v.LocalTransparencyModifier = 0
end

script.Parent.ChildAdded:Connect(function(v)
	if not v:IsA("BasePart") then
		return
	end
	v:GetPropertyChangedSignal("LocalTransparencyModifier"):Connect(function()
		v.LocalTransparencyModifier = 0
	end)

	v.LocalTransparencyModifier = 0
end)

Thank You All :happy2:

1 Like