Head and torso movement in relation to camera movement

Hello! Hope this message finds you well.

I am in the process of creating a Western Australian roleplay game, and scripting wise, I am having some difficulty. I am attempting to implement a system where the players body and head moves in regard to the turning of the players camera. Currently, this works perfectly, however it is not server sided. I have tried many avenues of ChatGPT, looking through forums, and believe me, this is my last resort option.

If someone could help me make this system work server sided, and possibly in addition have it disable in certain seats, that would be lovely. This is entirely optional about the seats, but if you have the time, it would be greatly appreciated. Reason being, I have a vehicle with a car driving animation, and when I enter the vehicle, it remembers the last camera angle they had it on before they entered, and locks the character into that position until they get out. In other seats, it works fine, just the driver seat - that could be from many things of course, but yea - would be nice.

Here is what I am trying to achieve to be server sided:

I have attached the current script below. It is a local script located under StarterPlayer → StarterCharacterScripts

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local Workspace = game.Workspace

local function onCharacterAdded(character)
	local head = character:WaitForChild("Head")
	local humanoid = character:WaitForChild("Humanoid")
	local humanoidRootPart = character:WaitForChild("HumanoidRootPart")
	local rigType = humanoid.RigType.Value == 0
	local torso = rigType and character:WaitForChild("Torso") or character:WaitForChild("UpperTorso")
	local neck = rigType and torso:WaitForChild("Neck") or head:WaitForChild("Neck")
	local waist = not rigType and torso:WaitForChild("Waist")
	neck.MaxVelocity = 0.2
	local currentCamera = Workspace.CurrentCamera
	local u2 = false
	local C0 = neck.C0
	local CFrame_Angles = CFrame.Angles
	local math_asin = math.asin
	local u6 = 0.5  -- Reduced intensity
	local u7 = not rigType and waist.C0
	local u8 = 0.3  -- Reduced intensity
	local math_atan = math.atan

	RunService.RenderStepped:Connect(function()
		local coordinateFrame = currentCamera.CoordinateFrame
		if (rigType and character.Torso or character.UpperTorso) ~= nil and character.Head ~= nil then
			local lookVector = torso.CFrame.lookVector
			local p = head.CFrame.p
			if (not (not rigType) and not (not neck) or neck and waist) and (currentCamera.CameraSubject:IsDescendantOf(character)) then
				if not u2 then
					local v15 = nil
					local v16 = nil
					v16 = (head.CFrame.p - coordinateFrame.p).magnitude
					v15 = head.CFrame.Y - coordinateFrame.Y
					if not rigType then
						neck.C0 = neck.C0:lerp(C0 * CFrame_Angles(math_asin(v15 / v16) * 0.4, -(p - coordinateFrame.p).Unit:Cross(lookVector).Y * u6, 0), 0.1)
						waist.C0 = waist.C0:lerp(u7 * CFrame_Angles(math_asin(v15 / v16) * 0.2, -(p - coordinateFrame.p).Unit:Cross(lookVector).Y * u8, 0), 0.1)
					else
						neck.C0 = neck.C0:lerp(C0 * CFrame_Angles(-(math_asin(v15 / v16) * 0.4), 0, -(p - coordinateFrame.p).Unit:Cross(lookVector).Y * u6), 0.1)
					end
				end
			end
		end
		humanoid.AutoRotate = true
	end)
end

Players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(onCharacterAdded)
end)

for _, player in Players:GetPlayers() do
	if player.Character then
		onCharacterAdded(player.Character)
	end
end

For this you usually do something like:

local updateTime = 0.5 -- in seconds
local lastSignal = tick()
local remoteEvent = game.ReplicatedStorage["<RemoteEventName>"] -- this remote event is used so that you can send the data to the server.
-- your runservice loop
	if tick()-lastSignal >= updateTime then
		remoteEvent:FireServer(Neck,NeckCFrame,Waist,WaistCFrame)
	end
end) -- end of the run service loop

remoteEvent.OnServerEvent:Connect(function(Neck,NeckCFrame,Waist,WaistCFrame)
	Neck.C0 = NeckCFrame
	Waist.C0 = WaistCFrame
end)

Thanks for the reply! apologies on my end for the delay it took for me to respond. I am not too sure on what you mean by this. Would I put this in server script service? add it into the local script? - I’m not too advanced in scripting so I am not quite sure. I appreciate the help!

You put it in the local script. But don’t just paste it randomly. You need to follow the order I used. The top variables should be above the runservice function and the code after the comment “your runservice loop” should go in the run service loop area, mainly at the end. After that you can put the other function anywhere you like.

Make sure you add a server script which receives the remote events and sends them back to all players except the one who fired the remote event. Something like:

local remoteEvent = game.ReplicatedStorage["<RemoteEventName>"]

remoteEvent.OnServerEvent:Connect(function(player,Neck,NeckCF,Waist,WaistCF)
	for _,plr in game.Players:GetPlayers()
		if plr ~= player then
			remoteEvent:FireClient(plr,Neck,NeckCF,Waist,WaistCF)
		end
	end)
end)

Also, here is the fixed code, I made one small bug.

local updateTime = 0.5 -- in seconds
local lastSignal = tick()
local remoteEvent = game.ReplicatedStorage["<RemoteEventName>"] -- this remote event is used so that you can send the data to the server.
-- your runservice loop
	if tick()-lastSignal >= updateTime then
		remoteEvent:FireServer(Neck,NeckCFrame,Waist,WaistCFrame)
	end
end) -- end of the run service loop

remoteEvent.OnClientEvent:Connect(function(Neck,NeckCFrame,Waist,WaistCFrame)
	Neck.C0 = NeckCFrame
	Waist.C0 = WaistCFrame
end)

I’ve done my best, but this is the most confusing thing ever to me. I’ve attempted to place it in the right areas, until I got to this, which is the only time it hasn’t displayed an error within the script (with the red underline).

Here’s my adjustments to the local script, which I’ve found to break the whole thing.

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local Workspace = game.Workspace

local function onCharacterAdded(character)
	local head = character:WaitForChild("Head")
	local humanoid = character:WaitForChild("Humanoid")
	local humanoidRootPart = character:WaitForChild("HumanoidRootPart")
	local rigType = humanoid.RigType.Value == 0
	local torso = rigType and character:WaitForChild("Torso") or character:WaitForChild("UpperTorso")
	local neck = rigType and torso:WaitForChild("Neck") or head:WaitForChild("Neck")
	local waist = not rigType and torso:WaitForChild("Waist")
	neck.MaxVelocity = 0.2
	local currentCamera = Workspace.CurrentCamera
	local u2 = false
	local C0 = neck.C0
	local CFrame_Angles = CFrame.Angles
	local math_asin = math.asin
	local u6 = 0.5  -- Reduced intensity
	local u7 = not rigType and waist.C0
	local u8 = 0.3  -- Reduced intensity
	local math_atan = math.atan

	local updateTime = 0.5 -- in seconds
	local lastSignal = tick()
	local remoteEvent = game.ReplicatedStorage["BodyHeadMovement"]
	RunService.RenderStepped:Connect(function()
		remoteEvent.OnClientEvent:Connect(function(Neck,NeckCFrame,Waist,WaistCFrame)
			Neck.C0 = NeckCFrame
			Waist.C0 = WaistCFrame
			if tick()-lastSignal >= updateTime then
				remoteEvent:FireServer(Neck,NeckCFrame,Waist,WaistCFrame)
			end
		end)
		end)
		local coordinateFrame = currentCamera.CoordinateFrame
		if (rigType and character.Torso or character.UpperTorso) ~= nil and character.Head ~= nil then
			local lookVector = torso.CFrame.lookVector
			local p = head.CFrame.p
			if (not (not rigType) and not (not neck) or neck and waist) and (currentCamera.CameraSubject:IsDescendantOf(character)) then
				if not u2 then
					local v15 = nil
					local v16 = nil
					v16 = (head.CFrame.p - coordinateFrame.p).magnitude
					v15 = head.CFrame.Y - coordinateFrame.Y
					if not rigType then
						neck.C0 = neck.C0:lerp(C0 * CFrame_Angles(math_asin(v15 / v16) * 0.4, -(p - coordinateFrame.p).Unit:Cross(lookVector).Y * u6, 0), 0.1)
						waist.C0 = waist.C0:lerp(u7 * CFrame_Angles(math_asin(v15 / v16) * 0.2, -(p - coordinateFrame.p).Unit:Cross(lookVector).Y * u8, 0), 0.1)
					else
						neck.C0 = neck.C0:lerp(C0 * CFrame_Angles(-(math_asin(v15 / v16) * 0.4), 0, -(p - coordinateFrame.p).Unit:Cross(lookVector).Y * u6), 0.1)
						
					end
				end
			end
		end
		humanoid.AutoRotate = true
	end


Players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(onCharacterAdded)
end)

for _, player in Players:GetPlayers() do
	if player.Character then
		onCharacterAdded(player.Character)
	end
end

Oh god.
I feel like you didn’t listen at all to " astrayed_girlawry_y"

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local Workspace = game.Workspace
local updateTime = 0.5 -- in seconds
local lastSignal = tick()
local remoteEvent = game.ReplicatedStorage["BodyHeadMovement"]

local function onCharacterAdded(character)
	local head = character:WaitForChild("Head")
	local humanoid = character:WaitForChild("Humanoid")
	local humanoidRootPart = character:WaitForChild("HumanoidRootPart")
	local rigType = humanoid.RigType.Value == 0
	local torso = rigType and character:WaitForChild("Torso") or character:WaitForChild("UpperTorso")
	local neck = rigType and torso:WaitForChild("Neck") or head:WaitForChild("Neck")
	local waist = not rigType and torso:WaitForChild("Waist")
	neck.MaxVelocity = 0.2
	local currentCamera = Workspace.CurrentCamera
	local u2 = false
	local C0 = neck.C0
	local CFrame_Angles = CFrame.Angles
	local math_asin = math.asin
	local u6 = 0.5  -- Reduced intensity
	local u7 = not rigType and waist.C0
	local u8 = 0.3  -- Reduced intensity
	local math_atan = math.atan

	RunService.RenderStepped:Connect(function()
		local coordinateFrame = currentCamera.CoordinateFrame
		if (rigType and character.Torso or character.UpperTorso) ~= nil and character.Head ~= nil then
			local lookVector = torso.CFrame.lookVector
			local p = head.CFrame.p
			if (not (not rigType) and not (not neck) or neck and waist) and (currentCamera.CameraSubject:IsDescendantOf(character)) then
				if not u2 then
					local v15 = nil
					local v16 = nil
					v16 = (head.CFrame.p - coordinateFrame.p).magnitude
					v15 = head.CFrame.Y - coordinateFrame.Y
					if not rigType then
						neck.C0 = neck.C0:lerp(C0 * CFrame_Angles(math_asin(v15 / v16) * 0.4, -(p - coordinateFrame.p).Unit:Cross(lookVector).Y * u6, 0), 0.1)
						waist.C0 = waist.C0:lerp(u7 * CFrame_Angles(math_asin(v15 / v16) * 0.2, -(p - coordinateFrame.p).Unit:Cross(lookVector).Y * u8, 0), 0.1)
					else
						neck.C0 = neck.C0:lerp(C0 * CFrame_Angles(-(math_asin(v15 / v16) * 0.4), 0, -(p - coordinateFrame.p).Unit:Cross(lookVector).Y * u6), 0.1)
					end
				end
			end
			if tick()-lastSignal >= updateTime then
				lastSignal = tick()
				remoteEvent:FireServer(neck,neck.C0,waist,waist.C0)
			end
		end
		humanoid.AutoRotate = true
	end)
end
remoteEvent.OnClientEvent:Connect(function(Neck,NeckCFrame,Waist,WaistCFrame)
	Neck.C0 = NeckCFrame
	Waist.C0 = WaistCFrame
end)

Players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(onCharacterAdded)
end)

for _, player in Players:GetPlayers() do
	if player.Character then
		onCharacterAdded(player.Character)
	end
end

; /

Even this solution is wrong, since it both plays inside the localScript,

What do you mean? My code is just sending a signal to the server to update body part with this property every 0.5 seconds then the server sends the signal back to all clients except the one that fired the event to update the properties.

It will glitch out all bodies thats why.

To get this, you’ll need to use a Remote Event to communicate between the client and server. Here are some changes to your script (You’ll want to make a Remote Event in ReplicatedStorage, or you can let the Server Script handle it):

Server Script (ServerScriptService):

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local Workspace = game.Workspace
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local moveitmoveit = Instance.new("RemoteEvent")
moveitmoveit.Name = "moveitmoveit"
moveitmoveit.Parent = ReplicatedStorage

local function onCharacterAdded(character)
    local head = character:WaitForChild("Head")
    local humanoid = character:WaitForChild("Humanoid")
    local humanoidRootPart = character:WaitForChild("HumanoidRootPart")
    local rigType = humanoid.RigType.Value == 0
    local torso = rigType and character:WaitForChild("Torso") or character:WaitForChild("UpperTorso")
    local neck = rigType and torso:WaitForChild("Neck") or head:WaitForChild("Neck")
    local waist = not rigType and torso:WaitForChild("Waist")
    neck.MaxVelocity = 0.2

    local u2 = false
    local C0 = neck.C0
    local CFrame_Angles = CFrame.Angles
    local math_asin = math.asin
    local u6 = 0.5  -- Reduced intensity
    local u7 = not rigType and waist.C0
    local u8 = 0.3  -- Reduced intensity
    local math_atan = math.atan

    moveitmoveit.OnServerEvent:Connect(function(player, cameraCFrame)
        if player.Character == character then
            local coordinateFrame = cameraCFrame
            if (rigType and character.Torso or character.UpperTorso) ~= nil and character.Head ~= nil then
                local lookVector = torso.CFrame.lookVector
                local p = head.CFrame.p
                if (not (not rigType) and not (not neck) or neck and waist) then
                    if not u2 then
                        local v15 = nil
                        local v16 = nil
                        v16 = (head.CFrame.p - coordinateFrame.p).magnitude
                        v15 = head.CFrame.Y - coordinateFrame.Y
                        if not rigType then
                            neck.C0 = neck.C0:lerp(C0 * CFrame_Angles(math_asin(v15 / v16) * 0.4, -(p - coordinateFrame.p).Unit:Cross(lookVector).Y * u6, 0), 0.1)
                            waist.C0 = waist.C0:lerp(u7 * CFrame_Angles(math_asin(v15 / v16) * 0.2, -(p - coordinateFrame.p).Unit:Cross(lookVector).Y * u8, 0), 0.1)
                        else
                            neck.C0 = neck.C0:lerp(C0 * CFrame_Angles(-(math_asin(v15 / v16) * 0.4), 0, -(p - coordinateFrame.p).Unit:Cross(lookVector).Y * u6), 0.1)
                        end
                    end
                end
            end
            humanoid.AutoRotate = true
        end
    end)
end

Players.PlayerAdded:Connect(function(player)
    player.CharacterAdded:Connect(onCharacterAdded)
end)

for _, player in Players:GetPlayers() do
    if player.Character then
        onCharacterAdded(player.Character)
    end
end

Local Script (StarterPlayer → StarterCharacterScripts):

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local Workspace = game.Workspace
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local moveitmoveit = ReplicatedStorage:WaitForChild("moveitmoveit")

local function onCharacterAdded(character)
    local currentCamera = Workspace.CurrentCamera

    RunService.RenderStepped:Connect(function()
        if character:FindFirstChild("HumanoidSeat") then
            local seat = character.HumanoidSeat.SeatPart
            if seat and seat:IsA("VehicleSeat") then
                return -- Disable movement in driver's seat
            end
        end
        
        moveitmoveit:FireServer(currentCamera.CFrame)
    end)
end

Players.PlayerAdded:Connect(function(player)
    player.CharacterAdded:Connect(onCharacterAdded)
end)

for _, player in Players:GetPlayers() do
    if player.Character then
        onCharacterAdded(player.Character)
    end
end

This setup uses the moveitmoveit event to synchronize the head and torso movement between the client and server. It also checks if the player is in a driver’s seat and disables the movement if they are.

(apologies for the silly Remote Event name lol)

I apologize for the inconvenience. While the script is functioning as usual, I’ve tested it in a 2 player local server and it doesn’t seem to work server sided. I have made the server script, going by the code you’ve provided, which is:

local remoteEvent = game.ReplicatedStorage["BodyHeadMovement"]

remoteEvent.OnServerEvent:Connect(function(player,Neck,NeckCF,Waist,WaistCF)
	for _,plr in game.Players:GetPlayers()
		if plr ~= player then
			remoteEvent:FireClient(plr,Neck,NeckCF,Waist,WaistCF)
		end
end)
end)

And all the remote events are named correctly. There is an error within the server script, with the ‘if’ function. I have attached a screenshot below.

Whilst testing in the local server, I have not seen any error messages in the F9 panel present regarding these scripts - just does not work server sided.

Here’s some simple thing I would make,

everything from this is client sided.

local localPlayer = game.Players.LocalPlayer

local fakeneck
local fakeshoulder

function CreateFake(neck,rightShoulder)
	if fakeneck then
		if fakeneck.Parent==nil then
			fakeneck = nil
		end
	end
	if fakeshoulder then
		if fakeshoulder.Parent==nil then
			fakeshoulder = nil
		end
	end
	if not fakeneck then
		fakeneck = neck:Clone()
		fakeneck.Parent = neck.Parent
		neck.Enabled=false
	end
	if not fakeshoulder then
		fakeshoulder = rightShoulder:Clone()
		fakeshoulder.Parent = rightShoulder.Parent
		rightShoulder.Enabled=false
	end
end

function getMotorInitialC0(motor6D: Motor6D)
	local initialC0 = motor6D:GetAttribute('InitialC0')
	if not initialC0 then
		initialC0 = motor6D.C0
		motor6D:SetAttribute('InitialC0', initialC0)
	end

	return initialC0
end

game:GetService("RunService").RenderStepped:Connect(function(deltaTime)
	local character = localPlayer.Character
	local head: BasePart = character:FindFirstChild('Head') :: any
	local torso: BasePart = character:FindFirstChild('Torso') :: any
	local rootPart: BasePart = character:FindFirstChild('HumanoidRootPart') :: any
	if not torso or not head or not rootPart then
		return
	end

	local neck: Motor6D = torso:FindFirstChild('Neck') :: any
	local waist: Motor6D = rootPart:FindFirstChild('RootJoint') :: any
	local rightShoulder: Motor6D = torso:FindFirstChild('Right Shoulder') :: any
	local rightHip: Motor6D = torso:FindFirstChild('Right Hip') :: any
	local leftHip: Motor6D = torso:FindFirstChild('Left Hip') :: any
	if not neck or not waist or not rightShoulder or not rightHip or not leftHip then
		return
	end
	CreateFake(neck,rightShoulder)

	local directionFromOrigin
	local mouse = localPlayer:GetMouse()
	directionFromOrigin = (mouse.Hit.Position - mouse.Origin.Position).Unit


	local mouseDirection = torso.CFrame.Rotation:PointToObjectSpace(directionFromOrigin)
	fakeshoulder.Transform = CFrame.new()


	local targetShoulder: CFrame = CFrame.new(getMotorInitialC0(fakeshoulder).Position)
		* CFrame.Angles(0, -math.asin(mouseDirection.X), 0)
		* CFrame.Angles(math.asin(mouseDirection.Y), 0, 0)
		* CFrame.Angles(math.pi / 2, math.pi / 2, 0)


	local targetNeck: CFrame = CFrame.new(getMotorInitialC0(fakeneck).Position)
		* CFrame.Angles(0, -math.asin(mouseDirection.X), 0)
		* CFrame.Angles(math.asin(mouseDirection.Y) * 0.5, 0, 0)
		* CFrame.Angles(math.pi / 2, math.pi, 0)

	fakeshoulder.C0 = fakeshoulder.C0:Lerp(targetShoulder, 0.25 * deltaTime * 60)
	fakeneck.C0 = fakeneck.C0:Lerp(targetNeck, 0.25 * deltaTime * 60)


	local mouse = localPlayer:GetMouse()
	directionFromOrigin = (mouse.Hit.Position - mouse.Origin.Position).Unit

	REmoteEventHereOmg:Fire(neck,fakeneck.C0,rightShoulder,fakeshoulder.C0)
end)

– Now The Server Script

Instance.new("RemoteEvent").OnServerEvent:Connect(function(player,neck,neckc0,shoulder,shoulderc0)
		if neck:IsDescendantOf(player.Character) then
			neck.C0 = neckc0
		end
		if shoulder:IsDescendantOf(player.Character) then
			shoulder.C0 = shoulderc0
		end
	end)

What am I doing above?

I am creating a FakeNeck, so that the Server can update the original necks and shoulders, without interrupting.

R6 Character
image

Another solution for R15

image

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

local LocalPlayer = Players.LocalPlayer
local character = LocalPlayer.Character or LocalPlayer.CharacterAdded:Wait()
local head = character:WaitForChild("Head")
local torso = character:WaitForChild("UpperTorso") or character:WaitForChild("Torso")
local camera = workspace.CurrentCamera

local neck = torso:FindFirstChild("Neck")
local waist = torso:FindFirstChild("Waist")

RunService.RenderStepped:Connect(function()
	local cameraCFrame = camera.CFrame
	local cameraLookVector = cameraCFrame.LookVector

	if neck then
		neck.C0 = neck.C0:Lerp(CFrame.new(neck.C0.Position) * CFrame.Angles(0, -math.asin(cameraLookVector.X), 0), 0.1)
	end

	if waist then
		waist.C0 = waist.C0:Lerp(CFrame.new(waist.C0.Position) * CFrame.Angles(0, -math.asin(cameraLookVector.X), 0), 0.1)
	end
end)

But I also would always recommend to create FakeNeck and FakeShoulder on Client, disabling the Original,

On Server you update the Original Neck and Shoulder using the FakeNecks C0 and FakeShoulder C0 data sent from Client.

Pretty sure not because I use the same method in my code but I suppose it’s due to OP’s code.
I am not going to read through OP’s code completely because I just had to give them an idea of how it should work and it’s not my issue that OP can’t understand this.

True sorry about that

Hello! Thanks for this. The script you’ve provided me does work server sided. However, it is not what I am trying to go for exactly. I’ve attached the output of the scripts below.

https://gyazo.com/0532317ec6403565f2cd66b2320b1f12

With this script, it makes the character look in the opposite direction, and it also does not move the characters body as I intended it to like my original local script. Baby steps through! This is the closest I’ve gotten so far, and my gratitude is immense.

This is the original local script for reference on what I am trying to achieve:

Hey there! This might be getting somewhere, however it does not operate as intended. I’ve linked a video of the output of the code you’ve provided below.

https://gyazo.com/ab6bc1cc28f17b4e753769025f13d90d

Also note that I put this localscript inside StarterPlayer>CharacterScripts

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

local LocalPlayer = Players.LocalPlayer
local character = LocalPlayer.Character or LocalPlayer.CharacterAdded:Wait()
local head = character:WaitForChild("Head")
local torso = character:WaitForChild("UpperTorso") or character:WaitForChild("Torso")
local camera = workspace.CurrentCamera

local neck = torso:FindFirstChild("Neck")
local waist = torso:FindFirstChild("Waist")

RunService.RenderStepped:Connect(function()
	local cameraCFrame = camera.CFrame
	local cameraLookVector = cameraCFrame.LookVector

	if neck then
		neck.C0 = neck.C0:Lerp(CFrame.new(neck.C0.Position) * CFrame.Angles(0, -math.asin(cameraLookVector.X), 0), 0.1)
	end

	if waist then
		waist.C0 = waist.C0:Lerp(CFrame.new(waist.C0.Position) * CFrame.Angles(math.asin(cameraLookVector.Y), -math.asin(cameraLookVector.X), 0), 0.1)
	end
end)

If you think that the character is moving to far up and down, you can use math.clamp()

This is definitely going places, however it still does not replicate what I am going for - also there is an issue with the server side of this script. I’ve attached another video below of what the script does.

https://gyazo.com/1df1ce6394e188c0062f4d3baf3d3ed8