Today I come to you seeking assistance. I’m trying to craft a system in which all players are shown to be looking in the direction of their camera. I’ve already got them to face the camera’s direction on the horizontal axis using the code below (thanks to LMH_Hutch). This is done every RenderStepped.
local cameraLook = cam.CFrame.LookVector
local rootCF = char.PrimaryPart.CFrame
local flatLook = Vector3.new(cameraLook.X, 0, cameraLook.Z)
local setCFrame = CFrame.new(rootCF.p, rootCF.p + flatLook)
char:SetPrimaryPartCFrame(setCFrame)
I’d like to have the head and arms facing where the camera is facing, with the uppertorso moving slightly as well, albeit less so than the arms and head. These parts only need to rotate up and down, since the character will already be rotating on the horizontal axis with the code above. I thought it may be best to have each player send values to ReplicatedStorage every so often. The clients would then change every other player’s Motor6Ds depending on what the player’s value is in ReplicatedStorage. I’ve been told this is a good method, however I’m unsure exactly what values to send and how to process them on the client’s side.
What do I send? Camera Focus? LookVector or something? And how do I process that value into affecting the head/arms to directly face that value and the uppertorso moving slightly up and down as well? For example, in my ideal outcome if the camera is facing 35 degrees up, the player’s head and arms would face 35 degrees up, and the uppertorso might face only 5 to 10 degrees up.
For this I would find the angle of elevation or depression between the current lookvector and the horizontal XZ plane.
Using this angle I would be able to transform the angle to my liking using maths or math.clamp in the post below to restrict the movement of the turret.
Then I transformed this angle into the new desired lookvector to use for the CFrame operation to move the motor6ds joint in the new limited direction.
However I think I messed up somewhere with the trigonometry in the post below converting the angle back into a vector.
Although I fixed it later on but it’s still jittery and odd in my own personal place file especially for the looking down scenario by changing the z value.
Personally now I would use CFrame.fromAxisAngle to obtain the new lookvector from the flat look vector as it’s easier to rotate around an axis since we already found the angle.
For the client replication I suggest sending the lookvector as using that Value I believe you can get all the rotations you need.
I personally like this procedural IK resource for it’s replication method and more as it uses the automatically replicated physics velocity value on each client to render the animation plus it checks if the other player is on screen to start doing the replication.
Unfortunately the camera look vector doesn’t replicate the same way so yeah remote events will be needed here.
Ah, thank you so much! This works perfectly. I only had to adjust one small part, shown below. If I don’t use neckOrigin (neck.CFrame.C0) then the head continually spins lol
One final question however, what method would you recommend to blend the CFrames together seamlessly? I’ve heard a player can only send out messages at 20Hz which is only 20 frames a second I believe, so there’s some ‘jitteriness’ to the movements unless I’m able to blend somehow between the previous rotation and the current one.
Oh ya if you repeat the code I showed you it’d just keep spinning haha
If you only care about how this looks on the client, then just running your line of code in a RenderStepped or Heartbeat should make it look pretty smooth. But it sounds like you need this motion to be visible to other player’s as well if I’m interpreting what you’re saying correctly. There are a couple different methods I can think of to accomplish this.
You could record the camera angles/neck positions on a 1 second delay and then transmit that info once per second to the server. This means technically all of the neck movements will be 1 second behind for all of the observers, but depending on what exactly your game is trying to do it shouldn’t be too big of a deal. The code might look something like this:
--The client of the player who is controlling the camera
local savedAngles = {}
local anglesPerPacket = 60
RunService.RenderStepped:Connect(function(dt)
local camAngle = math.asin(cameraLook.Y, 1)
--weld code here
--now cache this camera state
table.insert(savedAngles, camAngle)
--check if it's time to tell server
if #savedAngles >= anglesPerPacket then
RemoteEvent:FireServer(savedAngles)--tell server
savedAngles = {}--reset cache
end
end)
Then you connect that RemoteEvent on the server and have it pass those savedAngles to all of the other clients, and add something like this code on their clients:
--Client of observers
local camAngles = {}
RemoteEvent.OnClientEvent:Connect(function(savedAngles)
--add the 60 cam angles from the other player
for n,v in pairs(savedAngles) do
table.insert(camAngles, v)
end
end)
RunService.RenderStepped:Connect(function(dt)
local camAngle = camAngles[1]
if not camAngle then
--not loaded yet
return
end
--handle weld code using the camAngle
--remove the cam angle we just used
table.remove(camAngles, 1)
end)
There might be bugs but that’s the jist of what I’m thinking. I also mighta totally misinterpreted what you were saying so lmk if this helps at all!
I apologize for being unclear, my goal is to get this visible to all other players. So I have the camAngle variable sent to a ReplicatedStorage value then every 1/20Hz I have each client read them off and modify the player’s neck. What would you recommend doing to blend between the current position of the neck and the goal position in ReplicatedStorage?
Lerping is the way that most people make movements smooth.
Make sure you lerp via a clamped DT because if it is unclamped players with low FPS will see heads snap too far over.
CurrentCFrame:lerp(NextCFrame,math.clamp(DT*Speed,0,1))
Here is an implementation which immediately moves character to the position. (Note I set Y axis to 0 otherwise your player would tilt which would be useful for something like a helicopter simulator where tilt is useful). I use this if using events. You can lerp but typically youd want to lerp on client only which is why I store the new value in self.newCharacterCFrame for lerping animations after the fact.
function Ninja:FaceCharacterTowardsCamera()
if not self.camera or not self.character or not self.character:FindFirstChild("HumanoidRootPart")
then return end
local cameraCF = self.camera.CFrame
if not cameraCF then return end
-- Create flattened XZ axis version of the camera CFrame, ignoring vertical movement (Y-axis)
local flatCameraCF = CFrame.new(cameraCF.p, cameraCF.p + Vector3.new(cameraCF.LookVector.X, 0, cameraCF.LookVector.Z))
-- Calculate the new CFrame for the character's root part facing the direction of the flat camera CFrame
local newCharacterCFrame = CFrame.new(self.character.HumanoidRootPart.Position, self.character.HumanoidRootPart.Position + flatCameraCF.LookVector)
self.character.HumanoidRootPart.CFrame = newCharacterCFrame
end