Making a Player Face the Camera

Howdy developers,

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.

I appreciate any and all input!

6 Likes

You can get the camera angle using:

local camAngle = math.asin(cameraLook.Y, 1)

then apply that to the neck weld by doing something like this:

local neckWeld = char:WaitForChild("Head"):WaitForChild("Neck")
neckWeld.C0 = neckWeld.C0 * CFrame.Angles(camAngle, 0, 0)

You can use the same camera angle and the same method to alter the other welds however you’d like.

7 Likes

Hmm, I have some suggestions

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.

2 Likes

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

neckWeld.C0 = neckOrigin * CFrame.Angles(camAngle, 0, 0)

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.

2 Likes

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!

1 Like

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?

2 Likes

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))

2 Likes