You can write your topic however you want, but you need to answer these questions:
- What do you want to achieve? Keep it simple and clear!
- I want to achieve smoothness between Client and Server with a UnreliableRemoteEvent, but the Server-sided model is not replicating the CFrame as fast as the Client, causing jitteriness.
- What is the issue? Include screenshots / videos if possible!
- To clarify, the selected models are the server-sided models.
- What solutions have you tried so far? Did you look for solutions on the Developer Hub?
- SetNetworkOwner, BindToRenderStep and many more, none worked and no solutions on the Hub to help me out.
A fraction of the module’s code:
local AdvancedVRModule = {}
-- Services
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local player = Players.LocalPlayer
local ReplicateCFrames = game.ReplicatedStorage.NoVR.Events.ReplicateCFrames
local ReplicateInstances = game.ReplicatedStorage.NoVR.Events.ReplicateInstances
local ReplicateInstancesAsProps = game.ReplicatedStorage.NoVR.Events.ReplicateInstancesAsProps
local hands = {
Left = {model = nil, offset = CFrame.new(-config.handSpacing, 0, 0), scrollDistance = 0, heldObject = nil, gripState = "Open", savedRotation = Vector3.new(0, 0, 0)},
Right = {model = nil, offset = CFrame.new(config.handSpacing, 0, 0), scrollDistance = 0, heldObject = nil, gripState = "Open", savedRotation = Vector3.new(0, 0, 0)}
}
local currentHand = hands.Left
local isRightMouseHeld = false
local cameraOffset = config.initialOffset
-- UI setup for modal button control
local screenGui = Instance.new("ScreenGui")
screenGui.Parent = player:WaitForChild("PlayerGui")
local modalButton = Instance.new("TextButton")
modalButton.Size = UDim2.new(0, 0, 0, 0)
modalButton.BackgroundTransparency = 1
modalButton.Modal = true
modalButton.Parent = screenGui
-- Hand creation and positioning functions
function AdvancedVRModule.createHands()
local leftHand = script.Assets.LeftHand:Clone()
local rightHand = script.Assets.RightHand:Clone()
leftHand.Parent = workspace.CurrentCamera
rightHand.Parent = workspace.CurrentCamera
ReplicateInstances:FireServer("LeftHand", script.Assets.LeftHand, {Name = "LeftHand" .. "."})
ReplicateInstances:FireServer("RightHand", script.Assets.RightHand, {Name = "RightHand" .. "."})
hands.Left.model = leftHand
hands.Right.model = rightHand
-- Apply saved rotation and scroll distance
leftHand:PivotTo(leftHand.PrimaryPart.CFrame * CFrame.Angles(math.rad(hands.Left.savedRotation.X), math.rad(hands.Left.savedRotation.Y), math.rad(hands.Left.savedRotation.Z)))
rightHand:PivotTo(rightHand.PrimaryPart.CFrame * CFrame.Angles(math.rad(hands.Right.savedRotation.X), math.rad(hands.Right.savedRotation.Y), math.rad(hands.Right.savedRotation.Z)))
hands.Left.model:PivotTo(hands.Left.model.PrimaryPart.CFrame * CFrame.new(0, 0, -hands.Left.scrollDistance))
hands.Right.model:PivotTo(hands.Right.model.PrimaryPart.CFrame * CFrame.new(0, 0, -hands.Right.scrollDistance))
end
-- Update and control functions
local function updateHandPointingDirection()
local mouse = player:GetMouse()
if mouse and mouse.Hit then
local targetPosition = mouse.Hit.Position
local handPosition = currentHand.model.PrimaryPart.Position
local targetCFrame = CFrame.lookAt(handPosition, targetPosition)
local smoothedCFrame = currentHand.model:GetPivot():Lerp(targetCFrame, config.smoothness)
ReplicateCFrames:FireServer(script[currentHand.model.Name].Value, smoothedCFrame)
currentHand.model:PivotTo(smoothedCFrame)
end
end
local function positionHandsInFrontOfCamera()
local cameraCFrame = workspace.CurrentCamera.CFrame * cameraOffset
local function set(hand)
local handOffset = hand.offset
if hand == currentHand then
handOffset = hand.offset * CFrame.new(0, 0, -hand.scrollDistance)
end
local targetCFrame = cameraCFrame * handOffset
local smoothedCFrame = hand.model:GetPivot():Lerp(targetCFrame, config.smoothness)
ReplicateCFrames:FireServer(script[hand.model.Name].Value, smoothedCFrame)
hand.model:PivotTo(smoothedCFrame)
end
set(hands.Left)
set(hands.Right)
end
local function updateScrollMovement(scrollDelta)
local scrollAmount = scrollDelta * config.scrollSpeed
currentHand.scrollDistance = math.clamp(currentHand.scrollDistance + scrollAmount, 0, config.maxScrollDistance)
end
local function gripOrReleaseObject(state)
if state == "release" then
ReplicateInstancesAsProps:FireServer(nil, "release", {
rope = currentHand.rope,
heldObject = currentHand.heldObject,
name = currentHand.model.Name
})
currentHand.heldObject = nil
currentHand.gripState = "Open"
else
local handPosition = currentHand.model.PrimaryPart.Position
for _, object in pairs(workspace:GetDescendants()) do
if object:IsA("BasePart") and not object:HasTag("Ignore") and not object.Anchored and not object.Parent:FindFirstChildOfClass("Humanoid") and (object.Position - handPosition).Magnitude <= config.gripRange then
if object.Name ~= "Grip" and object:FindFirstChild("Grip") then
if object.Parent == workspace and currentHand.heldObject ~= object then
currentHand.heldObject = object
ReplicateInstancesAsProps:FireServer(object, "6D", {
rope = currentHand.rope,
heldObject = currentHand.heldObject,
name = currentHand.model.Name
})
currentHand.gripState = "Closed"
break
end
elseif object.Name ~= "Grip" and not object:FindFirstChild("Grip") then
if object.Parent == workspace and currentHand.heldObject ~= object then
currentHand.heldObject = object
ReplicateInstancesAsProps:FireServer(object, "Rope", {
rope = currentHand.rope,
heldObject = currentHand.heldObject,
name = currentHand.model.Name
})
currentHand.gripState = "Closed"
break
end
end
end
end
end
end
local function updateHandPositionWithMouse()
local delta = UserInputService:GetMouseDelta()
local move = Vector3.new(delta.X, -delta.Y, 0) * config.mouseMoveSpeed
local newOffset = currentHand.offset * CFrame.new(move)
local cameraCFrame = workspace.CurrentCamera.CFrame * cameraOffset
local handPosition = (cameraCFrame * newOffset).Position
if (handPosition - cameraCFrame.Position).Magnitude <= config.maxMoveDistance then
currentHand.offset = newOffset
end
end
-- Input and Render Connections
UserInputService.InputChanged:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseMovement then
if not UserInputService:IsKeyDown(Enum.KeyCode.LeftShift) then
updateHandPointingDirection()
else
updateHandPositionWithMouse()
end
elseif input.UserInputType == Enum.UserInputType.MouseWheel then
updateScrollMovement(input.Position.Z)
end
end)
RunService:BindToRenderStep("Camera", Enum.RenderPriority.Camera.Value + 1, function()
positionHandsInFrontOfCamera()
if not UserInputService:IsKeyDown(Enum.KeyCode.LeftShift) then
updateHandPointingDirection()
end
end)
UserInputService.InputBegan:Connect(function(input)
if input.KeyCode == Enum.KeyCode.Q then
AdvancedVRModule.toggleHand()
elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
isRightMouseHeld = true
modalButton.Modal = false
elseif input.UserInputType == Enum.UserInputType.MouseButton1 then
gripOrReleaseObject("grip")
elseif input.KeyCode == Enum.KeyCode.G then
gripOrReleaseObject("release")
end
end)
UserInputService.InputEnded:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton2 then
isRightMouseHeld = false
modalButton.Modal = true
end
end)
return AdvancedVRModule
The server code for replicating Client - Server
local ReplicateCFrames = game.ReplicatedStorage.NoVR.Events.ReplicateCFrames
local ReplicateInstances = game.ReplicatedStorage.NoVR.Events.ReplicateInstances
local ReplicateInstancesAsProps = game.ReplicatedStorage.NoVR.Events.ReplicateInstancesAsProps
local Replicate6D = game.ReplicatedStorage.NoVR.Events.Replicate6D
ReplicateInstances.OnServerEvent:Connect(function(Client, Hands, Model, Properties)
local NewModel = Model:Clone()
for index, values in pairs(Properties) do
NewModel[index] = values
end
NewModel.Parent = Client.Character
game.ReplicatedStorage.NoVR[Hands].Value = NewModel
for index, values in pairs(NewModel:GetDescendants()) do
if values:IsA("BasePart") then
values:SetNetworkOwner(Client)
end
end
end)
ReplicateInstancesAsProps.OnServerEvent:Connect(function(Client, object, Type, t)
local newHand = Client.Character:WaitForChild(t.name .. ".", 1)
if Type == "release" then
for index, values in pairs(t.heldObject:GetDescendants()) do
if values:IsA("BasePart") then
values:SetNetworkOwner(nil)
end
end
if t.heldObject:FindFirstChild("Owner") then
t.heldObject:FindFirstChild("Owner"):Destroy()
end
if t.heldObject then
if t.rope then
t.rope:Destroy()
t.rope = nil
elseif t.heldObject:FindFirstChild("Motor6D") then
t.heldObject:FindFirstChild("Motor6D"):Destroy()
end
end
elseif Type == "Rope" then
for index, values in pairs(object:GetDescendants()) do
if values:IsA("BasePart") then
values:SetNetworkOwner(Client.Character)
end
end
local Owner = Instance.new("StringValue") Owner.Parent = object Owner.Name = "Owner" Owner.Value = t.name
-- Create a RopeConstraint between the hand and the object
local rope = Instance.new("RopeConstraint")
rope.Parent = object
rope.Attachment0 = newHand.PrimaryPart:FindFirstChild("Attachment") or Instance.new("Attachment", newHand.PrimaryPart)
rope.Attachment1 = object:FindFirstChild("Attachment") or Instance.new("Attachment", object)
-- Set rope properties (adjust as needed)
rope.Length = (object.Position - newHand.PrimaryPart.Position).Magnitude
rope.Restitution = 0.2 -- adjust for bounce effect
currentHand.rope = rope
elseif Type == "6D" then
for index, values in pairs(object:GetDescendants()) do
if values:IsA("BasePart") then
values:SetNetworkOwner(Client.Character)
end
end
local Owner = Instance.new("StringValue") Owner.Parent = object Owner.Name = "Owner" Owner.Value = t.name
local offset = object:GetAttribute("Offset") or Vector3.new(0, 0, 0)
local rotation = object:GetAttribute("Rotation") or Vector3.new(0, 0, 0)
-- Create a Motor6D instead of a Weld
local motor6D = Instance.new("Motor6D")
motor6D.Part0 = newHand.PrimaryPart
motor6D.Part1 = object
motor6D.C1 = motor6D.C1 * CFrame.new(
object:FindFirstChild("Grip").CFrame.Position.X,
object:FindFirstChild("Grip").CFrame.Position.Y,
object:FindFirstChild("Grip").CFrame.Position.Z) * CFrame.Angles(-math.rad(rotation.X), -math.rad(rotation.Y), -math.rad(rotation.Z))
motor6D.Parent = object
end
end)
ReplicateCFrames.OnServerEvent:Connect(function(Client, Model, Pivot)
Model:PivotTo(Pivot)
end)
Replicate6D.OnServerEvent:Connect(function(Client, currentHandPrimaryPart, currentModel, object, rotation)
object.Parent = currentModel
local motor6D = Instance.new("Motor6D")
motor6D.Part0 = currentHandPrimaryPart
motor6D.Part1 = object
motor6D.C1 = motor6D.C1 * CFrame.new(
object:FindFirstChild("Grip").CFrame.Position.X,
object:FindFirstChild("Grip").CFrame.Position.Y,
object:FindFirstChild("Grip").CFrame.Position.Z) * CFrame.Angles(-math.rad(rotation.X), -math.rad(rotation.Y), -math.rad(rotation.Z))
motor6D.Parent = object
end)