I need to create a mechanic in my first person shooter where the pitch of a character’s arms and head rotate accordingly to the player’s camera. This is not a viewmodel. I have already implemented viewmodels and now need a server side way to show other players what the client is looking at. Also, it has to work with existing idle + fire animations
I’ve linked a video of how pitch is changed in the animation editor just as a quick reference.
I have since however, updated the system to run a lot better.
Here’s the updated code:
CLIENT
local RunService = game:GetService('RunService')
local Event = script:WaitForChild('LookEvent')
local Player = game.Players.LocalPlayer
local Camera = workspace:WaitForChild('Camera')
local Character = Player.Character or Player.CharacterAdded:Wait()
local Head = Character:WaitForChild('Head')
local Humanoid = Character:WaitForChild('Humanoid')
--// Here are the settings :3
local updateRate = 1/20 --// How many times per second the server will update, if you want 60, do 1/60
local accumulated = 0
local HeadHorFactor = 0.8
local HeadVertFactor = 0.7
local TorsoHorFactor = 0.8
local TorsoVertFactor = 0.7
local UpdateSpeed = 0.5
--// Settings end :3
local Torso
local Waist
local Neck
local WaistOrgnC0
if Humanoid.RigType == Enum.HumanoidRigType.R6 then
Torso = Character:WaitForChild('Torso')
Neck = Torso:WaitForChild('Neck')
else
Torso = Character:WaitForChild('UpperTorso')
Neck = Character:WaitForChild('Head'):WaitForChild('Neck')
Waist = Torso:WaitForChild('Waist')
WaistOrgnC0 = Waist.C0
end
local RightShoulder = Torso:WaitForChild('Right Shoulder')
local RightShoulderOrgnC0 = RightShoulder.C1
local LeftShoulder = Torso:WaitForChild('Left Shoulder')
local LeftShoulderOrgnC0 = LeftShoulder.C1
local runService = game:GetService("RunService")
if Humanoid.RigType == Enum.HumanoidRigType.R6 then
Torso = Character:WaitForChild('Torso')
Neck = Torso:WaitForChild('Neck')
else
Torso = Character:WaitForChild('UpperTorso')
Neck = Character:WaitForChild('Head'):WaitForChild('Neck')
end
local RootPart = Character:WaitForChild('HumanoidRootPart')
local Mouse = Player:GetMouse()
local Ang = CFrame.Angles
local aSin = math.asin
local aTan = math.atan
local NeckOrgnC0 = Neck.C0
local Dist
local Diff
local TorsoPosition
function getMath()
if Humanoid.RigType == Enum.HumanoidRigType.R6 then
local HeadPosition = Head.CFrame.Position
local TorsoLookVector = Torso.CFrame.lookVector
Dist = (Head.CFrame.Position - Camera.CFrame.Position).magnitude
Diff = Head.CFrame.Y - Camera.CFrame.Y
elseif Humanoid.RigType == Enum.HumanoidRigType.R15 then
local HeadPosition = Head.CFrame.Position
TorsoPosition = Torso.CFrame.Position
local TorsoLookVector = Torso.CFrame.lookVector
Dist = (Head.CFrame.Position - Camera.CFrame.Position).magnitude
Diff = Head.CFrame.Y - Camera.CFrame.Y
end
end
function updateChar()
if Humanoid.RigType == Enum.HumanoidRigType.R6 then
local HeadPosition = Head.CFrame.Position
local TorsoLookVector = Torso.CFrame.lookVector
getMath()
Neck.C0 = Neck.C0:Lerp(NeckOrgnC0*Ang(-(aSin(Diff/Dist)*HeadVertFactor), 0, -(((HeadPosition - Camera.CFrame.Position).Unit):Cross(TorsoLookVector)).Y*HeadHorFactor),UpdateSpeed/2)
local MouseOriginPosition = Player:GetMouse().Origin.Position
local X = -(math.asin((MouseOriginPosition - Camera.CFrame.Position).unit.y)) * -1
local _, Y, Z = RightShoulder.C0:ToEulerAnglesXYZ()
local OldRightC0 = RightShoulder.C0
RightShoulder.C0 = CFrame.new(RightShoulder.C0.Position) * CFrame.Angles(X, Y, Z)
local _, Y, Z = LeftShoulder.C0:ToEulerAnglesXYZ()
LeftShoulder.C0 = CFrame.new(LeftShoulder.C0.Position) * CFrame.Angles(X, Y, Z)
elseif Humanoid.RigType == Enum.HumanoidRigType.R15 then
local HeadPosition = Head.CFrame.Position
local TorsoPosition = Torso.CFrame.Position
local TorsoLookVector = Torso.CFrame.lookVector
getMath()
Neck.C0 = Neck.C0:lerp(NeckOrgnC0*Ang((aSin(Diff/Dist)*(HeadVertFactor/2)), -(((HeadPosition - Camera.CFrame.Position).Unit):Cross(TorsoLookVector)).Y*(HeadHorFactor/2), 0), UpdateSpeed/2)
Waist.C0 = Waist.C0:lerp(WaistOrgnC0*Ang((aSin(Diff/Dist)*(TorsoVertFactor/2)), -(((TorsoPosition - Camera.CFrame.Position).Unit):Cross(TorsoLookVector)).Y*(TorsoHorFactor/2), 0), UpdateSpeed/2)
end
end
runService.RenderStepped:Connect(function(deltaTime)
accumulated += deltaTime
updateChar() --// Update the character for the client, this line can be removed or kept, up to you
if accumulated >= updateRate then --// If how much time between frames and last update if greater than or equal to our goal
accumulated = 0
if Humanoid.RigType == Enum.HumanoidRigType.R6 and Player.CameraMode == Enum.CameraMode.LockFirstPerson then
Event:FireServer(Neck.C0, nil, RightShoulder.C0, LeftShoulder.C0) --// Update the character for other players (server)
elseif Humanoid.RigType == Enum.HumanoidRigType.R15 and Player.CameraMode == Enum.CameraMode.LockFirstPerson then
Event:FireServer(Neck.C0, Waist.C0) --// Update the character for other players (server)
end
end
end)
SERVER
local Event = script.Parent:WaitForChild('LookEvent')
local Character = script.Parent.Parent.Parent
local Head = Character:WaitForChild('Head')
local Humanoid = Character:WaitForChild('Humanoid')
local Torso
local Waist
local Neck
if Humanoid.RigType == Enum.HumanoidRigType.R6 then
Torso = Character:WaitForChild('Torso')
Neck = Torso:WaitForChild('Neck')
else
Torso = Character:WaitForChild('UpperTorso')
Neck = Character:WaitForChild('Head'):WaitForChild('Neck')
Waist = Torso:WaitForChild('Waist')
end
local RightShoulder = Torso:WaitForChild('Right Shoulder')
local LeftShoulder = Torso:WaitForChild('Left Shoulder')
Event.OnServerEvent:Connect(function(Player, NeckC0, WaistC0, RSc0, LSc0)
if Humanoid.RigType == Enum.HumanoidRigType.R6 then
Neck.C0 = NeckC0
RightShoulder.C0 = RSc0
LeftShoulder.C0 = LSc0
elseif Humanoid.RigType == Enum.HumanoidRigType.R15 then
Neck.C0 = NeckC0
Waist.C0 = WaistC0
end
end)
I might also quickly add, the system I have isn’t 100% identical to your video, as the arms rotate around their respective shoulder and the head around the neck, but it still gets almost the same result.
Personally, I just had a local script under StarterCharacterScripts, and had an event and the server-script under the local-script.
Optimized? Probably not. Works? Yes.