Character rotation not syncing with camera updates

Hello! I’m currently working on replacing my old forked (hacky) camera controller with a fork of the new one, but I’ve hit a snag. First, some background. I hacked the old one to show the character in first person view and move the camera’s origin to the character’s eyes (together with an even hackier forced shift-lock it’s meant to be immersive in first person and allow a third-person over-shoulder cam).

Being the lazy man I am, I don’t want to to add the new smooth zooming and better occlusion detection to the old script myself, so I instead opted to fork the new one with the changes I listed above. It’s going well, aside from one problem: The camera update no longer syncs to the character’s rotation.

I’ll just show you instead. Here’s the old camera script in action:

Smooth as butter, right? Here’s the new camera script in action:

Not optimal.

I ran into this problem before with the old script, and I don’t know what fixed it. I made the exact same changes to the equivalent places in the new scripts, and changed nothing else. I almost pulled my hair out last time over this, and I do not have the energy to go through it again for days on end.

Does anyone know what’s happening here, and what’s causing it? I’ve already tried shifting the order of updating the character rotation and camera, and that makes it worse if it even makes any difference.

Edit: Appending a few sections of my scripts and the modifications I made to the camera script.

This is at the end of the 'if bodyPartToFollow and bodyPartToFollow:IsA("BasePart")' check at line 338 or so of PlayerModule.CameraModule.BaseCamera
if bodyPartToFollow.Name == "HumanoidRootPart" then
    local subjectCFrame = bodyPartToFollow.CFrame
    local char = cameraSubject.Parent
    local width = cameraSubject.BodyWidthScale.Value
    local height = cameraSubject.BodyHeightScale.Value
    local headScale = cameraSubject.HeadScale.Value
    local headBobOffset = _G.headBobbing and subjectCFrame:VectorToObjectSpace(char.Head.Position - bodyPartToFollow.Position)
        + subjectCFrame:VectorToObjectSpace((char.Head.CFrame.UpVector*0.25*headScale) + (char.Head.CFrame.LookVector*0.4*headScale))
        -,(1.5/height),0) or

    result = subjectCFrame.p +
        subjectCFrame:vectorToWorldSpace(heightOffset + cameraSubject.CameraOffset + headBobOffset)
    result = bodyPartToFollow.CFrame.p + bodyPartToFollow.CFrame:vectorToWorldSpace(heightOffset + humanoid.CameraOffset)
This is in a RenderStepped loop in my client core
if char.Humanoid.Health > 0 then
    local camLevelledVector =,0,cam.CFrame.LookVector.Z)
    char.HumanoidRootPart.CFrame =,char.HumanoidRootPart.Position + camLevelledVector)
Finally, this function is called from the same RenderStepped loop and handled the character's vertical look movements (the camera's origin is the eyes so it's important to take this into account)
local function UpdateLookVertical(target,theta)
    if target.Humanoid.Health > 0 then
        local width = target.Humanoid.BodyWidthScale.Value
        local height = target.Humanoid.BodyHeightScale.Value
        local headScale = target.Humanoid.HeadScale.Value
        local neckC0 =,0.8*height,0, 1,0,0, 0,1,0, 0,0,1)
        local waistC0 =,0.2*height,0, 1,0,0, 0,1,0, 0,0,1)
        local leftArmC0 =*width,0.56*height,0, 1,0,0, 0,1,0, 0,0,1)
        local rightArmC0 =*width,0.56*height,0, 1,0,0, 0,1,0, 0,0,1)
        local toolEquipped = false
        if target.Humanoid.Sit then
            target.Head.Neck.C0 = neckC0
            target.UpperTorso.Waist.C0 = waistC0
            target.Head.Neck.C0 = neckC0*CFrame.fromEulerAnglesYXZ(theta*(toolEquipped and 0.5 or 0.6),0,0)
            target.UpperTorso.Waist.C0 = waistC0*CFrame.fromEulerAnglesYXZ(theta*(toolEquipped and 0.25 or 0.15),0,0)
        local mult = toolEquipped and 0.75 or (target.Humanoid.Sit and 0 or 0.05)
        target.LeftUpperArm.LeftShoulder.C0 = leftArmC0*CFrame.fromEulerAnglesYXZ(theta*mult,0,0)
        target.RightUpperArm.RightShoulder.C0 = rightArmC0*CFrame.fromEulerAnglesYXZ(theta*mult,0,0)
        if target == char then
--            char.Humanoid.CameraOffset = char.HumanoidRootPart.CFrame:vectorToObjectSpace(char.Head.Position - char.HumanoidRootPart.Position)
--                + char.HumanoidRootPart.CFrame:vectorToObjectSpace((char.Head.CFrame.UpVector*0.25*headScale) + (char.Head.CFrame.LookVector*0.4*headScale))
--                -,(1.5/height) + (0.5/headScale),0)

What are you using to update the Camera? If it isn’t using RenderStepped, you should use it as it is optimal for Camera manipulation and Player things in general. In the case that you are using it already, I’m kind of stumped too.

1 Like

I’m using RenderStepped, yes! I added a hook to the camera scripts that allows me to call the Update function anywhere else on the client, which is the way I had it before too (and it worked fine).

This is probably a dumb question, but are you using :BindToRenderStep() and setting the priority to the camera’s rendering prioertiy, or maybe something even higher? Wouldn’t that work

1 Like

That’s what I tried using to switch the order of camera and character updates - I use it to update the neck and waist motors for vertical look anyway, and that’s never synced properly no matter what I do with it.

1 Like

I see, as @DesiredFlamingFire said, you can optimize the render stepped a bit. Since adding some stuff is what broke the syncing. Maybe try to prioritize the character rotation part, by making it the first thing that happens.

1 Like

As far as I can reasonably tell, that’s not the problem here. I didn’t actually make any changes to my client core - it still interacts with the camera script in the same way, by simply calling the Update function in the camera script, which I aliased to _G.

If I switch back to the old camera script, it syncs 100% perfectly, but I lose out on smooth zooming and better occlusion checking. If I switch to the new one again, with no changes other than swapping the two camera controllers, it doesn’t sync. This is infuriating.

(I also appended some of my code to the original post, as I forgot to do that earlier. Sorry!)

1 Like