(URGENT!) How do I fix hands lagging behind in VR?

I have this issue in my VR game where the hands lag behind when I move.

I’m using the camera cframe times the UserCFrame of the hands

Am I doing something wrong?

8 Likes

You could create the hands on the server, but give network ownership to the Player, and on the Client use AlignPosition and AlignOrientation to move them around.

4 Likes

Are you using while loops that use wait()? If not, use some tweening function like Lerp to smooth it out.

2 Likes

It uses UserInputService for the UserCFrame. I learned about while loops a long time ago.

1 Like

RenderStepped could be use instead of UserCFrame:Connect() (I think).

RunService.RenderStepped:Connect(function()
    Hand.CFrame = UserCFrame
end)

If you don’t want to, it’s best to smooth out the lag.

2 Likes

To add to what everyone else has been saying, it is very important that you use the RunService rather than a while loop to get and update this data, as any frame missed will cause this ‘lag’ in your tracking.

I wrote these two simple scripts that acts as a great starting point based off my own VR code for updating VR data across the server.

The idea is that the client gets all the VR CFrames, aligns them in world-space, then tells the server these CFrames, so that the server can update the CFrames.

Here’s the local script, it simply gets the UserCFrames for the VR positions, calculates the world-space based off the CurrentCamera, positions the parts on the client, then sends those CFrames to the server to replicate. In theory, the client should update fast enough, that the server trying to reposition a part won’t do anything, but if it is a problem, you can just have the server send the data to the clients, then each individual client updates the user’s cframes. This approach would limit the server visibility (for things such as physics interactions) and could waste bandwidth, but would ensure there is 0 ‘lag’ on the client. (I wouldn’t recommend that approach as it would be more of a last scenario)

local RunService = game:GetService('RunService')
local VRService = game:GetService('VRService')

-- Change this to the RemoteEvent that updates the server
local RemoteEventToUpdate = nil

-- Change these three to your parts
local headPart = nil
local leftHandPart = nil
local rightHandPart = nil

-- Every Render Frame, run this code
RunService.RenderStepped:Connect(function()
    if RunService:IsServer() then
        warn("You cannot get User CFrames from the Server! Use remote events to update data.")
        return
    end
    -- HeadScale is used to align the hands in world-space while accounting for avatar size
    local HeadScale = workspace.CurrentCamera.HeadScale
    -- The HMDs CFrame
    local cfHMD = VRService:GetUserCFrame(Enum.UserCFrame.Head)
    -- The Left Hand's CFrame
    local cfLH = VRService:GetUserCFrame(Enum.UserCFrame.LeftHand)
    -- The Right Hand's CFrame
    local cfRH = VRService:GetUserCFrame(Enum.UserCFrame.RightHand)
    -- Now, align them all in world-space
    local Head = (workspace.CurrentCamera.CFrame*CFrame.new(cfHMD.p*HeadScale))*CFrame.fromEulerAnglesXYZ(cfHMD:ToEulerAnglesXYZ())
    local LeftHand = (workspace.CurrentCamera.CFrame*CFrame.new(cfLH.p*HeadScale))*CFrame.fromEulerAnglesXYZ(cfLH:ToEulerAnglesXYZ()) * CFrame.Angles(math.rad(90), 0, 0)
    local RightHand = (workspace.CurrentCamera.CFrame*CFrame.new(cfRH.p*HeadScale))*CFrame.fromEulerAnglesXYZ(cfRH:ToEulerAnglesXYZ()) * CFrame.Angles(math.rad(90), 0, 0)
    -- Then, you can set your target parts to the CFrames
    headPart:PivotTo(Head)
    leftHandPart:PivotTo(LeftHand)
    rightHandPart:PivotTo(RightHand)
    -- Then to replicate, fire a remote with the part CFrame across the server
    -- It is very important to note that firing a remote in a while loop can cause bandwidth issues, as RemoteEvents are optimized better for RenderSteps
    RemoteEventToUpdate:FireServer({
        ["Head"] = Head,
        ["LeftHand"] = LeftHand,
        ["RightHand"] = RightHand
    })
end)

Now, for the server script. All it does is it listens for a client to fire the specified RemoteEvent, then gets those parts to update from the server, and updates the parts CFrames to the ones that were provided from the client. You will need to change the GetPlayerParts(player) function as it is specific to how your game functions. It probably is possible to pipe the part directly from the client, but that would probably use more bandwidth (serializing the part and data for the new CFrames), and I believe it’s better to give the server full authority over which parts should be updated.

-- Change this to the RemoteEvent that updates the server
local RemoteEventToUpdate = nil

local function GetPlayerParts(player)
    -- TODO: Get the Head, LeftHand, and RightHand part from the player
    return {
        ["HeadPart"] = nil,
        ["LeftHandPart"] = nil,
        ["RightHandPart"] = nil
    }
end

RemoteEventToUpdate.OnServerEvent:Connect(function(player, data)
    -- If any of the data is nil, don't do anything
    if data["Head"] == nil or data["LeftHand"] == nil or data["RightHand"] == nil then return end
    -- Get all the player's parts
    local parts = GetPlayerParts(player)
    -- If any of the parts are nil, don't do anything
    if parts["HeadPart"] == nil or parts["LeftHandPart"] == nil or parts["RightHandPart"] == nil then return end
    -- Update the Head
    parts["HeadPart"]:PivotTo(data["Head"])
    -- Update the LeftHand
    parts["LeftHandPart"]:PivotTo(data["LeftHand"])
    -- Update the RightHand
    parts["RightHandPart"]:PivotTo(data["RightHand"])
end)

Before I end this off, I’d also like to point out that if you absolutely without a doubt, for some odd reason NEED to use a wait() or while loop in the body that updates your CFrames, I would look into coroutines, as they provide a way to execute a task on another thread in the same script.

There are lots of comments left in the code, so everything should be pretty straightforward, but let me know if you need any clarifications. Apologies in advance for any missing services or typos, haha. Good luck! :cat:

2 Likes

Wouldn’t this error anyway? You can’t use RenderStepped on server, only Heartbeat.

1 Like

Apologies, my reference code was built around modules, pretty much just for visibility haha.

I didn’t use while loops…

RenderStepped did work though.

To add as to why it worked, is RenderStepped occurs on the render thread, using different hooks will cause visual lag.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.