What do you want to achieve?
I want to create a script that rotates the head of an NPC on the Y-axis so that it faces the direction of the player head.
What is the issue?
Currently, I am having issues getting my head to face correctly. I have tried using this post discussing how to do this, and I have adapted the solution in this snippet of my script:
local YOffset = neck.C0.Y
while wait() do
if isWithinRadius(player) then
CameraDirection = (CFrame.new(NPC_HRP.Position, hrp.Position):inverse() * NPC_HRP.CFrame ).lookVector * -1
local neckCFrame = neck.C0
local Goal = CFrame.new(0, YOffset, 0) * CFrame.Angles(0, -math.asin(CameraDirection.x), 0) * CFrame.Angles(math.asin(CameraDirection.), 0, 0)
local tweenInfo = TweenInfo.new(.2,Enum.EasingStyle.Quad,Enum.EasingDirection.Out)
local Tween = TweenService:Create(neck, tweenInfo, {C0 = Goal})
Tween:Play()
end
end
but I still encounter this issue:
Iāve tried lots of stuff, but as I donāt understand CFrame that well itās difficult to understand whatās happening and fix the issue through fiddling around. Could someone let me know what I am doing wrong here?
Sorry, disregard what I just said.
I have changed my code to this, according to what you said:
while wait() do
if isWithinRadius(player) then
local _, directiony, _ = CFrame.lookAt(neck.C0.Position, hrp.Position):ToOrientation()
local Goal = CFrame.new(neck.C0.Position) * CFrame.Angles(math.rad(90), directiony, 0)
local Tween = TweenService:Create(neck, tweenInfo, {C0 = Goal})
Tween:Play()
else
local Goal = default
local Tween = TweenService:Create(neck, tweenInfo, {C0 = Goal})
Tween:Play()
end
end
I changed the first argument of CFrame.Angles because otherwise the characterās head would just point downwards.
Now I am encountering an issue where the neck.C0 will rotate on the X axis, even though the directionY is in the Y spot in CFrame.Angles?
Iām pretty sure you can just use CFrame.lookAt, which might not result in what you want but at the moment itās probably the best solution I can think of.
while wait() do
if otherhead.Parent:FindFirstChild("Humanoid").FloorMaterial ~= Enum.Material.Air or otherhead.Parent:FindFirstChild("Humanoid").FloorMaterial ~= Enum.Material.Plastic then -- prevents NPC from breaking or floating when you jump or stand on it.
NPChead.CFrame = CFrame.lookAt(NPChead.Position, otherhead.Position) -- NPChead being the NPC's head, and otherhead being the players head.
end
end
The npcās body moves with the head and Iām aware of that, I just donāt have the brain power to think right now. So hopefully this will help you. This script can just give you an idea of what you could do.
NOTE: You canāt stand on Plastic because if you were to stand on the NPC it starts floating. So the NPC cant look at you while your standing on plastic unless you can find a workaround.
And yes I just realized, CFrame.lookAt is already in the code, but this is a more sophisticated version. Maybe when I can think better I could make a better version of this.
Okay cool, Iāll see what I can do with that. Iām not at my desktop right now but once I experiment Iāll let you know if I can do anything. Since itās a non-moving R6 NPC, I could just make the head invisible and use another rig, with only the head visible and run the .lookat script on that Alternate rig. Then Iād just have to use :Dot() to decide what angles the NPC can look at you.
Okay, update: I think Iāve made a pretty decent script. I am gonna share it here for any poor soul whoās crawling through the devforum looking for an easy solution for this, but please suggest improvements because I donāt really know what Iām doing.
At first I just used a localscript to manually change the head of a single NPC, which gave me this:
But in order to have the same effect with more NPCs, I needed to do this:
My solution uses collection service to go through every instance with the tag āNPCā and then initialise the script for that NPC
Modulescript that goes in ReplicatedStorage (or wherever else youād like, of course):
-- this has been edited according to the post below
local TweenService = game:GetService("TweenService")
local NPCBehavior = {}
local tweenInfo = TweenInfo.new(0.25, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
function NPCBehavior.startup(npc, player)
local head = npc:WaitForChild("Head")
local defaultCFrame = head.CFrame
npc:FindFirstChild("Neck", true):Destroy()
local function isWithinRadius()
local distance = (player.Character.HumanoidRootPart.Position - npc.HumanoidRootPart.Position).Magnitude
return distance <= 15
end
local function isWithinNPCFOV()
local npcToCharacter = (player.Character.Head.Position - defaultCFrame.Position).Unit
local npcLookVector = defaultCFrame.LookVector
local dotProduct = npcToCharacter:Dot(npcLookVector)
return dotProduct >= 0.55 --about 80 degrees, check the image i attached for my reference
end
local function updateHeadOrientation()
if isWithinRadius() and isWithinNPCFOV() then
local goalCFrame = CFrame.lookAt(head.Position, player.Character.Head.Position)
local tween = TweenService:Create(head, tweenInfo, {CFrame = goalCFrame})
tween:Play()
else
local tween = TweenService:Create(head, tweenInfo, {CFrame = defaultCFrame})
tween:Play()
end
end
local runService = game:GetService("RunService")
runService.Heartbeat:Connect(updateHeadOrientation)
end
return NPCBehavior
A local script:
local CollectionService = game:GetService("CollectionService")
local Players = game:GetService("Players")
local NPCBehavior = require(game.ReplicatedStorage:WaitForChild("NPCBehaviour"))
local player = Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
for _, npc in ipairs(CollectionService:GetTagged("NPC")) do
NPCBehavior.startup(npc, player)
end
This gets us this result:
Although it doesnāt support adding new NPCs, thatās very easily done using CollectionService:GetInstanceAddedSignal(āNPCā):Connect(function())
Also I just want to note that I believe I have found the next easiest solution to the issue, which is to just destroy the neck joint and use the lookat function with the head, as the neck joint is the reason why adjusting the orientation of the head adjusts the orientation of the rest of the body, unless you needed the joint for some reason. So all the code I made is pretty pointless and the code can be redone to just this:
--npcbehaviour module redone
local TweenService = game:GetService("TweenService")
local NPCBehavior = {}
local tweenInfo = TweenInfo.new(0.25, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
function NPCBehavior.startup(npc, player)
local head = npc:WaitForChild("Head")
local defaultCFrame = head.CFrame
npc:FindFirstChild("Neck", true):Destroy()
local function isWithinRadius()
local distance = (player.Character.HumanoidRootPart.Position - npc.HumanoidRootPart.Position).Magnitude
return distance <= 15
end
local function isWithinNPCFOV()
local npcToCharacter = (player.Character.Head.Position - defaultCFrame.Position).Unit
local npcLookVector = defaultCFrame.LookVector
local dotProduct = npcToCharacter:Dot(npcLookVector)
return dotProduct >= 0.55 --about 80 degrees, check the image i attached for my reference
end
local function updateHeadOrientation()
if isWithinRadius() and isWithinNPCFOV() then
local goalCFrame = CFrame.lookAt(head.Position, player.Character.Head.Position)
local tween = TweenService:Create(head, tweenInfo, {CFrame = goalCFrame})
tween:Play()
else
local tween = TweenService:Create(head, tweenInfo, {CFrame = defaultCFrame})
tween:Play()
end
end
local runService = game:GetService("RunService")
runService.Heartbeat:Connect(updateHeadOrientation)
end
return NPCBehavior