Can't detect that humanoid is moving left when also going backwards/forwards

I am working on animating my first-person viewmodel. I decided to add some strafing animations, which means your hands will move to the side if you’re moving sideways(holding A or D on your keyboard).
I decided not to capture keys from the player and instead look at the humanoid.MoveDirection vector for easier multi-platform support later on.

The code fires whenever the humanoid has moved and determines the correct angle for the C0 of a Motor6D which rotates the arms in a viewmodel. It works all nice until I try to walk forward or backwards while going to the side as well.
[Backwards/Forwards] + Right = C0 rotates, works
[Backwards/Forwards] + Left = C0 doesn’t rotate at all, doesn’t work

What I have noticed is that the last if statement which should validate if there is sideways movement,
doesn’t pass when the player is going towards the left and backwards/forwards. I have tried replacing it/changing it but everything seems to break even more from there. I’ve been at this for hours and I always fail at doing CFrame stuff so help is needed.

local angleAmount = 15 --the amount in degrees in which the viewmodel should rotate
local lastAngle = 0 --starts of as 0(the viewmodel isn't angled)
humanoid:GetPropertyChangedSignal("MoveDirection"):Connect(function() --this event will fire when a player is moving
    local moveDirection = camera.CFrame:VectorToObjectSpace(humanoid.MoveDirection).Unit --get the current direction relative to the camera
    if not player.Character then return end --cancel if there isn't a player character
    if not viewmodel then return end --cancel if there is no viewmodel

    local angle = math.ceil(moveDirection.X) * angleAmount
    if angle == lastAngle then return end --cancel if the last and current angle is the same
    --the angle is different from the last one
    viewmodel.Head.UpperTorso.C0 = viewmodel.Head.UpperTorso.C0 * CFrame.Angles(0, 0, -math.rad(lastAngle)) --automatically reset to normal angle
    lastAngle = 0 --set last angle to 0
    if math.abs(moveDirection.X) > math.abs(moveDirection.Z) then --if the player is moving sideways
        viewmodel.Head.UpperTorso.C0 = viewmodel.Head.UpperTorso.C0 * CFrame.Angles(0, 0, math.rad(angle)) --set the angle to the current angle based on movement
        lastAngle = angle --set the last angle to the current angle

Here’s some code to determine the y direction and also to figure out the state of how their moving in case you want animations:

local yDirection = math.atan2(velocity.X, -velocity.Z)
local roundedDirection = math.ceil(math.deg(yDirection) - 0.5)
if roundedDirection > 45 and roundedDirection < 135 then --strafing right
	AnimationHandler.Strafing = true
elseif roundedDirection < -45 and roundedDirection > -135 then --strafing left
	AnimationHandler.Strafing = true
elseif roundedDirection <= -135 or roundedDirection >= 135 then
	AnimationHandler.WalkingBackwards = true
	yDirection += math.rad(180)

Here is sort of a wheel of what roundedDirection is:

I hope this is enough to solve your problem, cheers!
*if you’re curious why I added 180 degrees to the yDirection when walking backwards, I actually multiplied the waist C0 by the yDirection so the lowerTorso would face towards the direction you walked in and the upper torso stayed straight, like so:

local newWaistC0 = AnimationHandler.Waist.C0:Lerp(AnimationHandler.DefaultWaistC0 * CFrame.Angles(0, yDirection, 0), 0.2)
local newRootC0 = AnimationHandler.Root.C0:Lerp(AnimationHandler.DefaultRootC0 * CFrame.Angles(0, -yDirection, 0), 0.2)

Adding 180 degrees just inverted the yDirection so the torso would still face forward if you walked directly backward, since it looks more natural.


Wow, thanks for the really elaborate explanation I’m really digging it. However, I’ve got three questions though, I might not be getting the obvious here…

The variable velocity is set to what?
Is the AnimationHandler something from your own project or?
Also the last bit about the waist and the yDirection is not too clear for me but I guess I wouldn’t need it for my application.

I’m trying to do this only on the first person viewmodel, which is client only and is detached from the player character, but is the player character just with legs and lower torso removed and an AnimationController.
I am editing the C0 of a motor6D which connects the uppertorso and the head, and when changing that both of the arms rotate as well.(You don’t notice your torso rotating in first person though, just the arms moving so it works great)
I was thinking of animating the movement just using a lerp later on, just wanted to get the core mechanic working.

Velocity is just the velocity of the humanoidrootpart, and yeah the second part wasn’t really meant for your case. Since I am a horrible animator I’ve only got mocapped animations so that was the best solution for me. The main objective was to give you some code that just gives an angle based on the direction they’re walking towards.
velocity = rootPart.CFrame:Inverse() * (rootPart.Position + rootPart.Velocity)


Great! Got it to work now, thanks for the additional code I was missing.

I expanded the range by 10 degrees to make sure it also detected strafing while going backwards and forwards as the angle was a bit closer to 0/180.
I also added a debounce which makes sure that I only update my strafing if the direction is different than the last one.
And as a last I plugged it into runService.Heartbeat event to make sure it updates constantly.

The implementation and idea is great as I can really configure it the way I want it now. Cheers! Thanks a lot.

Thank you so much for this, you save me so much time.