Draggable object freezes mid air when thrown

Hello, this script I’m studying for dragging objects, for some reason causes the objects to freeze in place when thrown. I want it to travel smoothly when I release my mouse after dragging.

I thought it was the passing of network ownership to the server that made it lag, but I tried adding a delay that changes the owner after some time after the initial throw; it was still freezing in place.
My suspicions lie on the destruction of the AlignPosition and AlignOrientation attachments, but I’m not sure..

Here is the code:
Serverscript

local playerService = game:GetService("Players")
local replicatedStorage = game:GetService("ReplicatedStorage")
local collectionService = game:GetService("CollectionService")

local config = {
	max_drag_distance = 30,
	draggable_tag = "Draggable"
}

local requestDragRemote = replicatedStorage.Remotes:WaitForChild("RequestDrag")
local playerDragState = {}

--helper function
local function getPhysicsPart(object)
	if not object then return nil end
	return object:isA("Model") and object.PrimaryPart or object 
end

--main function
--this is where the STOPDRAG or STARTDRAG goes
local function onRequestDrag(player, targetObject)
	if not targetObject then
		local currentlyDraggedObject = playerDragState[player]
		if currentlyDraggedObject then
			local physicsPartToRelease = getPhysicsPart(currentlyDraggedObject)
			if physicsPartToRelease then
				physicsPartToRelease:SetNetworkOwner(nil)
				print("server is now the owner")
			end
			playerDragState[player] = nil
		end
		return false
	end
	
	if not collectionService:HasTag(targetObject, config.draggable_tag) then
		warn (player.Name .. " tried to drag an object without the " .. config.draggable_tag .. " tag.")
		return false
	end
	
	local physicsPart = getPhysicsPart(targetObject)
	if not physicsPart then
		warn(player.Name .. " tried to drag an object without a valid physics part.")
		return false
	end
	
	local character = player.Character
	local humanoidRootPart = character and character:FindFirstChild("HumanoidRootPart")
	if not humanoidRootPart then
		return false
	end	
	
	for _, draggedObject in pairs(playerDragState) do
		if draggedObject == targetObject then
			return false
		end
	end
	
	local distance = (humanoidRootPart.Position - physicsPart.Position).Magnitude
	if distance > config.max_drag_distance then
		warn(player.Name  .. " tried to drag an object from too far away.")
		return false
	end
	
	physicsPart:SetNetworkOwner(player)
	print("player is now the owner")
	playerDragState[player] = targetObject
	
	return true
end

local function onPlayerRemoving(player)
	if playerDragState[player] then
		local draggedObject = playerDragState[player]
		local physicsPartToRelease = getPhysicsPart(draggedObject)
		if physicsPartToRelease then
			physicsPartToRelease:SetNetworkOwner(nil)
		end
		playerDragState[player] = nil
	end
end

requestDragRemote.OnServerInvoke = onRequestDrag
playerService.PlayerRemoving:Connect(onPlayerRemoving)

Local Script

local userInputService = game:GetService("UserInputService")
local runService = game:GetService("RunService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local playerService = game:GetService("Players")
local collectionService = game:GetService("CollectionService")

local config = {
	draggable_tag = "Draggable",
	max_interaction_distance = 10
}

local localPlayer = playerService.LocalPlayer
local camera = workspace.CurrentCamera
local requestDragRemote = replicatedStorage.Remotes:WaitForChild("RequestDrag")

local isDragging = false
local draggedObject = nil
local dragConnection = nil
local grabDepth = 0
local grabOffset = CFrame.new()

local currentHoverTarget = nil
local currentHighlight = nil
local currentNameGui = nil

local objectAttachment = nil
local targetAttachment = nil
local alignPosition = nil
local alighnOrientation = nil
local anchored = false

local function getPhysicsPart(object)
	if not object then return nil end
	return object:isA("Model") and object.PrimaryPart or object 
end

local function getMouseTarget()
	local mousePosition = userInputService:GetMouseLocation()
	local mouseRay = camera:ViewportPointToRay(mousePosition.X, mousePosition.Y)
	
	local raycastParams = RaycastParams.new()
	raycastParams.FilterType = Enum.RaycastFilterType.Include
	raycastParams.FilterDescendantsInstances = collectionService:GetTagged(config.draggable_tag)
	
	local raycastResult = workspace:Raycast(mouseRay.Origin, mouseRay.Direction * 200, raycastParams)
	if raycastResult then
		local instance = raycastResult.Instance
		
		--while loop finds all the ancestors of the raycast result until 
		--it finds a part with the draggable tag
		while instance and not collectionService:HasTag(instance, config.draggable_tag) do
			instance = instance.Parent
		end
		return instance, raycastResult.Position
	end
	
	return nil, nil
end

local function updateDrag()
	if not isDragging or not targetAttachment then return end
	
	local mousePosition = userInputService:GetMouseLocation()
	local unitRay = camera:ViewportPointToRay(mousePosition.X, mousePosition.Y)
	
	local worldPointCFrame = CFrame.new(unitRay.Origin + unitRay.Direction * grabDepth)
	local finalCFrame = worldPointCFrame * grabOffset:Inverse()
	
	targetAttachment.WorldCFrame = finalCFrame
end

local function stopDrag()
	if not isDragging then return end
	
	if dragConnection then
		dragConnection:Disconnect()
		dragConnection = nil
	end
	
	if objectAttachment then objectAttachment:Destroy() end
	if targetAttachment then targetAttachment:Destroy() end
	

	
	objectAttachment,targetAttachment,alignPosition,alighnOrientation = nil,nil,nil,nil
	isDragging = false
	grabDepth = 0
	grabOffset = CFrame.new()
	
	requestDragRemote:InvokeServer(nil)
	draggedObject = nil
end

local function startDrag(target, hitPosition)
	if isDragging then return end
	
	local physicsPart = getPhysicsPart(target)
	if not physicsPart then return end
	
	local canDrag = requestDragRemote:InvokeServer(target)
	if not canDrag then return end
	
	isDragging = true
	draggedObject = target
	
	grabDepth = (camera.CFrame.Position - hitPosition).Magnitude
	grabOffset = physicsPart.CFrame:ToObjectSpace(CFrame.new(hitPosition))
	
	objectAttachment = Instance.new("Attachment", physicsPart)
	targetAttachment = Instance.new("Attachment", workspace.Terrain)
	physicsPart.Anchored = anchored
	
	alignPosition = Instance.new("AlignPosition", targetAttachment)
	alignPosition.Responsiveness = 100
	alignPosition.MaxForce = 100000
	alignPosition.Attachment0 = objectAttachment
	alignPosition.Attachment1 = targetAttachment
	
	
	alighnOrientation = Instance.new("AlignOrientation", targetAttachment)
	alighnOrientation.Responsiveness = 100
	alighnOrientation.MaxTorque = 100000
	alighnOrientation.Attachment0 = objectAttachment
	alighnOrientation.Attachment1 = targetAttachment
	
	--updateDrag()
	dragConnection = runService.Heartbeat:Connect(updateDrag)
end

local function updateHoverEffect()
	local character = localPlayer.Character
	local rootPart = character and character:FindFirstChild("HumanoidRootPart")
	if not rootPart then return end
	
	local target, _ = getMouseTarget()
	local physicsPart = getPhysicsPart(target)
	
	if physicsPart and (rootPart.Position - physicsPart.Position).Magnitude > config.max_interaction_distance then
		target = nil
	end
	
	if target ~= currentHoverTarget then
		if currentHighlight then currentHighlight:Destroy() end
		if currentNameGui then currentNameGui:Destroy() end
		currentHighlight, currentNameGui, currentHoverTarget = nil,nil,nil
		
		if target then
			currentHoverTarget = target
			
			currentHighlight = Instance.new("Highlight")
			currentHighlight.FillColor = Color3.fromRGB(255,255,255)
			currentHighlight.FillTransparency = 0.7
			currentHighlight.OutlineTransparency = 0.2
			currentHighlight.OutlineColor = Color3.fromRGB(255,255,255)
			currentHighlight.Parent = target
			
			local nameGui = Instance.new("BillboardGui")
			nameGui.Name = "ItemNameGui"
			nameGui.Adornee = physicsPart
			nameGui.Size = UDim2.new(4, 0, 1, 0)
			nameGui.StudsOffset = Vector3.new(0, 2.5, 0)
			nameGui.Parent = physicsPart
			
			local textLabel = Instance.new("TextLabel")
			textLabel.BackgroundTransparency = 1
			textLabel.Size = UDim2.new(1,0,1,0)
			textLabel.Font = Enum.Font.SourceSansBold
			textLabel.Text = target.Name
			textLabel.TextColor3 = Color3.fromRGB(255,255,255)
			textLabel.TextScaled = true
			textLabel.Parent = nameGui
			
			currentNameGui = nameGui
		end
	end
end

runService.Heartbeat:Connect(function()
	if not isDragging then
		updateHoverEffect()
	end
end)

userInputService.InputBegan:Connect(function(input, gameProcessedEvent)
	if gameProcessedEvent then return end
	if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
		local target, hitPosition = getMouseTarget()
		if target and hitPosition then
			local character = localPlayer.Character
			local rootPart = character and character:FindFirstChild("HumanoidRootPart")
			local physicsPart = getPhysicsPart(target)
			if rootPart and physicsPart and (rootPart.Position - physicsPart.Position).Magnitude <= config.max_interaction_distance then
				startDrag(target, hitPosition)
			end
		end
	end
end)

userInputService.InputEnded:Connect(function(input, gameProcessedEvent)
	if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
		stopDrag()
	end
end)
1 Like

I fixed a similar issue by just yielding the network ownership change until the object was no longer in motion - have you tried doing that yet? :slightly_smiling_face: Optionally you can also just simulate the movement on the server.

2 Likes

Blockquote
I fixed a similar issue by just yielding the network ownership change until the object was no longer in motion - have you tried doing that yet?

This was my initial fix, but it didn’t fix it.

I tested your code and the delay stopped when I removed the line where it sets the network owner to the server, so the issue is because of the network owner changing like you originally thought

1 Like

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