NPC wont look vertically only horizontally

I have an npc that looks at target, but for some reason it does not look Up or Down. Only the sides.
I dont understand what am i doing wrong, please help. this is the script inside the npc.

self = script.Parent
local Neck = self.Head:FindFirstChild("Neck", true)
local YOffset = Neck.C0
while task.wait() do
    local cameraDirection = (CFrame.new(self.HumanoidRootPart.Position, workspace.target.Position) * self.HumanoidRootPart.CFrame:Inverse()).lookVector
    local horizontalAngle = math.atan2(-cameraDirection.x, -cameraDirection.z)
    local verticalAngle = -math.asin(cameraDirection.Y)
	local goal = YOffset * CFrame.Angles(verticalAngle, 0, 0) *  CFrame.Angles(0, horizontalAngle, 0)
    Neck.C0 = Neck.C0:Lerp(goal, 0.1)
end
1 Like

You’re using two CFrame.Angles() and combining them in a way that might not be correctly updating the vertical angle for the neck. When applying the angles to the neck’s C0, you’re using both vertical and horizontal rotations, but the order of transformations may be incorrect.
You have to simplify the calculations by directly using the LookVector to calculate the angles needed for both horizontal and vertical rotation.

I wrote this, test it out!

self = script.Parent
local Neck = self.Head:FindFirstChild("Neck", true)
local YOffset = Neck.C0

while task.wait() do
    -- Get the direction vector towards the target
    local targetDirection = (workspace.target.Position - self.HumanoidRootPart.Position).unit
    
    -- Calculate the horizontal angle (yaw) using only the X and Z components
    local horizontalAngle = math.atan2(-targetDirection.X, -targetDirection.Z)
    
    -- Calculate the vertical angle (pitch) using the Y component
    local verticalAngle = math.asin(targetDirection.Y)
    
    -- Create the new goal CFrame for the neck, combining both rotations
    local goal = YOffset * CFrame.Angles(verticalAngle, horizontalAngle, 0)
    
    -- Smoothly transition the neck's C0 towards the new goal
    Neck.C0 = Neck.C0:Lerp(goal, 0.1)
end

so basically what I did is:

  1. Direction Vector: Instead of creating a lookVector and manually applying inverse CFrames, we directly calculate the direction from the NPC’s position to the target’s position using workspace.target.Position - self.HumanoidRootPart.Position. The .unit makes sure we get a normalized direction vector.
  2. Angles: We now directly calculate the horizontal (yaw) and vertical (pitch) angles using the atan2 function for horizontal rotation and asin for vertical rotation based on the direction vector.
  3. CFrame.Angles: We combine both angles directly into the CFrame.Angles() function, applying the vertical and horizontal rotations in the correct order.

this will mostly do work because

  • Horizontal rotation is handled by the yaw angle, which controls the NPC’s left-right movement.
  • Vertical rotation is handled by the pitch angle, which allows the NPC to look up and down at the target.
  • The Lerp function ensures smooth interpolation between the current and target neck positions.
1 Like

The code you provided makes the head rotate sidewards(Roll axis) instead of Pitch when the head looks 90 degrees left or right. when the target is getting higher or lower
Capture

1 Like

we should avoid combining pitch and yaw directly in CFrame.Angles() because that can misalign the neck’s orientation when yaw approaches ±90°.

try this once again:

self = script.Parent
local Neck = self.Head:FindFirstChild("Neck", true)
local YOffset = Neck.C0

while task.wait() do
	local targetPos = workspace.target.Position
	local headPos = self.Head.Position

	-- Use CFrame.lookAt to create a rotation towards the target
	local lookCFrame = CFrame.lookAt(headPos, targetPos)

	-- Convert world space target CFrame into relative space of the HumanoidRootPart
	local relativeCFrame = self.HumanoidRootPart.CFrame:ToObjectSpace(lookCFrame)

	-- Apply the new relative rotation to the neck offset
	local goal = YOffset * CFrame.new(Vector3.new(0, 0, 0)) * relativeCFrame.Rotation

	-- Lerp smoothly for natural movement
	Neck.C0 = Neck.C0:Lerp(goal, 0.1)
end
1 Like

There is no way this is not AI generated

Does not work with animations. the animations affect the head rotation

try this:

local self = script.Parent
local neck = self:WaitForChild("Head"):FindFirstChild("Neck", true)
local humanoidRoot = self:WaitForChild("HumanoidRootPart")
local target = workspace:WaitForChild("target")

local defaultC0 = neck.C0

while task.wait() do
    local toTarget = (target.Position - neck.Part0.Position)
    local lookCFrame = CFrame.lookAt(Vector3.zero, neck.Part0.CFrame:VectorToObjectSpace(toTarget))
    
    -- Break apart into pitch (X) and yaw (Y), no roll
    local _, yaw, _ = lookCFrame:ToEulerAnglesYXZ()
    local pitch, _, _ = lookCFrame:ToEulerAnglesXYZ()

    local goalC0 = defaultC0 * CFrame.Angles(pitch, yaw, 0)
    neck.C0 = neck.C0:Lerp(goalC0, 0.1)
end

no bro i’m doing it, please dont judge my code :sob: :pray:

Does not work, the thing is i have an animation. This is my animation:
Capture

And i want the NPC head to look at the red part while it has animation but i get this result:
Capture2
The head should face the red line and not the black line, I want the head to be able to aim the part regardless of what animation does to the head

Can you help on that?. i cant seem to figure it out